Validate pull requests
Run local or org-backed validation for pull requests with the validate-pr action.
The validate-pr action checks a pull request before it merges. It runs in one of two modes:
| Mode | What it does | Needs |
|---|---|---|
local (default) | Compile and dependency checks for changed packages, using the local nimbus validator. No org is used. | nimbus on the runner |
org | Deploys changed packages to a scratch org taken from a pool, and runs the org checks. | DevHub authentication, a pool tag, and a pool filled by fill-pool |
Local mode
Local mode needs no Salesforce access. It runs nimbus compile and dependency checks against the packages the pull request changes. The DevHub does not need to be authenticated.
Local checks are best-effort. The org is the authoritative check. In local mode, compile and dependency findings are reported as warnings.
If nimbus is not on the runner, validate-pr reports that nimbus is missing instead of running the local checks. The compile check is marked skipped and the dependency check reports an error. The run may still pass, so a green run is not proof that local validation ran. Check the job log for the nimbus message, or install nimbus through a channel your security review approves.
The boundary checks use nimbus graph. The actions do not start a nimbus daemon.
Org mode
Org mode deploys the changed packages to a scratch org from a pool. Each pull request reuses its own org for a set time, so repeated pushes to the same PR do not consume a new org each time.
- The action fetches an org from the pool named by
pool-tag. It caches the org for the pull request, keyed by PR number, forcache-ttl-hours. The default is 4 hours. Set it to0to disable the cache. - Unlocked packages are always deployed as source in org mode. A pull request never creates a package version. This guide's workflows create package versions only in
buildon pushes tomain, so concurrent pull requests do not compete for build numbers. That split is a recommended workflow convention; sfpm does not enforce it. - The pool must have orgs in it. Schedule the
fill-poolaction to keep it stocked, and useclean-poolto remove stale orgs.
Filling the pool
This job keeps a scratch org pool stocked on a weekday schedule.
on:
schedule:
- cron: '0 6 * * 1-5'
workflow_dispatch:
permissions:
contents: read
jobs:
fill:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Install Salesforce CLI
run: pnpm add -g @salesforce/cli
- name: Authenticate DevHub
env:
SFDX_CONSUMER_KEY: ${{ secrets.SFDX_CONSUMER_KEY }}
SFDX_JWT_KEY: ${{ secrets.SFDX_JWT_KEY }}
DEVHUB_USERNAME: ${{ secrets.DEVHUB_USERNAME }}
run: |
printf '%s' "$SFDX_JWT_KEY" > server.key
sf org login jwt \
--client-id "$SFDX_CONSUMER_KEY" \
--jwt-key-file server.key \
--username "$DEVHUB_USERNAME" \
--set-default-dev-hub \
--alias devhub
rm server.key
- name: Fill pool
uses: b64hub/sfpm/packages/actions/fill-pool@vX.Y.Z
with:
devhub-username: devhub
pool-tag: ci-pool
max-allocation: '10'The DevHub authentication step is the same as in the validation workflow below.
Changed-package detection
When you do not set packages, validate-pr validates only the packages that changed between the pull request's base commit and its head. The action runs git diff for this. The checkout must include the base commit, so set fetch-depth: 0 on actions/checkout.
Without full history, the diff fails. The action then logs a warning and validates all packages. Full history keeps validation limited to the changed packages.
packagesoverrides detection. The action validates exactly the packages you list.base-refoverrides the base commit used for the diff. It applies only whenpackagesis not set.
Workflow: local mode
name: SFPM Validate PR
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Validate PR
id: validate
uses: b64hub/sfpm/packages/actions/validate-pr@vX.Y.ZInstall nimbus on the runner before the validate step, through a channel your security review approves. The action does not install it.
Workflow: org mode
name: SFPM Validate PR
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Install Salesforce CLI
run: pnpm add -g @salesforce/cli
- name: Authenticate DevHub
env:
SFDX_CONSUMER_KEY: ${{ secrets.SFDX_CONSUMER_KEY }}
SFDX_JWT_KEY: ${{ secrets.SFDX_JWT_KEY }}
DEVHUB_USERNAME: ${{ secrets.DEVHUB_USERNAME }}
run: |
printf '%s' "$SFDX_JWT_KEY" > server.key
sf org login jwt \
--client-id "$SFDX_CONSUMER_KEY" \
--jwt-key-file server.key \
--username "$DEVHUB_USERNAME" \
--set-default-dev-hub \
--alias devhub
rm server.key
- name: Validate PR
id: validate
uses: b64hub/sfpm/packages/actions/validate-pr@vX.Y.Z
with:
mode: org
devhub-username: devhub
pool-tag: ci-pool
cache-ttl-hours: '6'
- name: Report outcome
if: always()
run: |
echo "Success: ${{ steps.validate.outputs.success }}"
echo "Org: ${{ steps.validate.outputs.org-username }}"
echo "Cache hit: ${{ steps.validate.outputs.cache-hit }}"Replace vX.Y.Z with the release tag you pin. See Versioning and pinning.
Repository secrets are not passed to workflows triggered by pull requests from forks. Org mode fails for those pull requests. Use local mode for fork pull requests, or review fork pull requests before they run with secrets.
Outputs
| Output | Meaning |
|---|---|
success | Whether all validations passed. |
org-username | Username of the scratch org used. Empty in local mode. |
org-id | Org ID of the scratch org used. Empty in local mode. |
cache-hit | Whether a cached org for this PR was reused. |
cache-age-minutes | Age of the reused org in minutes. Empty unless cache-hit is true. |
pr-number | Number of the validated pull request. |
result | Full JSON result with per-package outcomes, including coverage. |
For the full list of inputs and outputs, see each action's action.yml. For the permissions and network access the action needs, see Security and network.
Build, validate, and publish
A CI workflow that builds packages, resolves async validation for unlocked packages, and publishes only the packages that passed. Has a known DevHub-wiring limitation at the default settings, covered below.
CLI reference
Grouped summary of every sfpm command, its global flags, and the environment variables it reads.