$: npm publish -if-needed

Publish a new version of your libraries on NPM only if changes have been made.

Oct 27, 2023

#npm #javascript #programming #ci

Photo by Milad Fakurian on Unsplash


I’m not particularly strong in DevOps, but a few weeks ago, I found myself working on improving a monorepo that contains multiple JavaScript libraries published to NPM. Until then, we had been publishing all libraries with every release, whether they were modified or not. Surprisingly, there wasn’t an option to skip unmodified libraries. So, inspired by a GitHub thread, I decided to implement a small script that accomplishes this task. In this short blog post, I’m sharing the script with you.

Script

The script that publishes a library only if it has been modified is as follows.

#!/usr/bin/env bash function publish_npm() { local lib=$1 LOCAL_SHASUM=$(npm pack -w packages/"$lib" --json | jq '.[] | .shasum' | sed -r 's/^"|"$//g') NPM_TARBALL=$(npm show @dfinity/"$lib" dist.tarball) NPM_SHASUM=$(curl -s "$NPM_TARBALL" 2>&1 | shasum | cut -f1 -d' ') if [ "$LOCAL_SHASUM" == "$NPM_SHASUM" ]; then echo "No changes in $lib need to be published to NPM." else npm publish --workspace=packages/"$lib" --provenance --access public fi } # Tips: libs use by other libs first LIBS=utils,ledger-icrc,ledger-icp,nns-proto,nns,sns,cmc,ckbtc,ic-management for lib in $(echo $LIBS | sed "s/,/ /g"); do publish_npm "$lib" done

So, what does it do?

First, I use a list of libraries that should be published, as seen in the variable LIBS. I could have simply listed all the packages present in the workspace with a bash command, but there might be some folders that should be skipped. Therefore, I decided to use a manual list.

Note that I am using npm workspaces.

For each of these libraries, a function called publish_npm is executed.

To begin, the function calculates the shasum of the library as if it were to be published. It accomplishes this by using the npm command pack with the --json option. This command generates JSON output, including various information:

[ { "id": "@dfinity/utils@1.0.0", "name": "@dfinity/utils", "version": "1.0.0", "size": 35763, "unpackedSize": 124014, "shasum": "86f07bc3846e775ebc641486ff963f7b12e06476", "integrity": "sha512-xpsAgsx5jJZcdIYVbSHJ9XiNTtNIEzu8PH6Z2ALfYzF5an6B1HHTOGmhGUfLFBiEbQAyGb0zULcqBUV6lFVlCg==", "filename": "dfinity-utils-1.0.0.tgz", "files": [] } ]

As you can see, it displays the shasum of the library. Therefore, I use jq and sed to extract this value into a variable.

Next, the function uses the npm show command to fetch the URL of the tarball hosted on npm:

❯ npm show @dfinity/utils dist.tarball https://registry.npmjs.org/@dfinity/utils/-/utils-1.0.0.tgz

With this URL, the script can download the current version of the library without saving it to the filesystem. It does this by piping the data to the shasum command, allowing it to compute the current sha:

❯ curl -s https://registry.npmjs.org/@dfinity/utils/-/utils-1.0.0.tgz 2>&1 | shasum | cut -f1 -d' ' 86f07bc3846e775ebc641486ff963f7b12e06476

Finally, the script compares both the local and remote shasums. If they are different, it proceeds to publish the new version to npm, as a difference in the two shasums indicates that the local library has been modified.


Integration in GitHub Actions

Now that you have the script, here’s how you can integrate it into a GitHub Actions workflow that builds and publishes to npm each time you release your monorepo.

name: Publish on: release: types: [released] jobs: build: runs-on: ubuntu-latest permissions: contents: read id-token: write steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: "18.x" registry-url: "https://registry.npmjs.org" - run: npm install -g npm - run: npm ci - run: npm run build --workspaces - name: Publish run: ./scripts/publish-npm.sh env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}

This GitHub Actions workflow is configured to execute the NPM publishing script when a release is created in your repository. It checks out your code, sets up Node.js, installs dependencies, builds the project, and then runs our custom script, ensuring that libraries are selectively published only when changes have occurred. The NODE_AUTH_TOKEN secret is used to authenticate with npm during the publishing process.

To infinity and beyond.
David