How I publish to NPM

My favorite semantic release plugins.

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")

1
2
3
4
5
6
7
8
create new GitHub repo 'foo'
mkdir foo && chdir foo
git init
git remote add origin [email protected]:bahmutov/foo.git
yo node-bahmutov
Your project name: foo
Project description: this is the best foo ever
Comma separated keywords: foo,demo,awesome

The generated 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.

1
2
3
npm install --global semantic-release-cli
chdir foo
semantic-release-cli setup

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

1
2
3
git commit -m "feat(log): add logging feature"
git commit -m "fix(startup): fix wrong code"
git commit -m "chore(code): code refactoring"

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.

1
npm install --save-dev condition-circle

Then tell the semantic-release to use the module as a plugin using package.json

1
2
3
"release": {
"verifyConditions": "condition-circle"
}

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.

1
npm install --save-dev condition-node-version

Again, it is configured using release object in package.json

1
2
3
4
5
6
"release": {
"verifyConditions": {
"path": "condition-node-version",
"node": "4.3"
}
}

We can combine multiple verification plugins to run in sequence like this

1
2
3
4
5
6
7
8
"release": {
"verifyConditions": [{
"path": "condition-node-version",
"node": "4.3"
}, {
"path": "condition-circle"
}]
}

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

1
2
3
feat(something): adjust something

BREAKING CHANGE: the existing feature changed

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?

So instead I follow my own simple-commit-message format. It is very consistent: you just need to remember semver version format major.minor.patch

1
2
3
major(scope): breaking change in API
minor(something): this is a new feature
patch(login): an example fix message

There are also a few aliases for completeness.

1
2
3
"break(scope): ..." is synonym to "major(scope): ..."
"feat(scope): ..." is synonym to "minor(scope): ..."
"fix(scope): ..." is synonym to "patch(scope): ..."

If you want to commit without release you can use chore, WIP or skip pre-git commit message validation using git commit -n option.

1
2
chore(tests): writing more tests
WIP: work in progress, not ready yet

The best thing about simple-commit-message - it can be used as a plugin to semantic-release to determine the new version.

1
2
3
4
5
{
"release": {
"analyzeCommits": "simple-commit-message"
}
}

This makes it simple to commit local code, yet control how the new version is published.

1
2
3
4
5
6
7
// current latest version 1.0.0
git commit -m "feat(log): implement logging"
// publishes version 1.1.0
git commit -m "patch(log): oops, quick fix"
// publishes version 1.1.1
git commit -m "major(calc): reworked calculation API"
// publishes version 2.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 minor and 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 semantic-release plugin dont-crack.

1
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".

1
2
3
4
5
6
7
8
9
10
11
{
"release": {
"verifyRelease": {
"path": "dont-crack",
"test-against": [
"https://github.com/bahmutov/foo-client",
"https://github.com/bahmutov/foo-user"
]
}
}
}

When the semantic-release runs on CI and determines that there is a new minor or 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.

Final thoughts

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.

Update 1: project generator

I have extended my generator to setup semantic release with a couple of useful plugins. Just run yo node-bahmutov:release to get them!

Update 2: pre-release from branches

Since I use semantic-release I set up pre-release whenever I want to publish a beta version of a package. These releases are configured from branches in the package.json

For example, the next configuration published the latest tag from the branch master, and also published new pre-releases from the branch prepare-v2.

package.json
1
2
3
4
5
6
7
8
9
10
11
{
"release": {
"branches": [
{ "name": "master" },
{
"name": "prepare-v2",
"prerelease": true
}
]
}
}

So if our official latest published NPM version is 1.9.1 with the tag latest, then the new breaking feature would be published as 2.0.0-prepare-v2.1 with the tag prepare-v2 that you can try.

Related

I have written a lot about semantic release in these blog posts

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.