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 or debugger statements and fail if it does find them. A way to do it with a script and "easier" would be by using grep, but since Windows is not POSIX compliant, it doesn’t have grep 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 or debugger statements and fail if it does find them. A way to do it with a script and "easier" would be by using grep, but since Windows is not POSIX compliant, it doesn’t have grep 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!