Husky Setup
Husky is a tool in charge of policing git commits and/or pushes, before comitting anything to version control, before even saving a commit it can run linting, formatting, and other type of logic, that in case it fails it will short-circuit the process.
Husky is installed as a node package:
-
pnpm add --save-dev husky
And due to the nature of our setup we have to setup husky in a specific way so that
it makes use of the parent folder’s .git
whilst being inside of a folder one level
below: (reference).
After adding husky we can try and run npx husky init
. However this will fail since
it doesn’t find the .git
folder at our React App folder, hence we have to set it
up manually.
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview",
"prepare": "cd .. && husky kakeibro-web/.husky" (1)
},
1 | The prepare script will be created already, however you have to configure it
so that we go to the .git folder level. And then apply husky from the .husky
folder at the app folder level. |
Right after this, the pre-commit
hook should be configured as follows:
cd kakeibro-web
pnpm lint
Once that’s done, by running a pnpm run prepare
, inside of the .husky/
folder
necessary files that hook into git and add the logic will be auto-generated. Right
after those files are generated every time we try to commit something, all the logic
in the hooks under .husky/
should run and stop us from comitting things that are not up to
snuff.
Now, this is setup for a setup installation, the power of having setup husky in
a prepare
script is that next time someone runs a pnpm install
this script will
run as well and it will have husky setup for that specific workstation. This
is great, since developers will not have to worry about this setup whatsoever.
The main reasons as to why husky is so great is:
-
One-time setup, open for extensions.
-
Eases up the onboarding project, since any new workstation that wants to run the project will have the pre-commit and hooks already configured after a
pnpm install
. We leverage node and the module to take care of all the grunt work (seamless).
About Husky’s Hooks
Unlike the Backend Hooks approach, we will simplify things
a bit by leveraging Node. And so, all hooks that are under .husky/*
in theory
are expecting to run on a POSIX compliant CLI
however, due to our cross-platform requirement, we are going to put all the logic behind
a javascript file and we are going to run it with Node
.
And so the raw hook files should just reference the respective .js
file, e.g:
cd kakeibro-web
node .husky/pre-commit.js
Just as normal git hooks, the starting path will always be the root repository where
.git
is at. But it’s here where things can get a bit different than the raw git hooks approach.
First of all, we are leveraging pnpm, so everything is catered to how that specific tool works. Since
we will be running on node
, we get access to pnpm
, however, it’s through the leveraging of its
fallback configuration that we can simply register scripts at package.json
and then try to run them by inputting something like pnpm <script-name>
.
Looking at the .js
files that are next to the husky hooks, we will see how we are by the
most part making usage of those scripts. Except for the one specific check that will be explained
in a moment.
Pre-Commit Script
The pre-commit.js
hook script takes care of:
-
Running
ESLint
(it will not auto-fix the errors, developers will be forced to take notice of this and hopefully learn from it).-
HINT: Since
prettier
is installed, the command can be run to take care of stragglers.
-
-
Running
tsc and vite build
to make sure nothing in the solution is broken. -
Running custom node logic to scan all files in the project and pick up on any
console.log
ordebugger
statements and fail if it does find them. A way to do it with a script and "easier" would be by usinggrep
, but since Windows is not POSIX compliant, it doesn’t havegrep
installed by default and this accounts for that possible error case.
Pre-Push Script
The pre-push.js
hook script takes care of:
-
Running
ESLint
(it will not auto-fix the errors, developers will be forced to take notice of this and hopefully learn from it).-
HINT: Since
prettier
is installed, the command can be run to take care of stragglers.
-
-
Running
tsc and vite build
to make sure nothing in the solution is broken. -
Running a check for possible outdated packages, this follows the idea of the backend hooks behavior, in the sense that it has been configured to not fail, (this would fail if it does find it), it will only be a warning, and hopefully a point when pushing for a developer to take notice that some packages might be getting left behind on the release lifecycle.
-
Running custom node logic to scan all files in the project and pick up on any
console.log
ordebugger
statements and fail if it does find them. A way to do it with a script and "easier" would be by usinggrep
, but since Windows is not POSIX compliant, it doesn’t havegrep
installed by default and this accounts for that possible error case. -
Running a script to check for outdated packages we have, if any new version is found its default behavior is to fail. Updating node packages sometimes might be really disruptive, hence we don’t want to just update for the sake of updating, hence this will fail silently, whilst still printing about the new versions found to the developer to be mindful about them.
-
Running a script to check for packages with vulnerabilities, this is way more important than just outdated packages, so this will fail if something is found and the dev team should put efforts immediately to update the package (alongside all possible errors that might show due to said update).
The difference between the amount of operations each stage has has been configured with intent. Whilst simply comitting we shouldn’t have to disrupt the developer experience as much since changes might get overwritten later, and the flow state might end up taking a hit. However, before trying to push something to version control we will run as many checks as we can so that we are not pushing bad code that could end up breaking the build. However, the amount of time it takes for the frontend hooks to run is way shorter than the ones at the backend side, hence the differences in here are not as many. |
A study case
'Date: 23/02/2025'
When developing, after trying to make a push to GitHub, pnpm audit
detected a
dependency that had a vulnerability detected (esbuild). Reference.
It was Moderate, and it was already patched. The pre-push
hook took care of letting
us know about that, and immediately dev effort was put in order to remedy this issue.
Thanks to another command pnpm why esbuild
, it outputted a tree of the different
packages we had declared plus their dependencies, esbuild
isn’t noted in package.json
,
but it’s a transitive dependency used by vite
and vitest
libraries.
The way to remedy this was to run a pnpm update
, the tool is great and it bumped up
everything, however if your tool wouldn’t help with that you would have to go looking
at the output, and then editing the package.json
file so that all versions
start from those updated versions only. (e.g: 1.1.0 ⇒ 1.1.3).
However, and you have to be aware of this, the packages, sometimes might not get updated up until later, for whatever reason, and this would end up blocking your flow, since audit will keep complaining about the vulnerability that was found. You have two choices, fail silently, or just wait. No one in their right mind would go for the second one. Stay on top of it still! |