Skip to Content
CLICI/CD

CI/CD Integration

sfdt is built to run unattended. When stdin is not a TTY, commands run non-interactively — confirmation prompts are skipped and the command exits with an appropriate code.

Non-interactive mode

  • When stdin isn’t a TTY, sfdt skips confirm prompts and proceeds with safe defaults.
  • The VS Code extension and other automated callers set SFDT_NON_INTERACTIVE=true so the CLI never hangs waiting for input.
  • Destructive data operations require an explicit flag non-interactively (e.g. sfdt data delete <set> --yes).

Exit codes

All sfdt commands exit 0 on success and 1 on failure. Gate subsequent steps with set -e or by checking $?. sfdt changelog check exits 1 when the changelog needs updating, so it works as a pre-merge gate.

JSON output

Most operational commands accept --json for machine-readable output — audit, monitor, drift, rollback, doctor, scan, data, docs, flow, scratch, feature-flags, extension status, and others.

--json emits a Salesforce-native envelope on stdout, matching the shape sf itself uses so sfdt composes in the same pipelines:

// success { "status": 0, // numeric exit code (0 = success) "result": { /* … */ }, // the command payload "warnings": [] } // failure { "status": 1, // exit code: 1 general, 2 config, 3 connectivity "name": "Error", "message": "…", "exitCode": 1, "warnings": [] }

Pipe it into jq and read the payload from .result:

sfdt drift --json | jq '.result.driftStatus' sfdt audit --json | jq '.status' # 0 when healthy

The envelope is a stdout contract. The on-disk snapshot files some commands also write (logs/audit-latest.json, logs/monitor-latest.json, …) keep their raw payload shape — they are the result payload without the envelope wrapper.

Generating a pipeline — sfdt ci init

sfdt ci init generates a ready-to-run pipeline for your Git provider — four workflow types: scheduled org monitoring, PR smart-deploy validation, approval-gated release deploys, and scratch-org CI.

sfdt ci init --provider github --type monitor --cron "0 6 * * *" sfdt ci init --provider gitlab --type deploy --org uat --auth jwt sfdt ci init --provider github --type release --branch main --environment production sfdt ci init --provider azure --type scratch --org my-devhub sfdt ci init --provider github --type deploy --runner action # one-step workflows via the GitHub Action sfdt ci init --provider bitbucket --type deploy --print # print instead of write

sfdt monitor schedule --provider github is a thin alias for ci init --type monitor (it also accepts --auth and --runner).

FlagDefaultNotes
--provider <name>(required)github | gitlab | azure | bitbucket
--type <type>monitormonitor (scheduled health) | deploy (PR validation) | release (real deploy) | scratch (scratch-org CI)
--auth <method>config ci.authMethod or sfdx-urlsfdx-url | jwt — swaps the generated auth steps and the documented secrets
--runner <name>config ci.runner or npxnpx | docker | action. docker (GitLab/Bitbucket only): job runs on ghcr.io/scoobydrew83/sfdt:latest — no per-run CLI installs. action (GitHub only; monitor/deploy/release): one uses: scoobydrew83/sfdt@v0 step replaces setup/auth — see GitHub Action. Scratch is excluded from action because it drives raw sf commands with their own always-run cleanup
--cron <expr>0 6 * * *Schedule for monitor workflows
--org <alias>config.defaultOrgTarget org alias embedded in the workflow. For --type scratch this is the Dev Hub alias
--branch <name>config defaultBranch or mainProtected branch that triggers a release workflow
--environment <name>config ci.environment or productionApproval environment for release workflows
--delta-base <ref>deploy: main; release: HEAD~1Base ref for the smart-deploy delta. Release templates resolve the last git tag at runtime (git describe); this flag is only the no-tags fallback
--definition-file <path>config scratch.definitionFile or config/project-scratch-def.jsonScratch-org definition (scratch type)
--node <version>22Node.js version for the CI runner — @sfdt/cli requires Node ≥ 22.15
--outprovider conventionOutput path override
--printPrint to stdout instead of writing
--forceOverwrite an existing file
--jsonMachine-readable result

