Skip to content
Latchkey

CI workflow (trailheadapps/lwc-recipes)

The CI workflow from trailheadapps/lwc-recipes, explained and optimized by Latchkey.

C

CI health: C - fair

Run this on Latchkey for self-healing, caching, and up to 58% lower cost.

Grade your own workflow free or run it on Latchkey →
Source: trailheadapps/lwc-recipes.github/workflows/ci.ymlLicense CC0-1.0View source

What it does

This is the CI workflow from the trailheadapps/lwc-recipes repository, a real project running GitHub Actions. It is shown here with attribution under its CC0-1.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
# Unique name for this workflow
name: CI

# Definition when the workflow should run
on:
    workflow_dispatch:
    push:
        branches:
            - main
        paths-ignore:
            - 'sfdx-project.json'
            - 'README.md'

# Jobs to be executed
jobs:
    format-lint-lwc-tests:
        runs-on: trailheadapps-Ubuntu
        steps:
            # Checkout the source code
            - name: 'Checkout source code'
              uses: actions/checkout@v4

            # Install Volta to enforce proper node and package manager versions
            - name: 'Install Volta'
              uses: volta-cli/action@v4

            # Cache node_modules to speed up the process
            - name: 'Restore node_modules cache'
              id: cache-npm
              uses: actions/cache@v4
              with:
                  path: node_modules
                  key: npm-${{ hashFiles('**/package-lock.json') }}
                  restore-keys: |
                      npm-${{ env.cache-name }}-
                      npm-

            # Install npm dependencies for Prettier and Jest
            - name: 'Install npm dependencies'
              if: steps.cache-npm.outputs.cache-hit != 'true'
              run: HUSKY=0 npm ci

            # Prettier formatting
            - name: 'Code formatting verification with Prettier'
              run: npm run prettier:verify

            # Install Salesforce CLI
            - name: 'Install Salesforce CLI'
              run: |
                  npm install @salesforce/cli --location=global
                  nodeInstallPath=$(npm config get prefix)
                  echo "$nodeInstallPath/bin" >> $GITHUB_PATH
                  cd "$nodeInstallPath/bin"
                  ./sf --version

            # Install Salesforce CLI Code Analyzer plugin
            - name: 'Install Salesforce CLI Code Analyzer plugin'
              run: sf plugins install code-analyzer

            # Run Code Analyzer
            - name: 'Run Code Analyzer'
              id: run-code-analyzer
              uses: forcedotcom/run-code-analyzer@v2
              with:
                  run-arguments: --workspace "force-app" --view detail --output-file "sca-results.csv" --config-file "code-analyzer.yml"
                  results-artifact-name: code-analyzer-results

            # Check for Code Analyzer critical or high severity violations
            - name: 'Check for Code Analyzer critical or high severity violations'
              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 One of more Code Analyzer critical or high severity violations found
                  exit 1

            # LWC unit tests
            - name: 'Unit test Lightning Web Components'
              run: npm run test:unit:coverage

            # Upload code coverage data
            - name: 'Upload code coverage for LWC to Codecov.io'
              uses: codecov/codecov-action@v4
              with:
                  token: ${{ secrets.CODECOV_TOKEN }}
                  flags: LWC

    scratch-org-test:
        runs-on: trailheadapps-Ubuntu
        needs: format-lint-lwc-tests
        if: github.actor != 'dependabot[bot]'
        steps:
            # Checkout the source code
            - name: 'Checkout source code'
              uses: actions/checkout@v4

            # Install Salesforce CLI
            - name: 'Install Salesforce CLI'
              run: |
                  npm install @salesforce/cli --location=global
                  nodeInstallPath=$(npm config get prefix)
                  echo "$nodeInstallPath/bin" >> $GITHUB_PATH
                  sf --version

            # Store secret for dev hub
            - name: 'Populate auth file with DEVHUB_SFDX_URL secret'
              shell: bash
              run: |
                  echo ${{ secrets.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt
                  secretFileSize=$(wc -c "./DEVHUB_SFDX_URL.txt" | awk '{print $1}')
                  if [ $secretFileSize == 1 ]; then
                      echo "Missing DEVHUB_SFDX_URL secret. Is this workflow running on a fork?";
                      exit 1;
                  fi

            # Authenticate dev hub
            - name: 'Authenticate Dev Hub'
              run: sf org login sfdx-url -f ./DEVHUB_SFDX_URL.txt -a devhub -d

            # Create scratch org
            - name: 'Create scratch org'
              run: sf org create scratch -f config/project-scratch-def.json -a scratch-org -d -y 1

            # Deploy source to scratch org
            - name: 'Push source to scratch org'
              run: sf project deploy start

            # Assign permissionset
            - name: 'Assign permissionset to default user'
              run: sf org assign permset -n recipes

            # Import sample data
            - name: 'Import sample data'
              run: sf data tree import -p ./data/data-plan.json

            # Run Apex tests in scratch org
            - name: 'Run Apex tests'
              run: sf apex test run -c -r human -d ./tests/apex -w 20

            # Upload code coverage data
            - name: 'Upload code coverage for Apex to Codecov.io'
              uses: codecov/codecov-action@v4
              with:
                  token: ${{ secrets.CODECOV_TOKEN }}
                  flags: Apex

            # Housekeeping
            - name: 'Delete scratch org'
              if: always()
              run: sf org delete scratch -p -o scratch-org

    trigger-packaging:
        runs-on: trailheadapps-Ubuntu
        needs: scratch-org-test
        steps:
            # Checkout the source code
            - name: 'Checkout source code'
              uses: actions/checkout@v4

            # Check for package changes using git diff
            - name: 'Check for package changes'
              id: checkForChanges
              run: |
                  git fetch origin ${{ github.event.before }} --depth=1
                  changedPaths=$( git diff-tree --name-only ${{ github.event.before }} $GITHUB_SHA )
                  set +e
                  hasChanges='false'
                  if [ $(echo "$changedPaths" | grep -c '^force-app') == 1 ]; then
                      hasChanges='true'
                  fi
                  echo "hasChanges=$hasChanges" >> $GITHUB_OUTPUT

            # Trigger packaging workflow if needed
            - name: 'Trigger packaging workflow if needed'
              uses: peter-evans/repository-dispatch@v3
              if: steps.checkForChanges.outputs.hasChanges == 'true'
              with:
                  token: ${{ secrets.BOT_ACCESS_TOKEN }}
                  event-type: start-packaging
                  client-payload: '{ "ref": "${{ github.ref }}", "sha": "${{ github.sha }}" }'

The same workflow, on Latchkey

Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.

# Unique name for this workflow
name: CI
 
# Definition when the workflow should run
on:
    workflow_dispatch:
    push:
        branches:
            - main
        paths-ignore:
            - 'sfdx-project.json'
            - 'README.md'
 
# Jobs to be executed
concurrency:
    group: ${{ github.workflow }}-${{ github.ref }}
    cancel-in-progress: true
 
jobs:
    format-lint-lwc-tests:
        timeout-minutes: 30
        runs-on: trailheadapps-Ubuntu
        steps:
            # Checkout the source code
            - name: 'Checkout source code'
              uses: actions/checkout@v4
 
            # Install Volta to enforce proper node and package manager versions
            - name: 'Install Volta'
              uses: volta-cli/action@v4
 
            # Cache node_modules to speed up the process
            - name: 'Restore node_modules cache'
              id: cache-npm
              uses: actions/cache@v4
              with:
                  path: node_modules
                  key: npm-${{ hashFiles('**/package-lock.json') }}
                  restore-keys: |
                      npm-${{ env.cache-name }}-
                      npm-
 
            # Install npm dependencies for Prettier and Jest
            - name: 'Install npm dependencies'
              if: steps.cache-npm.outputs.cache-hit != 'true'
              run: HUSKY=0 npm ci
 
            # Prettier formatting
            - name: 'Code formatting verification with Prettier'
              run: npm run prettier:verify
 
            # Install Salesforce CLI
            - name: 'Install Salesforce CLI'
              run: |
                  npm install @salesforce/cli --location=global
                  nodeInstallPath=$(npm config get prefix)
                  echo "$nodeInstallPath/bin" >> $GITHUB_PATH
                  cd "$nodeInstallPath/bin"
                  ./sf --version
 
            # Install Salesforce CLI Code Analyzer plugin
            - name: 'Install Salesforce CLI Code Analyzer plugin'
              run: sf plugins install code-analyzer
 
            # Run Code Analyzer
            - name: 'Run Code Analyzer'
              id: run-code-analyzer
              uses: forcedotcom/run-code-analyzer@v2
              with:
                  run-arguments: --workspace "force-app" --view detail --output-file "sca-results.csv" --config-file "code-analyzer.yml"
                  results-artifact-name: code-analyzer-results
 
            # Check for Code Analyzer critical or high severity violations
            - name: 'Check for Code Analyzer critical or high severity violations'
              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 One of more Code Analyzer critical or high severity violations found
                  exit 1
 
            # LWC unit tests
            - name: 'Unit test Lightning Web Components'
              run: npm run test:unit:coverage
 
            # Upload code coverage data
            - name: 'Upload code coverage for LWC to Codecov.io'
              uses: codecov/codecov-action@v4
              with:
                  token: ${{ secrets.CODECOV_TOKEN }}
                  flags: LWC
 
    scratch-org-test:
        timeout-minutes: 30
        runs-on: trailheadapps-Ubuntu
        needs: format-lint-lwc-tests
        if: github.actor != 'dependabot[bot]'
        steps:
            # Checkout the source code
            - name: 'Checkout source code'
              uses: actions/checkout@v4
 
            # Install Salesforce CLI
            - name: 'Install Salesforce CLI'
              run: |
                  npm install @salesforce/cli --location=global
                  nodeInstallPath=$(npm config get prefix)
                  echo "$nodeInstallPath/bin" >> $GITHUB_PATH
                  sf --version
 
            # Store secret for dev hub
            - name: 'Populate auth file with DEVHUB_SFDX_URL secret'
              shell: bash
              run: |
                  echo ${{ secrets.DEVHUB_SFDX_URL }} > ./DEVHUB_SFDX_URL.txt
                  secretFileSize=$(wc -c "./DEVHUB_SFDX_URL.txt" | awk '{print $1}')
                  if [ $secretFileSize == 1 ]; then
                      echo "Missing DEVHUB_SFDX_URL secret. Is this workflow running on a fork?";
                      exit 1;
                  fi
 
            # Authenticate dev hub
            - name: 'Authenticate Dev Hub'
              run: sf org login sfdx-url -f ./DEVHUB_SFDX_URL.txt -a devhub -d
 
            # Create scratch org
            - name: 'Create scratch org'
              run: sf org create scratch -f config/project-scratch-def.json -a scratch-org -d -y 1
 
            # Deploy source to scratch org
            - name: 'Push source to scratch org'
              run: sf project deploy start
 
            # Assign permissionset
            - name: 'Assign permissionset to default user'
              run: sf org assign permset -n recipes
 
            # Import sample data
            - name: 'Import sample data'
              run: sf data tree import -p ./data/data-plan.json
 
            # Run Apex tests in scratch org
            - name: 'Run Apex tests'
              run: sf apex test run -c -r human -d ./tests/apex -w 20
 
            # Upload code coverage data
            - name: 'Upload code coverage for Apex to Codecov.io'
              uses: codecov/codecov-action@v4
              with:
                  token: ${{ secrets.CODECOV_TOKEN }}
                  flags: Apex
 
            # Housekeeping
            - name: 'Delete scratch org'
              if: always()
              run: sf org delete scratch -p -o scratch-org
 
    trigger-packaging:
        timeout-minutes: 30
        runs-on: trailheadapps-Ubuntu
        needs: scratch-org-test
        steps:
            # Checkout the source code
            - name: 'Checkout source code'
              uses: actions/checkout@v4
 
            # Check for package changes using git diff
            - name: 'Check for package changes'
              id: checkForChanges
              run: |
                  git fetch origin ${{ github.event.before }} --depth=1
                  changedPaths=$( git diff-tree --name-only ${{ github.event.before }} $GITHUB_SHA )
                  set +e
                  hasChanges='false'
                  if [ $(echo "$changedPaths" | grep -c '^force-app') == 1 ]; then
                      hasChanges='true'
                  fi
                  echo "hasChanges=$hasChanges" >> $GITHUB_OUTPUT
 
            # Trigger packaging workflow if needed
            - name: 'Trigger packaging workflow if needed'
              uses: peter-evans/repository-dispatch@v3
              if: steps.checkForChanges.outputs.hasChanges == 'true'
              with:
                  token: ${{ secrets.BOT_ACCESS_TOKEN }}
                  event-type: start-packaging
                  client-payload: '{ "ref": "${{ github.ref }}", "sha": "${{ github.sha }}" }'
 

What changed

4 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.

What Latchkey heals here

This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:

This workflow runs 3 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow