How to lint messages with CommitLint and Husky

What is Husky

Husky is a tool that allows you to easily add Git hooks to your project. Git hooks are scripts that are executed at certain points in the Git workflow, such as before a commit, after a commit, before a push, etc. Husky simplifies the process of setting up and managing these hooks.

With Husky, you can automate various tasks or enforce certain rules and checks before allowing a commit or a push. This can include running tests, linting code, formatting files, validating commit messages, and more.

what is CommitLint

CommitLint is a tool that helps enforce consistent and well-formed commit messages in a Git repository. It checks the commit messages against a set of predefined rules or guidelines to ensure that they follow a consistent format.

CommitLint uses a configuration file to define the rules for commit message validation. This configuration file specifies the pattern or format that commit messages should adhere to, as well as any additional rules or requirements.

The most commonly used configuration for CommitLint is the conventional commit format, which promotes a standardized format for commit messages. This format typically consists of a structured message that includes a type, an optional scope, and a subject. For example: feat(user): add login functionality.

Lets make Commitlint and Husky Together

# Step 1. Install commitlint dependency
npm install --save-dev @commitlint/cli
# Step 2. Install conventional commit config
npm install --save-dev @commitlint/config-conventional
# Step 3. Configure commitlint to use conventional commits
echo "module.exports = {extends: ['@commitlint/config-conventional']};" > commitlint.config.js
# Step 4. Install Husky (v7)
npm install --save-dev husky 
# Step 5. Enable Husky
# (NOTE: this will also add a "npm test" pre-commit hook)
npx husky-init && npm install
# Step 5.1 (Optionally) Delete the pre-commit hook
rm .husky/pre-commit
# Step 6. Add commitlint commit-msg hook 
cat <<EEE > .husky/commit-msg #!/bin/sh . "\$(dirname "\$0")/_/husky.sh"  npx --no -- commitlint --edit "\${1}" EEE

lets install Husky

npm install --save husky

we can run prepare script for Husky

  "scripts": {
    "lint": "node node_modules/prettier/bin-prettier --check \"**/*.{js,json,ts,yml,yaml}\"",
    "prepare": "husky install",
    "prettier": "node node_modules/prettier/bin-prettier --check '**/*.{js,json,ts,yml,yaml}'",
    "prettier:write": "node node_modules/prettier/bin-prettier --write \"**/*.{js,json,ts,yml,yaml}\""
  },

Lets add all required dependencies for Commitlint

{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "prepare": "husky install",
    "prettier": "node node_modules/prettier/bin-prettier --check '**/*.{js,json,ts,yml,yaml}'",
    "prettier:write": "node node_modules/prettier/bin-prettier --write \"**/*.{js,json,ts,yml,yaml}\""
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@commitlint/cli": "^17.6.3",
    "@commitlint/config-conventional": "^16.0.0",
    "commitizen": "^4.2.4",
    "conventional-changelog-cli": "^2.2.2",
    "cz-conventional-changelog": "^3.3.0",
    "husky": "^7.0.4",
  },
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-conventional-changelog"
    }
  }
}

create a commitlint config file commitlint.config.js

module.exports = { extends: ["@commitlint/config-conventional"] };

Lets create .husky configurations with prepare script from package json script

npm run prepare 

prepare will create .husky.sh in .husky folder _

#!/bin/sh
if [ -z "$husky_skip_init" ]; then
  debug () {
    if [ "$HUSKY_DEBUG" = "1" ]; then
      echo "husky (debug) - $1"
    fi
  }

  readonly hook_name="$(basename "$0")"
  debug "starting $hook_name..."

  if [ "$HUSKY" = "0" ]; then
    debug "HUSKY env variable is set to 0, skipping hook"
    exit 0
  fi

  if [ -f ~/.huskyrc ]; then
    debug "sourcing ~/.huskyrc"
    . ~/.huskyrc
  fi

  export readonly husky_skip_init=1
  sh -e "$0" "$@"
  exitCode="$?"

  if [ $exitCode != 0 ]; then
    echo "husky - $hook_name hook exited with code $exitCode (error)"
  fi

  exit $exitCode
fi

and finally create commit msg Husky Hook

commit-msg

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx --no-install commitlint --edit $1

pre-commit

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

if [ "$NO_VERIFY_YOLO" ]; then exit 0; fi
npm run lint && npm run prettier

prepare-commit-msg

#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

if [ "$NO_VERIFY" ] || [ "$NO_VERIFY_YOLO" ]; then exit 0; fi
exec < /dev/tty && node_modules/.bin/cz --hook || true

Now our setup is ready, we can test it

➜  express-apis-monorepo git:(develop)npm run prettier:write

> express-apis-app@1.0.0 prettier:write
> node node_modules/prettier/bin-prettier --write "**/*.{js,json,ts,yml,yaml}"

commitlint.config.js 30ms
jest.config.js 7ms
pnpm-lock.yaml 665ms
pnpm-workspace.yaml 24ms
➜  express-apis-monorepo git:(develop)
➜  express-apis-monorepo git:(develop)git commit -m ""

> express-apis-app@1.0.0 lint
> node node_modules/prettier/bin-prettier --check "**/*.{js,json,ts,yml,yaml}"

Checking formatting...
All matched files use Prettier code style!

> express-apis-app@1.0.0 prettier
> node node_modules/prettier/bin-prettier --check '**/*.{js,json,ts,yml,yaml}'

Checking formatting...
All matched files use Prettier code style!
cz-cli@4.2.4, cz-conventional-changelog@3.3.0

? Select the type of change that you're 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 (white-space, formatting, missing semi-colons, etc) 
  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 
(Move up and down to reveal more choices)