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:
- Some misspellings or small errors in the docs.
- Adding some tests but not changing code.
- Adding a missing property to the
package.json
file. - Small changes to code that are really easy to review.
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:
- On the one hand I want to respond in a timely manner
- On the other hand to publish the change I had to go to my terminal, git clone, npm install, run tests, blah blah blah…
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:
- The patched code gets merged
- The semver version gets appropriately bumped
- The changelog file gets updated
- The change gets published to npm
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:
- Settings
- Actions (in the “Code, planning, and automation” group)
- General
- Workflow permissions (a heading)
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:
- npmjs
- Click on your profile image
- Access Tokens
- Generate New Token (pick the “Granular Access Token”)
Settings:
- Give it a useful name like
$LIBRARY-changeset
- Expiration up to 365 (you’ll have to renew once a year…)
- Set the “Packages and scopes” to “Read and write”
- Select the libraries the token can update
- Generate token
Now go over to the GitHub repo:
- Settings
- Secrets and Variables (under the “Security” group)
- Actions
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:
- Each PR should have it’s own file added.
- Or multiple files, in different commits. Either way is fine.
- The file name doesn’t matter, you just need to pick something (the CLI tool does it for you) random enough that you won’t get merge collisions later.
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:
- The change files are deleted
- The changelog file is updated
- The
package.json
file version is updated
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:
- Here is a Workflow that triggered from me merging a normal PR to the main branch.
- Note that it’s the “Release” workflow
- Look at the output from the “Publish to npm” step, it doesn’t publish to npm.
- Here is a Workflow that triggered from me merging in the release PR that the changeset-bot made.
- Note it’s the same “Release” workflow
- Look again at the “Publish to npm” step output, and see the line “packages published successfully” and it tells you what version was published, e.g.
[email protected]
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.