How to Automate NPM Package Publishing With Azure DevOps?

At the end of this tutorial, you’ll have a release pipeline that's triggered by a merge (a PR) to a release/1.0.1 branch and only waits for your approval before publishing to NPM.

How to Automate NPM Package Publishing With Azure DevOps?

A step by step tutorial.

Let’s assume you’ve got some source code hosted on GitHub and you’d like to publish that as an NPM package using Azure DevOps .
Both GitHub and Azure DevOps accounts are free to sign up for and use on open source projects!

Here’s high level what this article covers:

  • create a Build pipeline triggered by a merge to a release/* branch
  • create a Publish pipeline triggered on successful Build pipeline
  • set up a manual approval requirement, so that you only publish what you approve i.e. you may have multiple merges to your release/1.0.0-rc1 branch, all triggering the Build step, but only approve and Publish the last one, where all tests are green
  • set up a pre-release Publish (beta channel) and stable release Publish (stable channel)

At the end of this tutorial, you’ll have a release pipeline that's triggered by a merge (a PR) to a release/1.0.1 branch and only waits for your approval before publishing to NPM.

If you only want the final result, here is the build script file .yml and an export of the final release pipeline that you can import (after you’ve imported the publish task) into your own project and modify.

You can see an example of one pipeline build and release.

Step by step:

Step 1: Authorize Azure Pipelines

Add Azure Pipelines to your GitHub app, so that GitHub will tell Azure DevOps (aka AzDO) whenever a pull request is started and a commit is merged (aka webhooks).
More details in this article.

Step 2: Build pipeline

Add/edit the Build pipeline in Azure Dev Ops to look like this:

    - release/*

pr: none

    vmImage: 'ubuntu-latest'

    - task: NodeTool@0
          versionSpec: '10.x'
      displayName: 'Install Node.js'

    - script: |
          npm install
      displayName: 'Install dependencies'
    - script: |
          npm pack
      displayName: 'Package package'
    - task: CopyFiles@2
          contents: '*.tgz'
          targetFolder: $(Build.ArtifactStagingDirectory)
      displayName: 'Copy archives to artifacts staging directory'

    - task: PublishBuildArtifacts@1
          path: $(Build.ArtifactStagingDirectory)
          artifact: package
      displayName: 'Publish artifacts'

What it does:

  1. Trigger this pipeline only on merges to branches starting with release/ and not on PR (*you may want to also run on PR or like me have a different PR build — testing, listing, build — no package)
  2. What virtual machine to use for the build — ubuntu-latest in this case
  3. The steps :
  • install deps
  • package your package to an npm package .tgz file
  • publish your artifacts (in this case the .tgz file) so it’s available for the upcoming Release Pipeline

After finishing the edit — chose “Save” (under “Save and run” button) and commit/open a PR:

As a result after this step, you should have a azure-pipelines.yml file in your repository with the above contents. It’s what tells AzDO* what to do. And being in your repository gives you control and history.
* Azure DevOps is apparently too long for the cool kids…?

Step 3: Release pipeline

Let’s create one. There are multiple buttons to do just that:

Select the empty job.

Select the Empty job
Let’s name the stage vNext

Chose the name vNext as to say: this is going to deploy the pre-release version (i.e. beta version). This vNext will publish your package with tag next (or beta, rc,etc.) which is a pre-release/beta/vNext version and npm install will not install it by default. Later we’ll create vLatest and it will publish with tag latest and that is what npm install will choose to install by default. See more about NPM tags here.
If you have published my-lib version1.0.0 with tag latest and 1.0.1-rc1 with tag next and a user does npm i my-lib they will get 1.0.0 and not 1.0.1-rc1 which is what you want. Whereas npm i my-lib@next or npm i my-lib@1.0.1-rc1 will install that specific version assuming the user knows the risk they take when using a pre-release version.

Have the release be triggered by the success of the build and package pipeline (i.e. when a new artifact is available)
Did you know? We can manually trigger releases with and use older artifacts i.e. some previous version of the package.

Enabling release VNext trigger after Build succeeds.

Make sure to Name and Save your pipeline!

Step 4: Publish to NPM with a token

Now to step away from AzDO and into NPM to get Read and Publish token. See the instructions here.

We’ll need the token in order to publish our package. It’s a good alternative to providing the NPM username and password for authentication to our pipeline. You can revoke it!
You can also create a token using the CLI. That allows you to specify the IP range that you allow this token to be used from.

Create an environment variable group

Now back in AzDo, create a release pipeline environment variable group that we’ll use from all our Release stages — vNext and vLatest.

Create a release pipeline environment variable, make it a secret one and store the token in it:

Paste your token and make it secret.

Making it a secret will hide it from the output so it’s safe even for public pipelines. Like this one is.

Link the environment group to the release. Otherwise, the token variable will not be available (like in the failed release here):

Step 5: Customize vNext Release pipeline

Now to make our pipeline do something actually useful.

Back to releases, click Edit

Let’s use ubuntu-18.04 for this example. If you need something else — go ahead and select that — Linux, Windows and macOS instances supported. And you can bring your own, on-prem hosted too.

Extracting the files from the artifact(the result from npm pack) is the first step. Add and configure the Extract files step to extract the package to ./my-package folder (or whatever you wish — but not . + Clean destingation … option because that will delete your artifact :)

Adding the Extract files step
Configuring the Extract files step

In order to use our NPM token, we need to provide it to the NPM CLI. To do this we’ll add a .npmrc file next to our package.json in the my-package/package folder (npm pack packages everything inside a package folder so after unpack my-package.tgz in ./my-package we get a nested folder structure ./my-package/package which contains our package contents like package.json, index.js, etc.)

Add a Bash step and configure it:

The script contents which you can store in your repo too (in ./ for example) and use the File Path option:

cd ./my-package/package
echo '[Action] logging package files'
echo '[Action] adding token to npmrc'
echo '//$(token)' > .npmrc
npm publish --tag next
  • Change the working directory to where our package is extracted to. Every command (or task) starts out in the agent’s working directory (d:\a\r1\a in this Windows agent example or /home/vsts/work/r1/a in this Linux agent example. Look for the agent job logs).
  • Then just list out the files — this can be used as a pipeline debug tool — since you don’t really have access to the build agent machine any other way.
  • Then add one line to a .npmrc file which is how the NPM CLI will know — okay, for this package repo use that token for authorization: echo echo '//$(token)' > .npmrc
  • Finally, publish the package — notice the tag.

Step 6: Manual approval

Now let’s make sure our pipeline only publishes after it has sent an email to a user (or group of users) and they’ve actually approved this release!

In the Pipelines tab (while editing the Pipeline) click on the round thing (2) at the start of the vNext stage. There is a trigger — don’t change that. Enable the Pre-deployment approval and select user(s) that need to approve. They’ll get an email:

And will be able to approve or reject from the AzDO UI.

Edit the Deployment queue settings to only try and release the latest artifact. That way if we push multiple updates to our release/ branch the Release pipeline will cancel all but the last run — we don’t want to push half-baked packages to NPM:

Step 7: Release (vNext)

Now commit to a branch release/1.0.1-rc.0 (or create it, if missing) to test out our CI/CD process, comprising of two Azure Pipelines — one Build and one Release. Make sure the azure-pipelines.yml file is in it!

I’ve created a repo specifically for this article here with a release candidate and final release branches.

You should get a Build run:

Which in turn should trigger a Release run:

Notice it’s waiting for approval. If left unapproved it will time out. Timeout is also editable from the ‘Pre-deployment conditions’ panel.

Step 8: Release (vLatest)

First, let’s make a Task group out of the tasks in vNext.

Fill in the token parameter value with $(token) and Save. This is now a Task group parameter that we’ll give the environment variable $(token) to.

Click on the task group created info message. Alternatively, find it in the ‘Task groups’ menu item.

Edit the Bash Script part of it. Make the publish use an environment variable for the tag. Then add it in the ‘Environment Variables’ section below. This will parameterize the task group and allow us to reuse it for our vLatest Release.

Now back in our Release pipeline edit.

Fill in the tag as nextremember, this is our pre-release deployment. Save and back in the main Release page, Add new stage. Make sure you’ve not selected the current stage, as that will add a subsequent stage to it. We want a parallel stage:

Use the empty template again and rename it :

Add the just created task group

And fill in the parameters token $(token) and taglatest


Repeat the Pre-deployment approval set up and the Deployment queue settings in the side panel in the main page

Select user(s) who can approve:

Done — test out the deployment. Commit to your release/1.0.0 branch and you should get

Approve vLatest and see verify package publish succeeds.

Did you notice I had to release 1.0.2 because the 1.0.1 was released with the next tag instead of 1.0.1-rc.0.

Above — showing the vLatest approved and successful release and the vNext still pending. Also — notice the skipped Release-6 deployment stages, which we configured with ‘Deployment queue’ settings.


Congrats! You now have a working NPM publish pipeline triggered by a commit to a branch with release/ prefix and then approve whichever release you’d like to.

How to find the logs?

There are multiple ways to get to the logs. Here's one:

How to debug pipeline?

It’s often the case things don’t work out the way we expected them to like this release. First thing to do is look at the error. In this case version 1.0.1 is already published.

Another thing is logging to the console, like ls to see all files in the working folder, pwd to see the working directory absolute path, echo to see env variables, and so on. Use the console, it’s your window into the agent’s machine and environment.

What if I accidentally log my secret?

The console is smart enough to scrub the secrets. If you, or some of the tools you use try to print out a secret, this is what you get in the console:

From a (failed) release.

Where can I learn more?

The main place I did my learning is the docs site. It’s what I would recommend — link.

Thanks for reading!

I am working on a few open source Angular dev-tool projects::
SCuri — Unit test boilerplate automation (with Enterprise support option too)
ngx-forms-typed — Angular forms, only strong typed!
ngx-show-form-control — Visualize/Edit any FormControl/Group