Automated releases with semantic-release

Philipp Giese
July 06, 2020
8 min

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 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 precommit 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 prefiltered 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 repository 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 prereleases 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 prereleases 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 lets 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 prereleases. Of course, this wasn't possible because the release it then tried to create already existed.

TL;DR do not rewrite 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 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.