Automated release process for (Lerna) monorepo

The process of publishing (releasing) a new version of software is one of the most important steps in a development lifecycle. In this article, we will take a look at how we can automate this in a monorepository managed with lerna.

Motivation

The most important things in the release process are simplicity, consistency, and clarity. A release process should be straightforward, so everybody on the team can do it easily by following a clear set of instructions. During the release process, a lot of things can go wrong - you forget to set a new version, modify the version in any of the package.json files, or even forget to create a new tag or merge hotfixes from the master branch. Therefore, it's good to clearly define this process and automatize it as much as possible.

Goals

The main goal of this article is to define the release process for monorepo using Lerna. It consists of the automatic creation of tags, incrementing the version, and changelog generation with the chronological arrangement of changes.

Project structure

For this project, we will be using a monorepo with two packages/modules: api and frontend. In the root folder, we have packages, root package.json, and Lerna config.

+-- api
| +-- package.json
| +-- ...
+-- frontend
| +-- package.json
| +-- ...
+-- CHANGELOG.md
+-- lerna.json
+-- package.json
+-- ...

In this article, we are fixed to monorepo projects managed by lerna. That means that you should be able to use this process with any project using this structure (maybe with some little changes in configs). You can find valuable information in this article even if you are not using this kind of setup.

Setup

In our release process solution, we are using a combination of GitHub, lerna and lerna-changelog.

Lerna

Lerna is a library that provides tooling to manage multi-package structure inside a single repository (sometimes called monorepos). Our main usage of lerna is for automatic versioning and tag generation. We are using lerna with the combination of lerna-changelog package which is used to generate a changelog based on the labels attached to PRs merged into the origin.

In the root of the project run npm i -s -d lerna (we are currently using 3.22.1). Create lerna configuration file (lerna.json).

{
	"packages": [
		"./*"
	],
	"version": "{{your_current_repository_version}}"
}
  • packages - Array of globs to use as package locations (locations of your not root package.json files)
  • version - the current version of the project (from the root package.json) More lerna config options can be found here.

Lerna-changelog

In the root of the project run npm i -s -d lerna-changelog (we are currently using 1.0.1) In the root package.json file we need to add config for lerna-changelog. You can map PR labels from Github to more meaningful titles here.

"changelog": {
	"labels": {
		"Type: Feature": "Features",
		"Type: Bug": "Bug fixes",
		"Type: Enhancement": "Enhancements"
	},
},

More options can be found in lerna-changelog doc.

For changelog generation, you'll need a personal access token for the GitHub API with the repo scope for private repositories or just public_repo scope for public repositories.

You can set the environment variable for the GitHub authentication by running this command:

export GITHUB_AUTH="<Your Github personal access token>"

or even better, define this variable in your .bash_profile

Git workflow

To understand this process you have to be familiar with git-flow. In our case, it's based on three main branches:

  • develop
  • staging
  • master

All new features or non-critical bug fixes are merged into the develop branch. When it's time for a new release (typically at the end of the sprint), all changes from develop are merged into staging branch, where we test it thoroughly if everything is working fine. After that we merge staging into the master branch.

Release process

A new release is introduced by creating a new git tag and bumping versions of packages. These are done in a semi-automatized way with lerna.

Creating a Release Candidate with changelog

Over the time, we figured out these few basic steps to create a release candidate:

  1. Pull the latest changes from origin to master, staging, develop branches to your local repository
  2. Checkout into the develop branch and make sure you have all bug fixes from master merged to develop
  3. Run npx lerna-changelog to generate a changelog and append it to the CHANGELOG.md
  4. Commit new CHANGELOG.md on develop
  5. Run npx lerna version and choose the appropriate version number.
    1. This will update version properties for all updated packages
    2. Commit all the changes
    3. Create a new git tag on the new commit
  6. Push changes to the origin
  7. Merge develop into staging branch
  8. Push staging to origin
  9. Create a new Pull Request with a base set as master from staging branch
  10. Copy changes from CHANGELOG.md and attach to the PR body with - [ ] list items to render a TODO list (- [ ] is rendered as a checkbox)
  11. Let your team know that there is a Release Candidate and wait until they will check every feature / bugfix they've merged
  12. When all checkboxes (all features / bugfixes) are checked and the PR is approved, merge it. Don't squash merge as it might cause conflicts for developed features

Example of PR from staging to master.

When to release?

It's hard to say exactly. There are few points that you should think of:

  • Do not release in rush - you should have enough time to fix any problem during and after release. There is always something unpredictable that can happen. Maybe you have forgotten to set a new environment variable or a migration has crashed.
  • You should release when there are as minimum customers as possible using the product - any error can occur and that can have a negative influence on their experience with the product. Impact on the customer experience could be minimized by using strategies like blue-green deployment or canary releases.
  • You should be sure of the code you are releasing.

How often to release?

The best option is to release in periodic intervals, such as one sprint. Periodicity is one of the most important factors. You don't want to release hundreds of hours of work at once. It's much easier to release smaller features more often, than one big feature after two months. Last but not least, there is a customer that wants to see some progress on his project as well. It's not a problem to release more often then you need to, but before every release, don't forget to check the points in the "When to release" paragraph!

Conclusion

In this article we've walked through the release process by creating a changelog with minimum effort. We've talked about a workflow and the tools we are currently using, and our method of creating a release candidate. As always, there is a lot of space to improve, e.g. it would be nice to automatize creating PR from staging to master branch with changelog description.

written by

Filip Jeník

Let’s build something that users love!

Contact us
hello@sudolabs.io

DUETT Business Residence
Námestie osloboditeľov 3/A
040 01 Košice, Slovakia

©2021 Sudolabs