Semantic release with commitizen and commitlint

22 Oct, 2019Versioning, Releasing

semantic-release is an awesome tool kit to help automate the versioning and releasing of npm libraries or Node.js projects. There are varieties of plugins to extend the functionality of semantic-release. Other than that, two libraries out of the semantic-release ecosystem can help to streamline the workflow, they are commitizen and commitlint.

Please go to the GitHub page of the each libraries for detailed description and usage guide. This post will demonstrate a conventional local setup and CI steps.

Install husky for git hooks management.

npm install --save-dev husky

Create a .huskyrc.js file:

module.exports = {
  hooks: {
    'prepare-commit-msg': 'exec < /dev/tty && git cz --hook || true', // commitizen
    'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS', // commitlint
    'pre-commit': 'lint-staged', // custom hook
    'pre-push': 'npm test', // custom hook

Install commitizen

npm install --save-dev commitizen
npx commitizen init cz-conventional-changelog --save-dev --save-exact

Now try it out:

git add . && git commit

You should see logs similar as blew which means it works.

husky > prepare-commit-msg (node v12.x.x)
[email protected]x.x.x, [email protected]x.x.x

? Select the type of change that you're committing: (Use arrow keys)
❯ feat:     A new feature
  fix:      A bug fix
  docs:     Documentation only changes

Install commitlint

npm install --save-dev @commitlint/config-conventional @commitlint/cli

Create a .commitlintrc.js file:

const { types } = require('conventional-commit-types');

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    // Follow the commit-types used by commitizen (
    'type-enum': [2, 'always', Object.keys(types)],
    // Blow lines are optional.
    'header-max-length': [2, 'always', 100],
    'body-max-line-length': [2, 'always', 100],
    'footer-max-line-length': [2, 'always', 100],

Now test it out:

git add . && git commit -m "test"

Even we explicitly provide commit message, the prepare-commit-msg hook will still be revoked and the commitizen will show the prompts. We can just press Ctrl + C to skip it and it comes to commit-msg hook and you can see the output as blow which means it works.

husky > commit-msg (node v12.x.x)
⧗   input: test
✖   subject may not be empty [subject-empty]
✖   type may not be empty [type-empty]

✖   found 2 problems, 0 warnings

Adding commitlint locally in the git hook can prevent developers to create invalid commit messages by mistake. However, local git hooks can always being bypassed.

In order to enforce the conventional commit format in the repository so that semantic-release can leverage on that to generate the correct version number, we need to run commitlint in CI to check against commit messages from the pull requests.

Here's the command to serve that purpose:

npx commitlint --verbose --from origin/$TARGET_BRANCH --to $FETCH_HEAD

Here the $TARGET_BRANCH is the branch that incoming pull request is merging to. The $FETCH_HEAD is the hash of the last commit on the source branch of the pull request. These two values can normally get from the environmental variables of the CI system.

Install semantic-release

npm install --save-dev semantic-release

Add and optional config file .releaserc.js:

module.exports = {
  plugins: [
        releaseRules: [
          { scope: 'no-release', release: false },
          { breaking: true, release: 'major' },
          { type: 'feat', release: 'minor' },
          { type: 'refactor', scope: 'core-*', release: 'minor' },
          { type: '*', release: 'patch' },

Refer to the CI Configuration chapter for guides of running semantic-release in CI.

Powered by Gatsby. Theme inspired by end2end.

© 2014-2021. Made withby mdluo.