Note: Check out the entire supplemental source code for this blog series.
If you’re using conventional commit messages, it’s important to enforce them. We can do just that using a combination of three tools: commitlint, husky, and commitizen.
Step 1 - Lint Commit Messages with commitlint
We can use commitlint to lint our commit messages. We can install commitlint with npm install commitlint --save-dev
. There are additional packages that we can install that give us predefined configurations, but we’ll go ahead and demonstrate how to make a custom configuration. To do that we’ll create a configuration file commitlint.config.js
in our root. Here is a sample that I’ll be working with:
./commitlint.config.js
module.exports = {
rules: {
'body-empty': [0, 'never'],
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [2, 'always'],
'scope-enum': [
2,
'always',
[
'scope1',
'scope2',
'scope3'
]
],
'subject-case': [2, 'always', 'lower-case'],
'subject-empty': [2, 'never'],
'subject-exclamation-mark': [2, 'never'],
'subject-full-stop': [2, 'never', '.'],
'subject-max-length': [2, 'always', 50],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'chore',
'fix',
'feat'
],
]
}
};
This configuration will require you to choose from a set of types and scopes. It makes the type and subject required while allowing the scope, body, and footer to be optional. It also specifies some formatting and casing for the subject.
We can test this by running echo "fix(invalid): fix some things" | npx commitlint
and we should see the following output:
⧗ input: fix(invalid): fix some things
✖ scope must be one of [scope1, scope2, scope3] [scope-enum]
✖ found 1 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
Here it tells me that there is a problem with my commit message because I have an invalid scope.
Step 2 - Enforce Commit Message Conventions with husky
Note: husky was designed to work with linux-based systems and this step may need to be modified if you’re running on Windows.
Now we have a way to lint our commit messages, but it’s still not enforced. To ensure that every commit message is linted and conforms to our convention, we can use husky.
First, we’ll install husky with npm install husky --save-dev
. Next we’ll add a prepare
npm script:
./package.json
"scripts": {
...
"prepare": "husky install",
...
},
prepare
is a special npm script that will run after all packages are installed. This will ensure that husky is installed. Now let’s run npm install
to trigger this.
You should notice a new .husky
folder was created. The folder consists of a nested _
folder which contains a bash script and a .gitignore
file so that the _
folder won’t get committed to source control.
Husky works by using git hooks, and we will tie into the commit-msg
hook. We’ll add a commit-msg
file (no file extension) to the root of the .husky
folder.
./.husky/commit-msg
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit "$1"
Also, we’ll need to make sure the commit-msg
file is executable by running chmod ug+x .husky/commit-msg
.
Now, when we try to commit using git commit
, our commit message will be linted and our commit will be prevented if it doesn’t meet our defined conventions.
Step 3 - Prompt for Commit Messages with commitizen
We can go one step further and make the developer experience even better by helping to format commit messages as we write them with commitizen.
Run npm install commitlint --save-dev
to install commitizen and npm install @commitlint/cz-commitlint --save-dev
to install the commitizen commitlint adapter. Commitizen is the tool that will prompt us for commit messages and @commitlint/cz-commitlint is the adapter for commitizen to use our existing commitlint configuration.
Next we’ll configure our package.json
. We need to add a configuration section to tell commitizen to use cz-commitlint:
./package.json
"config": {
"commitizen": {
"path": "@commitlint/cz-commitlint"
}
}
We can now trigger commitizen with npx cz
, but to make things a little easier to remember we can add a commit
npm script:
./package.json
"scripts": {
"commit": "cz",
...
},
Now we can trigger the prompt with npm run commit
and we’ll get an interactive prompt that will help us neatly format our commit messages according to our commitlint configuration:
? <type> holds information about the goal of a change.: (Use arrow keys)
❯ chore
fix
feat
You can also customize the messages the prompt gives by configuring adding a prompt
key to your configuration.
Now that we are enforcing conventional commits, we can set up continuous integration.