Semantic Releases With CI/CD
Semantic versioning is a versioning scheme that aims to communicate the level of compatibility between releases at a glance.
Join the DZone community and get the full member experience.
Join For FreeSoftware is constantly changing — the moment it is released, it begins to grow obsolete. Users need a constant stream of patches and want new features. At the same time, people hate when an update introduces a breaking change, especially when they were not warned about it.
Forever it has been common practice to use version numbers and codenames to track releases. Many projects have incrementing sequences (MS-DOS 6.2, 6.21, 6.22); others use part of the release date (Ubuntu 18.04, 20.04, 22.04). A few have a more whimsical disposition: TeX, for example, uses a numbering scheme that asymptotically approaches π (the current version is 3.141592653), while Metafont does the same with e. Its current version sits at 2.71828182.
What Is Semantic Versioning?
Semantic versioning is a versioning scheme aiming to communicate the compatibility level between releases at a glance. It uses a three-part numbering system: major
.minor
.patch
(e.g. 1.2.3). And is sometimes suffixed with special identifiers such as -alpha
or -rc1
.
Each part has a different meaning:
Major: incrementing this number (1.0.0 -> 2.0.0) indicates users should expect significant breaking changes.
Minor: the minor number (1.0.0 -> 1.1.0) is incremented when non-breaking features and changes have been released. Minor releases should be backward-compatible.
Patch: a patch-level change (1.0.0. -> 1.0.1) is a non-breaking upgrade that introduces low-risk changes like fixing bugs or patching security issues.
A developer can quickly assess the risk of upgrading by comparing version numbers. Major releases are risky and should be planned carefully. Minor and patch-level changes have a much lower chance of introducing incompatibilities and are safer to install.
Automating Versions With Semantic-Release
How do we determine the version number in a semantic versioning scheme? It's a tricky question since a typical release includes dozens of commits. Some contain bug fixes, while others may introduce breaking changes. In such a scenario, the only way to determine the appropriate version is by reviewing each commit individually and assessing the impact. If it sounds like a lot of repetitive and error-prone work that's best done with some automated tool, you're right.
Semantic-release is a versioning tool capable of computing semantic version numbers by reading commit messages. It can also generate release notes and publish packages to GitHub and NPM.
As you might imagine, for this to work, commit messages should follow a predefined pattern:
<header>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
The header is the only mandatory part of the message and should be formatted like this:
<type>(<scope>): <short summary>
The most important component of the header is the type, which helps semantic-release assess the importance of the changes introduced in the commit. The default behavior follows Angular's message format:
Header Type | Result |
---|---|
fix, perf | Bump version to the next patch level (1.0.0 -> 1.0.1) and release |
feat | Bump version to the next minor level (1.0.0 -> 1.1.0) and release |
docs, build, ci, refactor, test | No version bump. No release. |
Regardless of the header type, if the body of the commit message contains the string BREAKING CHANGE
or DEPRECATED
, semantic-release performs a major version increase.
To clarify, let's look at a few examples and their effects:
Commit message | Result |
---|---|
fix(pencil): stop graphite breaking when too much pressure applied | Releases a patch. |
feat(pencil): add 'graphiteWidth' option | Releases a minor version. |
perf(pencil): remove the graphiteWidth option BREAKING CHANGE: The graphiteWidth option has been removed. The default graphite width of 10mm is always used for performance reasons. | Releases a major version. |
How to Get Started With Semantic-Release
While semantic-release is a Node-based application, it supports any language, not just JavaScript or TypeScript. You'll need to have Node and NPM installed to use it, though.
To add semantic releases to your project, follow these steps:
Install and run the semantic-release wizard with
npx semantic-release-cli setup
. When asked which CI platform to use, selectOther
and copy the environment variables shown. You'll get a token for GitHub and, optionally, one for NPM.export GH_TOKEN=ghp_Lw83uUpu4paBlLKQuRijD3NMTDusAL07J89l export NPM_TOKEN=npm_xWtDqAasy9yBTPPuA6QppCJx7JIu5w1009KY8
Go to your project's Git repository. If the project runs on Node.js, add the
semantic-release
package:npm --save-dev semantic-release
Make some code changes and create a commit following the commit guidelines discussed before. For example:
feat: initial commit
Run
npx semantic-release
. In non-CI environments, the tool runs in dry-run mode. The log shows what version would be assigned in the next release (in the example below, v1.0.0).ℹ Running semantic-release version 19.0.5 ⚠ This run was not triggered in a known CI environment, running in dry-run mode. ✔ Allowed to push to the Git repository ✔ Completed step "verifyConditions" of plugin "@semantic-release/npm" ✔ Completed step "verifyConditions" of plugin "@semantic-release/Github" ℹ No git tag version found on branch master ℹ No previous release found, retrieving all commits ℹ There is no previous release, the next release version is 1.0.0 ℹ Start step "generateNotes" of plugin "@semantic-release/release-notes-generator" ✔ Completed step "generateNotes" of plugin "@semantic-release/release-notes-generator" ⚠ Skip v1.0.0 tag creation in dry-run mode ℹ Release note for version 1.0.0: # 1.0.0 (2022-08-23) ### Features * initial commit
When you're ready to release, execute:
npx semantic-release --no-ci
. This will tag the release and publish the package.
You can customize the tool's behavior by creating a .releaserc
or release.config.js
file in the project's root. This will allow you to tweak the commit message format, safelist the branches capable of triggering a release, and enable optional plugins. For more details, check the configuration docs.
Semantic Versioning With CI/CD
In this section, we'll configure a CI/CD pipeline to perform semantic versioning. I assume you have installed semantic-release and already have a continuous integration pipeline.
- Disable tags, so releases don't trigger CI builds.
- Add a secret containing the GitHub or NPM credentials.
Disable Tags on Semaphore
Semaphore triggers CI builds on all branches and Git tags by default. The problem with this behavior is that semantic-release creates and pushes a tag on every new version. So, if we don't disable (or safelist) tags on Semaphore, the tool’s release may trigger secondary (and useless) CI/CD runs.
Change the build settings by opening the Semaphore project settings and scrolling down to What to build?. Ensure the tags option is unchecked.
Add Authentication Secrets
Semaphore will publish releases to GitHub and NPM on your behalf. So, it needs access to your authorization credentials, which we'll store using a secret.
Go to your organization menu and click on Settings.
Click on Secrets > New Secret
The name of the secret should be
semantic-release-credentials
. Add your GitHub and/or NPM tokens as shown:
Set Up a Continuous Delivery Pipeline
Let's add a continuous delivery pipeline to automatically release new versions of the project.
Open your project on Semaphore and edit the workflow.
Click on +Add promotion to create a new pipeline. Enable automatic promotions if you want to release new versions automatically.
Select the new block and add the following commands.
sem-semantic-release
is a thin wrapper around the tool that handles the installation and exports release information into the pipeline.Open the secrets section and enable the secret created earlier.
Click on Run the workflow > Start to test your pipeline.
This setup will execute semantic-release on each commit or merge into the master branch. Depending on the content of the commit messages, the tool might bump the version numbers and publish the release.
Extending the Delivery Pipeline
Executing sem-semantic-release
in the CI environment exports special information about the release. For instance, you can determine if a release occurred by executing sem-context get ReleasePublished
in a later job.
We can use these details to perform more advanced workflows or continuous deployments. Let's say we want to build a Docker image and tag it with the release number. We can use a command along these lines for that:
# e.g. builds my-awesome-app:2.0.1
docker build . -t my-awesome-app:$(sem-context get ReleaseVersion)
Conclusion
Maintaining consistent version numbers will help you gain the trust of users and other developers. Simple projects may only require manual versioning, but highly active codebases with several contributors won't tolerate this. The only sensible alternative is to automate the release chores and remove the human element from the middle with a tool such as semantic-release.
Happy releasing, and thanks for reading!
Published at DZone with permission of Tomas Fernandez. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments