Tobias Davis

Designing better software systems.

Resources | Writings | Email List | RSS

Site logo image

Adding Changesets to Your GitHub Repository

If you’re a maintainer of open source libraries, using changesets is a great way to lower the friction of publishing updates e.g. to npm. This article shows every step required to set it up as a GitHub Workflow on a repository containing a single library published to npm. (Other repo configurations should be easy, but I haven’t tested them.)

Table of Contents #

1. Problem and proposed solution #

If you’re already convinced, skip to the instructions.

1.1 The problem as I see it #

As a maintainer of several open source libraries, here are some PRs that would be nice to merge:

However, as a very busy person, one thing that has been a source of tension is when someone opens up a small-but-important pull request to a library I maintain:

It’s small stuff, but when I’m busy the mental overhead is just big enough that the PR often stays unmerged. Sometimes for a really long time…

1.2 The ideal world #

What I want is a world where I have the option to simply click a button and have these things happen:

Of course, I want to be able to side-step that magical action where needed.

1.3 The solution that I think is reasonable #

If you set up changesets (an Atlassian sponsored tool but unsure of current status) you get almost all those things, and the setup and maintenance of it is pretty low-key.

The only big difference from my ideal world is that you have two buttons to click: one to merge the incoming PR, and that creates another PR (the bot does this automatically) that you have to merge to do the actual release.

In practice I think this two-step process is better, as it allows for more control e.g. stacking multiple changes into a version change.

2. Instructions #

Enough of that, here’s what you need to do to enable changeset on your open source, single library repository.

2.1 GitHub Workflows #

You need to enable and use GitHub Workflows.

Note: Minimally you need a new Workflow for the release, but you should have a test flow as well, to run tests etc. on pull requests.

2.1.a If you don’t have it set up yet #

This is as simple as adding a YAML file to .github/workflows e.g. perhaps .github/workflows/test.yml like so (here are the GitHub docs):

# This workflow will do a clean install of dependencies, optionally build, and run tests
# across different versions of node.
name: Test
on:
push:
branches:
- '**' # make it run on any branch
- '!main' # but exclude the 'main' and 'master' branches
- '!master'
pull_request:
branches:
- '**'
- '!main'
- '!master'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version:
- '16.x'
- '18.x' # whichever node versions you support
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build --if-present
- run: npm test

That’s really it, now those run commands will run on every pull request to any branch except main and master.

2.1.b Adding changeset to GitHub Workflows #

You will need a new YAML file, probably name it .github/workflows/release.yml, and it needs to look like this:

name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions: {} #reset
jobs:
release:
# IMPORTANT: prevent this action from running on forks
if: github.repository == 'USER_NAME/REPO_NAME'
permissions:
contents: write # to create release (changesets/action)
pull-requests: write # to create pull request (changesets/action)
name: Release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
- run: npm ci
- name: Publish to npm
id: changesets
uses: changesets/action@v1
with:
publish: npm run release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

Be sure to replace USER_NAME and REPO_NAME with your own, e.g. saibotsivad/blockdown.

2.2 Add changeset-bot in GitHub #

When someone opens a pull request, you want the changeset bot to check that PR, and when you merge it you want the bot to be able to open the second PR that (when merged) triggers the release.

There’s a GitHub setting you need to enable, to let bots do that.

Note: if you are setting this up for an org, not your personal account, you need to go to your overall org settings first, then set it on the individual repo. The settings are the same in either case.

Go to:

Check the box labeled “Allow GitHub Actions to create and approve pull requests”.

Now go here and authorize the changeset bot for the repo (or org I guess): https://github.com/apps/changeset-bot

2.3 Generate npm token and set in Github #

You should use the sweet new “granular access token” that npm has, so the GitHub Workflow can only publish to the libraries you specify.

Go to:

Settings:

Now go over to the GitHub repo:

Set that npm token as the variable NPM_TOKEN.

2.4 Update your package.json file #

Of course you need to install changeset, but wait it’s this one: @changesets/cli

npm install @changesets/cli

Notice in the release.yml file that the last steps entry has with.publish as npm run release?

Of course you’ll need to add that run command, it looks like this (if you don’t have a build step, just drop the prerelease run script line):

{
"name": "your-cool-lib",
"publishConfig": {
"access": "public"
},
"scripts": {
"changeset": "changeset",
"prerelease": "rollup -c",
"release": "changeset publish"
}
}

The publishConfig is to explicitly mark your library as public, it’s because of a weird quirk somewhere in the npm / GitHub / bot mixup.

The changeset line is so you can do npm run changeset and not need to install it globally, which is gross and a bad expectation to put on people wanting to help maintain your library.

2.5 Set up the changeset folder #

Changeset works by putting human+machine readable files inside a .changeset folder. The human text is the part that’s added to the CHANGELOG.md file, and the machine readable part is to tell it how to bump the semver version.

You need to add a configuration file for changeset at .changeset/config.json that looks like this:

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

Other than that baseBranch, which might be main or master, if you’re a single library repo you shouldn’t need to edit it at all.

(If you’re in a monorepo, I think this is where you’d configure that stuff.)

2.6 Add helpful instructions #

You should probably have a CONTRIBUTING.md file, or maybe that’s in your readme.

In that file, you might find it handy to copy+paste this markdown snippet, to help people who want to contribute:

This repo uses [changesets](https://github.com/changesets/changesets) to
make releasing updates easier. For you, the contributor, this means you
should run `npm run changeset` when you've got your changes ready. For
more details, see this short document on [adding a changeset](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md#i-am-in-a-single-package-repository).

3. How it all works #

You can look at the changeset common questions page, or read on for my summary:

3.1 When you make a pull request or change #

Say you’re working on fixing a bug or adding a feature. At some point along the way, either before you commit changes or after, it doesn’t functionally matter.

You can add a file manually, or if you added that line to your package.json you can run the command npm run changeset and it’ll walk you through some questions to help you write a change entry.

Either way, you end up with a file that looks like this:

---
"my-cool-lib": patch
---


Make sure sort query parameter is passed along untouched.

The main things to note:

3.2 Merging in a pull request #

When you merge in a normal PR to your main branch, the changeset-bot runs and looks at every change file in the .changeset folder, and then opens a new PR. Here’s an example from a different project of mine.

Every time you merge more things in to your main branch, that release PR will automatically get updated.

3.3 Merge the release pull request #

When you’re ready to go, you double check the release PR from the changeset-bot, and just go ahead and merge it.

You can see the files changed in the PR (look at that example again) they are:

As soon as you merge it, the actual Github Workflow will notice that the package.json version has been bumped, and will publish the latest to npm.

Examples:

4. Wrapping up #

If you’ve got all those things set up, you can go from PR to npm release entirely from the web, without a local version pulled down.

Find a problem, or have comments? Send me a message, I’d love keep this document updated!


Your thoughts and feedback are always welcome! 🙇‍♂️