In my twelve-factors methodology blog post I have mentioned Semantic Versioning (SemVer) which defines a set of rules on how the version should be incremented on new changes. A few weeks later I have been searching for tools which can generate changelog from commits and bump up the version in the package.json
file for me. Unsurprisingly, there are many packages available for this and semantic-release stands out of the crowd and offers many features beyond what I am after. A rough workflow is as follows:
- Commit new changes to the dev branch. Merge new features into the main branch.
- CI/CD pipeline runs all tests on new codes.
- Semantic-release package analysis commit messages, increment the version number in the
package.json
file accordingly, update the changelog file, and make a new commit to include all files that have changed. - Semantic-release package creates a new release on GitHub/GitLab.
- A new production is built and deployed.
At the moment you can have automated releases pushed to NPM, GitHub, and GitLab, or all of them if you wish to. In this post, I will give a simple guide on how to set up this package in GitHub actions and GitLab CI/CD pipeline for any JavaScript project.
Prerequisite
- You need to have the package manager npm installed.
- A GitHub/GitLab account.
- You are committed to write commit messages that follows certain convention, e.g. Conventional commits, Angular.
GitHub
The following steps will help you install and configure the semantic-release
package for use in GitHub actions.
-
Install the package using
npm install --save-dev semantic-release
. -
Install extra plugins:
npm install @semantic-release/git @semantic-release/changelog conventional-changelog-conventionalcommits -D
-
Add a new file called
release.config.js
to the root directory containing the following (see this [link] for detailed configuration):module.exports = { defaultBranch: 'main', // change this to match your repo branches: [ '+([0-9])?(.{+([0-9]),x}).x', 'main', 'next', 'next-major', { name: 'beta', prerelease: true }, { name: 'alpha', prerelease: true }, ], plugins: [ [ '@semantic-release/commit-analyzer', { preset: 'conventionalcommits', // see the official manual for more details releaseRules: './releaseRules.js', // custom release rules parserOpts: { noteKeywords: ['BREAKING CHANGE', 'BREAKING CHANGES'], }, }, ], [ '@semantic-release/release-notes-generator', { preset: 'conventionalcommits', presetConfig: { types: [ // map commit type to sections in the changelog. // If your commit message does not include any of these, it will not but included in the changelog { type: 'feat', section: '✨ Features', hidden: false, // whether to show this type of changes in the changelog }, { type: 'fix', section: '🐛 Bug Fixes', hidden: false, }, { type: 'docs', section: '📝 Documentation', hidden: false, }, { type: 'style', section: '🎨 Styles', hidden: false, }, { type: 'refactor', section: '♻️ Code Refactoring', hidden: false, }, { type: 'perf', section: '⚡️ Performance Improvement', hidden: false, }, { type: 'test', section: '✅ Testing', hidden: false, }, { type: 'build', section: '🔨 Build/Dependencies', hidden: false, }, { type: 'ci', section: '🔧 Continuous Integration', hidden: false, }, { type: 'chore', hidden: true, }, ], }, }, ], [ '@semantic-release/changelog', { changelogFile: 'CHANGELOG.md', // location of the changelog }, ], [ '@semantic-release/npm', { npmPublish: false, // disabled if you are not publish package to NPM }, ], '@semantic-release/github', [ '@semantic-release/git', { assets: [ // files to include with the release commit 'CHANGELOG.mdx', 'package.json', 'package-lock.json', 'npm-shrinkwrap.json', ], }, ], ], };
Add another file
releaseRules.js
containing all release rules:module.exports = [ { type: 'feat', release: 'minor' }, { type: 'fix', release: 'patch' }, { type: 'perf', release: 'patch' }, { type: 'docs', scope: 'new-*', release: 'minor' }, // e.g. docs(new-blog): my new blog post { type: 'docs', release: 'patch' }, { type: 'refactor', scope: 'core-*', release: 'minor' }, // e.g. refactor(core-utils): modify ... file { type: 'refactor', release: 'patch' }, { type: 'style', release: 'patch' }, { type: 'test', release: 'patch' }, { type: 'build', release: 'patch' }, { type: 'ci', release: 'patch' }, { type: 'chore', release: 'patch' }, { breaking: true, release: 'major' }, { revert: true, release: 'patch' }, { scope: 'no-release', release: false }, ];
Note that the version number is expressed as
major.minor.patch
. -
Run
npx semantic-release --dry-run
(or-d
) to do a dry-run which skips the prepare, publish, success and fail steps, and to get a preview of the pending release. In this step you will get an error something likeSemanticReleaseError: No GitHub token specified
. The reason is obvious, we have included the plugin@semantic-release/github
but didn’t pass a GitHub personal token. Thesemantic-release
package is supposed to be used and run in the CI pipeline after all tests are passed. To pass a GitHub token simply runGH_TOKEN=<your_github_token> npx semantic-release -d
.To actually making releases from a local machine (not recommended) you can run
GH_TOKEN=<your_github_token> npx semantic-release --no-ci
(or--ci false
) to skip Continuous Integration environment verifications. -
Add
npx semantic-release
to your GitHub Actions deployment workflows:
- name: Semantic Release
run: npx semantic-release
Some tips
- Make sure the repo url in the
package.json
file matches the one you are working on to avoid pushing to a different repository. - The latest tag must be in the format of
vx.x.x
, otherwise the version number will start fromv1.0.0
. - If you are using the
husky
package, addHUSKY=0
beforenpx semantic-release
to disable it in CI. - To set the author of releases, pass the built-in token
{{ secrets.GITHUB_TOKEN }}
to eitherGH_TOKEN
orGITHUB_TOKEN
variable in GitHub actions workflow files. - To set the author & committer details, include the following environment variables:
env:
GIT_AUTHOR_NAME: 'github-actions-bot'
GIT_AUTHOR_EMAIL: 'support+actions@github.com'
GIT_COMMITTER_NAME: 'github-actions-bot'
GIT_COMMITTER_EMAIL: 'support+actions@github.com'
- If you want to push to protected branches, make sure you have included the following in the workflow:
- uses: actions/checkout@v3
with:
ref: master
persist-credentials: false
...
- name: Semantic Release
run: HUSKY=0 npx semantic-release
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
// both GH token are included where GITHUB_TOKEN is the default one
...
Once you have included this package in the CI/CD pipeline, there is no more you need to do other than creating more fancy features to your applications!
GitLab
The set up for GitLab CI/CD is identical to GitHub except the following:
- Install
@semantic-release/gitlab
and replace'@semantic-release/github'
with'@semantic-release/gitlab'
in therelease.config.js
file. - Add
npx semantic-release
to the.gitlab-ci.yml
file.
Result
Click here to see an example of the end result, and the corresponding markdown file. In addition, if you have linked any issue or pull request in the commit message, the semantic-release package will also add a comment in the relevant issue/PR to say this is included in the new release. How exciting!
Other languages?
This package will also work with languages other than JavaScript, and there is an official guidance on this.