Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
sfdx-git-delta
Advanced tools
Generate the sfdx content in source format and destructive change from two git commits
SFDX plugin to generate Incremental Salesforce deployments manifests and artifacts
Report Bug
·
Request Feature
[!WARNING] Potentially breaking changes in v6: Check out the v6 announcement to see how you could be impacted by some changes in the new major
v6
version of the plugin, and how to prepare to migrate to this version.
sf sgd source delta -f <string> [-t <string>] [-r <filepath>] [-i <filepath>] [-D <filepath>] [-s <filepath>] [-W] [-o <filepath>] [-a <number>] [-d] [-n <filepath>] [-N <filepath>] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]
sf plugins install sfdx-git-delta
sf sgd source delta --to "HEAD" --from "HEAD~1" --output "."
sf project deploy start -x package/package.xml --post-destructive-changes destructiveChanges/destructiveChanges.xml
SFDX-Git-Delta (a.k.a. SGD) helps Salesforce Architects and Developers do 2 things with their source deployments:
Make deployments faster: identify the changed metadata since a reference commit.
Automate destructive deployments: build the destructiveChanges.xml from the deleted (or renamed) metadata
Have a look at this post on the Salesforce Developers Blog to dive into it: Optimizing Unpackaged Deployments Using a Delta Generation Tool.
If you are not a Salesforce Architect or Developer, probably not, sorry.
If you are a Technical Architect or Developer, then it’s a very useful tool for you, when meeting the 3 conditions below:
SGD is designed to be part of a CI/CD pipeline (Jenkins, Bitbucket Pipelines, GitLab CI, GitHub Actions, Azure DevOps...) that handles the deployment of the sources to the Salesforce org(s).
Pro tip: Make sure your pipeline works before implementing incremental deployments. Otherwise it will just make it harder to debug your pipeline. It's also important to implement a way to switch back to full deployment in case the incremental deployment does not behave as expected.
DISCLAIMER:
⚠️ SFDX-Git-Delta is NOT an officially supported tool ⚠️
👷 Use it at your own risk, wear a helmet, and test it first before adding it to your pipeline 🔥
The plugin requires git command line on the running environment.
Node v16.20.0 or above is required.
To check if Salesforce CLI runs under a supported node version for SGD, run sf --version
. You should see a node version above v.16.20.0 to use SGD.
If you encounter this issue whereas the node version is OK on the running environment, try to install the Salesforce CLI via npm (npm install @salesforce/cli --global
).
SGD is a Salesforce CLI plugin (sf sgd source delta
). Run the following command to install it:
sf plugins install sfdx-git-delta
Because this plugin is not signed, you will get a warning saying that "This plugin is not digitally signed and its authenticity cannot be verified". This is expected, and you will have to answer y
(yes) to proceed with the installation.
If you run your CI/CD jobs inside a Docker image, you can add the plugin to your image (such as in this example). If you use GitHub Actions, you can find some examples of using SGD here.
⚠️ The Salesforce CLI plugin is now the only supported way to install SGD. There used to be another way to install it using yarn or npm. The legacy sgd
command is now deprecated and decommissioned.
sfdx sgd:source:delta -f <string> [-t <string>] [-r <filepath>] [-i <filepath>] [-D <filepath>] [-s <filepath>] [-W] [-o <filepath>] [-a <number>] [-d] [-n <filepath>] [-N <filepath>] [--json] [--loglevel trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]
Generate the sfdx content in source format and destructive change from two git commits
USAGE
$ sfdx sgd:source:delta -f <string> [-t <string>] [-r <filepath>] [-i <filepath>] [-D <filepath>] [-s <filepath>] [-W]
[-o <filepath>] [-a <number>] [-d] [-n <filepath>] [-N <filepath>] [--json] [--loglevel
trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL]
OPTIONS
-D, --ignore-destructive=ignore-destructive file listing paths to explicitly
ignore for any destructive actions
-N, --include-destructive=include-destructive file listing paths to explicitly
include for any destructive actions
-W, --ignore-whitespace ignore git diff whitespace (space,
tab, eol) changes
-a, --api-version=api-version salesforce metadata API version,
default to sfdx-project.json
"sourceApiVersion" attribute or
latest version
-d, --generate-delta generate delta files in [--output]
folder
-f, --from=from (required) commit sha from where the
diff is done
-i, --ignore=ignore file listing paths to explicitly
ignore for any diff actions
-n, --include=include file listing paths to explicitly
include for any diff actions
-o, --output=output [default: ./output] source package
specific output
-r, --repo=repo [default: ./] git repository
location
-s, --source=source [default: ./] source folder focus
location related to --repo
-t, --to=to [default: HEAD] commit sha to where
the diff is done
--json format output as json
--loglevel=(trace|debug|info|warn|error|fatal|TRACE|DEBUG|INFO|WARN|ERROR|FATAL) [default: warn] logging level for
this command invocation
See code: src/commands/sgd/source/delta.ts
If you run SGD on a Windows system, use double quotes to prevent the terminal to interpret parameters
You should also avoid using the "^" character (shorthand for parent commit in git) because it is the escape character in Windows. So instead of:
sf sgd source delta --from "HEAD^" # wrong git shortcut with windows because it uses "^" syntax
You should write:
sf sgd source delta --from "HEAD~1" # right git shortcut with windows because it does not use "^", it uses "~n" syntax
In CI/CD pipelines, for most of the CI/CD providers, the checkout operation fetch only the last commit of the branch currently evaluated.
You need to fetch all the needed commits, as the plugin needs to have the branch to compare from as well,
Example for Github action checkout here.
If you use -n
(--include
) with metadata contained inside files you will need to have the full repo locally for the command to fully work.
In CI/CD pipelines, branches are not checked out locally when the repository is cloned, so you must specify the remote prefix.
If you do not specify the remote in CI context, the git pointer check will raise an error (as the branch is not created locally).
This applies to both --from
and --to
parameters as they both accept git pointers.
Example comparing HEAD
with a development
branch when the CI clone the repository with origin
set as reference to the remote:
sf sgd source delta --to "HEAD" --from "origin/development" --output .
Use global variable when you need to easily switch sgd version (vX.X.X
format) or channel (stable
, latest
, latest-rc
) in your pipeline, without having to commit a new version of your pipeline.
Example with github action, create a variable SGD_VERSION and use it in the plugin installation phase
- name: Install SGD
run: echo y | sf plugins install "sfdx-git-delta@${{ vars.SGD_VERSION }}"
The plugin is compatible with git LFS. It will be able to read content from LFS locally. It is the user responsibility to ensure LFS content is present when the plugin is executed. /!\ The plugin will not fetch content from the LFS server /!\
Any git sha pointer is supported: commit sha, branch, tag, git expression (HEAD, etc.).
--from
parameter is the base commit (the first, the oldest, the closest)
--to
parameter is the target commit (the last, the youngest, the farthest)
If you want to deploy incrementally the content of a PR, --from
parameter will be the base branch the PR branch wants to merge to, and --to
parameter will be the PR branch.
Here are examples of how to compare the content of different branches:
fbc3ade6
in branch develop
and commit 61f235b1
in branch main
:sf sgd source delta --to fbc3ade6 --from 61f235b1 --output .
develop
branch and the main
branch:sf sgd source delta --to develop --from main --output .
develop
branch since its common ancestor with the main
branch (i.e. ignoring the changes performed in the main
branch after develop
creation):sf sgd source delta --to develop --from $(git merge-base develop main) --output .
Let’s take a look at the following scenario:
The CI pipelines deploys the sources to Production anytime there is a new commit in the main branch.
In our example, the latest commit to main is composed of:
In this situation, we would expect the CI pipeline to:
TriggerHandler
, TriggerHandler_Test
, and TestDataFactory
AnotherTriggerFramework
So let’s do it!
From the project repo folder, the CI pipeline will run the following command:
sf sgd source delta --to "HEAD" --from "HEAD~1" --output .
which means:
Analyze the difference between HEAD (latest commit) and HEAD~1 (previous commit), and output the result in the current folder.
The sf sgd source delta
command produces 2 useful artifacts:
1) A package.xml
file, inside a package
folder. This package.xml
file contains just the added/changed metadata to deploy to the target org.
Content of the package.xml
file in our scenario:
2) A destructiveChanges.xml
file, inside a destructiveChanges
folder. This destructiveChanges.xml
file contains just the removed/renamed metadata to delete from the target org. Note: the destructiveChanges
folder also contains a minimal package.xml file, because deploying destructive changes requires a package.xml (even an empty one).
Content of the destructiveChanges.xml
file in our scenario:
Note: it is also possible to generate a source folder containing added/changed metadata with the --generate-delta (-d)
parameter. See the "Advanced use-cases" section for more examples.
The simplest option to deploy the incremental changes is to use force:source:deploy
command with -x
parameter:
sf project deploy start -x package/package.xml --post-destructive-changes destructiveChanges/destructiveChanges.xml
And voilà! 🥳
However, keep in mind that the above command will fail if the destructive change was supposed to be executed before the deployment (i.e. as --pre-destructive-changes
), or if a warning occurs during deployment. Make sure to protect your CI/CD pipeline from those scenarios, so that it doesn't get stuck by a failed destructive change.
If needed, you can also split the added/modified metadata deployment from the deleted/renamed metadata deployment, as in the below examples:
Use the package/package.xml
file to deploy only the added/modified metadata:
echo "--- package.xml generated with added and modified metadata ---"
cat package/package.xml
echo
echo "---- Deploying added and modified metadata ----"
sf project deploy start -x package/package.xml
Use the destructiveChanges
folder to deploy only the destructive changes:
echo "--- destructiveChanges.xml generated with deleted metadata ---"
cat destructiveChanges/destructiveChanges.xml
echo
echo "--- Deleting removed metadata ---"
sf project deploy start --pre-destructive-changes destructiveChanges/destructiveChanges.xml --manifest destructiveChanges/package.xml --ignore-warnings
Using a package.xml for deployment is the simplest approach to delta deployments. But in some cases you may want to have only the actual recently changed source files.
One example is to speed up object deployments: the package.xml approach will deploy the entire sub-folder for a given object. Having a copy of the actual sources added/modified allows you to deploy only those components.
This is where the --generate-delta (-d)
option comes handy!
Let's use this option with our previous example:
mkdir changed-sources
sf sgd source delta --to "HEAD" --from "HEAD~1" --output changed-sources/ --generate-delta
It generates the package
and destructiveChanges
folders, and copies added/changed files in the output folder.
Content of the output folder when using the --generate-delta option, with the same scenario as above:
⚠️ Use
--generate-delta (-d)
when--to (-t)
value is set to "HEAD" or to the "HEAD commit SHA". If you need to use it with--to (-t)
pointing to another commit than "HEAD", checkout that commit first. Example:
# move HEAD to the wanted past commit $ git checkout <not-HEAD-commit-sha> # You can omit --to, it will take "HEAD" as default value $ sf sgd source delta --from "HEAD~1" --output changed-sources/ --generate-delta
Then it is possible to deploy the change-sources
folder using force:source:deploy
command with -p
parameter:
sf project deploy start -p change-sources
The --ignore [-i]
parameter allows you to specify an ignore file to filter the
element on the diff to ignore. SGD ignores every diff line matching the pattern from the ignore file specified in the --ignore [-i]
. package.xml
generation, destructiveChanges.xml
generation and --delta-generate
will ignore those lines.
Sometimes you may need to have two different ignore policies. One for the package.xml
and another one for destructiveChanges.xml
files. This is where the --ignore-destructive [-D]
option comes handy!
Use the --ignore-destructive
parameter to specify a dedicated ignore file to handle deletions. It will apply to metadata listed in the destructiveChanges.xml
. In other words, this will override the --ignore [-i]
parameter for deleted items.
Consider the following:
The Custom__c object appears in the package.xml
and in destructiveChanges.xml
and fail the deployment. This is a situation where your may want to use the --ignore-destructive [-D]
parameter! Add the Custom__c object pattern in an ignore file and pass it in the CLI parameter:
# destructiveignore
*Custom\_\_c.object-meta.xml
$ sf sgd source delta --from commit --ignore-destructive destructiveignore
Note: when only using the --ignore [-i]
parameter (and not --ignore-destructive [-D]
) the plugin will apply it to added/changed/deleted elements.
The --include [-n]
parameter allows you to specify a file based on gitignore glob matching to include specific files. Regardless whether they appears in the diff or not.
Like the --ignore
flag, this file defines a list of glob file matchers to always include git
aware files in the package.xml
package.
SGD will include every metadata from the repo at the to
parameter state, matching the pattern from the include file specified in the --include [-n]
.
As with --ignore
, you may need different policies for the package.xml
and destructiveChanges.xml
files. This is where the --include-destructive [-N]
option comes handy!
Use the --include-destructive
parameter to specify a dedicated include file to handle deletions. Related metadata will appear in the destructiveChanges.xml
output.
/!\ In order to work properly with metadata contained inside files (Labels, Workflow, MatchingRules, etc) the local repo must have the full historic.
Consider the following:
force-app/generated/foo
file the source:deploy
command should not include.
You can create a file with a line matching this new file and specify this file using the --include-destructive [-N]
parameter.# .destructiveinclude
*generated/foo
$ sf sgd source delta --from commit --include-destructive .destructiveinclude
The path matchers in includes file must follow gitignore
spec and accept only unix path separator /
(even for windows system).
The --source [-s]
parameter allows you to specify a folder to focus on, making any other folder ignored.
It means the delta generation will only focus on the dedicated folder.
For example, consider a repository containing many sub-folders (force-app/package, force-app/unpackaged, etc). This repository contains packaged (deployed via package) and unpackaged (deployed via CLI) sources. You only want to apply delta generation for the unpackaged sources.
$ tree
.
├── force-app
├── packaged
│ └── classes
│ └── PackagedClass.cls
└── unpackaged
└── classes
└── UnpackagedClass.cls
├── ...
# scope the delta generation only to the unpackaged folder
$ sf sgd source delta --from commit --source force-app/unpackaged
The ignored patterns specified using
--ignore [-i]
and--ignore-destructive [-D]
still apply. The--source
path must be relative to the--repo
path
Depending on your testing strategy, you may want to generate a comma-separated list of the added and modified Apex classes. This list can feed the sf project deploy start --testlevel RunSpecifiedTests
command, for example.
To cover this need, parse the content of the package.xml file produced by SGD using yq:
xq . < package/package.xml | jq '.Package.types | [.] | flatten | map(select(.name=="ApexClass")) | .[] | .members | [.] | flatten | map(select(. | index("*") | not)) | unique | join(",")'
SGD does not always generate content in the package.xml (or destructiveChanges.xml). Sometimes the commit range contains changes only within files to ignore (using .sgdignore and --i
parameter).
Deploying empty package.xml can lead to deployment errors.
To avoid starting a failing deployment, test files content before execution:
# run deploy command only if the generated package contains metadata
if grep -q '<types>' ./package/package.xml ; then
echo "---- Deploying added and modified metadata ----"
sf project deploy start -x package/package.xml
else
echo "---- No changes to deploy ----"
fi
If you want to embed sgd in your node application, install it as a dependency for your application
yarn add sfdx-git-delta
Then use the JavaScript module
// sample/app.js
const sgd = require('sfdx-git-delta')
// or
// import sgd from 'sfdx-git-delta'
const work = await sgd({
to: '', // commit sha to where the diff is done. [default : "HEAD"]
from: '', // (required) commit sha from where the diff is done. [default : git rev-list --max-parents=0 HEAD]
output: '', // source package specific output. [default : "./output"]
apiVersion: '', // salesforce API version. [default : latest]
repo: '', // git repository location. [default : "."]
})
console.log(JSON.stringify(work))
/* {
* config: config,
* diffs: { package: {...}, destructiveChanges: {...} },
* warnings: []
* }
*/
Deleting a flow cannot be done by adding the flow in the destructiveChanges.xml
and deploy.
A known issue exist to cover this feature.
Please do not assume committing a flow metadata deletion to the repo, and then run sgd will allow you to delete a flow.
We suggest to deal with flow deletion in one go by following those steps (it requires the FlowDefinition
metadata which is not available in API v44+
)
FlowDefinition
activeVersionNumber
to 0
FlowDefinition
in a package.xml
Flow
in a destructiveChangesPost.xml
(can be fetch via SOQL using this query : SELECT FlowDefinitionView.ApiName, VersionNumber, Status FROM FlowVersionView WHERE FlowDefinitionView.ApiName='<FLOW_API_NAME>'
)FlowDefinition
with a package.xml
and post delete all the Flow
versions with a post destructiveChangesPost.xml
Example to delete the Flow Set_Account_Description
:
FlowDefinition
activeVersionNumber
to 0
<!--Set_Account_Description.flowDefinition-meta.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<FlowDefinition xmlns="http://soap.sforce.com/2006/04/metadata">
<activeVersionNumber>0</activeVersionNumber>
</FlowDefinition>
FlowDefinition
in a package.xml
<!--package.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Set_Account_Description</members>
<name>FlowDefinition</name>
</types>
<version>61.0</version>
</Package>
Flow
in a destructiveChangesPost.xml
<!--destructiveChangesPost.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
<types>
<members>Set_Account_Description-1</members>
<members>Set_Account_Description-2</members>
<members>Set_Account_Description-...</members>
<members>Set_Account_Description-n</members>
<name>Flow</name>
</types>
</Package>
package.xml
, destructiveChangesPost.xml
and FlowDefinition
# add `--ignore-warnings` parameter if you listed a deleted Flow version in the destructiveChangesPost.xml
sf project deploy start -x package.xml --post-destructive-changes destructiveChangesPost.xml
These plugins have been designed to work with SGD:
--from
and --to
commit hashes when using SGD and apex-tests-git-delta to determine the required Apex tests to run during deployment.changelog.md is available for consultation.
Versioning follows SemVer specification.
Contributions are what make the trailblazer community such an amazing place. I regard this component as a way to inspire and learn from others. Any contributions you make are appreciated.
See contributing.md for sgd contribution principles.
This project license is MIT - see the LICENSE.md file for details
FAQs
Generate the sfdx content in source format and destructive change from two git commits
The npm package sfdx-git-delta receives a total of 66,640 weekly downloads. As such, sfdx-git-delta popularity was classified as popular.
We found that sfdx-git-delta demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.