If you have been shipping packages on NPM, you may have heard of the semantic-release library, which allows you to automatically release a package as a final step in your CI build. To recap, it allows new package versions to be automatically released just by pushing specifically named commits to git. Following semantic versioning, bug fixes increment the package version by 0.0.1, features by 0.1, and pushing breaking changes release a new major version.
Our team has been using this approach to ship packages as part of Kendo UI for Angular for more than a year. However, shipping each commit directly to production is scary, if not reckless, if there are dependencies between multiple packages. We needed a way to test our packages in integration before releasing them to the public. This post shows how we solved this problem and how you can use our solution in your infrastructure, through the semantic-prerelease library.
Release channels on NPM: dist-tags
A useful idea for testing releases is to provide release channels, not unlike Chrome Canary or Firefox Nightly. NPM provides a way to create such release channels through dist-tags — they allow releases to be published behind a tag, hiding the release from the traditional npm install
. If you have a feature that you want to get feedback on, you can push it behind a dist-tag and get feedback from the early adopters. Iterate until you are all happy, and just then publish the package to all package consumers.
The ideal workflow
The working process that we imagined was to push new code to a develop
branch, which automatically releases a new package version to the dev
dist-tag. This way the command npm install package@dev
installs the cutting edge features.
Once we are confident in the release, we push it to production by fast-forwarning our master
branch to the develop
branch.
How does it tick?
The semantic-release
package does not support the above workflow, but it allows publishing behind dist-tags. It also provides a healthy dose of plugins that can be used to achive the above.
semantic-prerelease
checks if the current branch needs to be published to a dist-tag. If so, it activates semantic-release with the relevant configuration. Since we don't change the core functionality, this also publishes a GitHub release with a changelog.
Bonus: early preview of each feature
Enabling the prerelease workflow for more than one branch was just a matter of configuration. Any branch can push to a different dist-tag. This way, features in a branch can be pushed to their own dist-tags:
{
"branchTags": {
"keyboard-support": "keyboard-support",
"develop": "dev",
"v3": "next"
}
}
This enables customers to work with bleeding edge features, if they so desire.
Bonus: maintain LTS versions
semantic-prerelease
is not picky about how the git history is branched. If a branch is created from a release, suitable fixes can be backported to it, while development of new features continues.
{
"branchTags": {
"v1": "lts",
"develop": "dev"
}
}
Production-ready? Yes.
We have been using this approach in production for over six months now, with hundreds of releases. Quite a few bugs have been ironed out, and the package has been successfully deployed in public and private repositories, running in multiple CI environments.
If the above workflow is applicable to the software that you are shipping, give it a go!