Sets up Salesforce CI/CD pipelines using GitHub Actions, JWT auth, SF CLI v2 scratch org workflows, sandbox promotion, deployments, and Apex testing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/salesforce-claude-code:sf-devops-ci-cdThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Reference: @../_reference/DEPLOYMENT_CHECKLIST.md, @../_reference/DOCKER_CI_PATTERNS.md
Reference: @../_reference/DEPLOYMENT_CHECKLIST.md, @../_reference/DOCKER_CI_PATTERNS.md
sf org login web --alias myOrg # Browser-based login
sf org login jwt --client-id <id> --jwt-key-file server.key --username [email protected] --alias ci-org
sf org list # List all authenticated orgs
sf org open --target-org myOrg # Open org in browser
sf org create scratch --definition-file config/project-scratch-def.json --alias myScratch --duration-days 7
sf org delete scratch --target-org myScratch --no-prompt
sf org display --target-org myOrg # Show org details including access token
sf project deploy start --source-dir force-app --target-org myOrg
sf project deploy start --manifest manifest/package.xml --target-org myOrg
sf project deploy validate --manifest manifest/package.xml --target-org myOrg
sf project deploy quick --job-id <id> --target-org myOrg
sf project retrieve start --source-dir force-app --target-org myOrg
sf project deploy start --source-dir force-app --test-level RunLocalTests --target-org myOrg
sf apex run --file scripts/apex/setup.apex --target-org myOrg
sf apex run test --test-level RunLocalTests --result-format human --target-org myOrg
sf apex run test --class-names AccountServiceTest --result-format json --target-org myOrg
sf apex run test --test-level RunAllTestsInOrg --code-coverage --result-format json --output-dir results/
sf apex tail log --target-org myOrg # Stream live debug logs
JWT auth enables non-interactive CI/CD authentication without browser prompts.
http://localhost:1717/OauthRedirectapi, refresh_token, offline_accessopenssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr
openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt
base64 -i server.key | tr -d '\n' # Encode for GitHub Secrets storage
SALESFORCE_JWT_SECRET_KEY -- base64-encoded server.key contentSALESFORCE_CONSUMER_KEY -- Connected App Consumer KeySALESFORCE_USERNAME -- target org username# .github/workflows/ci.yml
name: Salesforce CI/CD
on:
push:
branches: [develop, staging, main]
pull_request:
branches: [develop, staging, main]
jobs:
validate-pr:
name: Validate Pull Request
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install SF CLI
run: npm install -g @salesforce/cli
- name: Authenticate to sandbox
env:
JWT_SECRET_KEY: ${{ secrets.SALESFORCE_JWT_SECRET_KEY }}
CONSUMER_KEY: ${{ secrets.SALESFORCE_CONSUMER_KEY }}
USERNAME: ${{ secrets.SALESFORCE_USERNAME_SANDBOX }}
run: |
echo "$JWT_SECRET_KEY" | base64 --decode > server.key
sf org login jwt \
--client-id "$CONSUMER_KEY" \
--jwt-key-file server.key \
--username "$USERNAME" \
--alias validation-org \
--set-default
rm server.key
- name: Validate deployment
run: |
sf project deploy validate \
--source-dir force-app \
--test-level RunLocalTests \
--target-org validation-org \
--wait 30
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: production
steps:
- uses: actions/checkout@v4
- name: Install SF CLI
run: npm install -g @salesforce/cli
- name: Authenticate to Production
env:
JWT_SECRET_KEY: ${{ secrets.SALESFORCE_PROD_JWT_SECRET_KEY }}
CONSUMER_KEY: ${{ secrets.SALESFORCE_PROD_CONSUMER_KEY }}
USERNAME: ${{ secrets.SALESFORCE_PROD_USERNAME }}
run: |
echo "$JWT_SECRET_KEY" | base64 --decode > server.key
sf org login jwt \
--client-id "$CONSUMER_KEY" \
--jwt-key-file server.key \
--username "$USERNAME" \
--instance-url https://login.salesforce.com \
--alias prod \
--set-default
rm server.key
- name: Validate deployment
id: validate
run: |
VALIDATION_RESULT=$(sf project deploy validate \
--source-dir force-app \
--test-level RunLocalTests \
--target-org prod \
--wait 60 \
--json)
echo "VALIDATION_JOB_ID=$(echo "$VALIDATION_RESULT" | jq -r '.result.id')" >> "$GITHUB_ENV"
- name: Quick deploy
run: |
sf project deploy quick \
--job-id "$VALIDATION_JOB_ID" \
--target-org prod \
--wait 10
Do not use
--use-most-recentfor quick deploy in multi-team orgs. Another team's deployment between validate and quick-deploy invalidates the job. Always pass--job-idexplicitly.
feature/ABC-123-account-service
|
v
develop ---- CI: validate + deploy to dev sandbox
|
v
staging ---- CI: validate + deploy to staging sandbox (RunLocalTests)
|
v
main ---- CI: deploy to production (RunLocalTests)
feature/* -- individual work, scratch org per developerdevelop -- integration branch, auto-deploys to dev sandboxstaging -- pre-production, mirrors production as closely as possiblemain -- production. Requires pull request review + CI greentest-in-scratch-org:
name: Test in Scratch Org
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/heads/feature/')
steps:
- uses: actions/checkout@v4
- name: Install SF CLI and authenticate Dev Hub
env:
JWT_SECRET_KEY: ${{ secrets.DEVHUB_JWT_SECRET_KEY }}
CONSUMER_KEY: ${{ secrets.DEVHUB_CONSUMER_KEY }}
USERNAME: ${{ secrets.DEVHUB_USERNAME }}
run: |
npm install -g @salesforce/cli
echo "$JWT_SECRET_KEY" | base64 --decode > server.key
sf org login jwt \
--client-id "$CONSUMER_KEY" \
--jwt-key-file server.key \
--username "$USERNAME" \
--alias devhub \
--set-default-dev-hub
rm server.key
- name: Create scratch org
run: |
sf org create scratch \
--definition-file config/project-scratch-def.json \
--alias ci-scratch \
--set-default \
--duration-days 1 \
--no-ancestors
- name: Push source and run tests
run: |
sf project deploy start --source-dir force-app --target-org ci-scratch
sf apex run test \
--test-level RunLocalTests \
--result-format human \
--code-coverage \
--target-org ci-scratch
- name: Delete scratch org
if: always()
run: sf org delete scratch --target-org ci-scratch --no-prompt
| Environment | Test Level | Rationale |
|---|---|---|
| Feature CI | RunLocalTests | Fast feedback, catches regressions |
| Dev Sandbox | RunLocalTests | Full local test suite |
| Staging | RunLocalTests | Near-production confidence |
| Production | RunLocalTests | Required by Salesforce (75% min) |
| Full release | RunAllTestsInOrg | Complete org-wide regression |
#!/bin/bash
# scripts/get-changed-metadata.sh
BASE_BRANCH=${1:-main}
CHANGED_FILES=$(git diff --name-only origin/$BASE_BRANCH...HEAD)
SF_CHANGED=$(echo "$CHANGED_FILES" | grep "^force-app/")
if [ -z "$SF_CHANGED" ]; then
echo "No Salesforce metadata changes detected"
exit 0
fi
# Use sfdx-git-delta plugin
# Verify command syntax with: sf sgd --help
sf sgd:source:delta \
--to HEAD \
--from origin/$BASE_BRANCH \
--output-dir changed-sources \
--generate-delta
# Deploy only changed sources
TEST_CLASSES=$(cat changed-sources/test-classes.txt 2>/dev/null | tr '\n' ',' | sed 's/,$//')
if [ -n "$TEST_CLASSES" ]; then
sf project deploy start \
--source-dir changed-sources/force-app \
--test-level RunSpecifiedTests \
--tests "$TEST_CLASSES" \
--target-org $TARGET_ORG
else
sf project deploy start \
--source-dir changed-sources/force-app \
--test-level RunLocalTests \
--target-org $TARGET_ORG
fi
Install sfdx-git-delta plugin:
sf plugins install sfdx-git-delta
sf-deployment-constraints -- deployment safety rulesnpx claudepluginhub jiten-singh-shahi/salesforce-claude-code --plugin salesforce-claude-codeSet up Salesforce CI/CD pipelines with GitHub Actions, SFDX deployments, JWT auth, and Apex testing. For automating metadata validation and tests in Salesforce repos.
Orchestrates Salesforce metadata deployments with sf CLI v2: dry-run validation, targeted deploys, CI/CD workflow advice, scratch-org management, and failure triage.
Provides expert patterns for Salesforce platform development including Lightning Web Components, Apex triggers, REST/Bulk APIs, Connected Apps, and Salesforce DX with scratch orgs and 2GP.