hardis:project:deploy:sources:dx
Description
Smart deploy of SFDX sources to target org, with many useful options.
In case of errors, tips to fix them will be included within the error messages.
See the whole sfdx-hardis smart deployment workflow explained in detail
Quick Deploy
In case Pull Request comments are configured on the project, Quick Deploy will try to be used (equivalent to button Quick Deploy)
If you do not want to use QuickDeploy, define variable SFDX_HARDIS_QUICK_DEPLOY=false
- GitHub Pull Requests comments config
- Gitlab Merge requests notes config
- Azure Pull Requests comments config
Delta deployments
To activate delta deployments, define property useDeltaDeployment: true in config/.sfdx-hardis.yml.
This will activate delta deployments only between minor and major branches (major to major remains full deployment mode)
If you want to force the delta deployment into major orgs (ex: preprod to prod), this is not recommended but you can use env variable ALWAYS_ENABLE_DELTA_DEPLOYMENT=true
Smart Deployments Tests
Not all metadata updates can break test classes, use Smart Deployment Tests to skip running test classes if ALL the following conditions are met:
- Delta deployment is activated and applicable to the source and target branches
- Delta deployed metadatas are all matching the list of NOT_IMPACTING_METADATA_TYPES (see below)
- Target org is not a production org
Activate Smart Deployment tests with:
- env variable
USE_SMART_DEPLOYMENT_TESTS=true - .sfdx-hardis.yml config property
useSmartDeploymentTests: true
Defaut list for NOT_IMPACTING_METADATA_TYPES (can be overridden with comma-separated list on env var NOT_IMPACTING_METADATA_TYPES)
- ActionLinkGroupTemplate
- AnalyticSnapshot
- AppMenu
- Audience
- AuraDefinitionBundle
- Bot
- BotVersion
- BrandingSet
- ContentAsset
- CustomApplication
- CustomApplicationComponent
- CustomLabel
- CustomFeedFilter
- CustomHelpMenuSection
- CustomObjectTranslation
- CustomPageWebLink
- CustomSite
- CustomTab
- CustomValueSetTranslation
- Dashboard
- DashboardFolder
- Document
- EmailTemplate
- ExperienceBundle
- FlexiPage
- GlobalValueSetTranslation
- HomePageComponent
- HomePageLayout
- Layout
- Letterhead
- LightningExperienceTheme
- LightningComponentBundle
- LightningMessageChannel
- ListView
- NavigationMenu
- PathAssistant
- QuickAction
- ReportType
- Report
- ReportFolder
- SiteDotCom
- StandardValueSetTranslation
- StaticResource
- Translations
- WebLink
Note: if you want to disable Smart test classes for a PR, add nosmart in the text of the latest commit.
Custom Apex Test Classes (optional)
You can force Smart Deploy to run a specific list of Apex Test Classes. This is not recommended because best practice is to run all local tests. Enable it only if you have a specific need.
enableDeploymentApexTestClasses(boolean, default: false): Activate the custom list.deploymentApexTestClasses(array of strings): The Apex Test Classes to run. Used only when the flag above is true.
Example configuration in config/.sfdx-hardis.yml (can also be scoped to branches in config/branches/.sfdx-hardis-BRANCHNAME.yml or in Pull Request description):
enableDeploymentApexTestClasses: true
deploymentApexTestClasses:
- MyTestClass1
- MyTestClass2
Dynamic deployment items / Overwrite management
If necessary,you can define the following files:
manifest/package-no-overwrite.xml: Every element defined in this file will be deployed only if it is not existing yet in the target org (can be useful with ListView for example, if the client wants to update them directly in production org).- Supports
<members>*</members>(all members of a type), exact names, and glob-style patterns such as<members>*__dlm</members>or<members>Prod_*</members>. - Can be overridden for a branch using .sfdx-hardis.yml property packageNoOverwritePath or environment variable PACKAGE_NO_OVERWRITE_PATH (for example, define:
packageNoOverwritePath: manifest/package-no-overwrite-main.xmlin config fileconfig/.sfdx-hardis.main.yml)
- Supports
manifest/packageXmlOnChange.xml: Every element defined in this file will not be deployed if it already has a similar definition in target org (can be useful for SharingRules for example)
See Overwrite management documentation
Packages installation
You can define a list of package to install during deployments using property installedPackages
- If
INSTALL_PACKAGES_DURING_CHECK_DEPLOYis defined astrue(orinstallPackagesDuringCheckDeploy: truein.sfdx-hardis.yml), packages will be installed even if the command is called with--checkmode - You can automatically update this property by listing all packages installed on an org using command
sf hardis:org:retrieve:packageconfig
Example:
installedPackages:
- Id: 0A35r0000009EtECAU
SubscriberPackageId: 033i0000000LVMYAA4
SubscriberPackageName: Marketing Cloud
SubscriberPackageNamespace: et4ae5
SubscriberPackageVersionId: 04t6S000000l11iQAA
SubscriberPackageVersionName: Marketing Cloud
SubscriberPackageVersionNumber: 236.0.0.2
installOnScratchOrgs: true // true or false depending you want to install this package when creating a new scratch org
installDuringDeployments: true // set as true to install package during a deployment using sf hardis:project:deploy:smart
installationkey: xxxxxxxxxxxxxxxxxxxx // if the package has a password, write it in this property
- Id: 0A35r0000009F9CCAU
SubscriberPackageId: 033b0000000Pf2AAAS
SubscriberPackageName: Declarative Lookup Rollup Summaries Tool
SubscriberPackageNamespace: dlrs
SubscriberPackageVersionId: 04t5p000001BmLvAAK
SubscriberPackageVersionName: Release
SubscriberPackageVersionNumber: 2.15.0.9
installOnScratchOrgs: true
installDuringDeployments: true
Deployment pre or post commands
You can define command lines to run before or after a deployment, with parameters:
- id: Unique Id for the command
- label: Human readable label for the command
- skipIfError: If defined to "true", the post-command won't be run if there is a deployment failure
- context: Defines the context where the command will be run. Can be all (default), check-deployment-only or process-deployment-only
- runOnlyOnceByOrg: If set to true (default), the action runs only once per target org - subsequent deployments skip it. State is tracked in the "Deployment Actions" PR comment.
After every action runs, its result (✅ success, ❌ failed, 👋 manual) is recorded in a dedicated "Deployment Actions" PR comment - ordered by org (integration → uat → preprod → prod) - regardless of runOnlyOnceByOrg.
If the commands are not the same depending on the target org, you can define them into config/branches/.sfdx-hardis-BRANCHNAME.yml instead of root config/.sfdx-hardis.yml
Example:
commandsPreDeploy:
- id: knowledgeUnassign
label: Remove KnowledgeUser right to the user who has it
command: sf data update record --sobject User --where "UserPermissionsKnowledgeUser='true'" --values "UserPermissionsKnowledgeUser='false'" --json
- id: knowledgeAssign
label: Assign Knowledge user to the deployment user
command: sf data update record --sobject User --where "Username='deploy.github@myclient.com'" --values "UserPermissionsKnowledgeUser='true'" --json
commandsPostDeploy:
- id: knowledgeUnassign
label: Remove KnowledgeUser right to the user who has it
command: sf data update record --sobject User --where "UserPermissionsKnowledgeUser='true'" --values "UserPermissionsKnowledgeUser='false'" --json
- id: knowledgeAssign
label: Assign Knowledge user to desired username
command: sf data update record --sobject User --where "Username='admin-yser@myclient.com'" --values "UserPermissionsKnowledgeUser='true'" --json
- id: someActionToRunJustOneTime
label: And to run only if deployment is success
command: sf sfdmu:run ...
skipIfError: true
context: process-deployment-only
runOnlyOnceByOrg: true
Flow deletion in destructive changes
Deleting a Flow through a metadata deployment is possible, but only if the org is already in the right state before the deployment runs:
- every version has to be named individually (
MyFlow-1,MyFlow-2, ...), as a bare<members>MyFlow</members>fails with "insufficient access rights", - the Flow has to be inactive already. Deactivating it in the same deployment does not help, since Salesforce tries to deactivate the flow that was deleted during a real deploy (
NoDataFoundException/UNKNOWN_EXCEPTION), so it takes a manual deactivation or an earlier deployment, - a
--checkdeployment never commits a deactivation, so a deletion that depends on one can not be validated.
Smart Deploy removes that manual step by taking Flow deletion out of the deployment. Any Flow member found in manifest/destructiveChanges.xml, manifest/preDestructiveChanges.xml, the packageXmlToDelete config or the delta-generated destructive changes is removed from the manifest sent to the org, and deleted through the Tooling API instead. Stripping is identical during validation and during the real deployment, so the constructive package stays quick-deploy eligible.
A --check simulation changes nothing in the org. A read-only preflight reports, for each Flow: the active version that will be deactivated, the versions that will be deleted, and how many Flow Interviews block the deletion. The check fails if Flow Interviews block a deletion and you have not authorized deleting them.
On a real deployment, each Flow goes through:
- Existence check. A Flow that is already gone is reported as
FLOW_DELETE_NOOP, not an error (same for a Flow with no deletable version, for example one from a managed package). - Deactivation through the Tooling API (
FlowDefinition.activeVersionNumber = 0), which stops new Flow Interviews from starting. - Flow Interview gate. If interviews remain and
FLOW_DELETE_INTERVIEWSis not set, the deployment fails withFLOW_DELETE_BLOCKED. The Flow stays deactivated, so retrying the pipeline once those interviews resolve completes the deletion. - Flow Interview deletion, only when
FLOW_DELETE_INTERVIEWSauthorizes it. - Version deletion through the Tooling API, oldest version first, then a check that no version is left.
When deleting Flow Interviews is authorized, step 5 retries: an interview that was still running when the Flow got deactivated can pause mid-sequence and block a version. Both bounds can be tuned, as an env variable or as a .sfdx-hardis.yml property (the env variable wins). A value that is not an integer, or is below the minimum, is ignored with a warning and the default applies.
| Env variable | .sfdx-hardis.yml property |
Default | Minimum | Purpose |
|---|---|---|---|---|
| FLOW_DELETE_MAX_ATTEMPTS | flowDeleteMaxAttempts | 3 | 1 | Number of version deletion attempts per Flow. 1 disables the retry. Only used when FLOW_DELETE_INTERVIEWS authorizes deleting interviews: without that authorization a block is final. |
| FLOW_DELETE_RETRY_DELAY_MS | flowDeleteRetryDelayMs | 10000 | 0 | Delay in milliseconds between two attempts, to give a paused interview time to be deleted. |
Any failure that is not an interview block (insufficient access, network error mid-run...) is reported as FLOW_DELETE_ERROR and also fails the deployment. After a network error the org can be further along than the report shows: every step is re-runnable, so retry and trust the new report.
Notes:
- A bare member (
MyFlow) deletes all versions of the Flow. A versioned member (MyFlow-3) deletes that version only, and deactivates the Flow only if that version is the active one. A wildcard (*) is refused. - Flows are processed independently: one blocked Flow does not prevent the others from being deleted.
- The deactivation is committed immediately and is not rolled back if a later step fails, so a Flow can be left deactivated but not deleted. Every step is re-runnable, so a pipeline retry converges.
- Flows listed in
preDestructiveChanges.xmlare deleted before the constructive deployment, the others after it. Both happen outside the deployment transaction: a deployment that fails after a Flow was deleted does not bring that Flow back, where apreDestructiveChanges.xmlhandled inside the deployment used to be rolled back.
Pull Requests Custom Behaviors
If some words are found in the Pull Request description, special behaviors will be applied
| Word | Behavior |
|---|---|
| NO_DELTA | Even if delta deployments are activated, a deployment in mode full will be performed for this Pull Request |
| PURGE_FLOW_VERSIONS | After deployment, inactive and obsolete Flow Versions will be deleted (equivalent to command sf hardis:org:purge:flow) Caution: This will also purge active Flow Interviews ! |
| DESTRUCTIVE_CHANGES_AFTER_DEPLOYMENT | If a file manifest/destructiveChanges.xml is found, it will be executed in a separate step, after the deployment of the main package |
| FLOW_DELETE_INTERVIEWS | Authorizes deleting the Flow Interviews that block the deletion of a Flow listed in destructive changes. The directive must be on its own line (or in a checked Markdown checkbox). Caution: deleting Flow Interviews is irreversible and destroys in-flight process state ! |
You can also override some .sfdx-hardis.yml properties directly in the Pull Request description using YAML blocks. Supported keys: deploymentApexTestClasses, commandsPreDeploy, commandsPostDeploy.
Example (in PR description):
deploymentApexTestClasses:
- MyTestClass1
- MyTestClass2
For example, define
PURGE_FLOW_VERSIONSandDESTRUCTIVE_CHANGES_AFTER_DEPLOYMENTin your Pull Request comments if you want to delete fields that are used in an active flow.
Note: it is also possible to define these behaviors as ENV variables:
- For all deployments (example:
PURGE_FLOW_VERSIONS=true) - For a specific branch, by appending the target branch name (example:
PURGE_FLOW_VERSIONS_UAT=true)
FLOW_DELETE_INTERVIEWS can also be set as a .sfdx-hardis.yml property (flowDeleteInterviews: true).
Deployment plan (deprecated)
This feature is deactivated by default (enable with
enableDeprecatedDeploymentPlanin project configuration). Use preCommands and postCommands instead.
If you need to deploy in multiple steps, you can define a property deploymentPlan in .sfdx-hardis.yml.
- If a file
manifest/package.xmlis found, it will be placed with order 0 in the deployment plan
- If a file
manifest/destructiveChanges.xmlis found, it will be executed as --postdestructivechanges
- If env var
SFDX_HARDIS_DEPLOY_IGNORE_SPLIT_PACKAGESis defined asfalse, split of package.xml will be applied
Example:
deploymentPlan:
packages:
- label: Deploy Flow-Workflow
packageXmlFile: manifest/splits/packageXmlFlowWorkflow.xml
order: 6
- label: Deploy SharingRules - Case
packageXmlFile: manifest/splits/packageXmlSharingRulesCase.xml
order: 30
waitAfter: 30
Automated fixes post deployments
List view with scope Mine
If you defined a property listViewsToSetToMine in your .sfdx-hardis.yml, related ListViews will be set to Mine ( see command https://sfdx-hardis.cloudity.com/hardis/org/fix/listviewmine/ )
Example:
listViewsToSetToMine:
- "Operation__c:MyCurrentOperations"
- "Operation__c:MyFinalizedOperations"
- "Opportunity:Default_Opportunity_Pipeline"
- "Opportunity:MyCurrentSubscriptions"
- "Opportunity:MySubscriptions"
- "Account:MyActivePartners"
Troubleshooting: if you need to fix ListViews with mine from an alpine-linux based docker image, use this workaround in your dockerfile:
# Do not use puppeteer embedded chromium
RUN apk add --update --no-cache chromium
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD="true"
ENV CHROMIUM_PATH="/usr/bin/chromium-browser"
ENV PUPPETEER_EXECUTABLE_PATH="$\{CHROMIUM_PATH}" // remove \ before {
If you need to increase the deployment waiting time (sf project deploy start --wait arg), you can define env variable SFDX_DEPLOY_WAIT_MINUTES (default: 120)
If you need notifications to be sent using the current Pull Request and not the one just merged (see use case), define env variable SFDX_HARDIS_DEPLOY_BEFORE_MERGE=true
If you want to disable the calculation and display of Flow Visual Git Diff in Pull Request comments, define variable SFDX_DISABLE_FLOW_DIFF=true
Agent Mode
Supports non-interactive execution with --agent:
sf hardis:project:deploy:smart --agent --check --source-branch feature/my-feature --target-branch integration --target-org deploy@myclient.com.integration
Important:
--target-orgmust be the target deployment org (e.g. the integration sandbox), not the developer's current working org. The Salesforce CLI must be authenticated to that org before running this command.
In agent mode:
- The interactive org selection prompt is skipped.
- Deployment is forced into simulation/check mode -
--checkis implicit, but should be passed explicitly to make the intent clear. No changes are applied to the org. - Use
--source-branchto specify the source git branch (overrides local git branch detection viaFORCE_SOURCE_BRANCH). - Use
--target-branchto specify the target git branch. This setsFORCE_TARGET_BRANCHfor delta/PR scope and also setsCONFIG_BRANCHso the target branch config file (config/branches/.sfdx-hardis-BRANCHNAME.yml) is loaded - providing the correcttargetUsernamefor that org automatically. - If a deployment action requires a
customUsernameand authentication for that user fails, the action is skipped (not failed) so the simulation can continue.
Parameters
| Name | Type | Description | Default | Required | Options |
|---|---|---|---|---|---|
| agent | boolean | Run in non-interactive mode for agents and automation | |||
| check -c |
boolean | Only checks the deployment, there is no impact on target org | |||
| debug -d |
boolean | Activate debug mode (more logs) | |||
| delta | boolean | Applies sfdx-git-delta to package.xml before other deployment processes | |||
| flags-dir | option | undefined | |||
| json | boolean | Format output as json. | |||
| packagexml -p |
option | Path to package.xml containing what you want to deploy in target org | |||
| runtests -r |
option | If testlevel=RunSpecifiedTests, please provide a list of classes. | |||
| If testlevel=RunRepositoryTests, can contain a regular expression to keep only class names matching it. If not set, will run all test classes found in the repo. | |||||
| skipauth | boolean | Skip authentication check when a default username is required | |||
| source-branch | option | Source git branch name (agent mode: overrides local git branch detection via FORCE_SOURCE_BRANCH) | |||
| target-branch | option | Target git branch name (agent mode: sets CONFIG_BRANCH so the target branch config is loaded, providing the correct targetUsername) | |||
| target-org -o |
option | undefined | |||
| testlevel -l |
option | Level of tests to validate deployment. RunRepositoryTests auto-detect and run all repository test classes | NoTestRun RunSpecifiedTests RunRepositoryTests RunRepositoryTestsExceptSeeAllData RunLocalTests RunRelevantTests RunAllTestsInOrg |
||
| websocket | option | Websocket host:port for VsCode SFDX Hardis UI integration |
Examples
$ sf hardis:project:deploy:smart
$ sf hardis:project:deploy:smart --check
$ sf hardis:project:deploy:smart --check --testlevel RunRepositoryTests
$ sf hardis:project:deploy:smart --check --testlevel RunRepositoryTests --runtests '^(?!FLI|MyPrefix).*'
$ sf hardis:project:deploy:smart --check --testlevel RunRepositoryTestsExceptSeeAllData
$ sf hardis:project:deploy:smart
$ FORCE_TARGET_BRANCH=preprod NODE_OPTIONS=--inspect-brk sf hardis:project:deploy:smart --check --websocket localhost:2702 --skipauth --target-org nicolas.vuillamy@myclient.com.preprod
$ SYSTEM_ACCESSTOKEN=xxxxxx SYSTEM_COLLECTIONURI=https://dev.azure.com/xxxxxxx/ SYSTEM_TEAMPROJECT="xxxxxxx" BUILD_REPOSITORY_ID=xxxxx SYSTEM_PULLREQUEST_PULLREQUESTID=1418 FORCE_TARGET_BRANCH=uat NODE_OPTIONS=--inspect-brk sf hardis:project:deploy:smart --check --websocket localhost:2702 --skipauth --target-org my.salesforce@org.com
$ CI_SFDX_HARDIS_BITBUCKET_TOKEN=xxxxxx BITBUCKET_WORKSPACE=sfdxhardis-demo BITBUCKET_REPO_SLUG=test BITBUCKET_BUILD_NUMBER=1 BITBUCKET_BRANCH=uat BITBUCKET_PR_ID=2 FORCE_TARGET_BRANCH=uat NODE_OPTIONS=--inspect-brk sf hardis:project:deploy:smart --check --websocket localhost:2702 --skipauth --target-org my-salesforce-org@client.com
$ GITHUB_TOKEN=xxxx GITHUB_REPOSITORY=my-user/my-repo FORCE_TARGET_BRANCH=uat NODE_OPTIONS=--inspect-brk sf hardis:project:deploy:smart --check --websocket localhost:2702 --skipauth --target-org my-salesforce-org@client.com
$ sf hardis:project:deploy:smart --agent --check
$ sf hardis:project:deploy:smart --agent --check --source-branch feature/my-feature --target-branch integration --target-org deploy@myclient.com.integration