Continuous Deployment for publishing NPM packages
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).
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.
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
- 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.
- 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-
- “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
- “main”: This is the main entry point to your package. https://docs.npmjs.com/files/package.json#main
- “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
- “repository”: This contains the information about the repository where the source code is maintained
- “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:
I used some plugins for the tasks to be done:
- @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 theeslint
convention for generating releases. - @semantic-release/release-notes-generator: This will generate the release notes for the release.
- @semantic-release/npm: This updates the version in the package json file and publishes the package to npm.
- @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
- @semantic-release/changelog: This plugin generates the changelog file with release notes.
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!