Maciej Ptak
written byMaciej Ptak
posted on July 4, 2024
DevOps Engineer/Consultant. Specialized in Salesforce and CICD tooling around it. Focused on removing the bottlenecks and streamlining the Salesforce delivery process since 2017.

Simple Salesforce CICD for Developers

Hello Salesforce Developers!

Are you ready to automate your life away with a few lines of YAML? Today, I will walk you through setting up a CI/CD pipeline that's so simple, that even your pet could probably do it.

Overview

The pipeline we will set up today will automatically deploy your code to a Salesforce scratch org, run tests, and handle clean-up tasks whenever you push changes to your main branch or create a pull request.

What's CI/CD? It automates all the boring stuff like unit testing and deployment so you can focus on what developers should do.

The pipeline we will set up today will:

  • Deploy your code to a Salesforce scratch org
  • Run unit tests
  • Upload coverage reports
  • Delete Scratch Org

Prerequisites

Before we start, you need:

  1. GitHub Account: We will create a repository where your Salesforce project will be stored.
  2. Salesforce CLI: Locally installed for initial setup.
  3. Salesforce Dev Hub: Access to a Salesforce Production Org where you can enable Dev Hub (available in Developer, Enterprise, Performance, and Unlimited Editions - more info)
    If you don't have any private Salesforce Org you can create one for free - Developer Edition

Step 1: Create Github Repository

  1. Go to GitHub and create a new repository - https://github.com/new
    You can set it as Public or Private, it is up to you.

  2. Clone the newly created repo on your local PC.
    git clone https://github.com/0ptaq0/simple-cicd.git

Step 2: Generate SFDX Project

  1. Open the newly cloned GIT repository and type the below commands in the bash terminal:
cd ..
sf project generate --name simple-cicd
cd simple-cicd

Step 3: Prepare Your Salesforce Dev Hub

  1. Enable Dev Hub in your Salesforce Production Org:

    • Navigate to Setup, enter Dev Hub in the Quick Find box, then select Dev Hub.
    • Turn on the Enable Dev Hub feature (Be aware it cannot be undone, wise you must be my young Padawan)

Step 4: Generate Dev Hub Auth URL

  1. Open your command line interface.
  2. Log in to your Dev Hub org via Salesforce CLI by running:
    sfdx auth:web:login --setdefaultdevhubusername --setalias myDevHub
  3. Retrieve an SFDX Auth URL which is used in the CI/CD pipeline for authentication:
sfdx force:org:display --target-org myDevHub --verbose --json
OR
sf force org display --target-org myDevHub --verbose --json

SFDX Auth URL

  1. Securely store the sfdxAuthUrl value
    This will be used as a secret in GitHub.
    In my example, it will be like force://PlatformCLI::5Aep..

    • Navigate to your GitHub repository.
    • Go to Settings > Secrets > Actions.
    • Click on New repository secret.
    • Add the following secrets:
    • SFDX_AUTH_URL_DEVHUB: Paste the SFDX Auth URL you generated from your Dev Hub.

    Github Repository Secrets

Step 5: Create GitHub Pipeline

  1. Configure GitHub Actions Workflow
    • In your repository, create a new file under .github/workflows/ named salesforce-ci.yml.
      mkdir -p .github/workflows && touch .github/workflows/salesforce-ci.yml
    • Copy and paste the below CI configuration into this file:
      (be careful about indentations, they do matter in YAML files)
name: Salesforce CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:

jobs:
  deploy-to-scratch-org-and-run-tests:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Salesforce CLI with NPM
        run: |
          npm install @salesforce/cli --global

      - name: Authorize with the dev hub
        run: echo "${{ secrets.SFDX_AUTH_URL_DEVHUB }}" | sf org login sfdx-url --alias DevHub --set-default-dev-hub --sfdx-url-stdin

      - name: Create scratch org
        run: sf org create scratch --definition-file config/project-scratch-def.json --alias ScratchOrg --wait 30 --duration-days 1 --set-default --json

      - name: Push source
        run: sf project deploy start --target-org ScratchOrg --wait 30

      - name: Run tests
        run: sf apex run test --target-org ScratchOrg --code-coverage --result-format human --output-dir ./tests/apex --test-level RunLocalTests --wait 30

      # - name: Upload coverage reports to Codecov
      #   uses: codecov/codecov-action@v4.0.1
      #   with:
      #     token: ${{ secrets.CODECOV_TOKEN }}
      #     flags: Apex

      - name: Delete scratch org
        if: always()
        run: sf org delete scratch --target-org ScratchOrg --no-prompt
  1. Understanding Your GitHub Actions Workflow

    • on: Specifies the GitHub events that trigger the workflow.
    • jobs: Defines the jobs that the workflow will execute. Our job, deploy-to-scratch-org-and-run-tests, performs several steps:
    • Checkout: Checks out your repository content into the GitHub Actions runner.
    • Install Node.js: Salesforce CLI requires Node.js, installed here using a GitHub Action.
    • Install Salesforce CLI with NPM: Installs Salesforce CLI globally on the runner.
    • Authorize with Dev Hub: Uses the stored SFDX Auth URL to authenticate.
    • Create Scratch Org: Generates a temporary org for testing.
    • Push Source and Run Tests: Deploys all your Salesforce Metadata to the scratch org and runs unit tests.
    • Upload Coverage Reports: Optional step to upload test coverage to Codecov - I will cover that later.
    • Delete Scratch Org: Cleans up by deleting the scratch org after tests are complete.
  2. Now you should understand how the pipeline is going to work. Before we see it in action, let's add at least one Salesforce Metadata to be deployed to our Scratch Org. We'll create a simple Apex Class and Unit Test to see if our CICD works.

    • In Visual Studio Code press Ctrl/Cmd + Shift + P and add Apex Class and then Apex Unit Test Class

    • SimpleCalculator Apex Class
