By admin , 29 April 2026

What you will achieve

You will set up automated version bumping and changelog generation driven by Conventional Commits. Push a feature, get a minor version bump and changelog entry; push a fix, get a patch bump - all without manual intervention.

The mechanism

Tooling reads commit messages following the Conventional Commits format (feat:, fix:, chore:) and decides what kind of release to cut. BREAKING CHANGE: footers trigger major bumps. The result: a strict mapping from commit history to release version.

Step 1: adopt Conventional Commits

git commit -m "feat(checkout): add Apple Pay support"
git commit -m "fix(auth): handle expired refresh tokens"
git commit -m "chore(deps): bump axios to 1.7.0"
git commit -m "feat!: drop Node 16 support

BREAKING CHANGE: minimum Node version is now 18."

Common types: feat, fix, chore, docs, refactor, test, perf, style.

Step 2: enforce the format

npm install --save-dev @commitlint/cli @commitlint/config-conventional husky

# commitlint.config.js
module.exports = { extends: ['@commitlint/config-conventional'] };

# .husky/commit-msg
#!/usr/bin/env sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit $1

Now badly formatted commits are rejected at commit time.

Step 3: install release tooling

Common choices: semantic-release, release-please, changesets, standard-version. We use release-please:

# .github/workflows/release-please.yml
name: release-please
on:
  push:
    branches: [main]
permissions:
  contents: write
  pull-requests: write
jobs:
  release-please:
    runs-on: ubuntu-latest
    steps:
      - uses: googleapis/release-please-action@v4
        with:
          release-type: node
          package-name: my-app

Step 4: how release-please works

On every push to main, release-please:

  1. Reads commits since the last release tag.
  2. Categorises by type and decides the next version.
  3. Updates CHANGELOG.md and package.json version.
  4. Opens (or updates) a "release PR" with these changes.

When you merge the release PR, release-please:

  1. Tags the merge commit (v1.4.0).
  2. Creates a GitHub release.
  3. Triggers any tag-based publish workflow.

Step 5: trigger the publish workflow

# .github/workflows/publish.yml
name: publish
on:
  push:
    tags: ['v*.*.*']
jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          registry-url: 'https://registry.npmjs.org'
      - run: npm ci
      - run: npm run build
      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

Step 6: alternative - changesets

For monorepos, changesets works well:

npm install --save-dev @changesets/cli
npx changeset init

Each PR runs npx changeset, which produces a markdown file describing the change and its impact. On merge, a "Version packages" PR rolls up changesets into version bumps and changelog entries; merging that PR cuts the release.

Step 7: alternative - semantic-release

npm install --save-dev semantic-release
# .releaserc.json
{
  "branches": ["main"],
  "plugins": [
    "@semantic-release/commit-analyzer",
    "@semantic-release/release-notes-generator",
    "@semantic-release/changelog",
    "@semantic-release/npm",
    "@semantic-release/github",
    "@semantic-release/git"
  ]
}

semantic-release is fully automated - merging to main triggers the release pipeline directly, no separate release PR. Tradeoff: less human review of the version bump.

Step 8: pre-releases

For betas:

git checkout -b beta
git push -u origin beta
# release-please configured to handle beta channel

Or with semantic-release:

{
  "branches": ["main", { "name": "beta", "prerelease": true }]
}

Step 9: write good commit messages

The system is only as good as its inputs:

feat(checkout): add Apple Pay support

Implements Apple Pay via the Web Payments API. Falls back to the
existing card flow if Apple Pay is unavailable. Closes #1234.

Step 10: handle squashes

If you squash-merge, the squash commit message becomes the entry release-please reads. Configure GitHub to use the PR title as the squash subject - then the PR title needs to follow Conventional Commits format. commitlint on PR titles in CI helps:

- uses: amannn/action-semantic-pull-request@v5
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The result

Versions and changelogs maintain themselves. Releases are predictable and auditable. The discipline pays off the first time someone asks "when did this regression appear?" and the answer is in the auto-generated changelog.