Using Salesforce DX with GitHub Actions

GitHub Actions are one of the new cool kids in town. They are deeply embedded into the GitHub experience and aim to help you automating and simplifying your common tasks around your GitHub repositories. In this blog post we cover GitHub Actions: what they are, how they fit into the Continuous Integration (CI) pipeline for your Salesforce DX projects, and how you can use them.

3…2…1… Actions!

Let’s start with what a GitHub Action is. From the official GitHub documentation:

Actions are individual tasks that you can combine to create jobs and customize your workflow. You can create your own actions, and use and customize actions shared by the GitHub community.

Basically, think of it this way: you can run an action for everything a user does on a GitHub repository. They are not limited to code-related tasks, you can run actions when an issue gets opened, a new GitHub package is released, or when you clean up your wiki. And they can do whatever you code.

From the point of view of the consumer of an action, the usage would look like this:

# Checkout the code in the pull request
- name: 'Checkout source code'
  uses: actions/checkout@v1
# ESlint
- name: 'Lint Lightning Web Components'
  run: npm run lint:lwc

The first item is an action we use from the checkout GitHub repository, which checks out the source of the current GitHub repository. This is, in my opinion, one of the powerful aspects of GitHub Actions – you can centralize your custom functionality or use community-build actions like those from the GitHub Marketplace, and then re-use it across many repositories, similar to CircleCI Orbs. The second action is a simple command-line invocation of an npm script. Which means you can basically run anything against your source code like you would do on your local Windows, Linux, or macOS machine.

If you’re interested in not only consuming, but also building a GitHub Action I highly recommend the GitHub Action developer section. It shows step-by-step instructions on how to get started.

Now, an action defines what to run. But you need a place to define when, how and in which order. For that, you define a GitHub workflow.

GitHub workflows

A GitHub workflow is a YAML based configuration file in which you define the order of execution for the to-be-used GitHub Action(s), their configuration, and when the workflow should run. In a simplified way this is what it looks like:

Ok, that was quite a lot of information! But we aren’t quite finished. Let’s walk through the base parts of a GitHub workflow configuration file to make it more practical. You store workflow configuration files within your GitHub repository under ./github/workflows/. The system automatically picks them up from there. At the beginning of a workflow configuration file you define–based on which GitHub events you want –the workflow to run. Let’s look at an example:

on:
    pull_request:
        types: [opened, synchronize, reopened]
        branches:
            - master
        paths:
            - 'force-app/**'

This workflow gets executed for

  • all new/changed pull requests on a repository and
  • when the target branch for the pull request is the master branch and
  • when files within the force-app directory have changed.

You can have many different workflows at the same time for the same event, or combine the usage of different events into the same workflow. The different event options are described here in the GitHub documentation.

Next you define the jobs (you need at least one). A job represents a group of GitHub Actions that belong together.

jobs:
    validate-branch-name:
        runs-on: ubuntu-latest
        steps:
            - name: Validate branch name requirements
              uses: deepakputhraya/action-branch-name@master
              with:
                  regex: '([a-z])+\/([a-z])+'
                  min_length: 6

This configuration shows the jobs section of a workflow that we use to validate proper branch naming on our repositories.
We use only one job – validate-branch-name. Every job needs a virtual environment to run on. You can choose from the default provided environments (Linux, Windows, macOS), or even provide your own. The default virtual environments come already with a plethora of software pre-installed, which makes your DevOps team lives much easier.

Next, you define steps. A step is basically just a single GitHub Action. You can have many different steps, or only a single step, based on your needs.

As every action runs in its own isolated context within a workflow, you can make use of the GitHub workflow syntax to make them work together. The workflow configuration allows you to define dependencies like when an action needs the execution of another action as a pre-condition, or you want to consume output of one action in another action, and more.

# Update sfdx-project.json and README to use only latest package version
- name: 'Update sfdx-project.json and README'
  id: packaging-updater
  uses: muenzpraeger/github-action-sfdx-packaging-updater@master

# Re-add changes back to source
- name: 'Add new package version, updated sfdx-project.json and README to source'
  if: steps.packaging-updater.outputs.isSuccess
  uses: Automattic/action-commit-to-branch@master
  with:
      branch: ${{ github.head_ref }}
      commit_message: 'Added new package version ID'
  env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The first action is a custom action hosted here on GitHub. I’m not going into the details of what it does (you can read the comment ;-)). The important information here is that it produces an output variable isSuccess. That variable is then used in the second action within an if condition. That way you can chain actions, exclude action execution based on conditions, and more. (One tip: use the set-output method to create output variables from shell execution, as there is no predefined variable.)

Salesforce CLI and GitHub Actions

