Photo by RetroSupply on Unsplash
Setup Husky and Commitlint to our repo
Apply code formatting and preserve a neat commit log.
Table of contents
Sometimes, we put anything in our code commits, but this doesn't help when checking what changes were made if it was a fix or part of a new feature. Keeping order in our repositories is important. Tools like Husky and Commitlint help us add structure to our work. I'll show you how to use these tools to make this task more organized.
Husky
Husky is a tool for developers that works with Git. It's like a guard: when you "commit" (save changes to your code), Husky checks if everything follows the rules you set (like code style or tests). If something isn't right, Husky stops it until you fix it. This helps keep your code clean and without errors before saving it permanently.
Install Husky with yarn
yarn add --dev husky
Init the configuration
npx husky init
This creates the file .husky/pre-commit
and adds it to our package.json
in the scripts section:
"scripts": {
"prepare": "husky" // <- added this automaticly
}
In our case, prepare
doesn't work with yarn
, so we'll change it from prepare
to postinstall
, making it look like this:
"scripts": {
"start": "nx serve",
"build": "nx build",
"test": "nx test",
"postinstall": "husky" // <- we use yarn
},
The magic comes from the .husky/pre-commit
file, which you can think of as a bash file, where each line is a command that will run. In our case, before each commit, we want to execute:
nx format:write
nx affected -t lint,test --parallel=2
With these, we ensure that before making a commit, our affected files are formatted, and then it checks the lint and makes sure all tests are running perfectly.
Now, when we make a commit, something like this happens:
Great, we now have Husky installed and configured. If something goes wrong, the commit won't be executed, giving us the chance to fix the lint or tests locally (instead of finding out through the GitHub Action).
Commit Lint
Commitlint is a software development tool that ensures commit messages (the records of code changes) follow a certain format. It works like an automatic checker, looking at each commit message before it's confirmed. This helps keep the project's change history clear and consistent.
We install commitlint
yarn add -d @commitlint/{config-conventional,cli}
Create the configuration file:
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
Husky + Commitlint
To have Husky detect the commit, we'll add another Git hook (just like we did with pre-commit
), in this case, it's commit-msg
. We'll create a file in .husky/commit-msg
and add the following:
npx --no -- commitlint --edit ${1}
Now, when we try to make a simple commit and it doesn't have the correct format, we see something like this:
So, the commit doesn't go through because it doesn't have the correct format.
But what is the correct format? Well, commitlint
follows something called conventional commit format, which expects a commit message with the format <type>[optional scope]: <description>
By default, we have these types: build
, chore
, ci
, docs
, feat
, fix
, perf
, refactor
, revert
, style
, test
For example, if we want to define the list of types and scopes to use, we can modify the configuration file commitlint.config.js
with something like this:
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'],
],
'scope-enum': [
2,
'always',
['api', 'frontend', 'backend', 'ui', 'database'],
],
},
};
What does the added rules
:
'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore']]
: This defines the allowed commit types. The custom types here arefeat
,fix
,docs
,style
,refactor
,test
, andchore
. These represent different kinds of changes you might make in your project (new features, fixes, documentation, etc.).'scope-enum': [2, 'always', ['api', 'frontend', 'backend', 'ui', 'database']]
: Here, in addition to the scopesapi
,frontend
, andbackend
, we addui
anddatabase
as valid scopes.The
2
means that this is a rule that must always be followed (error if not met), andalways
indicates that this rule is always active.
With this configuration, a commit message must use one of the defined types and one of the defined scopes. For example:
git commit -m "feat(ui): add new button component"
This message indicates that a new feature (feat
) related to the user interface (ui
) is being added.
If are you using Angular can use this (link) default configuration, or Nx, this link to get all the scopes of your Nx apps to dynamically generate the scopes.
Conclusion
Husky and Commitlint are helpful tools for keeping code quality and consistency in a project. Husky checks code against rules before a commit, while Commitlint makes sure commit messages follow a specific format. This keeps the project's change history clear and consistent. By using these tools in your development process, you can avoid errors, enforce code style, and keep a clean commit history. This improves readability and makes teamwork more efficient.
Resources
commitlint doc: https://commitlint.js.org/#/
Conventional Commits: https://conventionalcommits.org/