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.
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
- 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-rc1branch, 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
.ymland 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:
trigger: - release/* pr: none pool: vmImage: 'ubuntu-latest' steps: - task: NodeTool@0 inputs: versionSpec: '10.x' displayName: 'Install Node.js' - script: | npm install displayName: 'Install dependencies' - script: | npm pack displayName: 'Package package' - task: CopyFiles@2 inputs: contents: '*.tgz' targetFolder: $(Build.ArtifactStagingDirectory) displayName: 'Copy archives to artifacts staging directory' - task: PublishBuildArtifacts@1 inputs: path: $(Build.ArtifactStagingDirectory) artifact: package displayName: 'Publish artifacts'
What it does:
- 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)
- What virtual machine to use for the build —
ubuntu-latestin this case
- The steps :
- install deps
- package your package to an npm package
- publish your artifacts (in this case the
.tgzfile) 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.
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
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
1.0.0 with tag
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 email@example.com 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.
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:
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.
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 :)
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
./deploy.sh for example) and use the File Path option:
cd ./my-package/package echo '[Action] logging package files' ls echo '[Action] adding token to npmrc' echo '//registry.npmjs.org/:_authToken=$(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\ain this Windows agent example or
/home/vsts/work/r1/ain 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
.npmrcfile which is how the NPM CLI will know — okay, for this package repo use that token for authorization: echo
echo '//registry.npmjs.org/:_authToken=$(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 https://github.com/scuri-lib/automate-package-with-azure 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
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 tag
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.
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