Conventional commits with Commitizen and Commitlint

Streamlining commits and releases with commitizen and commitlint.

Mike Henderyckx
written by Mike Henderyckx
published 2022-03-015 min read
Conventional commits 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.

.czrc
{
  "path": "@commitlint/cz-commitlint"
}
.commitlintrc.json
{
  "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.

package.json
{
  "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.

package.json
{
  "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.

package.json
{
  "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.

;