Introduction
sfpm is pnpm and Turborepo for Salesforce metadata. It builds, caches, and deploys packages in dependency order.
sfpm is pnpm and Turborepo for Salesforce metadata. Each Salesforce package
lives in a workspace directory with its own package.json. sfpm builds those
packages in dependency order, skips packages whose source has not changed, and
deploys or installs them across orgs.
The command is sfpm. It runs on your machine and in CI. The same flows are
also available as GitHub Actions.
sfpm is pre-1.0. Commands and flags may change between releases. Check
sfpm --help when you upgrade.
What it does
- Dependency-ordered builds. A package builds after the packages it
depends on. Pass
--no-dependenciesto build only the packages you name. - Change detection and caching. sfpm skips a package when its source has
not changed. Pass
--forceto build it anyway. In a Turborepo workspace, the generatedturbo.jsonalso cachessfpm:buildoutput. - A pnpm workspace. Every package has its own
package.json. Thesfpmfield in that file holds the package type, source path, and options. Thesfdx-project.jsonfile is generated from these files bysfpm project sync. Do not edit it by hand. - Deploy and install.
sfpm deploydeploys package source to an org.sfpm installinstalls built artifacts. - Script hooks. Run your own npm scripts or script files around build and install operations. sfpm does not compile LWC, TypeScript, or SCSS itself. Compile first, then run sfpm. See the section below.
- Custom lifecycle hooks. You can write your own hooks against the
LifecycleHookstype from@b64hub/sfpm-core, and register them insfpm.config.tswithdefineConfig. Built-in hooks ship in@b64hub/sfpm-hooks. - Local validation. When Nimbus is installed,
sfpm buildruns compile and dependency checks at the defaultlocalvalidation level.
Compile before sfpm
sfpm builds the source that is on disk when it runs. A script hook cannot add
compiled output to a build. Run your compiler as its own step before sfpm. In a
Turborepo workspace, make the compile task a dependency of the package's
sfpm:build task, so Turborepo runs it first.
The package declares its compile script in its own package.json. The
compiler must write its output into the sfpm.path directory, because that is
the source sfpm builds:
{
"name": "@myorg/core",
"version": "1.0.0",
"scripts": {
"compile": "tsc -p tsconfig.json"
},
"sfpm": {
"packageType": "unlocked",
"path": "force-app"
}
}In the workspace root turbo.json, sfpm:build depends on compile:
{
"tasks": {
"compile": {
"cache": false
},
"sfpm:build": {
"cache": true,
"dependsOn": ["compile", "^sfpm:build"],
"env": ["SF_DEV_HUB", "SFPM_FORCE_BUILD", "SFDX_AUTH_URL", "SF_ACCESS_TOKEN"],
"inputs": ["$TURBO_DEFAULT$"],
"outputs": ["dist/**"]
}
}
}Run the root sfpm:build script as usual. Turborepo runs each package's
compile script before that package's build.
sfpm project init turbo writes the sfpm:build entry in turbo.json. If
you run it again, add the compile dependency back.
A pre build script hook runs after sfpm has already copied the package into
dist/, so anything it writes is not part of that build. A pre install
script runs from the installed package directory, which has no tsconfig.json
and no dev toolchain. Compile before you invoke sfpm in both cases.
Packages
| Package | Purpose |
|---|---|
@b64hub/sfpm-cli | The sfpm command. This is the package you install. |
@b64hub/sfpm-core | Build and install orchestration, the project model, and the types for sfpm.config.ts and lifecycle hooks. |
@b64hub/sfpm-hooks | Built-in lifecycle hooks, including script hooks. |
@b64hub/sfpm-sfdmu | Data package support, through the SFDMU plugin for the Salesforce CLI. |
When it fits
sfpm fits teams that maintain several Salesforce packages with dependencies between them, deploy those packages to more than one org, and want CI to build and deploy them in dependency order.
When it does not
A single org with one package and no internal dependencies usually does not
need sfpm. The Salesforce CLI covers that case with less setup. sfpm adds a
workspace layout, a generated sfdx-project.json, and a build step that you
then maintain.