Version numbers and changelogs on autopilot
Let commit messages decide the version and write the changelog.
Let commit messages decide the version and write the changelog.

Releasing used to involve a little ritual. Someone decides it's time. They look through the commits since the last release, try to remember what changed, guess whether it's a minor or major version, write a changelog by hand, tag it, and hope they didn't forget anything.
Automating that process usually gives better results than what we managed by hand.
Most projects use Semantic Versioning (SemVer): MAJOR.MINOR.PATCH, like 2.4.1.
2.4.1 → 2.4.2): bug fixes, nothing new, nothing broken.2.4.1 → 2.5.0): new features, backward compatible.2.4.1 → 3.0.0): breaking changes. Users may need to change something.The version number is a promise to your users. A minor upgrade should be safe. A major one deserves attention.
The trick to automating versions is making commit messages machine-readable. Conventional Commits is a simple convention:
feat: add Apple Pay to checkout
fix: handle empty cart in order summary
docs: explain retry configuration
chore: update dependencies
feat!: remove deprecated v1 payment API
feat means a new feature, so a minor bump.fix means a bug fix, so a patch bump.! or a BREAKING CHANGE: footer means a major bump.docs, chore, refactor, test, ci) don't trigger a release by themselves.You can add a scope too: fix(api): ..., feat(ui): ....
To keep everyone consistent, add commitlint as a check, or use squash merges where the pull request title follows the convention, which is often easier for teams.
If your commit history is clear enough for a machine to understand, it's clear enough for your future self too.
release-please, from Google, is my favourite tool for this. It watches your main branch and keeps an open "release pull request" that contains:
CHANGELOG.md, grouped into features, bug fixes and breaking changes.package.json, pyproject.toml or a Helm Chart.yaml.When you're ready to release, you merge that pull request. release-please creates the Git tag and the GitHub release. Your normal pipeline sees the tag and builds and publishes the artifacts.
name: release-please
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@<sha> # v4
with:
release-type: go
What I like: a human still decides when to release, by merging the PR. The tool just does all the bookkeeping.
semantic-release is a popular alternative that releases fully automatically on every merge. Great for libraries with frequent small releases, a bit too eager for some applications.
Generated changelogs are only as good as your commit messages. A few tips:
Automated versioning works best with trunk-based development: short-lived branches, merged often into main, with main always releasable. Releases are just tags on main.
GitFlow, with long-lived develop, release and hotfix branches, was designed for a world of infrequent, big releases. It still makes sense for some products (like software shipped to customers who install specific versions), but for most web services it adds complexity without much benefit.
Agree on Conventional Commits for pull request titles, enforce it with a simple check, and add release-please to one repository. After a few releases, nobody will want to go back to writing changelogs by hand.
My take: let the robot count and write. Keep the humans for deciding when it's time to ship.
— Desly