Setting up Automated Release Workflow with GitHub Actions

Your quick guide into Continuous Integration / Continuous Deployment (CI/CD) workflow with GitHub Actions.

Anton Biriukov
6 min readFeb 3, 2021
A cargo ship departing into the sea
Photo by Chris Pagan on Unsplash

“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:

Changelog 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 :)

--

--

Anton Biriukov

Enthusiastic Junior Software Developer striving for discoveries & curious about technology