Changelog Automation
Patterns and tools for automating changelog generation, release notes, and version management following industry standards.
When to Use This Skill
- Setting up automated changelog generation
- Implementing Conventional Commits
- Creating release note workflows
- Standardizing commit message formats
- Generating GitHub/GitLab release notes
- Managing semantic versioning
Core Concepts
1. Keep a Changelog Format
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- New feature X
## [1.2.0] - 2024-01-15
### Added
- User profile avatars
- Dark mode support
### Changed
- Improved loading performance by 40%
### Deprecated
- Old authentication API (use v2)
### Removed
- Legacy payment gateway
### Fixed
- Login timeout issue (#123)
### Security
- Updated dependencies for CVE-2024-1234
[Unreleased]: https://github.com/user/repo/compare/v1.2.0...HEAD
[1.2.0]: https://github.com/user/repo/compare/v1.1.0...v1.2.0
2. Conventional Commits
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
| Type | Description | Changelog Section | |------|-------------|-------------------| | feat | New feature | Added | | fix | Bug fix | Fixed | | docs | Documentation | (usually excluded) | | style | Formatting | (usually excluded) | | refactor | Code restructure | Changed | | perf | Performance | Changed | | test | Tests | (usually excluded) | | chore | Maintenance | (usually excluded) | | ci | CI changes | (usually excluded) | | build | Build system | (usually excluded) | | revert | Revert commit | Removed |
3. Semantic Versioning
MAJOR.MINOR.PATCH
MAJOR: Breaking changes (feat! or BREAKING CHANGE)
MINOR: New features (feat)
PATCH: Bug fixes (fix)
Implementation
Method 1: Conventional Changelog (Node.js)
# Install tools
npm install -D @commitlint/cli @commitlint/config-conventional
npm install -D husky
npm install -D standard-version
# or
npm install -D semantic-release
# Setup commitlint
cat > commitlint.config.js << 'EOF'
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
[
'feat',
'fix',
'docs',
'style',
'refactor',
'perf',
'test',
'chore',
'ci',
'build',
'revert',
],
],
'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
'subject-max-length': [2, 'always', 72],
},
};
EOF
# Setup husky
npx husky init
echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
Method 2: standard-version Configuration
// .versionrc.js
module.exports = {
types: [
{ type: 'feat', section: 'Features' },
{ type: 'fix', section: 'Bug Fixes' },
{ type: 'perf', section: 'Performance Improvements' },
{ type: 'revert', section: 'Reverts' },
{ type: 'docs', section: 'Documentation', hidden: true },
{ type: 'style', section: 'Styles', hidden: true },
{ type: 'chore', section: 'Miscellaneous', hidden: true },
{ type: 'refactor', section: 'Code Refactoring', hidden: true },
{ type: 'test', section: 'Tests', hidden: true },
{ type: 'build', section: 'Build System', hidden: true },
{ type: 'ci', section: 'CI/CD', hidden: true },
],
commitUrlFormat: '{{host}}/{{owner}}/{{repository}}/commit/{{hash}}',
compareUrlFormat: '{{host}}/{{owner}}/{{repository}}/compare/{{previousTag}}...{{currentTag}}',
issueUrlFormat: '{{host}}/{{owner}}/{{repository}}/issues/{{id}}',
userUrlFormat: '{{host}}/{{user}}',
releaseCommitMessageFormat: 'chore(release): {{currentTag}}',
scripts: {
prebump: 'echo "Running prebump"',
postbump: 'echo "Running postbump"',
prechangelog: 'echo "Running prechangelog"',
postchangelog: 'echo "Running postchangelog"',
},
};
// package.json scripts
{
"scripts": {
"release": "standard-version",
"release:minor": "standard-version --release-as minor",
"release:major": "standard-version --release-as major",
"release:patch": "standard-version --release-as patch",
"release:dry": "standard-version --dry-run"
}
}
Method 3: semantic-release (Full Automation)
// release.config.js
module.exports = {
branches: [
'main',
{ name: 'beta', prerelease: true },
{ name: 'alpha', prerelease: true },
],
plugins: [
'@semantic-release/commit-analyzer',
'@semantic-release/release-notes-generator',
[
'@semantic-release/changelog',
{
changelogFile: 'CHANGELOG.md',
},
],
[
'@semantic-release/npm',
{
npmPublish: true,
},
],
[
'@semantic-release/github',
{
assets: ['dist/**/*.js', 'dist/**/*.css'],
},
],
[
'@semantic-release/git',
{
assets: ['CHANGELOG.md', 'package.json'],
message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
},
],
],
};
Method 4: GitHub Actions Workflow
# .github/workflows/release.yml
name: Release
on:
push:
branches: [main]
workflow_dispatch:
inputs:
release_type:
description: 'Release type'
required: true
default: 'patch'
type: choice
options:
- patch
- minor
- major
permissions:
contents: write
pull-requests: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Run semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
# Alternative: manual release with standard-version
manual-release:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- name: Configure Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Bump version and generate changelog
run: npx standard-version --release-as ${{ inputs.release_type }}
- name: Push changes
run: git push --follow-tags origin main
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.version.outputs.tag }}
body_path: RELEASE_NOTES.md
generate_release_notes: true
Method 5: git-cliff (Rust-based, Fast)
# cliff.toml
[changelog]
header = """
# Changelog
All notable changes to this project will be documented in this file.
"""
body = """
{% if version %}\
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else %}\
## [Unreleased]
{% endif %}\
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group | upper_first }}
{% for commit in commits %}
- {% if commit.scope %}**{{ commit.scope }}:** {% endif %}\
{{ commit.message | upper_first }}\
{% if commit.github.pr_number %} ([#{{ commit.github.pr_number }}](https://