Automated releases with semantic-release
Philipp Giese
Posted on September 8, 2020
During the last year, I've become more involved in building the design system we use at Signavio. While doing so rolling out changes to the company turned out to be a major challenge. We had been doing it for some time but we somehow managed to get certain parts wrong all the time. Sometimes we released breaking changes with minor updates or forgot to write proper release notes. A developer who did not contribute to the design system on a regular basis could make mistakes way too easy. Initially, we thought that what we were missing was proper documentation but it turned out no one reads the docs anyway.
When we rebooted the design system I took it upon myself to try to solve at least some of these issues. We had been using a tool called sematic-release
on another project already (so I had at least some experience) and decided to use it here as well. I made a small list of requirements that I would like the solution to support. It should:
- force developers to think about the implications of their work while they were doing it
- automate most of the process to remove the possibility of human error
- be nice enough that even managers see the value
Semantic versioning
Much to the surprise of some developers the numbers we put behind our releases actually have a well-defined meaning. Well, that means at least if you follow semantic versioning, or SemVer.
A version number follows the major.minor.patch
schema. If you increase the patch
number you've, for instance, fixed a bug. You definitively have not added some new functionality. Because then you would have increased the minor
version. Generally speaking increases to the patch
or minor
numbers represent non-breaking changes. This is helpful to determine whether your software is compatible with an update. Let's say you're running on version 1.0.2
and there is an update incoming with the version 1.3.2
.As this means that the changes are either new functionality or bug fixes, you can upgrade without breaking your existing code. This isn't the case when the major
version increases. If this happens it indicates a breaking change. The biggest question you now have to answer is "What is the breaking change?" and "Does it affect me?".
We see that the version number plays an important role in figuring out whether an update is safe or not. If we want to know what changed then the version number isn't enough. We need to see a changelog or release notes.
If we assume that developers understand what they're doing while they are doing it we can leverage this fact.semantic-release
creates release notes based on the commit history of a repository. In order for this to work, developers need to adhere to a format called semantic commit messages. A regular commit includes information about what has changed. A semantic commit also adds context to that information. For instance the commit message
button did not accept onClick handler
becomes
fix: button did not accept onClick handler
This is a small change but now semantic-release
is able to figure out that this commit contains a bug fix and can use the commit message as the description for what the developer fixed. Admittedly, this requires some effort by developers but tools like commitizen
help to make the transition less painful. We also introduced husky
so that there is a pre-commit hook that makes sure every commit follows this pattern.
Benefits of this approach
Every time you automate something the immediate benefit is that you reduce the chance of human error. Restricting yourself to a certain way to phrase commit messages yields some immediate improvements (e.g. automated release notes) but is also a forcing function that influences how people work. Automating away a task that people did not want to do was a great incentive to write better commit messages. And if you get into the habit of slicing your work into smaller pieces then this also improves the work on other projects.
No manual releases
The most obvious benefit, of course, is that you'll never have to write npm publish
again. Even better no one has to do that anymore so you've prevented people from making this mistake.
Structured release notes
From now on your release notes will all follow the same structure. If you're like me then this already will make you happy. You get separate lists for features and bug fixes and breaking changes always stick out.
Automatic notifications for developers
When semantic-release
releases a new version it also automatically adds a comment and a label to PRs and issues that make up the release. Now developers know when their changes are live and which version includes them. This makes communication much easier. By also tagging PRs and issues you always know which issues are still unresolved and which ones have been fixed and released.That manager of yours who always wants the latest status report? Send him a link to a pre-filtered list on GitHub.
Way better commit messages
If you thought writing some meaningful comments is hard, you haven't looked at commit messages. Even though developers will complain in the beginning, the commit messages in the repo will get noticeably better. You can support this by posting a preview of the release notes into each PR.
Release channels
By default semantic-release
will use master
as the main release channel. This means that whenever someone pushes new commits to master
semantic-release
analyzes them and creates a new release if necessary.
If you like to learn more about all the options then have a look at the docs. For instance, we are using a beta
branch to create pre-releases of certain upcoming major updates. This helps developers as they get beta versions that they can take for a test drive and report errors back to you. Also, this creates defined spots in your repository that will have these kinds of changes. Whenever someone wants to know whether there is a big thing upcoming they can look for pre-releases or commits on the respective beta branches.
What this means for your git workflow
That hugely depends on what your git workflow is. For the sake of this example, let's assume that you're using feature branches. Since semantic-release
gets all information from the individual commits you will get into trouble when you're squashing commits. That's because semantic-release
solely considers the header (i.e. the first line) of your commit message. The body of your commit message should contain information about breaking changes. When you feel like you have more to say about a change than fits in one line you should consider making it two smaller changes. This means that semantic-release
works best when you always rebase branches on the latest version of master
and then use a rebase merge. Personally, I had to get used to this because I liked how squash commits encapsulate a thing in one commit. In theory, you can still use squash commits if you restrain yourself to do one thing in a PR. Then you can make the commit message of the squash commit express what you did. In our use case that did not align with how people wanted to work. We then ended up with release notes that didn't contain all changes or PRs that did not result in a release because someone didn't pay enough attention to what the final commit message would look like.
Caveats
Even though I might have made it sound like semantic-release
is the solution to all your problems (and it solved a lot of ours) there are some caveats to consider.
History rewrites of release branches
When I was dealing with both our master
and beta
branches at some point I wanted to update beta
.Out of habit, I decided to rebase beta
on the latest version of master
.In hindsight that was a mistake. The issue is that semantic-release
now could not associate the previous releases on beta
with the commits that it saw. That meant it tried to start over with the pre-releases. Of course, this wasn't possible because the release it then tried to create already existed.
TL;DR do not rewrite the history of release branches!
Release previews
semantic-release
offers a dry-run option.This one sounds pretty much like the thing you want to do to preview what the next release would be. At least this was what I thought. Even though I was able to get it to work it turns out that the output of that command resembles the final release notes but isn't identical to what you will see on GitHub. You will need to be careful about what you present as a preview to not give your developers a false impression of what is about to happen.
Package version fixed to 0.0.0-development
To discourage developers even further from fiddling with the package version you need to set it to 0.0.0-development
in your package.json
.That's fine because semantic-release
will take care of that for you. It turns out that this can be confusing for developers who don't know what's going on. Make sure you either on-board people properly or add a large enough hint to the README
of the respective repository.
No monorepo support
semantic-release
does not support monorepos. You can find an exhaustive explanation on GitHub. The gist is that it can not determine which package to release first.
Imagine a package structure A > B > C
which means that package A
depends on package B
which depends on package C
.In this scenario, semantic-release
would need to release package C
first, then update the package.json
of B
and release package B
.Then it can update the package.json
of package A
and release this one as well. This makes the whole process of what needs to happen much more complicated.
Posted on September 8, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.