Versioning & Changelog Automation
Automate semantic versioning, changelogs, and npm dist-tags for library releases with Changesets and conventional commits across single and multi-package repos.
Hand-written version bumps and changelogs drift out of sync with reality the moment more than one contributor is shipping changes — a minor release gets tagged as patch, an entry is forgotten, or two people bump the same version number in parallel pull requests. This section covers automating both concerns with Changesets, the de facto standard for versioning npm packages from monorepos and single-package repositories alike, and shows how it interacts with the exports field your dual-format build already relies on.
Prerequisites
Before adopting automated versioning, confirm:
Canonical Configuration Block
Initialize Changesets once per repository; it creates a .changeset/config.json file that governs how versions are bumped and changelogs are written.
npx changeset init
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
access: "public" is required for scoped packages (@acme/sdk) to publish publicly rather than defaulting to npm’s private-by-default behavior for scopes. updateInternalDependencies: "patch" controls how a bump in one workspace package ripples to internal dependents — set it to "minor" if you want internal consumers to receive a larger bump whenever a dependency changes.
Release Flow
Step-by-Step Implementation
Step 1 — Record intent-to-release with a changeset
Whenever a pull request changes published behavior, run changeset to describe it. This is an interactive prompt that writes a markdown file, not a version bump itself:
npx changeset
🦋 Which packages would you like to include? · @acme/sdk
🦋 Which packages should have a major bump? · (none selected)
🦋 Which packages should have a minor bump? · (none selected)
🦋 Which packages should have a patch bump? · @acme/sdk
🦋 Please enter a summary for this change
🦋 Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition
This produces a file like .changeset/tall-cats-jam.md:
---
"@acme/sdk": patch
---
Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition
Commit this file alongside the code change in the same pull request. Multiple changesets can accumulate on main between releases — nothing is bumped or published until Step 2 runs.
HAZARD PREVENTION
Symptom: A release ships with no changelog entry for a change that clearly altered behavior.
Root cause: The pull request that introduced the change never ran
npx changeset, so there was nothing for the version command to consume.Fix: Add a CI check that fails a pull request touching
src/orpackage.jsonif no.changeset/*.mdfile is present in the diff (Changesets ships achangeset status --since=maincommand for exactly this check).
Step 2 — Bump versions and generate the changelog
Run the version command to consume every pending changeset, bump each affected package’s version field according to semver, delete the consumed changeset files, and prepend entries to CHANGELOG.md:
npx changeset version
--- a/package.json
+++ b/package.json
@@
- "version": "2.0.0",
+ "version": "2.0.1",
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@
+## 2.0.1
+
+### Patch Changes
+
+- Fix ERR_REQUIRE_ESM for CJS consumers by adding a require condition
+
## 2.0.0
This step never touches the npm registry — it only edits files in your working tree. Review and commit the result like any other change.
Step 3 — Publish with the correct dist-tag
npx changeset publish
changeset publish runs npm publish for every package whose version no longer matches what’s on the registry, tagging each with latest by default. For prerelease builds, see Managing Prerelease and dist-tags on npm, which covers npx changeset pre enter beta and publishing without disturbing the default tag.
HAZARD PREVENTION
Symptom: A patch release accidentally becomes the default install for users, even though it was meant as an internal preview.
Root cause:
npm publish(andchangeset publish) applies thelatestdist-tag unless told otherwise, regardless of the version’s semver prerelease suffix.Fix: Enter Changesets’ pre-release mode (
npx changeset pre enter <tag>) before publishing previews, which automatically applies the matching dist-tag instead oflatest.
Step 4 — Automate the release PR in CI
The changesets/action GitHub Action watches main for pending changesets. When it finds them, it opens (or updates) a “Version Packages” pull request running Step 2 automatically; when that PR is merged, the same workflow run publishes to npm:
name: Release
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run build
- name: Create release PR or publish
uses: changesets/action@v1
with:
publish: npx changeset publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
For the full picture of the surrounding publish pipeline — including provenance attestation and OIDC — see Automating npm Releases with GitHub Actions.
Tooling Validation
Check for pending changesets before merging, so a release branch never silently loses a changelog entry:
npx changeset status --since=main
Sample output when a changeset is missing:
🦋 warn It seems this repo does not have any changesets.
🦋 warn Warning: this workflow will not create a release.
Sample output when everything is wired correctly:
🦋 info @acme/sdk: patch
🦋 This Changeset release will release 1 package
Compatibility Matrix
| Feature | Changesets | Conventional commits + semantic-release | Manual npm version |
|---|---|---|---|
| Monorepo/workspace aware | Yes (native) | Partial (plugins required) | No |
| Changelog generation | Yes, per-package | Yes | No |
| Requires strict commit message format | No | Yes | No |
| Prerelease / dist-tag support | Yes (changeset pre) |
Yes (branch-based) | Manual |
| Human-editable release notes | Yes (edit the changeset file) | No (derived from commits) | Yes |
Guides in This Section
- Automating Changelogs with Changesets — a closer look at changeset file formats, monorepo linking strategies, and custom changelog generators.
- Managing Prerelease and dist-tags on npm — publishing beta and canary builds without disturbing the
latesttag that most consumers install by default.
Related
- Automating npm Releases with GitHub Actions — the CI pipeline that wraps the version-and-publish flow described here, including provenance attestation.
- Publishing Packages from a pnpm Workspace — how Changesets’
fixedandlinkedpackage groups interact with workspace protocol dependencies across multiple published packages. - Validating Packages Before Publish — run these checks before
changeset publishexecutes, so a version bump never ships a broken exports map.