cloudparts

Release automation

Tools that turn merged changes into a release without hand edits: they pick the next version number, write the changelog, tag the commit and publish the package.

4 technologies on this page ↓
Live model
What you’re watchingEach merge to main runs the release job, which reads the commit type: a `fix` cuts a patch release, a `feat` a minor one and a breaking change a major one, while `docs` and `chore` commits release nothing. Every release is tagged, gets notes and is published.

How it works

Cutting a release is four mechanical steps: choose the next version, record what changed, tag the commit, and publish the artifacts. Release tools do them from information already in the repository. Most follow Semantic Versioning, MAJOR.MINOR.PATCH, where an incompatible API change bumps the major number, a backward-compatible feature the minor, and a backward-compatible fix the patch. Tools differ first in where the bump comes from. semantic-release and release-please read the Conventional Commits since the last version tag: fix: means a patch, feat: a minor, and a ! or a BREAKING CHANGE: footer a major; commits such as chore: release nothing. Changesets instead has each pull request add a small Markdown file whose front matter names the packages and their bump types and whose body becomes the changelog entry, which suits monorepos where one change touches several packages. GoReleaser takes the version from the latest Git tag, and release-it asks for it in a prompt unless run with --ci. Tools also differ in when a release happens. semantic-release runs in CI after every successful build on a release branch and publishes at once. release-please and the Changesets GitHub Action keep a release pull request open that collects the pending version bumps and changelog; merging it is the decision to release, after which the tool tags the commit and creates a GitHub release. Changesets then publishes every package whose version is newer than the registry’s, while release-please leaves publishing to a separate job. Publishing from CI needs registry credentials; npm’s trusted publishing replaces long-lived tokens with short-lived OIDC tokens issued to one authorized workflow, and adds provenance automatically.

Think of it as

A newspaper’s print run. Reporters file stories all day; at press time the edition gets the next issue number and a front-page index of what’s inside, and goes to every newsstand at once. Nobody retypes the issue number by hand, and nothing reaches a stand without being in the index.

Reach for it when

  • Libraries, SDKs and CLIs that others pin by version, where a wrong number or a missing changelog entry breaks users
  • Monorepos whose packages depend on each other and must be bumped and published together (Changesets)
  • Teams that want a person to choose the moment of release but no person to do the steps (a release pull request)

Think twice when

  • Applications deployed continuously that nobody installs by version: a commit SHA or build number already identifies each deploy
  • Histories without a commit convention: commit-driven tools will skip or misjudge releases; use changeset files or choose versions by hand
  • Giving every CI job a long-lived publish token: publish from one restricted job, with trusted publishing where the registry supports it

A real example

A team maintains a TypeScript SDK at version 1.4.2 with release-please. Over two weeks they squash-merge fix: retry uploads on 503 and feat: add progress callback to upload. After each merge release-please updates one open release pull request, now proposing 1.5.0 because a feat means a minor release, with both entries added to CHANGELOG.md and the version in package.json bumped. On Thursday a maintainer reviews and merges it; release-please tags v1.5.0 and creates the GitHub release, and a second workflow job, triggered by that release, runs npm publish with trusted publishing. Nobody typed a version number.

Technologies

Specific products, and what each does differently.

Also used for release automation

Products of other kinds that are often used this way, grouped by what they are.

Six ways to cut a release#

The tools agree on Semantic Versioning and differ in where the next version comes from and who decides when to ship.

