Setting up automated version release should be easy. Luckily for NPM it almost is. Here is how I setup all my NPM modules to stop thinking about details of the publishing process.
Use a good generator to start
I finally listened to the voice of reason by Kent C. Dodds in An Argument for Automation. It does make sense to invest time and automate tasks that one does repetitively. In my case, I have created more than 300 Node packages. Every new package requires writing the same boilerplate text, installing the usual packages, etc.
I wanted something more than
npm init --yes and wrote a Yeoman
generator generator-node-bahmutov that asks only 3 questions,
yet provides at least 10 features for more robust modules: git hooks,
linting and unit tests, security checks, package size info, etc.
My workflow currently involves very fews steps (let us create a module named "foo")
create new GitHub repo 'foo'
package.json has ALL fields NPM and module users need:
license, author, bugs, home page.
Setup semantic release
If you have not seen semantic-release demo videos, please go and watch it now. This awesome tool allows the CI to publish new version of your module to NPM (and create beautiful, readable Git tag) only after unit tests pass. The setup is very simple. Install the tool once and then call it in your folder.
npm install --global semantic-release-cli
Answer a couple of questions and either copy the tokens to your CI, or allow the setup to create a Travis build (my preferred choice).
The best feature: the new version will only be published if there are
meaningful changes. The
semantic-release inspects your git log since the
last release to find if there are commit messages that follow
semantic format. Here is an example of my commits
git commit -m "feat(log): add logging feature"
The above 3 commits follow the convention and can be parsed automatically. When the CI sees the above 3 messages, it will decide what the version bump should be. I have a module largest-semantic-change that can give you the bump based on the messages. The largest change wins; in this case the version should increment the "minor" number because the first commit added a new feature.
Adjust semantic release
The default semantic release settings should be enough for 90% of open source libraries that use GitHub with Travis CI. But there are a few changes I sometimes make to better suit my needs.
Non Travis CI
If you use something other than TravisCI, you need to grab the GitHub and
NPM tokens created during
semantic-release-cli setup step and set them
as environment variables on your CI.
You also need to tell
semantic-release step that it is OK to release
from a different CI environment. For example if you use CircleCI you
can use my condition-circle
module as a plugin.
npm install --save-dev condition-circle
Then tell the
semantic-release to use the module as a plugin using
It is simple to write your own environment check as a plugin, see the Travis verification plugin code.
Use specific Node version to publish
NPM registry used to be finicky when publishing from some Node / NPM versions, thus it was useful when testing across multiple versions to only publish from a specific one. Module condition-node-version restricts semantic release to run on a matching Node version prefix.
npm install --save-dev condition-node-version
Again, it is configured using
release object in
We can combine multiple verification plugins to run in sequence like this
The above configuration will allow publishing from CircleCI running Node v4.3.x.
Simple commit format
I usually like the Git commit message convention, but one thing bothers me about it. See if you notice the inconsistency.
I fixed something
fix(something): now it works
I added a new feature without breaking stuff
feat(feature): something new
I rewrote something, breaking existing API
feat(something): adjust something
Notice how we have to use multi-line comment to describe a breaking change?
I hate this, and always forget the "magic word" - is it "BREAKING", "BREAK",
"BREAKING CHANGE" or "BREAKING CODE"? Do I need a colon after "BREAKING"?
Why the breaking change is using the message
feat(...): ... with "feat" type?
major(scope): breaking change in API
There are also a few aliases for completeness.
"break(scope): ..." is synonym to "major(scope): ..."
If you want to commit without release you can use
WIP or skip
pre-git commit message validation using
git commit -n option.
chore(tests): writing more tests
The best thing about
simple-commit-message - it can be
used as a plugin
semantic-release to determine the new version.
This makes it simple to commit local code, yet control how the new version is published.
// current latest version 1.0.0
It has never been easier to keep pumping out features, yet allowing the users to make an intelligent decision about upgrading.
Never break dependent modules
By semantic version rules, a
patch releases should be backwards
compatible. Yet, the NPM (and every other artifact repository) is full of
modules where a fix release breaks every other module.
Relying on humans to write a lot of tests and be very diligent
about commit messages is not a foolproof strategy.
We can do better. We can test a new version of our module against
dependent projects in the wild before publishing it. There is a stand alone
tool dont-break, but there is also a
npm i -D dont-crack
Determine which modules are dependent on the current module, hopefully
these modules have good tests that exercise our dependency.
Add the GitHub urls to the
release configuration. For example
for our made up module "foo", we found two modules "foo-client" and "foo-user".
semantic-release runs on CI and determines that there is a new
patch version to be published, it first tests these projects
with new version of foo installed. Only the these tests pass against the
new code, the new version is allowed to be published.
This preserves the semantic backwards compatibility when releasing small
updates. It is ok to break the tests for dependent projects, but only
if you release a
major version increment.
With the above approach I never think about versions. I just think about each code change and its commit message. The rest is fully automated.
I have written a lot about semantic release in these blog posts
- Automatically publish to NPM
- How to setup semantic release on Circle CI
- Manual semantic release setup
- Semantic release on GitLab
I also recommend checking out semantic-release-gitlab if you use GitLab for OSS or for private modules. If you want to bump version using simple message convention according to semantic rules, take a look at stand alone next-ver tool. If you want to publish module from CI, but only for manually upgraded versions, check out ci-publish.