Automatic Release Notes in Any Project
Ever felt the anxiety of pushing software to production while not 100% sure what the release contains? I have. In doing so, I also felt the anxiety of some unknown change causing an error. Or worse, crashing a customer-facing production service.
Release notes (usually contained in a CHANGELOG.md) easily solve that problem, giving complete visibility into every software release. Manually compiling release notes requires time and effort I would rather spend improving my code. Thankfully, this process is easily automated.
Enter: Conventional Commits.
By aligning all Git commit messages and descriptions to a standardized format, release notes generate automatically in seconds, new version numbers determined, and bump files updated automatically.
This works in nearly any project that supports yarn
or npm
, including JavaScript, React, Next.js, and Gatsby.js (which this blog is built on).
The next section dives deeper into how each of the tools work. Skip to TL;DR for installation instructions.
How it works
1. Consistent Commit Messages
The first step for automated release notes to use consistent commit message and description formats. This allows the CHANGELOG.md generator to differentiate between commit types, whether a commit is related to an issue, and especially if the commit contains breaking changes.
Three parts make up our commit flow: (1) use Conventional Commit format, (2) enable easy Git hook integration with husky
, and (3) enforce commit message validation based on Conventional Commit rules.
CONVENTIONAL COMMITS: STANDARDIZING COMMIT MESSAGES
Based on guidelines designed by the Angular team, Conventional Commits is a precise set of rules ensuring commit messages include important context and details easily consumed by others.
Read their documentation to learn about the format’s syntax and requirements, they are required for successful commit message validation.
HUSKY: PROGRAMMATIC GIT-HOOKS
Before adding custom linting rules for commit messages, let’s install husky
. Described as “Git hooks made easy”, this package allows easier setup of Git hooks through the package.json file, increasing visibility of development tools to outside eyes.
Husky works by automatically adding scripts in the .git folder, which plug into predefined hook functionality in Git. Upon execution of Git a command, Husky’s script will be called, which will in-turn call the scripts specified in the project’s package.json file.
COMMITLINT: ENFORCE GIT COMMIT MESSAGE FORMATS
The final step for commit message validation: linting. Using commitlint
in tandem with husky
allows easy integration of pre-made lint rules, specifically following Conventional Commit standards.
COMMITIZEN: EASY INTERACTIVE UTILITY FOR COMMITTING
Bonus: Especially helpful for onboarding developers unfamiliar with Conventional Commit standards, commitizen
presents a command line wizard for pushing a Conventional-Commits-compatible commit message.
While not required, this helps discovery of more arguments and commits configuration types. It shines when needing to specify breaking changes or other less frequently used commit details.
2. Generate Release Notes
Now the fun part. You’re following all the commit rules and guidelines established above, and want a CHANGELOG.md that you don’t have to type manually anymore… finally.
STANDARD VERSION
The package standard-version
does just that, and a whole lot more. It will:
- Generate a changelog from all commits following Conventional Commit standards
- Bump any tracked version numbers (in accordance with Semantic Versioning)
- Commit all changes to your Git repo
- Tag that commit with the new version number.
All of this is done by standard-version
locally, leaving you to push the changes when you are ready. Allowing control over specific processes and verification of its outputs.
Alternative: semantic-release
An alternative to standard-version
is semantic-release
. Which one you choose depends on your development pipeline. If you prefer or need manual control of versioning, release notes, or other pieces then standard-version
is the right solution for you. If instead you have a complete CI/CD pipeline and don’t need granular control over versioning, sematic-release
may fit better.
TL;DR
(If not already created) Initialize new NPM config:
npm init
Install and configure packages:
npm i -D husky @commitlint/{config-conventional,cli,prompt} commitizen cz-conventional-changelog standard-version
Add a new file in the root of your project called commitlint.config.js, with this content:
// commitlint.config.js
module.exports = { extends: ["@commitlint/config-conventional"] };
Add these two lines under the scripts
key of your package.json:
// package.json
"commit": "./node_modules/.bin/git-cz",
"release": "standard-version"
Append husky config at end of package.json:
// package.json
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
Append commitizen config at end of package.json, to use Conventional Commits rules:
// package.json
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
Finally, append standard-version
config at end of package.json:
This controls which headers are visible/hidden from generated CHANGELOG.md
// package.json
"standard-version": {
"types": [
{ "type": "chore", "section": "Others", "hidden": false },
{ "type": "revert", "section": "Reverts", "hidden": false },
{ "type": "fix", "section": "Bug Fixes", "hidden": false },
{ "type": "feat", "section": "Features", "hidden": false },
{ "type": "docs", "section": "Docs", "hidden": false },
{ "type": "style", "section": "Styling", "hidden": false },
{ "type": "refactor", "section": "Code Refactoring", "hidden": false },
{ "type": "perf", "section": "Performance Improvements", "hidden": false },
{ "type": "test", "section": "Tests", "hidden": false },
{ "type": "build", "section": "Build System", "hidden": false },
{ "type": "ci", "section": "CI", "hidden": false }
]
}
VERIFY: With all those packages and configurations installed you’re ready to go! Let’s go through the commands and make sure everything works.
Test failed commit message:
First let’s ensure commit linting is functional. Make a change in your code, then commit with a (now invalid) message like: “saving changes”. You should see an error.
Commit proper message with tool:
Now, let’s do a proper commit by using our new command line tool: npm run commit
FINISH: Generate initial release notes
Commit linting is working and there is a fresh new commit in the repo’s history waiting to be effortlessly printed onto release notes using the new command, npm run release
.
- Since this is the first release using this tool, append the command with
-- --first-release
, so it looks likenpm run release -- --first-release
. - To try out the commands first before having any changes committed, attach
--dry-run
onto the command, like so:npm run release -- --first-release --dry-run
. - Upon completion,
standard-version
gives the Git command to copy + paste for pushing the shiny new CHANGELOG.md, bumped version numbers, and tagged release up to your remote repository host. ory host.