Conventional commits with Commitizen and Commitlint
Streamlining commits and releases with commitizen and commitlint.
Commitizen and commitlint are a perfect duo for conventional commits. Adding husky and standard-version in the mix makes commiting and releasing code almost as easy as pie.
The Conventional Commits specification
The Conventional Commits specification is a lightweight convention on top of commit messages. It provides an easy set of rules for creating an explicit commit history, which makes it easier to write automated tools on top of. This convention dovetails with SemVer, by describing the features, fixes, and breaking changes made in commit messages.
Conventional Commits in practice
Install the following dependencies to enable conventional commits.
npm i -D commitizen @commitlint/config-conventional @commitlint/cz-commitlint
- commitizen
- @commitlint/config-conventional
- @commitlint/cz-commitlint
Let's break down the purpose of each dependency.
commitizen
When you run commitizen, you'll be prompted to fill out any required commit fields at commit time which looks something like this:
? <type> holds information about the goal of a change.:
feat
? <scope> marks which sub-component of the project is affected:
deps
? <subject> is a short, high-level description of the change:
add axios dependency for fetching data
[main ccc8740] feat(deps): add axios dependency for fetching data
2 files changed, 16 insertions(+)
@commitlint/cz-commitlint
Commitizen adapter, makes commitizen use commitlint.config.js
or .commitlintrc.json
for configuration.
@commitlint/config-conventional
Shareable commitlint config enforcing conventional commits.
Configuring commitizen and commitlint
Add the following 2 files to configure commitizen and commitlint.
{
"path": "@commitlint/cz-commitlint"
}
{
"extends": ["@commitlint/config-conventional"]
}
Let's take a look at how much better commitizen looks now.
? Select the type of change that you are committing: (Use arrow keys)'
❯ feat: A new feature
fix: A bug fix
docs: Documentation only changes
style: Changes that do not affect the meaning of the code
refactor: A code change that neither fixes a bug nor adds a feature
perf: A code change that improves performance
test: Adding missing tests or correcting existing tests
build: Changes that affect the build system or external dependencies
ci: Changes to our CI configuration files and scripts
chore: Other changes that do not modify src or test files
revert: Reverts a previous commit
...'
[main 4aa72a5] feat(deps): add axios dependency to fetch data
2 files changed, 16 insertions(+)
Custom npm script
We can use a custom npm script to easily trigger commitizen.
{
"scripts": {
+ "commit": "cz"
}
}
Now we can simply run npm run commit
in our terminal and commitizen will be triggered.
Enforcing conventional commits with husky
Husky provides us with git hooks which we can use to enforce conventional commits. If we combine this with @commitlint/cli we can even lint commit messages after commiting.
Let's start by installing the following 2 dependencies followed by adding a commit message hook with husky. This hook runs commitlint which validates if the commit is formatted correctly.
npm i -D husky @commitlint/cli
npx husky install
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
It's a good idea to add husky install
to the npm prepare
script. This way we can ensure that husky is installed right after npm install
.
{
"scripts": {
"commit": "cz",
+ "prepare": "husky install"
}
}
Testing our commit message hook
Let's see what happens if we don't use commitizen and don't respect the commit message format.
$ git commit -m 'totally not a conventional commit'
⧗ input: totally not a conventional commit
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
✖ found 2 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
husky - commit-msg hook exited with code 1 (error)
Pretty neat if you ask me! Let's take this even further by using conventional commits to automatically generate a CHANGELOG.md file.
Using Standard Version for automatic changelog generation
standard-version
enables us to automatically generate a changelog file based on the formatted conventional commits.
npm i -D standard-version
Again we can use a custom npm script to easily run standard-version.
{
"scripts": {
"commit": "cz",
"prepare": "husky install",
+ "release": "standard-version"
}
}
In the following release we've fixed a bug and also made a breaking change to our DatePicker component.
$ npm run release
> standard-version
✔ bumping version in package.json from 1.1.0 to 2.0.0
✔ bumping version in package-lock.json from 1.1.0 to 2.0.0
✔ outputting changes to CHANGELOG.md
✔ committing package-lock.json and package.json and CHANGELOG.md
✔ tagging release v2.0.0
Example CHANGELOG.md
This is a simplified version of the generated changelog. The real version even has links to the specific commits.
# Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## 2.0.0 (2022-02-28)
### ⚠ BREAKING CHANGES
* **deps:** DatePicker has a different API
### Features
* **deps:** major bump of component library (10a7e59)
### Bug Fixes
* add removeEventListner to scroll in sidebar (29bc502)
## 1.1.0 (2022-02-27)
...
Example app
I've created an example repository on github which uses commitizen, commitlint, husky and standard-version.
Feel free to fork it and make some example commits with npm run commit
.
You can also see the automatic CHANGELOG.md generation in action when running npm run release
.
Conclusion
Commitizen and commitlint are a perfect duo for conventional commits. Adding husky and standard-version in the mix makes commiting and releasing code almost as easy as pie.