public class SimpleCalculator {
    public static Integer addNumbers(Integer num1, Integer num2) {
        return num1 + num2;
    }
}
  • SimpleCalculatorTest - Apex Unit Test Class
@isTest
public class SimpleCalculatorTest {
    @isTest
    static void testAddNumbers() {
        Integer a = 5;
        Integer b = 3;
        Integer result = SimpleCalculator.addNumbers(a, b);
        System.assertEquals(8, result, 'The addNumbers method did not return the expected result.');
    }
}
  1. Nice, we have all we need, now time for testing!

Step 7: Test Your CI/CD Pipeline

  • Commit and Push your changes to the main branch

  • Go to the Actions tab in GitHub to see your workflow in action.

Step 8: Optional - Set Up CodeCov integration

If you want to see Unit Test Coverage information in Pull Requests add Codecov integration.

  1. Login into CodeCov app

  2. Find your repository and click the Configure button
    (if you cannot find your repository - make sure you have installed CodeCov application in your GitHub account)

  3. Add CODECOV_TOKEN repository secret

  4. Uncomment below code in your Pipeline

 - name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@v4.0.1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          flags: Apex
  1. Open Pull Request to the main branch and see beautiful comments with coverage information

Step 9: Optional - Add Static Code Analysis

A quick way to check your code against the most common best practices is to run a static code analysis.

There are multiple tools on the market for that, but here we will use a dedicated tool from Salesforce - Salesforce Code Analyzer - which you can run locally and in CICD!

It comes with built-in scanning engines like:

  • Salesforce Graph Engine
  • PMD
  • ESLint
  • RetireJS

Also, you can customize it by writing your own rules, but come on - this article is about simple CICD, so let's use the default options.

We can implement in 3 steps:

  1. First we need to install it using this command
    sf plugins install @salesforce/sfdx-scanner@latest
  2. Then execute scan
    sf scanner run --target="force-app" --outfile sf-code-scanner-results.html

    You can use flag --severity-threshold i.e. --severity-threshold 3 to throw error if there are rule violations

  3. At last archive the HTML report and that's it!

The Final code assuming CodeCov integration will look like this:

name: Salesforce CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
  workflow_dispatch:

jobs:
  deploy-to-scratch-org-and-run-tests:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install Salesforce CLI with NPM
        run: |
          npm install @salesforce/cli --global
          sf plugins install @salesforce/sfdx-scanner@latest

      - name: Run SF Code Scanner
        run: |
          sf scanner run --target="force-app" --outfile sf-code-scanner-results.html

      - name: Upload SF Code Scanner Analysis
        uses: actions/upload-artifact@v4
        with:
          name: sf-code-scanner-results
          path: |
            sf-code-scanner-results.html

      - name: Authorize with the dev hub
        run: echo "${{ secrets.SFDX_AUTH_URL_DEVHUB }}" | sf org login sfdx-url --alias DevHub --set-default-dev-hub --sfdx-url-stdin

      - name: Create scratch org
        run: sf org create scratch --definition-file config/project-scratch-def.json --alias ScratchOrg --wait 30 --duration-days 1 --set-default --json

      - name: Push source
        run: sf project deploy start --target-org ScratchOrg --wait 30

      - name: Run tests
        run: sf apex run test --target-org ScratchOrg --code-coverage --result-format human --output-dir ./tests/apex --test-level RunLocalTests --wait 30

      - name: Upload coverage reports to Codecov
        uses: codecov/codecov-action@v4.0.1
        with:
          token: ${{ secrets.CODECOV_TOKEN }}
          flags: Apex

      - name: Delete scratch org
        if: always()
        run: sf org delete scratch --target-org ScratchOrg --no-prompt

Conclusion

With this setup, you've created a robust CI/CD pipeline that requires minimal maintenance and helps you ensure the quality and reliability of your Salesforce applications. This pipeline not only automates your deployments but also reinforces best practices by running unit tests automatically and performing static code analysis.

Feel free to adjust the workflow to fit more complex development processes, or keep it as is for a simple, effective CI/CD solution.

Happy coding!

Buy Me A Coffee