23 KiB
CI/CD Pipeline Templates
Ready-to-use templates for integrating Code Analyzer into CI/CD pipelines.
GitHub Actions (using forcedotcom/run-code-analyzer@v2)
The official GitHub Action for Code Analyzer. It orchestrates the run, uploads artifacts, and optionally creates PR reviews with violation counts.
Action inputs:
| Input | Required | Default | Description |
|---|---|---|---|
run-arguments |
No | --view detail --output-file sfca_results.json |
Flags passed to sf code-analyzer run |
results-artifact-name |
No | salesforce-code-analyzer-results |
Name of the uploaded ZIP artifact |
github-token |
No | (none) | Enables PR reviews + *-in-changed-files outputs |
Action outputs: exit-code, num-violations, num-sev1-violations through num-sev5-violations, num-violations-in-changed-files, num-sev1-violations-in-changed-files through num-sev5-violations-in-changed-files, review-id.
Key run-arguments flags:
| Flag | Purpose | Example |
|---|---|---|
--workspace |
Root directory to scan (scans all eligible files recursively) | --workspace . |
--target |
Specific files/directories to scan (comma-separated or repeated) | --target force-app/main/default/classes --target force-app/main/default/triggers |
--rule-selector |
Which rules to run | Recommended, all, all:Security, pmd, "Severity:1,2" |
--config-file |
Path to code-analyzer.yml — omit if no config file exists |
--config-file code-analyzer.yml |
--output-file |
Output file (repeat for multiple formats; format inferred from extension) | --output-file results.html --output-file results.sarif |
--view |
Output verbosity | detail or summary |
--workspace vs --target:
- Use
--workspace .for full-repo scans (most common in CI) - Use
--target <path>to scan specific directories (monorepos, scoped scans, faster feedback) - If both are omitted, defaults to current working directory
- You can combine:
--workspace . --target force-app/scans onlyforce-app/within the workspace
Basic Quality Gate (Pull Requests)
# .github/workflows/code-analyzer.yml
name: Salesforce Code Analyzer
on:
pull_request:
branches: [main, develop]
jobs:
code-analysis:
permissions:
pull-requests: write
contents: read
actions: read
runs-on: ubuntu-latest
steps:
- name: Check out files
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '>=20.9.0'
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '>=11'
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli@latest
- name: Install Code Analyzer Plugin
run: sf plugins install code-analyzer@latest
- name: Run Salesforce Code Analyzer
id: run-code-analyzer
uses: forcedotcom/run-code-analyzer@v2
with:
run-arguments: --workspace . --rule-selector Recommended --output-file sfca_results.html --output-file sfca_results.json
results-artifact-name: code-analyzer-results
github-token: ${{ github.token }}
- name: Check Quality Gate (Changed Files Only)
if: |
steps.run-code-analyzer.outputs.num-sev1-violations-in-changed-files > 0 ||
steps.run-code-analyzer.outputs.num-sev2-violations-in-changed-files > 0
run: |
echo "Critical/High violations found in changed files!"
echo "Sev1: ${{ steps.run-code-analyzer.outputs.num-sev1-violations-in-changed-files }}"
echo "Sev2: ${{ steps.run-code-analyzer.outputs.num-sev2-violations-in-changed-files }}"
exit 1
Strict Quality Gate (All Files + SARIF)
# .github/workflows/code-analyzer-strict.yml
name: Salesforce Code Analyzer (Strict)
on:
pull_request:
branches: [main]
push:
branches: [main]
jobs:
code-analysis:
permissions:
pull-requests: write
contents: read
actions: read
security-events: write
runs-on: ubuntu-latest
steps:
- name: Check out files
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '>=20.9.0'
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '>=11'
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli@latest
- name: Install Code Analyzer Plugin
run: sf plugins install code-analyzer@latest
- name: Run Salesforce Code Analyzer
id: run-code-analyzer
uses: forcedotcom/run-code-analyzer@v2
with:
run-arguments: --workspace . --rule-selector Recommended --output-file sfca_results.html --output-file sfca_results.json --output-file sfca_results.sarif --config-file code-analyzer.yml
results-artifact-name: code-analyzer-results
github-token: ${{ github.token }}
- name: Upload SARIF to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: sfca_results.sarif
- name: Check Quality Gate (All Files)
if: |
steps.run-code-analyzer.outputs.exit-code > 0 ||
steps.run-code-analyzer.outputs.num-sev1-violations > 0 ||
steps.run-code-analyzer.outputs.num-sev2-violations > 0
run: |
echo "Quality gate failed!"
echo "Total violations: ${{ steps.run-code-analyzer.outputs.num-violations }}"
echo "Sev1: ${{ steps.run-code-analyzer.outputs.num-sev1-violations }}"
echo "Sev2: ${{ steps.run-code-analyzer.outputs.num-sev2-violations }}"
exit 1
Security-Focused (AppExchange Prep)
# .github/workflows/code-analyzer-security.yml
name: Security Analysis
on:
pull_request:
branches: [main]
schedule:
- cron: '0 6 * * 1' # Weekly Monday 6am
jobs:
security-scan:
permissions:
pull-requests: write
contents: read
actions: read
security-events: write
runs-on: ubuntu-latest
steps:
- name: Check out files
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '>=20.9.0'
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '>=11'
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli@latest
- name: Install Code Analyzer Plugin
run: sf plugins install code-analyzer@latest
- name: Run Security Scan
id: run-code-analyzer
uses: forcedotcom/run-code-analyzer@v2
with:
run-arguments: --workspace . --rule-selector "all:Security" --output-file security-results.html --output-file security-results.json --output-file security-results.sarif --config-file code-analyzer.yml
results-artifact-name: security-scan-results
github-token: ${{ github.token }}
- name: Upload SARIF to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: security-results.sarif
- name: Fail on Any Security Violations
if: steps.run-code-analyzer.outputs.num-sev1-violations > 0
run: |
echo "Critical security violations found!"
echo "Sev1: ${{ steps.run-code-analyzer.outputs.num-sev1-violations }}"
exit 1
With Path Filters (Faster Feedback)
Use paths: to only trigger the workflow when relevant source files change. This avoids running scans on README edits, CI config changes, etc.
# .github/workflows/code-analyzer-filtered.yml
name: Salesforce Code Analyzer
on:
pull_request:
branches: [main, develop]
paths:
- 'force-app/**'
- '**/*.cls'
- '**/*.trigger'
- '**/*.js'
- '**/*.ts'
- '**/*.html'
- '**/*.flow-meta.xml'
- 'code-analyzer.yml'
jobs:
code-analysis:
permissions:
pull-requests: write
contents: read
actions: read
runs-on: ubuntu-latest
steps:
- name: Check out files
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '>=20.9.0'
- name: Setup Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '>=11'
- name: Install Salesforce CLI
run: npm install -g @salesforce/cli@latest
- name: Install Code Analyzer Plugin
run: sf plugins install code-analyzer@latest
- name: Run Salesforce Code Analyzer
id: run-code-analyzer
uses: forcedotcom/run-code-analyzer@v2
with:
run-arguments: --workspace . --rule-selector Recommended --output-file sfca_results.html --output-file sfca_results.json
results-artifact-name: code-analyzer-results
github-token: ${{ github.token }}
- name: Check Quality Gate
if: |
steps.run-code-analyzer.outputs.num-sev1-violations-in-changed-files > 0 ||
steps.run-code-analyzer.outputs.num-sev2-violations-in-changed-files > 0
run: exit 1
When to use path filters:
- Use when the repo contains non-Salesforce code (docs, scripts, infra) that shouldn't trigger scans
- Always include
code-analyzer.ymlin paths so config changes trigger a validation run - Don't use if you want every PR to get a scan regardless of what changed
With Flow Engine (Python Required)
If the project contains .flow-meta.xml files and uses the Flow engine, Python 3.10+ must be installed:
- name: Setup Python (required for Flow engine)
uses: actions/setup-python@v5
with:
python-version: '>=3.10'
Insert this step after the Java setup step. If you're unsure whether the project uses Flows, include it — it adds ~5s and avoids a partial scan failure.
Monorepo / Scoped Scan
For monorepos where Salesforce code lives in a subdirectory, use --target to scope the scan:
- name: Run Salesforce Code Analyzer
id: run-code-analyzer
uses: forcedotcom/run-code-analyzer@v2
with:
run-arguments: --workspace . --target packages/salesforce-app/force-app --rule-selector Recommended --output-file sfca_results.html --output-file sfca_results.json
results-artifact-name: code-analyzer-results
github-token: ${{ github.token }}
For multiple packages:
--target packages/app-a/force-app --target packages/app-b/force-app
Without a Config File
If the project has no code-analyzer.yml, simply omit --config-file. Code Analyzer uses built-in defaults:
- name: Run Salesforce Code Analyzer
id: run-code-analyzer
uses: forcedotcom/run-code-analyzer@v2
with:
run-arguments: --workspace . --rule-selector Recommended --output-file sfca_results.html --output-file sfca_results.json
results-artifact-name: code-analyzer-results
github-token: ${{ github.token }}
To conditionally use a config file if it exists:
- name: Check for config file
id: config-check
run: |
if [ -f "code-analyzer.yml" ]; then
echo "config-flag=--config-file code-analyzer.yml" >> $GITHUB_OUTPUT
else
echo "config-flag=" >> $GITHUB_OUTPUT
fi
- name: Run Salesforce Code Analyzer
id: run-code-analyzer
uses: forcedotcom/run-code-analyzer@v2
with:
run-arguments: --workspace . --rule-selector Recommended --output-file sfca_results.html --output-file sfca_results.json ${{ steps.config-check.outputs.config-flag }}
results-artifact-name: code-analyzer-results
github-token: ${{ github.token }}
Jenkins
Jenkinsfile (Declarative Pipeline)
// Jenkinsfile
pipeline {
agent {
docker {
image 'node:20'
args '-v /usr/local/share/java:/usr/local/share/java'
}
}
environment {
JAVA_HOME = '/usr/lib/jvm/java-11-openjdk-amd64'
}
stages {
stage('Setup') {
steps {
sh 'npm install -g @salesforce/cli@latest'
sh 'sf plugins install code-analyzer@latest'
}
}
stage('Code Analysis') {
steps {
sh '''
sf code-analyzer run \
--workspace . \
--rule-selector Recommended \
--output-file results.json \
--output-file results.html \
--config-file code-analyzer.yml 2>&1 | tee sfca_output.txt
'''
script {
def output = readFile('results.json')
def json = new groovy.json.JsonSlurper().parseText(output)
def sev1Count = json.violations?.count { it.severity == 1 } ?: 0
def sev2Count = json.violations?.count { it.severity == 2 } ?: 0
if (sev1Count > 0 || sev2Count > 0) {
error("Quality gate failed! Sev1: ${sev1Count}, Sev2: ${sev2Count}")
}
}
}
post {
always {
archiveArtifacts artifacts: 'results.*', allowEmptyArchive: true
publishHTML(target: [
reportName: 'Code Analyzer Report',
reportDir: '.',
reportFiles: 'results.html'
])
}
}
}
}
post {
failure {
echo 'Code analysis found violations above severity threshold!'
}
}
}
GitLab CI
# .gitlab-ci.yml
code-analysis:
image: node:20
stage: test
before_script:
- apt-get update && apt-get install -y openjdk-11-jdk python3
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
- npm install -g @salesforce/cli@latest
- sf plugins install code-analyzer@latest
script:
- |
sf code-analyzer run \
--workspace . \
--rule-selector Recommended \
--output-file results.json \
--output-file results.html \
--config-file code-analyzer.yml
- |
# Quality gate: fail on sev1 or sev2 violations
SEV1=$(cat results.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(sum(1 for v in d.get('violations',[]) if v.get('severity')==1))")
SEV2=$(cat results.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(sum(1 for v in d.get('violations',[]) if v.get('severity')==2))")
echo "Sev1: $SEV1, Sev2: $SEV2"
if [ "$SEV1" -gt 0 ] || [ "$SEV2" -gt 0 ]; then
echo "Quality gate failed!"
exit 1
fi
artifacts:
paths:
- results.json
- results.html
reports:
codequality: results.json
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
Bitbucket Pipelines
# bitbucket-pipelines.yml
pipelines:
pull-requests:
'**':
- step:
name: Code Analysis
image: node:20
script:
- apt-get update && apt-get install -y openjdk-11-jdk
- export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
- npm install -g @salesforce/cli@latest
- sf plugins install code-analyzer@latest
- |
sf code-analyzer run \
--workspace . \
--rule-selector Recommended \
--output-file results.json \
--output-file results.html \
--config-file code-analyzer.yml
- |
# Quality gate: fail on sev1 or sev2 violations
SEV1=$(cat results.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(sum(1 for v in d.get('violations',[]) if v.get('severity')==1))")
SEV2=$(cat results.json | python3 -c "import sys,json; d=json.load(sys.stdin); print(sum(1 for v in d.get('violations',[]) if v.get('severity')==2))")
echo "Sev1: $SEV1, Sev2: $SEV2"
if [ "$SEV1" -gt 0 ] || [ "$SEV2" -gt 0 ]; then
echo "Quality gate failed!"
exit 1
fi
artifacts:
- results.json
- results.html
Configuration Tips for CI/CD
Quality Gating Strategy
With forcedotcom/run-code-analyzer@v2, you control the quality gate via output checks:
| Strategy | Condition | Use Case |
|---|---|---|
| Block on critical only | num-sev1-violations > 0 |
Permissive — only block security vulnerabilities |
| Block on critical + high | num-sev1-violations > 0 || num-sev2-violations > 0 |
Recommended for most teams |
| Block on changed files only | num-sev1-violations-in-changed-files > 0 |
Great for legacy codebases — don't block on pre-existing issues |
| Block on total count | num-violations > 10 |
Budget approach — allow some violations but cap total |
| Zero tolerance | num-violations > 0 |
For new greenfield projects only |
For non-GitHub platforms (Jenkins, GitLab, Bitbucket), parse results.json to count violations by severity and fail the pipeline accordingly.
Output Formats
Specify multiple --output-file flags (format inferred from extension):
| Format | Extension | Best For |
|---|---|---|
| SARIF | .sarif |
GitHub Code Scanning integration (requires security-events: write permission) |
| JSON | .json |
Programmatic processing, quality gating scripts, custom dashboards |
| HTML | .html |
Human-readable reports in artifacts |
| CSV | .csv |
Spreadsheet analysis |
| XML | .xml |
Legacy tool integration |
Caching for Faster CI
# GitHub Actions: Cache sf CLI plugins
- uses: actions/cache@v4
with:
path: ~/.local/share/sf/
key: sf-plugins-${{ hashFiles('**/code-analyzer.yml') }}
restore-keys: sf-plugins-
Recommended PR Workflow
- On PR open/update: Use
forcedotcom/run-code-analyzer@v2withgithub-token— gate on changed-files outputs only - On merge to main: Full scan with SARIF upload to GitHub Security tab
- Weekly schedule: Full security scan with
--rule-selector "all:Security"
When to Use Path Filters
| Situation | Use Path Filters? | Reasoning |
|---|---|---|
| Mixed repo (docs, infra, salesforce code) | Yes | Avoid wasting CI minutes on non-code PRs |
| Pure Salesforce project | Optional | Every PR likely touches scannable files anyway |
| You want config changes to trigger a scan | Yes, include code-analyzer.yml |
Validates config changes don't break the scan |
| Monorepo with multiple apps | Yes, scope to your package path | Avoid scanning unrelated packages |
Composition & Adaptation Guide
When a user's request doesn't match an existing template exactly, compose from these building blocks:
Prerequisites Block (always required)
# ALWAYS include these — the action does NOT install them
- uses: actions/setup-node@v4 # Node >= 20.9.0
- uses: actions/setup-java@v4 # Java >= 11 (for PMD/CPD/SFGE)
- uses: actions/setup-python@v5 # Python >= 3.10 (ONLY if project has .flow-meta.xml files)
- run: npm install -g @salesforce/cli@latest
- run: sf plugins install code-analyzer@latest
Composing run-arguments
Build the run-arguments string by combining these independent flags as needed:
| Need | Flag to Add |
|---|---|
| Scan the whole repo | --workspace . |
| Scan specific directory | --target force-app/main/default/classes |
| Scan multiple directories | --target dir1 --target dir2 |
| Use recommended rules | --rule-selector Recommended |
| Use all rules | --rule-selector all |
| Security rules only | --rule-selector "all:Security" |
| Specific engine only | --rule-selector pmd or --rule-selector eslint |
| Specific severity filter | --rule-selector "Severity:1,2" |
| Combined selector | --rule-selector "pmd:Security:(1,2)" |
| Apply custom config | --config-file code-analyzer.yml |
| No custom config | (omit --config-file entirely) |
| HTML output | --output-file results.html |
| JSON output | --output-file results.json |
| SARIF for GitHub Security | --output-file results.sarif |
| Multiple formats | --output-file a.html --output-file b.json --output-file c.sarif |
| Verbose output | --view detail |
Permissions Required
| Feature | Permission Needed |
|---|---|
| PR review comments | pull-requests: write |
| Upload SARIF to Security tab | security-events: write |
| Private repo checkout | contents: read |
| Download artifacts | actions: read |
Decision Tree for Template Selection
User wants CI/CD for Code Analyzer
├── Platform?
│ ├── GitHub Actions → Use forcedotcom/run-code-analyzer@v2
│ ├── Jenkins → Use Jenkinsfile template + JSON parsing for quality gate
│ ├── GitLab → Use .gitlab-ci.yml template + script-based quality gate
│ └── Bitbucket → Use bitbucket-pipelines.yml template + script-based quality gate
├── Scope?
│ ├── Full repo → --workspace .
│ ├── Specific folder → --target <path>
│ └── Changed files only → use github-token + *-in-changed-files outputs (GitHub only)
├── Strictness?
│ ├── Block on sev1 only → check num-sev1-violations
│ ├── Block on sev1+sev2 → check both (RECOMMENDED DEFAULT)
│ ├── Block on any violation → check num-violations
│ └── Legacy codebase → use *-in-changed-files to avoid blocking on old debt
├── Has config file?
│ ├── Yes → add --config-file code-analyzer.yml
│ ├── No → omit --config-file
│ └── Maybe → use conditional check pattern
├── Has Flow files?
│ ├── Yes → add actions/setup-python step
│ └── No → skip Python setup
└── Wants GitHub Security integration?
├── Yes → add --output-file *.sarif + upload-sarif step + security-events: write
└── No → skip SARIF
Common User Requests → Modifications
| User Says | What to Change |
|---|---|
| "Only scan on PRs to main" | Set on: pull_request: branches: [main] |
| "Scan only Apex classes" | Use --target force-app/main/default/classes |
| "Don't fail on existing violations" | Use *-in-changed-files outputs for quality gate |
| "I want to see results in GitHub Security tab" | Add --output-file *.sarif + upload-sarif step + security-events: write |
| "Run a nightly full scan" | Add schedule: - cron: '0 0 * * *' trigger |
| "We don't have a config file" | Remove --config-file from run-arguments |
| "Only run security rules" | Change --rule-selector to "all:Security" |
| "Block the PR if there are more than 5 violations" | Change gate to num-violations > 5 |
| "We have Flows in our project" | Add actions/setup-python@v5 with python-version: '>=3.10' |
| "Scan only what changed in the PR" | Use github-token + gate on *-in-changed-files outputs |
| "We're a monorepo" | Use --target packages/my-sf-app/force-app instead of --workspace . |
| "Cache the plugin install" | Add actions/cache@v4 step for ~/.local/share/sf/ |
| "I want an HTML report I can download" | Add --output-file results.html (auto-uploaded via results-artifact-name) |