The previous section describes what a GitHub Action is and how to use it. What can you do with GitHub Actions for Salesforce DX? Almost any and everything that you can imagine 🙂

The possibilities are endless but to get your imagination flowing, here are some examples that we have used it for in our Trailhead Sample Gallery Apps.

  • We automatically run ESLint tto ensure that each PR doesn’t introduce any LWC violations.
  • We create and test a new unlocked 2GP package version when we open a PR against the default branch.
  • We run all unit tests, LWC and Apex, and store them in a 3rd party system to see how our code coverage evolves.

There are multiple ways you can use GitHub Actions in combination with the Salesforce CLI. One option is to use the (unofficial) GitHub Action on the forcedotcom repo here. That action is a Docker-based action with the most current Salesforce CLI. You use this action by providing the CLI command via the args parameter.

# Create scratch org
- name: 'Create scratch org'
  uses: forcedotcom/salesforcedx-actions@master
  with:
      args: 'force:org:create -f config/project-scratch-def.json -a scratch-org -s -d 1'

Using the action is simple, maintenance-free for you, and sufficient for many use cases. However it has a few limitations that you should be aware of. You can only run a single command, as that’s how Docker-based actions work. And that command has to be an sfdx command. Also you can’t set another working directory other than the main one. So within a monorepo like Redwoods Insurance you won’t be able to use it.

The second option is to install the Salesforce CLI, either via a binary file or directly from npm, into the workflow job default environment. For the Trailhead Sample Gallery Apps we use the binary installation.

# Install Salesforce CLI
- name: Install Salesforce CLI
  run: |
      wget https://developer.salesforce.com/media/salesforce-cli/sfdx-linux-amd64.tar.xz
      mkdir sfdx-cli
      tar xJf sfdx-linux-amd64.tar.xz -C sfdx-cli --strip-components 1
      ./sfdx-cli/install

You may wonder why we don’t use the provided Docker image? We could have used the Docker image for most of the apps. At the same time we wanted more flexibility. And we wanted to have the same setup across all sample apps. And why not the npm installation of the Salesforce CLI? Good question. The main reason is that you have to install the Salesforce CLI globally via npm. While you can do this (you have to sudo the install command) you may run into filesystem permission issues when installing additional CLI plugins. These issues don’t occur with the binary installation option.

Considerations and examples

We could have added many different examples on how to configure actions here but then this blog post would be mostly long YAML files. Instead we recommend you look at what we did within the sample apps. On a high level our workflows look like this:

The example flow is not complete, as every sample app is a bit different. The great advantage of having the Salesforce CLI is that you can make the workflow your own, based on your needs. And all the example GitHub workflow configurations can also be applied as similar recommendations for any CI/CD system:

  • Be as specific as possible when defining which GitHub event should cause your workflow to run. For example we only run CI/CD when content within a Salesforce DX package directory changed. That way we prevent runs, for example, on simple README changes.
  • Split different tasks into different workflows. It may look like more work as you get setup, but it will give you much better control and maintainability in the long run.
  • Run all tasks first that don’t need a scratch org, like Prettier verification or unit tests. Based on your Salesforce license entitlements you have a specific amount of daily and active scratch orgs. You don’t want to waste a scratch org just because someone forgot to lint their code appropriately.
  • Automate and extend shared tasks with your own custom actions. For the sample apps we always update the README with the latest package version id. Now, with GitHub Actions, we let the system do that automatically for us.

What’s Next?

GitHub Actions is a great way to automate any of your GitHub interactions. Given the tight integration you get a lot of contextual information, and access, to your GitHub repositories. I encourage you to test them out. Dig into the sample app repositories to learn how to apply actions and workflows for your projects. Here are some great resources to get started:

GitHub Actions Documentation
GitHub Marketplace: Actions
Salesforce CLI documentation
Trailhead: Git and GitHub Basics
Demo repo: Salesforce DX and GitHub Kanban board

GitHub Actions are a flexible way to customize your CI/CD flow with Salesforce DX. We have just scratched the surface of what’s possible in this blog post. If you are using Github Actions in your own Salesforce projects, let us know! We are always interested to see what the Salesforce developer community is coming up with.

And if GitHub Actions are not for you or your company – you should take a look here where you’ll find many example configurations for other CI/CD systems. With the flexibility that the Salesforce CLI gives you, setting up and using CI/CD is much more approachable and easier to manage nowadays.

About the auhor

René Winkelmeyer works as Architect, Developer Evangelism, at Salesforce. He focuses on enterprise integrations, Lightning, and all the other cool stuff that you can do with the Salesforce Platform. You can follow him on Twitter @muenzpraeger.