afv-library/skills/dx-code-analyzer-configure/references/ci-cd-templates.md

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.ymlomit 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 only force-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.yml in 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-
  1. On PR open/update: Use forcedotcom/run-code-analyzer@v2 with github-token — gate on changed-files outputs only
  2. On merge to main: Full scan with SARIF upload to GitHub Security tab
  3. 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)