ToolNext version comes fromRelease happens whenPublishes packages?
semantic-releaseConventional Commits since the last tagEvery CI run on a release branch that has releasable commitsYes, through plugins for each package manager
release-pleaseConventional Commits since the last releaseSomeone merges the release pull requestNo: it tags and creates a GitHub release, and another job publishes
ChangesetsChangeset files added with each pull requestThe version pull request is merged and the publish step runsYes: changeset publish publishes each package newer than the one on npm
GoReleaserThe latest Git tag, pushed by a person or another toolA run starts on the tagged commitYes: binaries, archives, packages and Docker images to the forge, registries and blob storage
release-itA prompt, or no prompts with --ciSomeone runs it, locally or in CIYes: npm, plus a GitHub or GitLab release
release-plzCommits since the version on the cargo registry, raised to a major bump if cargo-semver-checks finds an API breakSomeone merges the release pull requestYes: cargo publish for each crate not yet on the registry, plus tags and forge releases
  • GitHub can also generate release notes for a release, listing its merged pull requests and contributors with a link to the full changelog, without any tool in the repository.

In code · yaml

# .github/workflows/release.yml: release every green push to main
on:
  push:
    branches: [main]
permissions:
  contents: read
jobs:
  release:
    runs-on: ubuntu-latest
    permissions:
      contents: write   # push the tag, publish the GitHub release
      id-token: write   # npm trusted publishing (OIDC), no NPM_TOKEN
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # every commit and tag since the last release
      - uses: actions/setup-node@v4
        with:
          node-version: "lts/*"
      - run: npm clean-install
      - run: npm test
      - run: npx semantic-release   # version, notes, tag, publish
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Often confused with

Where it came from2013–2019

Version numbers used to mean whatever each project wanted. Tom Preston-Werner, a cofounder of GitHub, wrote Semantic Versioning to get out of what it calls “dependency hell”: if the numbers follow fixed rules, a user can tell from MAJOR.MINOR.PATCH alone whether an upgrade might break them. Version 2.0.0 of the specification was marked final in June 2013. Olivier Lacan’s Keep a Changelog, first published in May 2014 and at 1.0.0 in June 2017, made the matching case for release notes: “Changelogs are for humans, not machines,” and a dump of commit messages is “full of noise”.

With rules for both, a machine could do the work. semantic-release, started by Stephan Bönnemann and first published to npm in February 2015, runs in CI after every green build on a release branch, reads Angular-style commit messages, picks the version, writes the notes and publishes, so releases are “unromantic and unsentimental”. In 2019 two tools put a person back in at different points. Changesets, first built for monorepos managed with the Bolt tool, has each contributor write the bump and the changelog line as a file in the pull request, and Google’s release-please collects pending changes in a release pull request that a maintainer merges when ready. Both released 1.0.0 on npm within three days of each other in May 2019.

The weak point left was the publish credential: a long-lived registry token stored in CI. npm’s trusted publishing, built on the OpenSSF trusted publishers standard that PyPI and RubyGems also follow, lets a named CI workflow publish with short-lived OIDC tokens that can’t be extracted or reused, and generates provenance automatically; semantic-release now recommends it as the default path.

Why it’s built this way

Derive the version from commit messages
If commits already say fix or feat, a tool can compute the bump and nobody argues over numbers. semantic-release says this “removes the immediate connection between human emotions and version numbers”.
A release pull request instead of releasing on every merge
release-please keeps one pull request up to date as work lands, so a maintainer can review the proposed version and changelog and choose the moment to ship by merging it.
Intent files instead of parsing commits
Changesets lets contributors “declare how their changes should be released” while the change is fresh, and in a monorepo it bumps the packages that depend on a changed one; commit messages can stay informal.
Short-lived OIDC tokens for publishing
A long-lived write token “can be compromised, accidentally exposed in logs, or require manual rotation”. Trusted publishing issues a token per run, valid only for the workflow the package owner authorized.

Timeline

  1. 2013

    Semantic Versioning 2.0.0 is marked final (June 18)

  2. 2014

    Keep a Changelog’s first version is published (May 31)

  3. 2015

    semantic-release is first published to npm (February 2)

  4. 2017

    Keep a Changelog reaches 1.0.0 (June 20)

  5. 2019

    Changesets (May 6) and release-please (May 9) publish 1.0.0 to npm

Sources15

Improve this page on GitHub