Setting up Automated Release Workflow with GitHub Actions
Your quick guide into Continuous Integration / Continuous Deployment (CI/CD) workflow with GitHub Actions.
“There should be two tasks for a human being to perform to deploy software into a development, test, or production environment: to pick the version and environment and to press the “deploy” button.”
― David Farley, Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation
In this article, we will review the typical software release process and how it can be automated with GitHub Actions.
Automation with GitHub Actions Workflows
GitHub Actions and Workflows
GitHub Actions is a great tool for continuous integration (CI) and continuous deployment (CD). It allows us to set up automated workflows directly in our GitHub repository. Moreover, Marketplace would give you access to numerous ready-to-use actions prepared by the open-source community with love. You are free to combine multiple actions in any shape and form to generate a workflow that would solve your tasks the best. A workflow file is a place where you specify all instructions for the action to execute.
YAML
GitHub Actions stick to .yml
(YAML) workflow files. YAML stands for ‘yet-another-markdown-language’ and is quite a common choice in CI/CD pipelines. It features a fairly simple syntax and this article will set you up to start coding in a manner of minutes. Also, make sure to check official documentation in case you still have some questions.
Release Process Overview
Tests
The first thing that we want to do before release a new version of the software is to make sure it’s working. Unit tests, snapshots, functional tests, linter, prettier…there can be a long list of tasks which we’d want to execute before proceeding with the release. With GitHub Actions, we can create a job to run all of those tasks for us, automatically. Let’s take a look at the following workflow example:
jobs:
build:
runs-on: ${{matrix.os}}strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [12.x, 14.x]steps:
- uses: actions/checkout@v2
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-- name: Test
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}- run: npm install
- run: cp env.example .env
- run: npm run test
Prettier-Check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install && npm run prettier-check
Lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- run: npm install && npm run eslint
The code above would specify three jobs, which would run tests, prettier and lint our code.
Git Tagging
Like many other version control systems, git
provides the ability to mark certain points in the code history as important through git tags
. Tags can contain a simple tagging message as well as other metadata about the user. They are usually used to mark versions of the software. For instance, in Telescope we use the Semantic Versioning system. Thus, our tags would like somewhat like this: 1.0.0
, 1.1.0
, 1.2.0
, 1.3.0
, etc. Tags can also play another major role — they can trigger our workflow file. For example, we can specify the following:
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- '[0-9]+.[0-9]+.[0-9]+'
Unfortunately, regex are not fully supported in YAML files, so we cannot use the officially recommended regex to verify the semantic versioning tag:
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
As a result, we just perform a simple regex '[0–9]+.[0–9]+.[0–9]+'
to check the tag. Learn more about Git Tagging here ant about GitHub filter patterns here.
Version Bump
Another thing that you would likely want to do upon your release is to increase the version of your software. For web projects, this usually means that the version
field in package.json
has to be updated. There are a few alternatives to how we can approach this step. The most obvious option is to use npm-version
, a built-in tool in npm cli
. We a free to configure steps to execute in the process of updating the version. For instance, we can specify the number of steps to be executed when we run npm version
:
"scripts": {
"preversion": "npm test",
"version": "npm run build && git add -A dist",
"postversion": "git push && git push --tags && rm -rf build/temp"
}
Alternatively, there is a number of actions available on the GitHub Marketplace, for example the Package Version Check Action. This ready-to-use action will check the latest git tag
and update the version in package.json
accordingly. The action will generate an authorized commit and push it directly to the branch you are running the release workflow on. The only downside is that the update in package.json will be added in a subsequent commit, which will not match the one that was tagged.
We can add this action to our workflow file like the following:
# Update version in package.json according to the tag
- name: Check package version
uses: technote-space/package-version-check-action@v1
Changelog
Changelog is essential a summary of the changes that were made in the code since the last release. With help of version control systems, such as `git`, and GitHub Actions it is possible to automate the generation of the changelog. For instance, we could use the generate-changelog-action in our workflow file like the following:
- name: Changelog
uses: scottbrenner/generate-changelog-action@master
id: Changelog
env:
REPO: ${{ github.repository }}
This is how the generated changelog would like on the GitHub Release page:
GitHub Release
Finally, we would want to create a release on the GitHub repository. For this, we would use the create-release action. It will create a release through GitHub Release API. It is fairly simple to set up in our workflow file as well:
- name: Create Release
id: create_release
uses: actions/create-release@latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
body: |
${{ steps.Changelog.outputs.changelog }}
draft: false
prerelease: false
Publishing a Release
So, let’s release it! At first, we need to put all of the steps from above together in a .yml
file under .github/workflows
folder in our repository and push the update to GitHub. See release.yml
file from Telescope project for reference.
At this point, we are a couple of command-line instructions away from publishing a new version of our project on GitHub. Depending on whether we use npm-version
or action to bump the version there will be two sequences of commands.
In the case of npm-version
, we should do the following:
npm version minor -m "Release 1.6.0"
git push upstream master
git push upstream 1.6.0
For GitHub action:
git tag -a 1.6.0 -m "Release 1.6.0
git push upstream 1.6.0
Congrats! We have just automated our release process and made our first automated release :)