How to publish Node.js packages with Travis CI and packagecloud

This is a guest blog post from Julio Capote, cofounder at packagecloud.

This post will show you how to take advantage of Node.js Scoped Packages and how to setup continuous delivery for them using Travis CI and packagecloud.

We’ll build a software delivery pipeline that looks like the following:

  1. Tagged commits are pushed to Github

  2. Tests are run on Travis CI

  3. Successfully built artifacts are deployed to an NPM registry on packagecloud

Example Scenario

For our completely fictitious example, imagine we have various Node.js micro-services using the popular left-pad NPM library. However, it appears that this library exhibits mysterious behavior that has only been witnessed in production.

Using an NPM registry to distribute changes

The natural approach to this problem is to deploy a bunch of extra logging in hopes that the mystery reveals itself. But, since several of our systems use this library in concert, it would be impractical to vendor our modifications inside every project that needs them. Instead, we’ll push our modified package to our own NPM registry, which our services are already configured to use.

Creating an NPM registry

If you don’t have a packagecloud account already, you can get your own hosted NPM registry by signing up at packagecloud.io.

Scoped Node.js Packages

Node.js Scoped Packages are a feature that lets us take put any package under our own namespace, so it’s always clear which fork of the library is being used.

To scope a Node.js package fork it on GitHub, then prepend @your-name/ to the name field of the package.json file, like so:

{
  "name": "@computology/left-pad",
  "version": "1.2.2",
  ...
}

You can follow along with this post by cloning our modified library over at computology/left-pad.

Configure Travis CI

We’ll set up a continuous delivery pipeline on Travis CI to make it easy to ship new versions of our package to all of our services.

Sign into Travis CI with our Github Account

This will let Travis CI access our repositories.

Signing into Travis CI with Github account

Enable the repository

Enable Repository on Travis CI

Set API_TOKEN environment variable

Go to your packagecloud API Token page and set your token to the value of an environment variable named API_TOKEN.

Adding API_TOKEN environment variable

NOTE: Make sure that “Display value in build log” is set to OFF, or else your API token might be revealed to anyone that browses your project!

Configuring our Node.js project for Travis CI

Add a .travis.yml file

We’ll start by placing a .travis.yml file into the root of your project.

language: node_js
node_js:
- "9"

deploy:
  provider: script
  script: "cp .npmrc.template $HOME/.npmrc && npm publish"
  skip_cleanup: true
  on:
    tags: true

notifications:
  email:
    recipients:
    - julio@packagecloud.io
    on_success: change
    on_failure: always

This file is pretty straightforward, we are telling Travis CI that this is a node_js project and to load Node.js version “9.x.x”.

Under the deploy: section, we are using the script provider to run two commands sequentially:

  1. copy over our .npmrc.template (see below) into $HOME/.npmrc
  2. npm publish

Our job doesn’t generate anything that needs cleaning up, so we can speed it up by passing skip_cleanup: true. The on: part ensures that we only publish on tagged commits. Finally, under notifications: we set our notification email and preferences.

Add a .npmrc.template file

When you run npm publish, the npm command for a settings file located at $HOME/.npmrc, which is where our Travis CI script is configured to copy the following template:

always-auth=true
registry=https://packagecloud.io/capotej/left-pad-example/npm/
//packagecloud.io/capotej/left-pad-example/npm/:_authToken=${API_TOKEN}

You’ll want to replace capotej/left-pad-example on both lines with your own packagecloud username and repository. But leave the string ${API_TOKEN} as is, as we want npm to interpolate this value from the environment variable we set in our Travis CI settings earlier.

Bump the version and push

We’ll be using the npm version command to bump and tag our package versions.

Using npm version to bump and tag new versions

Using the patch argument bumps our package a single patch version and the %s inside our commit message will be replaced with that version. This command also takes care of creating a git tag for that version.

npm version patch -m "Bumping to %s"

Push all of our changes to Github (origin):

git push origin master

This will start a build on Travis CI, but it will not deploy.

Because we specified on: tags in our .travis.yml file, we have to push up our the git tag to Github (origin) in order to actually deploy our new package:

git push --tags

Using the packagecloud NPM registry

After the build for the tagged commit finishes, we see should hopefully see our scoped package in our packagecloud NPM registry.

packagecloud NPM registry with our package in it

Adding the NPM registry to our services

To use our modified package we’ll need configure our NPM registry for any project that depends on it.

npm config set registry https://packagecloud.io/capotej/left-pad-example/npm/

Note: the trailing slash is important!

Scoped NPM registry

You can also pass a scope when configuring a registry, like so:

npm config set @computology:registry https://packagecloud.io/capotej/left-pad-example/npm/

This tells npm to use this NPM registry only for packages using that scope. This is useful if your project already uses an NPM registry, but would like to keep your scoped packages separate from it.

Depend on the scoped package

To depend on our scoped package, we’ll add an entry to the dependencies section of our services’ package.json file, like so:

"dependencies": {
  "@computology/left-pad": "latest"
}

We’re also using the special latest version string, which tells npm to always install the latest available version, this way we only have to tag, push and redeploy our services for our modifications to take effect.

Conclusion

While our example might have been made up, the usefulness of continuous delivery and Node.js scoped packages is very real. Anything you can do to shorten iterations and feedback cycles will pay back dividends in productivity across your organization.