Automate NPM releases on GitHub using changesets 🦋
Tags: javascript, github, npm, typescript
Published: 2023-10-18 16:11:17

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](https://github.com/changesets/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:

```sh
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](https://semver.org/) 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:

```sh
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](https://github.com/apps/changeset-bot) from the GitHub Marketplace.

![Changeset bot comment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5znf3sszi8l9mpuauxw.png)

## 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:

```yml
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](https://www.npmjs.com/). Make sure the token supports automation as it will be used to run in CI.

![NPM token secret](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfkd0lm77kw3ac21bz4u.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39qgxfckyx8gbsltm3po.png)

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.

## Bonus: upgrade your changelog game with GitHub links

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:

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

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

```diff
 {
   "$schema": "https://unpkg.com/@changesets/config@2.3.1/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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8v6q1s5z5gghi2qfjofu.png)

## 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.

---

Do you automate your releases already? And which tool do you prefer?

Questions? Leave a comment or reach out on ~~Twitter~~X [@Ignace_Maes](https://twitter.com/Ignace_Maes)!