Continuous Deployment for publishing NPM packages

Ramakrishna
MindTickle
Published in
6 min readMay 10, 2019

Currently, the npm registry has the most number of packages/modules in comparison to any other package registry for other languages(Source). It is also the most growing registry with more than 500 new packages per day(and that’s just public packages).

From modulecounts.com, comparison of the number of packages per month for different languages

So how can one contribute to this growing list of packages? It’s pretty easy actually…just develop something and run npm publish. Oh, wait…there was some error regarding authentication. Well just run npm login enter your credentials and run the command again. It will succeed(most probably).

But is it the right way to do it? How can one manage the credentials of npm? Can the versioning process be made smoother? And wouldn’t it be good to generate a changelog as well? And of course, before publishing the package, it would be great if tests are run just to be sure.

I faced this literally!

All of these processes can be done automatically and with minimal developer intervention. After all, the main purpose of a developer should be to write code. So how does the magic happen?

Prerequisites

  1. First of all, there should be a CI environment present. There are a lot of free CI platforms available, eg. travisCI. That is where the build/tests/publishing would run.
  2. Secondly, you should have an npm account through which you will publish a package.

Understanding the Package.json file

The package.json is the entry point/manifest to every package. Node and npm can only understand the package json file. The package json should contain all the information about the package. Some of the relevant properties are which have to be configured are-

  1. “name”: The name by which the package should be published. You can also scope your package as well. For more information: https://docs.npmjs.com/files/package.json#name
  2. “main”: This is the main entry point to your package. https://docs.npmjs.com/files/package.json#main
  3. “files”: This includes the list of files/directories to be bundled in the npm package. Generally, this is the build directory. https://docs.npmjs.com/files/package.json#files
  4. “repository”: This contains the information about the repository where the source code is maintained
  5. “version”: This has the version number by which the package will be published. https://docs.npmjs.com/files/package.json#version

For publishing, only the “name” and “version” fields are required. The rest of the fields are additional information and are good to have.

Publishing your package

The command for publishing is npm publish but we are not going to use that. Instead, we will use another package that will help us publish and do many more things. The package I am talking about is semantic-release. It is a fully automated version management and package publishing tool(in their own words) and I found it to be quite true. NPM packages follow semantic versioning. The version by which a package is published is defined in the package.json file. The semantic-release package provides a mechanism through which one can detect the type of release(MAJOR, MINOR or PATCH), increase the version in package.json and publish the package.

But for it to perform efficiently, you need to configure it correctly and follow a discipline during development. For configuration, you can either have a .releaserc file, release.config.js file or a release property in the package.json file. Here is a sample for release property:

Part of the package.json file

I used some plugins for the tasks to be done:

  1. @semantic-release/commit-analyzer: This plugin analyzes the commit messages from the last release and the current changes to find out the current type of release (major, minor or patch). By default the plugin uses angular convention. I have used the eslint convention for generating releases.
  2. @semantic-release/release-notes-generator: This will generate the release notes for the release.
  3. @semantic-release/npm: This updates the version in the package json file and publishes the package to npm.
  4. @semantic-release/git: This plugin does multiple things. First, it generates a new git tag for the release. It commits the files mentioned in the assets with a user-defined message and then it pushes the commit and tag to the git repository mentioned in the package.json file
  5. @semantic-release/changelog: This plugin generates the changelog file with release notes.
Assembly line

Semantic release plugins are very much similar to an assembly line. Each plugin does its job and prepares the files for other plugins to act on.

So after configuring semantic-release, all you need to do is just run npx semantic-release . It will do all the work and all you need to do is just sit and relax.

But wait, one important point is still missing: how can you publish to npm or push to git without authentication? You simply cannot run npm login command in your CI pipeline as its an interactive command(try running it in your local). You need to generate a token from npm and add it to the CI environment as an environment variable. For git, you need to generate a token and add it as GH_TOKEN . These methods are also documented in the respective plugins’ documentation as well.

Beware: semantic-release is quite smart. If you try to run it in local for testing, it will only run if the current branch is master and it will run in dry-run mode since it will not detect a CI environment. Semantic release automatically detects whether it's running in a CI environment or on your local machine. In dry run mode, it will generate the release notes, get the updated version number but will output everything to the console instead of making changes in the files and git. You can, of course, configure it to run on branches other than master.

In the CI pipeline, you can also add the configuration to run your build and test before running semantic-release. Or if you are really impressed with semantic-release and want everything to happen through it then you can use @semantic-release/exec plugin for running custom commands before publishing.

One thing you will have to make sure is that the commits you do should follow a standard(whatever standard you are using the commit-analyzer with). Otherwise, automatic versioning would fail and hence the further pipelines would also fail. One way to enforce this is to use a git hook to check if the commit message follows a particular pattern. I personally found that the eslint versioning scheme was quite concise and worked well for me.

I found that husky with commitlint is a very potent combination for setting up this check. Husky can set up lots of githooks. You can set up the commit-msg githook to run the commitlint command. In your package.json

"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}

But commitlint does not have the support for eslint versioning scheme config. So you need to make sure that your versioning scheme and the commit message pattern enforcing scheme should be consistent. Example commitlint.config.js for enforcing eslint versioning

module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
"type-case": [0, "always", "start-case" ],
"type-enum": [2, "always", ["Fix", "Chore", "New", "Docs", "Breaking", "Upgrade", "Update", "Build"]],
"subject-case": [0, "always", "start-case"]
}
}

Phew! At first, it looks like too much configuration. But believe me, after this hundreds of minutes will be saved in the future.

Happy Publishing!

--

--