Skip to main content

Semantic Versioning and Automated Releases in Monorepos

Manual versioning in a monorepo is error-prone: someone forgets to bump the version, another publishes the wrong package, or two packages get the same version number. Semantic versioning (MAJOR.MINOR.PATCH) standardizes this. Changesets automate the process: developers create changeset files, CI generates changelogs, bumps versions based on changes, and publishes. This article shows you how to set up Changesets, write changeset files, and automate releases.

Understanding Semantic Versioning

Semantic versioning follows three rules:

  • MAJOR: breaking changes (e.g., removing a function, changing a prop name).
  • MINOR: backward-compatible features (e.g., adding an optional prop).
  • PATCH: backward-compatible bug fixes (e.g., fixing a color value).

Format: MAJOR.MINOR.PATCH, e.g., 1.2.3. When you release, bump only the most relevant number. If you fix a bug, release patch 1.2.4. If you add a feature, release minor 1.3.0. If you break the API, release major 2.0.0.

Installing and Initializing Changesets

Install Changesets at the monorepo root:

pnpm add -D @changesets/cli @changesets/changelog-github
pnpm exec changeset init

This creates .changeset/config.json:

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

Key fields:

  • changelog: tool to generate changelogs (GitHub markdown links).
  • access: "public" for public packages, "private" for private scopes.
  • baseBranch: branch to compare against for affected detection.

Creating Changeset Files

Before merging a PR, create a changeset describing your change:

pnpm exec changeset

Follow the prompts:

[?] Which packages would you like to include? (use arrow keys)
◯ @myapp/design-tokens
◉ @myapp/ui-library
◯ web

Select the packages you changed. Then:

[?] What kind of change is this for @myapp/ui-library? (Use arrow keys)
◯ major
◯ minor
◉ patch

[?] Please enter a summary for this change:
› Fixed Button component color on hover

Changesets creates a file like .changeset/gentle-pandas-play.md:

---
"@myapp/ui-library": patch
"@myapp/design-tokens": patch
---

Fixed Button component text color on hover for accessibility (WCAG AA compliance).

Commit this file with your PR. Do not bump versions manually; Changesets does it.

Running the Version Workflow

When you are ready to release, run the version command:

pnpm exec changeset version

Changesets reads all .changeset/*.md files and:

  1. Bumps package versions based on the type (major/minor/patch).
  2. Deletes the changeset files.
  3. Generates a CHANGELOG.md in each package directory.
  4. Updates package.json version fields.

Example CHANGELOG.md:

# @myapp/ui-library

## 1.1.0

### Patch Changes

- Fixed Button component text color on hover for accessibility (WCAG AA compliance).

### Updated Dependencies

- `@myapp/[email protected]`

---

## 1.0.0

### Features

- Initial release with Button, Card, and Input components.

Commit the changes:

git add .
git commit -m "chore: version bump [skip ci]"

Publishing Packages

After versioning, publish to npm:

pnpm exec changeset publish

Changesets publishes all updated packages to npm and creates git tags for each version (e.g., @myapp/[email protected]).

Automating Releases in CI

Create a GitHub Actions workflow to automate the version and publish cycle. Create .github/workflows/release.yml:

name: Release

on:
push:
branches: [main]

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
node-version: 18
cache: pnpm

- name: Install
run: pnpm install --frozen-lockfile

- name: Build
run: turbo build

- name: Test
run: turbo test

- name: Create Release PR or Publish
uses: changesets/action@v1
with:
publish: pnpm exec changeset publish
version: pnpm exec changeset version
commit: "chore: version bump"
title: "chore: version bump"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

This workflow:

  1. On every push to main, checks if there are changeset files.
  2. If yes, creates a "Release" PR with versioning changes.
  3. When the Release PR is merged, publishes to npm.

Developers create changesets in their feature branches. When the PR is merged, CI creates a Release PR. Merge the Release PR to publish.

Managing Interdependencies

If package A depends on package B, and B gets a major version bump, A should also bump at least minor. Configure this in .changeset/config.json:

{
"linked": [
["@myapp/ui-library", "@myapp/design-tokens"]
],
"updateInternalDependencies": "minor"
}

Now, if design-tokens bumps major, ui-library also bumps at least minor.

Skipping Releases for Documentation Changes

Some changes (docs only, README updates) do not need a version bump. Mark files to ignore in .changeset/config.json:

{
"ignore": ["docs/**", "*.md"]
}

Or use the [skip changesets] comment in your PR to skip creating a changeset when you forget.

Monitoring Release Status

View release history:

npm view @myapp/ui-library

Lists all published versions and publish dates. Or check GitHub Releases:

https://github.com/mycompany/monorepo/releases

Each release shows the tag, changelog, and publish date.

Key Takeaways

  • Use semantic versioning (MAJOR.MINOR.PATCH) to signal the impact of changes.
  • Create changeset files (pnpm exec changeset) before merging code to document breaking/feature/fix changes.
  • Run pnpm exec changeset version to bump versions and generate changelogs.
  • Run pnpm exec changeset publish to publish all updated packages to npm.
  • Automate the release process with GitHub Actions so releases happen on a schedule or on demand.
  • Use updateInternalDependencies to auto-bump dependent packages when their dependencies change.

Frequently Asked Questions

What if I merge a PR without creating a changeset?

Changesets does not force changesets but warns you. Best practice: use a CI check to require a changeset for all PRs that modify code.

Can I create multiple changesets for a single PR?

Yes, but avoid it. One changeset per PR is the norm. If your PR covers multiple unrelated changes, split the PR.

How do I backport a fix to an old major version?

Create a branch from the old version tag, cherry-pick the fix, create a changeset, and release as a new patch. Example:

git checkout @myapp/[email protected]
git checkout -b fix/old-version
git cherry-pick <commit>
pnpm exec changeset
pnpm exec changeset version
pnpm exec changeset publish

What if a changeset describes the wrong change?

Delete the .changeset/*.md file and create a new one. Changesets has not run yet, so you can adjust.

How do I publish a prerelease (alpha/beta)?

Create a prerelease branch and release with a tag:

pnpm exec changeset version --snapshot alpha
pnpm exec changeset publish --tag alpha

This publishes as 1.1.0-alpha.0, letting users test without affecting the stable release.

Further Reading