// project 03 DEV

CI/CD Pipeline Blueprint

Set up once with Pipeline Manager, develop daily with Developer Tools

A ready-made release system for Salesforce teams. I built it so each release ships only what actually changed, proves the change with Apex tests, and rolls itself back automatically if anything fails. The same delta-deployment logic is templated across five CI platforms, with secrets handling built in. Two VSCode extensions sit on top: Pipeline Manager sets the pipeline up once per project, and Developer Tools handles the daily development work.

05 ci platforms templated 02 vscode extensions Δ delta-only deploys Auto rollback on failure
pipeline_architecture [01]

How A Release Runs

Every push reads ci-config.json to decide what happens next. A feature branch runs a check-only validation and stops there, so a pull request is proven against the target org before it merges. An environment branch runs the whole sequence: authenticate, work out the delta, deploy it, run the tests, then tag on success or roll back on failure.

flowchart TD
    A["Git Push"] --> B["Read ci-config.json"]
    B --> C{"Branch type?"}

    C -->|"feature/*"| D["feature/* branch"]
    D --> F["Validate only
(check-only)"] F --> G["Apex tests"] G --> H["PR into base"] C -->|"env branch"| E["env branch
(dev / sit / qa / uat / staging / main)"] E --> I["Authenticate org
(SFDX Auth URL)"] I --> J["sfdx-git-delta
detect changed metadata"] J --> K["Generate destructiveChanges.xml
for deletions"] K --> L["sf project deploy start
deploy the delta"] L --> M["sf apex run test
gate on test results"] M --> N{"Success?"} N -->|"Yes"| O["Tag deployed/<env>"] N -->|"No"| P["scripts/rollback.sh"] %% Blueprint tokens as literal hex (Mermaid cannot parse CSS vars or color-mix) classDef trigger fill:#e0be621f,stroke:#e0be62,stroke-width:1.5px; classDef ci fill:#8b93e81f,stroke:#8b93e8,stroke-width:1.5px; classDef sf fill:#62d99a1f,stroke:#62d99a,stroke-width:1.5px; classDef decision fill:#e0be621f,stroke:#e0be62,stroke-width:1.5px; classDef ok fill:#62d99a1f,stroke:#62d99a,stroke-width:1.5px; classDef fail fill:#e697521f,stroke:#e69752,stroke-width:1.5px; class A trigger; class C,N decision; class B,D,E,F,G,H ci; class I,J,K,L,M sf; class O ok; class P fail;
Branch Model
Set in ci-config.json

Map any branch pattern to any org alias. The default uses feature/* for validation, a base branch for integration, and named environment branches (dev, sit, qa, uat, staging, main) for sequential promotion.

First Run
Falls Back to a Full Deploy

With no deployed/<env> tag to diff against, the first run deploys everything from package.xml. Every run after that is a delta from the last successful tag.

core_strategy [02]

Delta Deployment

Each release ships only the things that actually changed. sfdx-git-delta diffs the current commit against the last successful deploy tag to work out exactly what moved, including anything deleted, so removals get cleaned out of Salesforce in the same release. A commit that only touches docs skips the deploy entirely: an empty delta never fires.

Faster Deploys
Deploy Only What Changed

A one-file change deploys one file. No full-org deploy on every push, so release time does not balloon as the org grows.

Smaller Blast Radius
Lower Risk Per Deploy

Touching only the changed components keeps each deploy scoped to the work in flight, which means fewer unintended side effects.

Deletions Handled
Removals Cleaned Up Automatically

Anything deleted from the project is removed from Salesforce in the same release. sfdx-git-delta generates destructiveChanges.xml for it, so there is no hand-maintained delete manifest and nothing gets left behind.

Tag-based accuracy: each successful deploy pushes a deployed/<env> git tag, and the next run diffs from exactly that point. What ships always reflects the real state of the org, not just the previous commit.
ci_platforms [03]

Five Platforms, One Logic

The delta-deploy logic is identical across platforms. Each template just handles its own quirks: how to fetch full git history, what the branch variable is called, how secrets are injected, and how test results are published.

ci_platform_templatesfive templates, same logic
GitHub Actions
.github/workflows/sfdx-ci.yml

fetch-depth: 0 for full history. Secrets via secrets.<NAME>. Test artifacts via upload-artifact.

Azure DevOps
azure-pipelines.yml

Full clone via fetchDepth: '0'. Auth URLs injected via env block. Results via PublishTestResults.

GitLab CI/CD
.gitlab-ci.yml

GIT_DEPTH: 0 for history. Base image node:20. JUnit via artifacts:reports:junit.

Jenkins
Jenkinsfile

GitSCM checkout with shallow: false. Credentials via credentials(). Results via junit post step.

Bitbucket Pipelines
bitbucket-pipelines.yml

clone: depth: full. Base image node:20. Pipelines can be toggled on via REST API.

recovery_and_quality_gate [04]

Automatic Rollback

Each deploy is preceded by a metadata backup via sf project retrieve. If the deploy errors or a test fails, scripts/rollback.sh restores the org from that backup before the pipeline exits.

Apex Test Gating

After each deploy, scripts/run-tests.sh runs local Apex tests via sf apex run test. Results publish as JUnit artifacts, and any failure triggers rollback before the run ends.

rollback_and_test_commandsthe exact CLI calls
# Pre-deploy backup
sf project retrieve start \
--output-dir backups/<env>/<timestamp>
# On failure
scripts/rollback.sh --env staging
sf apex run test \
--test-level RunLocalTests \
--result-format junit \
--output-dir test-results
# Failure → rollback → pipeline fails
security [05]

Secrets Management

SFDX Auth URLs live as CI secrets, never in source. One secret per org alias, named <ORGALIAS_UPPER>_AUTH_URL. The Pipeline Manager extension generates the checklist of exactly which secrets to add.

secret_nameshow to fetch them, what each is for
# Get the auth URL for any org
sf org display \
--verbose --json \
--target-org sit \
| jq -r '.result.sfdxAuthUrl'
Secret name Purpose
DEV_AUTH_URLDev environment, feature validation target
SIT_AUTH_URLSIT environment
QA_AUTH_URLQA environment
UAT_AUTH_URLUAT environment
STAGING_AUTH_URLStaging environment
PROD_AUTH_URLProduction
configuration [06]

ci-config.json

The single source of truth for pipeline behaviour. Generated by the setup wizard, validated by a JSON schema, and read at runtime. Branch-to-org mappings, deploy options and test settings all live here.

ci_config_shapethe generated schema
{
"branchingModel": {
"feature": "feature/*",
"base": "dev",
"environments": [
{ "name": "Dev", "branch": "dev", "orgAlias": "dev" },
{ "name": "SIT", "branch": "sit", "orgAlias": "sit" },
{ "name": "UAT", "branch": "uat", "orgAlias": "uat" },
{ "name": "Production", "branch": "main", "orgAlias": "prod" }
]
},
"deployOptions": {
"checkOnlyOnFeature": true,
"destructiveChanges": true,
"sourceDir": "force-app"
},
"testOptions": { "runAllTests": true }
}
repository_structure [07]

What Gets Generated

The Pipeline Manager extension writes everything from the setup wizard. Nothing here needs to be handwritten.

generated_filesthe full file tree
salesforce-cicd-blueprint/
├── ci-config.json           # Branch-to-org mapping
├── .github/workflows/     # GitHub Actions pipeline
├── azure-pipelines.yml     # Azure DevOps
├── .gitlab-ci.yml          # GitLab CI
├── Jenkinsfile             # Jenkins
├── bitbucket-pipelines.yml  # Bitbucket
├── manifests/
├── package.xml          # Full deploy fallback
└── destructiveChanges.xml
└── scripts/
├── delta-deploy.sh
├── rollback.sh
└── run-tests.sh
editor_extensions [08]

Two extensions. Two jobs.

Two VSCode extensions split the work cleanly. Pipeline Manager is the one-time setup tool: a wizard generates and tracks the pipeline. Developer Tools is the daily driver: deploy, validate, test and manage orgs without leaving the editor.