Never Forget to Remember with Githooks and Husky
Tags: webdev, git, productivity
Published: 2023-03-07 17:48:22

*Don’t forget to run the tests before you merge into main! Remember to run ESLint before pushing! Please include the ticket number in your commit message so {APP_INTEGRATION} can do its thing.*

Agreeing to common conventions and processes frees up bandwidth for engineers to focus on tasks that are interesting, meaningful, and add value for our clients. Onboarding new engineers to a project takes time and energy and can be inconsistent depending on how often documentation is updated. Most importantly: executing manual tasks is a computer’s job!

With [Husky](https://typicode.github.io/husky/#/) + [githooks](https://git-scm.com/docs/githooks), developers can automate boring day-to-day housekeeping tasks, easily share them with colleagues and choose whether to make them required or “opt-in” for all contributors. Moreover, we can document and enforce our repository’s standards with code rather than an SOP or checklist that quickly goes out of date. Below is one approach you could adopt on your own projects!

### Installation and Setup

First we install husky as a dev dependency.

```json
npm install husky --save-dev
```

Now to finish setting up husky run:

```json
husky install
```

At this point you should see a `.husky/` directory in your project. 🎉

[🐶 One thing I love about Husky’s latest version is that *all* of its magic is contained in that one, conspicuous directory.]

### Does it work?

Let’s test our install and setup by adding a post-commit hook that logs an important affirmation to our fellow contributors. To do that we can use the `husky add` command:

`npx husky add .husky/post-commit "echo You. Are. Enough."`

Now we can take comfort in these wise words after we commit. 🧘

### Run Tests

Affirmations are nice and all, but it would be much more practical to do something like run unit tests before each commit so I don’t break the build for my team over lunch. To do that, we’d pull out the `husky add` command again:

`npx husky add .husky/pre-commit "npm run test"`

If I attempt to commit code that breaks tests, the commit will abort and log failures to the console.

### Linting

We can also lint and fix before we commit. If I have an existing script that does linting for me, i.e.,:

```json
"scripts": {
		...
    "lint-fix": "eslint ./ --ignore-path .gitignore --fix",
  },
```

then I can run `npx husky add .husky/pre-commit "npm run lint-fix"`. No more wrangling with built-in IDE/editor settings and extensions!

### Other applications

Rinse. Wash. Repeat! At this point you’re all set to figure out which hooks and commands make the most sense on your project.

### FAQ + Gotchas

*Why do I get following error`.git can't be found` when trying to run `husky install`* ?

Husky assumes that your `package.json` and `.git` directory are at the same level. This might not be the case in, say, a mono repo with distinct `frontend/` `backend/` packages. Husky supports this by means of a [custom-directory](https://typicode.github.io/husky/#/?id=custom-directory). If I only really want to run use githooks to manage my frontend code, I can add this npm script to the `scripts` object in `package.json`:

```json
"scripts": {
		...
    "onetime-setup-husky": "cd .. && husky install frontend/.husky"
  },
```

Now when we run `npm run onetime-setup-husky` we will first navigate to the directory that contains `.git` and then install the `.husky` directory in the `frontend/` directory.  This only needs to be run once!

*I doubt I’m going to get folks on my team to set all this up and run all these commands in the right order. Is there a way to make this easier?*

Yes! One approach would be to create a script that does, well, *everything*:

```json
"scripts": {
		...
    "onetime-setup-all-the-hooks-and-all-the-things": "cd .. && husky install frontend/.husky && npx husky add frontend/.husky/pre-commit \"npm run test\" && npx husky add frontend/.husky/post-commit \"echo you are doing great\""
  },
```

Now all you’d have to do is ask your colleague to run `npm run onetime-setup-all-the-hooks-and-all-the-things` and they would be in business. I’d advise you rename it to something a little more reasonable, but this gets the point across 🙂. 

*Can I make all of this mandatory for all contributors? How do I make sure everyone else sets it up properly?*

To make setup as painless and automatic as possible, ensure your `.husky/` directory is tracked in your repository and then rename the `onetime-setup*` script to  `prepare` in your `package.json` scripts, i.e.,

```json
"scripts":{
    "prepare": "cd .. && husky install frontend/.husky"
  },
```

The `prepare` script will be executed auto-magically after `npm install` executes. Check out [the npm docs](https://docs.npmjs.com/cli/v9/using-npm/scripts#prepare-and-prepublish) to learn more about it. And now that the `.husky` directory is tracked by git, you and your colleagues will be running the exact same scripts across machines so no need to inline them and pollute your `package.json`

*I want to add husky + git hooks for myself, but my team isn’t totally convinced— can we meet in the middle?*

Understandable. Nobody likes to be micro-managed and admittedly some tasks are less important than others. One dev might prefer to use their IDE’s built-in prettier/ESLint tooling. Another dev might elect to run unit tests constantly using Their Favorite Tool, in which case a pre-commit hook doing the same would be frustrating and redundant. That’s okay! 

For one, be sure and add `.husky/` to your `.gitignore` so it remains untracked and out of your colleagues’ hair.

To “opt-out” of these hooks, do nothing! If you don’t ever run  `npm run onetime-setup*` script then you are all set. If you do and later regret it: delete the `.husky/` directory.

*Why didn’t you use husky’s built in super quick auto setup?*
There is indeed an [even quicker way](https://typicode.github.io/husky/#/?id=automatic-recommended) to set up Husky. For the sake of learning, however, I thought it’d be more illustrative to setup husky the slightly longer way. Additionally, the above approach above works in the case of mono-repos where `.git` and `package.json` are located in different directories.

*Why do you need Husky for this if githooks work out of the box?*

You do not need Husky to leverage githooks. But I have found Husky quite useful for all of the reasons above: shareability, opt-in, enforcing code standards with code etc.