Using Husky Git Hooks and Lint-Staged With Nested Folders

What is husky?

Husky is a JavaScript package that allows you to run some code during various parts of your git workflow. Husky leverages git hooks to allow you to hook into various git events such as pre-commit and pre-push.


What is lint-staged?

Lint-staged allows you to run code against your staged git files to automate the tedious part of your workflows, such as formatting with Prettier and/or linting with ESLint.


Using husky with lint-staged

You can use husky to trigger lint-staged during the pre-commit hook, so that your coding standards are enforced right as you commit. The major benefit of this is it enforces your coding standards without someone needing to configure and install certain extensions in their IDE to enforce them on save or remembering to do anything. Your code gets fixed before it ever leaves your machine, so you don’t have to wait for your CI to inform you that you forgot to run the formatter or linter.


The Problem: husky expects your package.json at the root

The problem is, husky expects your package.json to be at the root of your project. That’s a fine assumption to make a lot of times, but sometimes we might be in more of a monorepo situation, or where a single repo contains both the Server and Client in separate folders.


The Solution

I’m going to use the example from the default React and ASP.NET Core template when you run dotnet new react. The default folder structure has the React code nested under a /ClientApp folder with the corresponding package.json. The folder structure looks like this:

Let’s dive into the steps. You can view the completed repo here.

  1. cd into the directory with your package.json 
    • In my case: ./ClientApp

  1. Install husky and lint-staged:
    • npm i husky lint-staged -D

  1. Add a “prepare” npm script to your package.json with the following contents (note: the initial cd goes to the repo root and the husky install goes from the repo root down to your directory with the package.json):
  "scripts": {
      // other scripts omitted 
      "prepare": "cd ../ && husky install ./ClientApp/.husky"
  1. Run npm install

  1. FYI – a .husky folder will appear in the same path as your package.json, in my case under ./ClientApp

  1. Create a pre-commit file with no file extension under the .husky folder with the following contents (note: the ./ClientApp is the path to your package.json relative to the root of your repository):
. "$(dirname "$0")/_/"
cd ./ClientApp && npx lint-staged 
  1. Make sure the pre-commit file is executable via chmod:
chmod +x ./husky/pre-commit
  1. Add a .lintstagedrc file under your ./ClientApp folder (or wherever your package.json lives) with the following contents:
  "*.{js,ts,tsx,scss,css,md}": ["eslint", "prettier --write"]

As you can probably guess, the above says look for any changes to any of the file extensions defined on the left, and run the commands in the array on the right if you find any changes to those files.

And that’s it! Now if you try to make a commit, you will see that eslint and prettier will run and fix themselves as you would expect. See an example of it in action below: