Continuous Deployment to npm using GitLab CI

Deploying a Node.js module to npm can be a complicated process. Even for a simple module, your deployment process probably looks something like this:

npm run lint
npm run build
npm test
npm version <newversion>
git push --follow-tags
npm publish

This process still does not cover all of the concerns around deploying a Node.js module.

  • How do you guarantee this process is followed for every build?
  • Do the tests run in a clean environment?
  • Are you supporting multiple versions of Node.js?
  • How do you run your tests in multiple Node.js versions?

Using GitLab Continuous Integration (CI), we can address all of these concerns and automate the entire deployment process so deployments can be triggered via a single command.

Configure GitLab CI

GitLab CI is configured by a YAML file named .gitlab-ci.yml within the root of the source repository.

Let's walk through the process of using this file to configure GitLab CI to test and publish a Node.js module.

For reference, GitLab also maintains a
collection of example configuration files
for various programming languages including Node.js.

Create Your .gitlab-ci.yml File

We begin by creating the .gitlab-ci.yml file in the project's root directory and opening it in your editor of choice. GitLab CI runs jobs in Docker containers, so first we tell GitLab CI that our jobs need to run using a Node.js image.

image: node:6

Each job that we add will now use the node:6 image by default.

I chose to use node v6 because it is the current Active LTS Release,
but you are free to use any version of node.
A complete list of the official Node.js images is available on the Docker Hub.

Next we can configure GitLab CI to install the module's dependencies before each job runs and cache those dependencies between builds.

  - npm install

    - node_modules/

Add Tests for Multiple Node.js Versions

At this point, we should create some jobs for GitLab CI to run. Provided that a test script has been added to the project's package.json file, tests can be run in our default node v6 image by adding a few lines to the configuration.

    - npm run build
    - npm test

The first line test:node:6 defines the name of the job. This is the name that will be displayed in the GitLab web interface when the job runs. The script clause defines a series of commands to run within the job. In this case, we run a build script that is also defined in the project's package.json file and then run the test script. These tests will always run in a clean environment since they run inside a fresh Docker container created by GitLab CI.

Running tests in multiple versions of Node.js also becomes a fairly trivial process in GitLab CI. We can run our tests in a different version of Node.js by creating an additional job and specifying which Node.js image the job will use.

  image: node:4
    - npm run build
    - npm test

  image: node:7
    - npm run build
    - npm test

Code linting and other tests can also run as separate jobs.

    - npm run lint

    - npm run vuln-test

GitLab CI can run each of these jobs in parallel speeding up the overall testing process.

Add a Job to Publish the Package to npm

GitLab CI will now run all of our tests whenever a change is pushed to the repository. Test automation is a very import part of continuous deployment, but now we need to get our module published to npm when the tests pass.

The first thing we will need is an authorization token to be able to publish the module to npm. The authorization token can be found in the .npmrc file in your home directory (~/.npmrc). The file should contain a line that looks similar to //

If the file does not exist or you do not see a line similar to the line above,
verify that you have logged into npm using the adduser command.

Copy the authorization token from the .npmrc file and add it in GitLab as a secret variable named NPM_TOKEN. Now NPM_TOKEN will be available as an environment variable within the GitLab CI jobs. This allows us to create a job to publish the module to the npm repository without exposing the token in the .gitlab-ci.yml file where it would be viewable to anyone with read access to the repository.

  stage: deploy
    - tags
    - triggers
    - npm run build
    - echo '//${NPM_TOKEN}'>.npmrc
    - npm publish

Breaking this down, we create a job named publish and set the stage to deploy which will only run when all jobs in the test stage have completed successfully. We did not need to set the stage for the previous jobs, because test is the default stage value for a job. Next, this job is limited to only run when a new tag is pushed or it is manually triggered in the GitLab web interface. Finally, we define a script to build the module, create a .npmrc file with the proper authorization token, and run npm publish to publish the module to the npm repository.

Putting it all together, your .gitlab-ci.yml file should end up looking like this gist.

Finishing Touches

The entire deployment process to publish a new module version has been reduced to the following commands.

npm version <newversion>
git push --follow-tags

This can be simplified even further by adding a postversion script to your package.json file that automatically performs the git push command when the version is updated.

"scripts": {
  "postversion": "git push --follow-tags"

With this small change, running npm version will now push our changes to GitLab, GitLab CI will run all of the tests in each of the specified versions of node, and automatically publish the new version to npm only when all the tests pass. We have successfully reduced the entire process of deploying a Node.js module to npm down to a single command.

Bill Thornton
or drop us a note and say hello!