← About me

Managing NPM releases

January 26, 2025

As someone who manages quite a few NPM packages, one aspect I struggled with was managing NPM releases. How do you streamline the process of developing and releasing packages? It’s something I think a lot of people have figured out but yet it’s rarely discussed or documented.

The most basic approach would be to just run npm publish manually but there are obvious downsides to it:

There are existing solutions for it like Changesets, which are used to by a lot of major JavaScript packages. However, documentation is a bit lacking and I didn’t feel like using it. So I decided to build my own solution like I often do. Reinventing the wheel and whatnot but I enjoy the process.

I had a few requirements in mind.

First, most of my projects have their package source code and documentation in the same repository. If the documentation site is deployed with every commit but the package is only released every other day, it can cause unreleased features to be documented prematurely. At the same time, if I held off all updates to the documentation until a new version was released, it can cause pull requests to back up and prevent even small changes (like typo and grammar fixes) from being merged. I wanted something that would neatly keep the package and documentation in-sync.

Second, I wanted to manage multiple major versions in a single repository. For example, the main branch would hold the latest version and v2 branch would hold the legacy v2 source code. I should be able to release both latest and legacy versions anytime.

On the other hand, there were a few limitations I was willing to accept to keep the tool simple. Mainly, it didn’t need to support monorepos with multiple packages. I don’t like monorepos and avoid it unless it’s absolutely necessary. Most of my packages are small in scale and I just need something simple.

I was also fine with, even preferred, manually writing the changelogs. Changesets (and likely other tools) organize changes by having a big directory with changeset files each documenting a single change since the previous release. This allows contributors to add to the changelog by simply committing a markdown file or two alongside their pull requests. However, since the changelog is usually just a list with all these individual changeset files combined, the format and style of the changelog can become inconsistent. I was also worried this approach would make customizing the changelog format and managing changelogs for pre-releases cumbersome.

So here’s my current process.

I now maintain 2 branches - main and next. main holds the source code of the latest published version, while next holds the source code for the upcoming release. For some projects I have similar branches for previous versions (e.g. v1 and v1-next). All commits to main triggers an action that publish the documentation, while the package itself is published when an action detects that the package.json version is not yet published. Importantly, all pull requests that update the package source code is made against next while pull requests for small documentation fixes is made against main. This ensures that typo and grammar fixes are deployed instantly while documentation for new features can be held off until the next release.

The changelog is just a .RELEASE.md file in the next branch. Whenever I want to trigger a release, I update version field in package.json, write the changelog, and create a PR that merges the next branch into main. While I’ve only used this workflow for latest and legacy releases for now, it’s a simple workflow that can also be adapted for prereleases.

I created Auri to support this workflow. It’s open source like most of my projects but it’s not really intended for public use. The package is still very bare-bones but I plan to make it more opinionated once I find the exact workflow I like. I’ve been using it in Arctic for a month now and I’m really enjoying it. Everything is published with a provenance statement and changelogs are nicely organized on GitHub Releases. I’ve also found that it’s pretty friendly to new contributors since Auri is hidden for most contributions.