Automated releases with semantic-release
Tags: semanticrelease, semver, npm, tooling
Published: 2020-09-08 09:52:36

During the last year, I've become more involved in building the design system we use at [Signavio](https://www.signavio.com). 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`](https://semantic-release.gitbook.io/semantic-release/) on [another project](https://github.com/signavio/react-mentions) 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](https://semver.org/).

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](https://www.conventionalcommits.org/en/). 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`](https://github.com/commitizen/cz-cli) help to make the transition less painful. We also introduced [`husky`](https://github.com/typicode/husky) so that there is a [pre-commit hook](https://githooks.com/) 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](https://github.com/).

### 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](https://semantic-release.gitbook.io/semantic-release/usage/configuration#branches) 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](https://semantic-release.gitbook.io/semantic-release/usage/configuration#dryrun) 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](https://github.com/semantic-release/semantic-release/issues/193). 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.