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
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
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
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.
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.
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.
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.
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.
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.
semantic-release will use
master as the main release channel. This means that whenever someone pushes new commits to
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.
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.
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.
When I was dealing with both our
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!
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.
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.
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
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.