Our goals
- Auto-format source code before each commit
- Fast local tests before each commit
- Auto-install npm dependencies when switching branches
The base: Git hooks with Husky
Husky is a thin wrapper around git hooks - shell scripts that execute in response to git actions.
Husky configures git to use the .husky
folder for reading hooks.
Setup is straightforward, install husky
β npm install husky is-ci --save-dev
β npx husky install
and setup the husky install
command to run automatically outside of CI environments
// package.json
{
"scripts": {
"prepare": "is-ci || husky install"
}
}
And finally, add a test pre-commit
hook:
β npx husky add .husky/pre-commit 'echo "the pre-commit hook ran π"'
This will put our script in the .husky
directory:
β test-repo git:(master) β tree .husky
.husky
βββ _
β βββ husky.sh
βββ pre-commit
.husky/pre-commit
:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
echo "the pre-commit hook ran π"
And we can test that our hook executes when we commit:
β git add .husky && git commit -m 'add husky'
the pre-commit hook ran π
....
1. Auto-format source code before each commit
Using prettier and pretty-quick auto-formatting code is as simple as:
β npm install prettier pretty-quick --save-dev
Update .husky/pre-commit
:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx pretty-quick --staged # β
replace the dummy "echo" command
And weβre done, now our files will be auto-formatted by prettier
before each commit!
β git add . && git commit -m 'add pretty-quick'
π Finding changed files since git revision ff5738c.
π― Found 2 changed files.
βοΈ Fixing up package.json.
β
Everything is awesome!
....
2. Fast local tests before each commit
We will use lint-staged to execute minimal local tests before each commit.
The following setup will only execute tests related to the files that have actually been changed in the current commit instead of your full test suite. We can leave the full test suite execution to our CI.
β npm install lint-staged jest --save-dev
Update .husky/pre-commit
:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx pretty-quick --staged
npx lint-staged # β
add lint-staged
Add .lintstagedrc.js
// .lintstagedrc.js
module.exports = {
// If any ts/js(x) files changed.
"**/*.{ts,js}?x": [
// Execute tests related to the staged files.
"npm run test -- --passWithNoTests --bail --findRelatedTests",
// Run the typechecker.
// Anonymous function means: "Do not pass args to the command."
() => "tsc --noEmit",
],
}
3. Auto-install npm dependencies when switching branches
A great way to lose time chasing non-existent bugs is to forget that different branches can have different dependencies in package.json
.
The following setup will automatically run npm install
after you switch branches or execute git commands that can modify package.json
, e.g. a merge.
β npx husky add .husky/post-checkout 'echo "the post-checkout hook ran π"'
Update .husky/post-checkout
:
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# From the post-checkout docs https://git-scm.com/docs/githooks#_post_checkout
# The hook is given three parameters:
# - the ref of the previous HEAD
# - the ref of the new HEAD (which may or may not have changed)
# - a flag indicating whether the checkout was:
# - a branch checkout (changing branches, flag=1)
# - a file checkout (retrieving a file from the index, flag=0).
# When the third script parameter is "1" we are executing a branch checkout
if [ "$3" = "1" ]; then
# install npm packages
npm install
# or with yarn
# yarn install --frozen-lockfile
fi
Additionally, we should run npm install
after a merge or a rewrite.
We can setup those up with just 2 commands:
β npx husky add .husky/post-merge 'npm ci'
β npx husky add .husky/post-rewrite 'npm install'
Or with yarn
:
β npx husky add .husky/post-merge 'rm -rf node_modules && yarn install --frozen-lockfile'
β npx husky add .husky/post-rewrite 'yarn install'