Optional pre-commit checks with husky

on Dragoș Străinu's blog

If you are working on a JavaScript project, you probably are using husky to check your commit messages, maybe do some Prettier, Eslint formatting, or TypeScript checks.

Checking commit messages is fast, but running formatting and type checking takes more time as your project grows.

You can use lint-staged for prettier and eslint, but using it for TypeScript check makes no sense because if you change types in a git staged file it may break typing in another file.

All engineers have different workflows. To fix an issue from the TypeScript compiler, you could run a tsc --watch process and make sure nothing is broken.

I don't want to run tsc --watch because constantly running it slows the laptop. I don't mind if this check will be done at pre-commit stage even if the commit will take 10,20,30+ seconds.

But how to make this pre-commit optional? So the teammates that do manually formatting and type checking will not be frustrated.

husky version 7 enables pre-commit configuration using a bash script. So why not do checking before running tsc or lint-staged?!

This will be the beginning of the .husky/pre-commit file

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

APP_PRE_COMMIT_OPTIONS="$(dirname "$0")/_/pre-commit.options"

So we can put our options in a file named pre-commit.options inside the .husky/_/ folder.

.husky/_/ should have a .gitingore file that will ignore our option file.

Next, let's print some message if the options files do not exist

YELLOW="\033[1;33m"
GREEN="\033[1;32m"
RESET="\033[0m"
if ! [ -f "$APP_PRE_COMMIT_OPTIONS" ]; then
  echo "${YELLOW}\nSkipping pre-commit hook."
  echo "If you want to use pre-commit for TypeScript check and lint-staged, run:\n"
  echo "  ${GREEN}echo -e 'APP_TS=true;\\\nAPP_LINT=true;' > $P_APP_PRE_COMMIT_OPTIONS${RESET}"
  echo ${YELLOW}\nIt will add some delay before committing!\n${RESET}"
  exit 0
fi

optional-pre-commit

Now, let's source the options file and check if the user enabled the linting:

source $APP_PRE_COMMIT_OPTIONS

if [ -n "${APP_LINT}" ] && [ "${APP_LINT}" == "true" ]; then
  echo "${GREEN}[husky] [pre-commit] [lint-staged]${RESET}"
  npx lint-staged
fi

You can add more options and checks if needed!

Final result

The .husky/pre-commit file should look similar to this:

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

APP_PRE_COMMIT_OPTIONS="$(dirname "$0")/_/pre-commit.options"

YELLOW="\033[1;33m"
GREEN="\033[1;32m"
RESET="\033[0m"
if ! [ -f "$APP_PRE_COMMIT_OPTIONS" ]; then
  echo "${YELLOW}\nSkipping pre-commit hook."
  echo "If you want to use pre-commit for TypeScript check and lint-staged, run:\n"
  echo "  ${GREEN}echo -e 'APP_TS=true;\\\nAPP_LINT=true;' > $P_APP_PRE_COMMIT_OPTIONS${RESET}"
  echo ${YELLOW}\nIt will add some delay before committing!\n${RESET}"
  exit 0
fi

source $APP_PRE_COMMIT_OPTIONS

if [ -n "${APP_TS}" ] && [ "${APP_TS}" == "true" ]; then
  echo "${GREEN}[husky] [pre-commit] [tsc]${RESET}"
  npx tsc
fi

if [ -n "${APP_LINT}" ] && [ "${APP_LINT}" == "true" ]; then
  echo "${GREEN}[husky] [pre-commit] [lint-staged]${RESET}"
  npx lint-staged
fi