All posts

Automate NPM releases on GitHub using changesets 🦋

Ignace Maes
Ignace Maes Oct 18, 2023

You've built an awesome JavaScript library and can't wait to share it with the world. But how do you package and release your library, manage versioning, and maintain a tidy, structured changelog?

Doing these tasks by hand can quickly become a hassle. If you're open to receiving contributions from others, it can be an even bigger headache. In this post we'll cover how to use changesets to automate all of these steps.

Setting up changesets

Changesets is a tool to manage versioning and changelogs in an automated way. It works by keeping a markdown file per change, conveniently called a "changeset". This file is used to track the type of change together with a description.

To install changesets, run the following command in your project root:

npm install --save-dev @changesets/cli && npx changeset init

Running this command will generate a .changeset folder. This folder contains a README.md with some general info and a config.json file to customize the tool. All "changesets" will also be stored in this folder.

Versioning

Now that changesets is set up you can start making improvements to your project. Once you're ready to open a PR and merge your changes, it is time to add a "changeset".

Changesets follow semantic versioning conventions. In a nutshell, this indicates that the version number follows the fixed format of major.minor.patch. A patch is a backwards-compatible bugfix, a minor is a feature that is backwards compatible, and a major is for any change that isn't backwards compatible.

To create a "changeset", simply run:

npx changeset

It will first ask to choose the type of version change (patch, minor, or major) followed by a description of the change that's made. This description is what will end up in the changelog when a release is done. After running this command a new markdown file will be created in the .changeset folder. Congratulations, you've made your first "changeset"!

To remind contributors (and yourself!) to add this changeset to PRs, install the Changeset bot from the GitHub Marketplace.

Changeset bot comment

Release action on GitHub

You've now merged a couple of great new features and it's time for a release of your package! While changesets offers commands to do this process manually, let's go all out and automate this using GitHub actions. This will save time, and perhaps more importantly, reduce potential manual errors.

Create a file .github/workflows/release.yml with the following content:

name: Release

on:
  push:
    branches:
      - main

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Repo
        uses: actions/checkout@v3

      - name: Setup Node.js 20.x
        uses: actions/setup-node@v3
        with:
          node-version: 20.x

      - name: Install Dependencies
        run: npm install

      - name: Create Release Pull Request or Publish to npm
        id: changesets
        uses: changesets/action@v1
        with:
          publish: npx changeset publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

The action needs access to your NPM token in order to be able to do releases. Add this secret in your repository's settings located at Settings > Secrets and variables > Actions using the name NPM_TOKEN. If you do not yet have an NPM token, create one on npmjs.com. Make sure the token supports automation as it will be used to run in CI.

NPM token secret

The newly added GitHub action will run every time a commit is made on the main branch. If the commit has a "changeset" it will open the release PR. (or update, if there is already one open) This PR gives an overview of all changes which are currently on the main branch but not yet in the latest release. The PR will include three changes:

  1. The removal of all "changeset" markdown files
  2. The package version is bumped accordingly
  3. A changelog entry is added for every "changeset"

The next package version is determined by looking at all "changeset" entries. When two "changesets" are included which are of the type minor, the minor release will only be bumped once. Likewise, when a mix of version types are included, the most significant version is bumped. For example: if both a bugfix and minor are included, only the minor version will be bumped. The days of manually calculating the correct package version are gone!

Release PR

Finally, when you're ready to go, the release PR can be merged and the GitHub Action will automatically do a publish of the package to NPM.

By default the changelog just combines all changeset descriptions. With custom formatters, it can be extended to do more.

The @changesets/changelog-github package formats the changelog to include links to the merged PRs as well as a reference to the GitHub user who submitted this change. This is great to give proper credit to contributors as well as easily finding back changes that have been made. It can be installed with the following command:

npm install --save-dev @changesets/changelog-github

Next, register it as the changelog formatter in your .changeset/config.json.

{
  "$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
  "changelog": "@changesets/cli/changelog", 
  "changelog": ["@changesets/changelog-github", { 
    "repo": "your-org/your-repo"
  }], 
  "commit": false,
  "fixed": [],
  "linked": [],
  "access": "restricted",
  "baseBranch": "main",
  "updateInternalDependencies": "patch",
  "ignore": []
}

Upon merging the release PR, the changelog will now include links to PRs as well as the authors.

Changelog

Conclusion

Changesets is a great tool to automate versioning and changelogs for JavaScript packages. By streamlining the release process, it not only saves time but also reduces the potential for errors.

Ignace Maes
Ignace Maes

A software engineer from Belgium with a strong interest in technology. I love creating digital products that make people's life more enjoyable.