Workflow types

  • monitor — scheduled org health: runs sfdt monitor all --notify --json on the --cron schedule. See Notifications.
  • deploy — PR validation: runs an advisory sfdt quality scan, then sfdt deploy --smart --dry-run against the PR’s base branch (full git history is fetched so the delta can be computed). On GitHub the quality scan emits SARIF and uploads it to the Security → Code scanning tab (the generated workflow includes permissions: security-events: write). The scan is non-blocking by design. See Smart Deploy and sfdt quality.
  • release — approval-gated real deploy from the protected --branch: deploys the delta since the last git tag with deploy --smart --notify. Tag each release (e.g. sfdt release <version> or sfdt deploy --tag) so the delta stays small.
  • scratch — scratch-org CI: creates a 1-day scratch org from the Dev Hub, deploys source, runs RunLocalTests with coverage, optionally runs LWC Jest tests (the step is emitted commented-out unless a Jest setup is detected in the project), and always deletes the org (if: always() / after_script / condition: always() / after-script, per provider).

Release approval gating, per provider

  • GitHub — the job runs in a GitHub Environment: add required reviewers under Settings → Environments, and scope the auth secret to the environment.
  • GitLab — the job is when: manual and bound to an environment: protect it via Settings → CI/CD → Protected environments.
  • Azure — uses a deployment: job bound to an Environment: create it first (Pipelines → Environments) and add “Approvals and checks”; plain script jobs do not honor approvals.
  • Bitbucket — the step is bound to a deployment environment; restricting deployers requires Premium. The template header shows a manual-trigger custom: pipeline alternative for other plans.

Where files land

  • GitHub.github/workflows/ (e.g. sfdt-monitor.yml, sfdt-deploy.yml)
  • GitLab / Azure / Bitbucket → a standalone fragment under .sfdt/ci/ for you to merge/include (these providers use a single top-level pipeline file).

Required secrets

Secrets depend on --auth (they’re listed in the generated file’s header and the post-generate hint):

  • sfdx-urlSFDX_AUTH_URL (from sf org auth show-sfdx-auth-url)
  • jwtSFDX_CONSUMER_KEY, SFDX_JWT_SECRET_KEY (the key contents, not a path), SFDX_USERNAME, optional SFDX_INSTANCE_URL
  • optional for monitor/release: SLACK_WEBHOOK_URL, TEAMS_WEBHOOK_URL

Add them in your provider’s secret store:

  • GitHub → Settings → Secrets and variables → Actions
  • GitLab → Settings → CI/CD → Variables (masked)
  • Azure → Pipelines → Library / pipeline variables (secret)
  • Bitbucket → Repository settings → Pipelines → Variables (secured)

For the full auth setup — retrieving the auth URL, creating the JWT connected app and certificate, multiline-key handling, rotation, and troubleshooting — see CI Authentication.

Defaults for --auth, --runner, and --environment can be set once in the ci config block — flags override config; config overrides built-in defaults.

GitHub Actions

For a single-step setup, use the official GitHub Actionuses: scoobydrew83/sfdt@v0 handles Node, the CLI install, and org auth. Or script it yourself:

- name: Preflight and deploy run: | sfdt preflight sfdt test sfdt deploy --skip-preflight env: SFDX_AUTH_URL: ${{ secrets.SFDX_AUTH_URL }}

Explain failures automatically:

- name: Explain failure if: failure() run: sfdt explain --latest

For AI commands in CI, install and authenticate Gemini or OpenAI before running sfdt. Claude requires an interactive session and isn’t suitable for CI. See AI Providers.

Docker

For a self-contained toolchain (Node + sf CLI + git/jq/bash), run sfdt from the official Docker image — see Docker.

Last updated on