Note: Check out the entire supplemental source code for this blog series.
Continuous Deployment
In this tutorial, we’ll use the popular library semantic-release to automate releases to npm and github as well as automatically generating release notes, tagging release commits, deprecating old versions of our release, and publishing notifications to Slack.
semantic-release
semantic-release has various plugins that each have different functionality. In this tutorial, we’ll install semantic-release and six different plugins.
Let’s start by running npm install semantic-release --save-dev
.
The default options would serve most teams well, but it defaults to managing several branches and running several plugins. We’ll start blank and add things one at a time so we can get familiar with all the different pieces. First, we’ll create a .releaserc.js
configuration file. We’ll specify that we want semantic release to manage the main
branch and we won’t specify any plugins yet:
./.releaserc.js
module.exports = {
branches: ['main'],
plugins: []
};
Plugin 1 - commit-analyzer
The first plugin we’ll add is the @semantic-release/commit-analyzer plugin. This plugin will scan your commits since your last release and determine if the next release should be major
, minor
, or patch
release in accordance with semantic versioning.
The following is a brief description of SemVer posted on semver.org:
Given a version number MAJOR.MINOR.PATCH, increment the:
- MAJOR version when you make incompatible API changes,
- MINOR version when you add functionality in a backwards compatible manner, and
- PATCH version when you make backwards compatible bug fixes.
Following SemVer and our previous commit conventions, we’ll specify that a chore
should not cause a version bump, a fix
should cause a patch
version bump, a feat
should cause a minor
version bump, and any commit that has the words “BREAKING CHANGE” in it should cause a major
version bump.
This is what our updated configuration looks like now:
./.releaserc.js
...
plugins: [
["@semantic-release/commit-analyzer", {
"releaseRules": [
{ "breaking": true, "release": "major" },
{ "type": "feat", "release": "minor" },
{ "type": "fix", "release": "patch" },
{ "type": "chore", "release": false }
],
"parserOpts": {
"noteKeywords": ["BREAKING CHANGE", "BREAKING CHANGES"]
}
}]
...
Plugin 2 - release-notes-generator
The second plugin we’ll configure is @semantic-release/release-notes-generator. This plugin will generate release notes. There are several options that we could configure, but for this plugin we’ll just leave the default options:
./.releaserc.js
plugins: [
...
"@semantic-release/release-notes-generator"
]
Plugin 3 - github
Next, we’ll install the @semantic-release/github plugin. This will publish a GitHub release and comment on linked pull requests and issues. There are several configuration options for this plugin as well, but we’ll leave the defaults:
./.releaserc.js
plugins: [
...
"@semantic-release/github"
]
Plugin 4 - npm
We’ll setup the @semantic-release/npm plugin next and leave the defaults as well. This plugin will publish our library to npm:
./.releaserc.js
plugins: [
...
"@semantic-release/npm"
]
Plugin 5 - npm-deprecate-old-versions
The @semantic-release-npm-deprecate-old-versions plugin can automatically deprecate old versions of our packages. This is not a built-in plugin so we’ll need to install it with npm install semantic-release-npm-deprecate-old-versions --save-dev
.
We’ll configure it to only support the latest version for each of the two latest major versions and to deprecate the rest:
./.releaserc.js
plugins: [
...
["semantic-release-npm-deprecate-old-versions", {
"rules": [
{
"rule": "supportLatest",
"options": {
"numberOfMajorReleases": 2,
"numberOfMinorReleases": 1,
"numberOfPatchReleases": 1
}
}
"deprecateAll"
]
}]
]
Plugin 6 - slack-bot
Lastly, we’ll install the semantic-release-slack-bot plugin. This plugin will post to Slack whenever a new version is released. This plugin is also not built-in so we’ll need to install it with npm install semantic-release-slack-bot --save-dev
.
Here is the configuration:
./.releaserc.js
plugins: [
...
[
'semantic-release-slack-bot',
{
notifyOnSuccess: true,
notifyOnFail: true
}
]
]
Release workflow
Now we should have semantic-release fully configured. Our .release.js
should look like this:
./.releaserc.js
module.exports = {
branches: ['main'],
plugins: [
[
'@semantic-release/commit-analyzer',
{
releaseRules: [
{ breaking: true, release: 'major' },
{ type: 'feat', release: 'minor' },
{ type: 'fix', release: 'patch' },
{ type: 'chore', release: false }
],
parserOpts: {
noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES']
}
}
],
'@semantic-release/release-notes-generator',
'@semantic-release/github',
'@semantic-release/npm',
[
'semantic-release-npm-deprecate-old-versions',
{
rules: [
{
rule: 'supportLatest',
options: {
numberOfMajorReleases: 2,
numberOfMinorReleases: 1,
numberOfPatchReleases: 1
}
},
'deprecateAll'
]
}
],
[
'semantic-release-slack-bot',
{
notifyOnSuccess: true,
notifyOnFail: true
}
]
]
};
Now we’ll create a new Release
workflow. First we’ll extract out a build action that can be used by both our Build
and Release
workflows:
./.github/actions/build/action.yml
name: Build
runs:
using: "composite"
steps:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 'lts/*'
- name: Install dependencies
run: npm ci
shell: bash
- name: Lint
run: npm run lint
shell: bash
- name: Test
run: npm run test
shell: bash
Our Build
workflow will now have just one step:
./.github/workflows/build.yml
name: Build
on:
pull_request:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
uses: ./.github/actions/build
And our Release
workflow will have two steps:
./.github/workflows/release.yml
name: Release
on:
push:
branches:
- 'main'
jobs:
release:
name: Release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Build
uses: ./.github/actions/build
- name: Release
env:
GITHUB_TOKEN: $
NPM_TOKEN: $
SLACK_WEBHOOK: $
run: npx semantic-release
It’s important that we set our Release
workflow to only run on the main
branch.
You’ll notice that there are three environment variables listed under the env
key in the Release workflow:
- GITHUB_TOKEN: This is automatically provided as a GitHub secret when using GitHub actions, so this will just work.
- NPM_TOKEN: For this we will need to create an npm automation token and add it as a GitHub secret in our repository settings.
- SLACK_WEBHOOK: For this we will need to create a slack app and activate incoming webhooks for a particular channel. This will give you a url in the form of
https://hooks.slack.com/services/XXX/YYY/ZZZ
. This is the url that you must provide as the SLACK_WEBHOOK GitHub secret.
Summary
Now, when we open a pull request our CI system will automatically ensure the code builds and lints as well as all tests pass. Whenever we merge a pull request, our library will automatically be published and our release notes will automatically be generated.
Next, we can look at how to maintain different versions of the same library using maintenance branches.