Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: typicode/husky
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v8.0.3
Choose a base ref
...
head repository: typicode/husky
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v9.0.1
Choose a head ref

Commits on Mar 10, 2023

  1. chore(deps): bump http-cache-semantics from 4.1.0 to 4.1.1 (#1234)

    Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
    - [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
    - [Commits](kornelski/http-cache-semantics@v4.1.0...v4.1.1)
    
    ---
    updated-dependencies:
    - dependency-name: http-cache-semantics
      dependency-type: indirect
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored Mar 10, 2023
    Copy the full SHA
    09658a6 View commit details
  2. Copy the full SHA
    eadf173 View commit details

Commits on Apr 25, 2023

  1. chore: improve ci speed by splitting lint and test (#1252)

    Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
    typicode and paescuj authored Apr 25, 2023
    Copy the full SHA
    65b161f View commit details
  2. docs: typo (#1253)

    Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
    typicode and paescuj authored Apr 25, 2023
    Copy the full SHA
    34ef246 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    6937fd5 View commit details
  4. docs: add message about restarting GUI (#1224)

    * update docs to add restarting vs code
    
    * Update docs/README.md
    
    Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
    
    ---------
    
    Co-authored-by: typicode <typicode@gmail.com>
    Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
    3 people authored Apr 25, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    926811f View commit details

Commits on Apr 26, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    16bf08a View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a54d403 View commit details

Commits on Apr 28, 2023

  1. chore: use Node 16 in tsconfig.json (#1256)

    Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
    typicode and paescuj authored Apr 28, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3749193 View commit details
  2. refactor: comments (#1257)

    Co-authored-by: Pascal Jufer <pascal-jufer@bluewin.ch>
    typicode and paescuj authored Apr 28, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    622e4db View commit details

Commits on Apr 29, 2023

  1. chore: GitHub Workflows security hardening (#1209)

    * build: harden node.js.yml permissions
    
    Signed-off-by: Alex <aleksandrosansan@gmail.com>
    
    * build: harden npm_publish.yml permissions
    
    Signed-off-by: Alex <aleksandrosansan@gmail.com>
    
    ---------
    
    Signed-off-by: Alex <aleksandrosansan@gmail.com>
    sashashura authored Apr 29, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    08fedc2 View commit details

Commits on Apr 30, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a88b58f View commit details

Commits on May 11, 2023

  1. feat: use zsh (#1262)

    typicode authored May 11, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d9131d6 View commit details

Commits on May 13, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1e52695 View commit details

Commits on May 15, 2023

  1. chore: vitepress (#1264)

    typicode authored May 15, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    58da6f6 View commit details
  2. chore: clean devDeps

    typicode committed May 15, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9baeb13 View commit details
  3. docs: typo

    typicode authored May 15, 2023
    Copy the full SHA
    89189d4 View commit details
  4. chore: vitepress config

    typicode committed May 15, 2023
    Copy the full SHA
    f07f701 View commit details
  5. docs: clean

    typicode committed May 15, 2023
    Copy the full SHA
    cdd8376 View commit details
  6. docs: clean

    typicode committed May 15, 2023
    Copy the full SHA
    c5c1af2 View commit details
  7. docs: typo

    typicode committed May 15, 2023
    Copy the full SHA
    ac2b52f View commit details

Commits on May 16, 2023

  1. 5
    Copy the full SHA
    5ac4829 View commit details

Commits on May 18, 2023

  1. style: format

    typicode committed May 18, 2023
    Copy the full SHA
    0eba7dd View commit details

Commits on May 19, 2023

  1. docs: reorg

    typicode committed May 19, 2023
    Copy the full SHA
    7543615 View commit details

Commits on May 21, 2023

  1. Copy the full SHA
    fd32c22 View commit details

Commits on Jun 7, 2023

  1. fix: docs link (#1274)

    2g4 authored Jun 7, 2023
    Copy the full SHA
    97b149d View commit details

Commits on Sep 16, 2023

  1. docs: clarify client-side hooks are supported (#1300)

    Implementing #1298 (verified/proof: #1249).
    garrett-green authored Sep 16, 2023
    Copy the full SHA
    358f833 View commit details

Commits on Sep 22, 2023

  1. Copy the full SHA
    4f7f37f View commit details

Commits on Sep 24, 2023

  1. Copy the full SHA
    8c5a08c View commit details

Commits on Oct 2, 2023

  1. Copy the full SHA
    94d0393 View commit details

Commits on Oct 12, 2023

  1. Copy the full SHA
    46325f8 View commit details

Commits on Oct 26, 2023

  1. docs: use NODE_ENV to conditionally install husky (#1310)

    See npm's documentation at https://docs.npmjs.com/cli/v8/commands/npm-install#omit
    
    > If the resulting omit list includes `'dev'`, then the `NODE_ENV` environment
    > variable will be set to `'production'` for all lifecycle scripts.
    forivall authored Oct 26, 2023
    Copy the full SHA
    06df89a View commit details

Commits on Nov 16, 2023

  1. Copy the full SHA
    6a5290c View commit details

Commits on Nov 21, 2023

  1. Update troubleshooting.md (#1320)

    Having a pre-commit the previous documentation failed with
    
    ```shell
    $ git commit
    .husky/pre-commit: 9: /home/user/.huskyrc: [[: not found
    ```
    
    Using this patch is compatible with /bin/bash
    hexagon6 authored Nov 21, 2023
    Copy the full SHA
    9d3eb31 View commit details

Commits on Dec 11, 2023

  1. fix: posts urls

    typicode authored Dec 11, 2023
    Copy the full SHA
    3dd4ea2 View commit details

Commits on Jan 22, 2024

  1. Add simplified Chinese documentation (#1290)

    * build: update vitepress to 1.0.0-beta.6 version
    
    * docs: add simplified Chinese translation
    CosmoLau authored Jan 22, 2024
    Copy the full SHA
    a5c36f5 View commit details

Commits on Jan 25, 2024

  1. v9 (#1333)

    typicode authored Jan 25, 2024
    7
    Copy the full SHA
    ec13855 View commit details
  2. v9

    typicode committed Jan 25, 2024
    Copy the full SHA
    c67a57a View commit details
  3. chore: fix deploy

    typicode committed Jan 25, 2024
    Copy the full SHA
    4bf0f79 View commit details
  4. 9.0.0

    typicode committed Jan 25, 2024
    Copy the full SHA
    c68cc26 View commit details
  5. update npm_publish.yml

    typicode committed Jan 25, 2024
    Copy the full SHA
    e48ee6c View commit details
  6. 9.0.1

    typicode committed Jan 25, 2024
    1
    Copy the full SHA
    513c2c9 View commit details
3 changes: 0 additions & 3 deletions .commitlintrc.json

This file was deleted.

5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
root = true
[*]
end_of_line = lf
indent_style = tab
insert_final_newline = false
3 changes: 0 additions & 3 deletions .eslintrc.json

This file was deleted.

2 changes: 0 additions & 2 deletions .gitattributes

This file was deleted.

4 changes: 2 additions & 2 deletions .github/ISSUE_TEMPLATE/issue.md
Original file line number Diff line number Diff line change
@@ -9,10 +9,10 @@ assignees: ''

**Troubleshoot**
- [ ] Before creating an issue, please check:
https://typicode.github.io/husky/#/?id=troubleshoot
https://typicode.github.io/husky/troubleshooting.html

If you're migrating from husky 4, see:
https://typicode.github.io/husky/#/?id=migrate-from-v4-to-v7
https://typicode.github.io/husky/migrating-from-v4.html

**Context**
Please describe your issue and provide some context:
68 changes: 12 additions & 56 deletions .github/README.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,34 @@
# husky

[![Open Collective](https://opencollective.com/husky/all/badge.svg?label=financial+contributors)](https://opencollective.com/husky) [![](https://img.shields.io/npm/dm/husky.svg?style=flat)](https://www.npmjs.org/package/husky) [![Node.js CI](https://github.com/typicode/husky/workflows/Node.js%20CI/badge.svg)](https://github.com/typicode/husky/actions)
[![](https://img.shields.io/npm/dm/husky.svg?style=flat)](https://www.npmjs.org/package/husky) [![Node.js CI](https://github.com/typicode/husky/workflows/Node.js%20CI/badge.svg)](https://github.com/typicode/husky/actions)

> Modern native Git hooks made easy
Husky improves your commits and more 🐶 _woof!_

# Install

```
npm install husky -D
```

# Usage

Edit `package.json > prepare` script and run it once:

```sh
npm pkg set scripts.prepare="husky install"
npm run prepare
```

Add a hook:

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

Make a commit:

```sh
git commit -m "Keep calm and commit"
# `npm test` will run every time you commit
```

_For more use cases (project in sub-directory, custom directory, CI support, ...), see documentation._

## Documentation

https://typicode.github.io/husky

**Important** Upgrading from v4 to v7 requires migrating previous config, please see the docs.

## Articles

- [Why husky has dropped conventional JS config](https://blog.typicode.com/husky-git-hooks-javascript-config/)
- [Why husky doesn't autoinstall anymore](https://blog.typicode.com/husky-git-hooks-autoinstall/)
**Important** Upgrading from v4 to v9 requires migrating previous config, please see the docs.

## License
## Sponsors

MIT
Support this project by becoming a sponsor [here](https://github.com/sponsors/typicode) 💖

# Sponsors
### GitHub

## Companies
<p align="center">
<a href="./sponsorkit/sponsors.svg">
<img src='./sponsorkit/sponsors.svg'/>
</a>
</p>

Does your company use husky? Ask your manager or marketing team if your company would be interested in supporting this project.
### Open Collective

<a href="https://opencollective.com/husky/tiers/company/0/website"><img src="https://opencollective.com/husky/tiers/company/0/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/1/website"><img src="https://opencollective.com/husky/tiers/company/1/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/2/website"><img src="https://opencollective.com/husky/tiers/company/2/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/3/website"><img src="https://opencollective.com/husky/tiers/company/3/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/4/website"><img src="https://opencollective.com/husky/tiers/company/4/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/5/website"><img src="https://opencollective.com/husky/tiers/company/5/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/6/website"><img src="https://opencollective.com/husky/tiers/company/6/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/7/website"><img src="https://opencollective.com/husky/tiers/company/7/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/8/website"><img src="https://opencollective.com/husky/tiers/company/8/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/9/website"><img src="https://opencollective.com/husky/tiers/company/9/avatar.svg?avatarHeight=120"></a>

## Individuals

Find husky helpful? Become a backer and show your appreciation with a monthly donation on [Open Collective](https://opencollective.com/husky). You can also tip with a one-time donation.

<a href="https://opencollective.com/husky" target="_blank"><img src="https://opencollective.com/husky/tiers/individual.svg?avatarHeight=32"/></a>

GitHub sponsors can be viewed on my [profile](https://github.com/typicode). All past and current Open Collective sponsors can be viewed on [here](https://opencollective.com/husky).
<a href="https://opencollective.com/husky/tiers/company/5/website"><img src="https://opencollective.com/husky/tiers/company/5/avatar.svg?avatarHeight=120"></a>
36 changes: 36 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Deploy
on:
workflow_dispatch: {}
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
defaults:
run:
working-directory: ./docs
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 20
cache: npm
- run: npm ci
- name: Build
run: npx vitepress build
- uses: actions/configure-pages@v2
- uses: actions/upload-pages-artifact@v1
with:
path: docs/.vitepress/dist
- name: Deploy
id: deployment
uses: actions/deploy-pages@v1
40 changes: 13 additions & 27 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
@@ -4,35 +4,21 @@ name: Node.js CI

on: [push]

permissions:
contents: read # to fetch code (actions/checkout)

jobs:
build:
runs-on: ${{ matrix.os }}
test:
strategy:
matrix:
node-version: [14, 16, 18]
os: [ubuntu-latest, macOS-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm --version
- run: npm ci --ignore-scripts
- run: npm test

# Node 14 comes with npm 6 which has issues on Windows
# Skipping tests on Windows with Node 14
build-windows:
node-version: [18, 20]
os: [ubuntu-latest, macOS-latest, windows-latest]
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [16, 18]
os: [windows-latest]
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm --version
- run: npm ci --ignore-scripts
- run: npm test
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm --version
- run: npm ci --ignore-scripts
- run: ./test.sh
29 changes: 16 additions & 13 deletions .github/workflows/npm_publish.yml
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages

name: Node.js Package

on:
release:
types: [created]
types: [published]

permissions:
contents: read

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 16
- run: npm ci --ignore-scripts
- run: npm test
node-version: 20
- run: ./test.sh

publish-npm:
needs: build
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci --ignore-scripts
- run: npm run build
- run: npm publish
node-version: 20
registry-url: 'https://registry.npmjs.org'
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
9 changes: 6 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
lib
node_modules
husky-*.tgz
tsconfig.tsbuildinfo
*.log
docs/.vitepress/cache
docs/.vitepress/dist
docs/.env
docs/sponsorkit/.cache.json
docs/sponsorkit/sponsors.json
docs/sponsorkit/sponsors.png
4 changes: 0 additions & 4 deletions .husky/commit-msg

This file was deleted.

5 changes: 1 addition & 4 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm test
./test.sh
3 changes: 3 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.*
test*
docs
1 change: 1 addition & 0 deletions .shellcheckrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
disable=SC1090,SC2164
1 change: 0 additions & 1 deletion CONTRIBUTING.md

This file was deleted.

37 changes: 0 additions & 37 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1 @@
# husky

> Modern native Git hooks made easy
Husky improves your commits and more 🐶 *woof!*

# Install

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

# Usage

Edit `package.json > prepare` script and run it once:

```sh
npm pkg set scripts.prepare="husky install"
npm run prepare
```

Add a hook:

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

Make a commit:

```sh
git commit -m "Keep calm and commit"
# `npm test` will run
```

# Documentation

https://typicode.github.io/husky
21 changes: 21 additions & 0 deletions bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import f, { writeFileSync as w } from 'fs'
import i from './index.js'

let a = process.argv[2]

if (a == 'init') {
let p = process.env.npm_package_json
let d = JSON.parse(f.readFileSync(p))
d.scripts.prepare = 'husky'
w('package.json', JSON.stringify(d, null, /\t/.test() ? '\t' : 2))
process.stdout.write(i())
w('.husky/pre-commit', process.env.npm_config_user_agent.split('/')[0] + ' test')
process.exit()
}

let d = c => console.error(`${c} command is deprecated`)
if (['add', 'set', 'uninstall'].includes(a)) { d(a); process.exit(1) }
if (a == 'install') d(a)

process.stdout.write(i(a == 'install' ? undefined : a))
Empty file removed docs/.nojekyll
Empty file.
25 changes: 25 additions & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { defineConfig } from 'vitepress'

// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Husky",
description: "Git hooks made easy",
base: '/husky/',
themeConfig: {
// outline: [2, 3],
socialLinks: [
{ icon: 'github', link: 'https://github.com/typicode/husky' },
],
carbonAds: {
code: 'CWYDP53L',
placement: 'typicodegithubio',
},
sidebar: [
{ text: 'Introduction', link: '/' },
{ text: 'Get Started', link: '/get-started' },
{ text: 'How To', link: '/how-to' },
{ text: 'Troubleshoot', link: '/troubleshoot' },
{ text: 'Migrate from v4', link: '/migrate-from-v4' },
],
}
})
520 changes: 0 additions & 520 deletions docs/README.md

This file was deleted.

63 changes: 63 additions & 0 deletions docs/get-started.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Get started

## Install

::: code-group

```shell [npm]
npm install --save-dev husky
```

```shell [pnpm]
pnpm add --save-dev husky
```

```shell [yarn]
yarn add --dev husky
# Add pinst ONLY if your package is not private
yarn add --dev pinst
```

```shell [bun]
bun add --dev husky
```

:::

## `husky init` (recommended)

The `init` command simplifies setting up husky in a project. It creates a `pre-commit` script in `.husky/` and updates the `prepare` script in `package.json`. Modifications can be made later to suit your workflow.

::: code-group

```shell [npm]
npx husky init
```

```shell [pnpm]
pnpm exec husky init
```

```shell [yarn]
# Due to specific caveats and differences with other package managers,
# refer to the How To section.
```

```shell [bun]
bunx husky init
```

:::


## Try it

Congratulations! You've successfully set up your first Git hook with just one command 🎉. Let's test it:

```shell
git commit -m "Keep calm and commit"
# test script will run every time you commit
```

_For manual setup and more information, see the [How To](how-to) section._

281 changes: 281 additions & 0 deletions docs/how-to.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
# How To

## Startup files

Husky allows you to execute local commands before running hooks. It reads commands from these files:

- `$XDG_CONFIG_HOME/husky/init.sh`
- `~/.config/home/init.sh`
- `~/.huskyrc` (deprecated)

## Skipping Git Hooks

### For a Single Command

Most Git commands include a `-n/--no-verify` option to skip hooks:

```sh
git commit -m "..." -n # Skips Git hooks
```

For commands without this flag, disable hooks temporarily with HUSKY=0:

```shell
HUSKY=0 git ... # Temporarily disables all Git hooks
git ... # Hooks will run again
```

### For multiple commands

To disable hooks for an extended period (e.g., during rebase/merge):

```shell
export HUSKY=0 # Disables all Git hooks
git ...
git ...
unset HUSKY # Re-enables hooks
```

### For a GUI or Globally

To disable Git hooks in a GUI client or globally, modify the husky config:

```sh
# ~/.config/husky/init.sh
export HUSKY=0 # Husky won't install and won't run hooks on your machine
```

## CI server and Docker

To avoid installing Git Hooks on CI servers or in Docker, use `HUSKY=0`. For instance, in GitHub Actions:

```yml
# https://docs.github.com/en/actions/learn-github-actions/variables
env:
HUSKY: 0
```
If installing only `dependencies` (not `devDependencies`), the `"prepare": "husky"` script may fail because Husky won't be installed.

You have multiple solutions.

Modify the `prepare` script to never fail:

```json
// package.json
"prepare": "husky || true"
```

You'll still get a `command not found` error message in your output which may be confusing. To make it silent, create `.husky/install.js`:

```js
// Skip Husky install in production and CI
if (process.env.NODE_ENV === 'production' || process.env.CI === '1') {
process.exit(0)
}
const husky = await import('husky')
husky()
```

Then, use it in `prepare`:

```json
"prepare": "node .husky/install.js"
```

## Testing Hooks Without Committing

To test a hook, add `exit 1` to the hook script to abort the Git command:

```shell
# .husky/pre-commit
# Your WIP script
# ...
exit 1
```

```shell
git commit -m "testing pre-commit code"
# A commit will not be created
```

## Project Not in Git Root Directory

Husky doesn't install in parent directories (`../`) for security reasons. However, you can change the directory in the `prepare` script.

Consider this project structure:

```
.
├── .git/
├── backend/ # No package.json
└── frontend/ # Package.json with husky
```

Set your prepare script like this:

```json
"prepare": "cd .. && husky frontend/.husky"
```

In your hook script, change the directory back to the relevant subdirectory:

```shell
# frontend/.husky/pre-commit
cd frontend
npm test
```

## Node Version Managers and GUIs

If you're using Git hooks in GUIs with Node installed via a version manager (like `nvm`, `n`, `fnm`, `asdf`, `volta`, etc...), you might face a `command not found` error due to `PATH` environment variable issues.

### Understanding `PATH` and Version Managers

`PATH` is an environment variable containing a list of directories. Your shell searches these directories for commands. If it doesn't find a command, you get a `command not found` message.

Run `echo $PATH` in a shell to view its contents.

Version managers work by:
1. Adding initialization code to your shell startup file (`.zshrc`, `.bashrc`, etc.), which runs each time you open a terminal.
2. Downloading Node versions to a directory in your home folder.

For example, if you have two Node versions:

```shell
~/version-manager/Node-X/node
~/version-manager/Node-Y/node
```

Opening a terminal initializes the version manager, which picks a version (say `Node-Y`) and prepends its path to `PATH`:

```shell
echo $PATH
# Output
~/version-manager/Node-Y/:...
```

Now, node refers to `Node-Y`. Switching to `Node-X` changes `PATH` accordingly:

```shell
echo $PATH
# Output
~/version-manager/Node-X/:...
```

The issue arises because GUIs, launched outside a terminal, don't initialize the version manager, leaving `PATH` without the Node install path. Thus, Git hooks from GUIs often fail.

### Solution

Husky sources `~/.config/husky/init.sh` before each Git hook. Copy your version manager initialization code here to ensure it runs in GUIs.

Example with `nvm`:

```shell
# ~/.config/husky/init.sh
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
```

Alternatively, if your shell startup file is fast and lightweight, source it directly:

```shell
# ~/.config/husky/init.sh
. ~/.zshrc
```

## Manual setup

Git needs to be configured and husky needs to setup files in `.husky/`.

Run the `husky` command once in your repo. Ideally, include it in the `prepare` script in `package.json` for automatic execution after each install (recommended).

::: code-group

```json [npm]
{
"scripts": {
"prepare": "husky" // [!code hl]
}
}
```

```json [pnpm]
{
"scripts": {
"prepare": "husky" // [!code hl]
}
}
```

```json [yarn]
{
"scripts": {
// Yarn doesn't support prepare script
"postinstall": "husky",
// Include this if publishing to npmjs.com
"prepack": "pinst --disable",
"postpack": "pinst --enable"
}
}
```

```json [pnpm]
{
"scripts": {
"prepare": "husky" // [!code hl]
}
}
```

:::

Run `prepare` once:

::: code-group

```sh [npm]
npm run prepare
```

```sh [pnpm]
pnpm run prepare
```

```sh [yarn]
yarn run prepare
```

```sh [bun]
bun run prepare
```

:::

Create a `pre-commit` file in the `.husky/` directory:

::: code-group

```shell [npm]
# .husky/pre-commit
npm test
```

```shell [pnpm]
# .husky/pre-commit
pnpm test
```

```shell [yarn]
# .husky/pre-commit
yarn test
```

```sh [bun]
# .husky/pre-commit
bun test
```

:::
43 changes: 0 additions & 43 deletions docs/index.html

This file was deleted.

67 changes: 67 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
![npm](https://img.shields.io/npm/dm/husky)

> Modern native git hooks made easy
Husky enhances your commits and more 🐶 _woof!_

Automatically **lint your commit messages**, **code**, and **run tests** upon committing or pushing.

Get started [here](/get-started).

See what's new in `v9` in the [Changelog]() 🚀

## Sponsors

Support this project by becoming a sponsor [here](https://github.com/sponsors/typicode) 💖

### GitHub

<p align="center">
<a href="./sponsorkit/sponsors.svg">
<img src='./sponsorkit/sponsors.svg'/>
</a>
</p>

### Open Collective

<a href="https://opencollective.com/husky/tiers/company/0/website"><img src="https://opencollective.com/husky/tiers/company/0/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/1/website"><img src="https://opencollective.com/husky/tiers/company/1/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/2/website"><img src="https://opencollective.com/husky/tiers/company/2/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/3/website"><img src="https://opencollective.com/husky/tiers/company/3/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/4/website"><img src="https://opencollective.com/husky/tiers/company/4/avatar.svg?avatarHeight=120"></a>
<a href="https://opencollective.com/husky/tiers/company/5/website"><img src="https://opencollective.com/husky/tiers/company/5/avatar.svg?avatarHeight=120"></a>

## Features

- Just `2 kB` (📦 _gzipped_) with no dependencies
- Uses new Git feature (`core.hooksPath`)
- Adheres to Git's native hook organization
- Aligns with [npm](https://docs.npmjs.com/cli/v10/using-npm/scripts#best-practices) best practices using `prepare` script
- Clear user messages
- O pt-in/opt-out options
- Branch-specific hooks
- Supports:
- macOS, Linux, Windows
- Git GUIs, Node version managers, custom hooks directory, nested projects, monorepos
- [All 13 client-side Git hooks](https://git-scm.com/docs/githooks)

## Used by

Husky is used in [**over 1.3M projects**](https://github.com/typicode/husky/network/dependents?package_id=UGFja2FnZS0xODQzNTgwNg%3D%3D) on GitHub, including:

- [vercel/next.js](https://github.com/vercel/next.js)
- [vercel/hyper](https://github.com/vercel/hyper)
- [webpack/webpack](https://github.com/webpack/webpack)
- [angular/angular](https://github.com/angular/angular)
- [facebook/docusaurus](https://github.com/facebook/docusaurus)
- [microsoft/vscode](https://github.com/microsoft/vscode)
- [11ty/eleventy](https://github.com/11ty/eleventy)
- [stylelint/stylelint](https://github.com/stylelint/stylelint)
- [colinhacks/zod](https://github.com/colinhacks/zod)
- [rollup/rollup](https://github.com/rollup/rollup)
- ...

## Articles

- [Why husky has dropped conventional JS config](https://blog.typicode.com/husky-git-hooks-javascript-config/)
- [Why husky doesn't autoinstall anymore](https://blog.typicode.com/husky-git-hooks-autoinstall/)
72 changes: 72 additions & 0 deletions docs/migrate-from-v4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Migrate from v4

If you were calling `package.json` scripts using `npm` or `yarn`, **you can simply copy your commands** from your config file to the corresponding hook:

Husky v4

```json
// package.json
{
"hooks": {
"pre-commit": "npm test && npm run foo" // [!code hl]
}
}
```

Husky v9

```shell
# .husky/pre-commit
# Note that you can now have commands on multiple lines
npm test // [!code hl]
npm run foo // [!code hl]
```

If you were calling locally installed binaries, **you need to run them via your package manager now**:

::: code-group

```js [.huskyrc.json (v4)]
{
"hooks": {
"pre-commit": "jest"
}
}
```

```shell [.husky/commit-msg (v9)]
# ...
npx --no jest
# or
yarn jest
```

:::

`HUSKY_GIT_PARAMS` environment variable is replaced now by native params `$1`, `$2`, etc.

::: code-group

```js [.huskyrc.json (v4)]
{
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
}
```

```shell [.husky/commit-msg (v9)]
# ...
npx --no -- commitlint --edit $1
# or
yarn commitlint --edit $1
```

:::

Other environment variables changes:

- `HUSKY_SKIP_HOOKS` is replaced by `HUSKY`.
- `HUSKY_SKIP_INSTALL` is replaced by `HUSKY`.
- `HUSKY_GIT_PARAMS` is removed. Instead Git parameters should be used directly in scripts (e.g. `$1`).
- `PATH` for locally installed tools is not automatically set anymore. You'll need to use your package manager to run them.
3,936 changes: 3,936 additions & 0 deletions docs/package-lock.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions docs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"scripts": {
"dev": "vitepress",
"build": "sponsorkit"
},
"devDependencies": {
"sponsorkit": "^0.9.1",
"vitepress": "^1.0.0-rc.40"
}
}
40 changes: 40 additions & 0 deletions docs/sponsorkit.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineConfig, presets } from 'sponsorkit'

export default defineConfig({
// includePrivate: true,
tiers: [
{
title: 'Past Sponsors',
monthlyDollars: -1,
preset: presets.xs,
},
{
title: 'Backers',
preset: presets.base,
},
{
title: 'Sponsors',
monthlyDollars: 10,
preset: presets.medium,
// to insert custom elements after the tier block
composeAfter: (composer, _tierSponsors, _config) => {
composer.addSpan(10)
},
},
{
title: 'Bronze Sponsors',
monthlyDollars: 100,
preset: presets.large,
},
{
title: 'Silver Sponsors',
monthlyDollars: 250,
preset: presets.large,
},
{
title: 'Gold Sponsors',
monthlyDollars: 500,
preset: presets.xl,
},
],
})
533 changes: 533 additions & 0 deletions docs/sponsorkit/sponsors.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions docs/troubleshoot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Troubleshoot

## Command not found

See [How To](how-to) for solutions.

## Hooks not running

1. Verify the file name is correct. For example, `precommit` or `pre-commit.sh` are invalid names. Refer to the Git hooks [documentation](https://git-scm.com/docs/githooks) for valid names.
2. Run `git config core.hooksPath` and ensure it points to `.husky/_` (or your custom hooks directory).
1. Confirm your Git version is above `2.9`.

## `.git/hooks/` Not Working After Uninstall

If hooks in `.git/hooks/` don't work post-uninstalling `husky`, execute `git config --unset core.hooksPath`.

## Yarn on Windows

Git hooks might fail with Yarn on Windows using Git Bash (`stdin is not a tty`). For Windows users, implement this workaround:

1. Create `.husky/common.sh`:

```shell
command_exists () {
command -v "$1" >/dev/null 2>&1
}

# Workaround for Windows 10, Git Bash, and Yarn
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi
```

2. Source it where Yarn commands are run:

```shell
# .husky/pre-commit
. "$(dirname -- "$0")/common.sh"

yarn ...
```
44 changes: 14 additions & 30 deletions husky.sh
Original file line number Diff line number Diff line change
@@ -1,36 +1,20 @@
#!/usr/bin/env sh
if [ -z "$husky_skip_init" ]; then
debug () {
if [ "$HUSKY_DEBUG" = "1" ]; then
echo "husky (debug) - $1"
fi
}
[ "$HUSKY" = "2" ] && set -x
h="${0##*/}"
s="${0%/*/*}/$h"

readonly hook_name="$(basename -- "$0")"
debug "starting $hook_name..."
[ ! -f "$s" ] && exit 0

if [ "$HUSKY" = "0" ]; then
debug "HUSKY env variable is set to 0, skipping hook"
exit 0
fi
for f in "${XDG_CONFIG_HOME:-$HOME/.config}/husky/init.sh" "$HOME/.huskyrc.sh"; do
# shellcheck disable=SC1090
[ -f "$f" ] && . "$f"
done

if [ -f ~/.huskyrc ]; then
debug "sourcing ~/.huskyrc"
. ~/.huskyrc
fi
[ "$HUSKY" = "0" ] && exit 0

readonly husky_skip_init=1
export husky_skip_init
sh -e "$0" "$@"
exitCode="$?"
sh -e "$s" "$@"
c=$?

if [ $exitCode != 0 ]; then
echo "husky - $hook_name hook exited with code $exitCode (error)"
fi

if [ $exitCode = 127 ]; then
echo "husky - command not found in PATH=$PATH"
fi

exit $exitCode
fi
[ $c != 0 ] && echo "husky - $h script failed (code $c)"
[ $c = 127 ] && echo "husky - command not found in PATH=$PATH"
exit 1
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default function (dir?: string): string;
23 changes: 23 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import c from 'child_process'
import f, { writeFileSync as w } from 'fs'
import p from 'path'

let l = [ 'pre-commit', 'prepare-commit-msg', 'commit-msg', 'post-commit', 'applypatch-msg', 'pre-applypatch', 'post-applypatch', 'pre-rebase', 'post-rewrite', 'post-checkout', 'post-merge', 'pre-push', 'pre-auto-gc' ]

export default (d = '.husky') => {
if (process.env.HUSKY === '0') return 'HUSKY=0 skip install'
if (d.includes('..')) return '.. not allowed'
if (!f.existsSync('.git')) return `.git can't be found`

let _ = (x = '') => p.join(d, '_', x)
let { status: s, stderr: e } = c.spawnSync('git', ['config', 'core.hooksPath', _()])
if (s == null) return 'git command not found'
if (s) return '' + e

f.mkdirSync(_(), { recursive: true })
w(_('.gitignore'), '*')
f.copyFileSync(new URL('husky.sh', import.meta.url), _('h'))
l.forEach(h => w(_(h), `#!/usr/bin/env sh\n. "\${0%/*}/h"`, { mode: 0o755 }))
w(_('husky.sh'), '')
return ''
}
9,392 changes: 20 additions & 9,372 deletions package-lock.json

Large diffs are not rendered by default.

61 changes: 23 additions & 38 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,40 +1,25 @@
{
"name": "husky",
"version": "8.0.3",
"description": "Modern native Git hooks made easy",
"keywords": [
"git",
"hooks",
"pre-commit"
],
"homepage": "https://typicode.github.io/husky",
"repository": "typicode/husky",
"funding": "https://github.com/sponsors/typicode",
"license": "MIT",
"author": "Typicode <typicode@gmail.com>",
"bin": "lib/bin.js",
"main": "lib/index.js",
"files": [
"lib",
"husky.sh"
],
"scripts": {
"build": "tsc",
"test": "sh test/all.sh",
"lint": "eslint src --ext .ts",
"serve": "docsify serve docs",
"prepare": "npm run build && node lib/bin install"
},
"devDependencies": {
"@commitlint/cli": "^17.3.0",
"@commitlint/config-conventional": "^17.3.0",
"@tsconfig/node14": "^1.0.3",
"@types/node": "^18.11.18",
"@typicode/eslint-config": "^1.1.0",
"docsify-cli": "^4.4.4",
"typescript": "^4.9.4"
},
"engines": {
"node": ">=14"
}
"name": "husky",
"version": "9.0.1",
"description": "Modern native Git hooks",
"keywords": [
"git",
"hooks",
"pre-commit"
],
"repository": {
"type": "git",
"url": "git+https://github.com/typicode/husky.git"
},
"funding": "https://github.com/sponsors/typicode",
"license": "MIT",
"author": "typicode",
"type": "module",
"bin": {
"husky": "bin.js"
},
"exports": "./index.js",
"engines": {
"node": ">=18"
}
}
42 changes: 0 additions & 42 deletions src/bin.ts

This file was deleted.

94 changes: 0 additions & 94 deletions src/index.ts

This file was deleted.

9 changes: 5 additions & 4 deletions test/all.sh → test.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/bin/sh
# To run tests, type ./test.sh in your terminal
set -e
npm run build
npm pack && mv husky-*.tgz /tmp/husky.tgz
sh test/1_default.sh
sh test/2_in-sub-dir.sh
sh test/3_from-sub-dir.sh
sh test/4_not-git-dir.sh
sh test/5_set-add.sh
sh test/6_git_command_not_found.sh
sh test/7_command_not_found.sh
sh test/5_git_command_not_found.sh
sh test/6_command_not_found.sh
# sh test/7_time.sh
13 changes: 5 additions & 8 deletions test/1_default.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
. "$(dirname -- "$0")/functions.sh"
#!/bin/sh
. test/functions.sh
setup
install

npx --no-install husky install
npx --no-install husky

# Test core.hooksPath
expect_hooksPath_to_be ".husky"
expect_hooksPath_to_be ".husky/_"

# Test pre-commit
git add package.json
npx --no-install husky add .husky/pre-commit "echo \"pre-commit\" && exit 1"
echo "echo \"pre-commit\" && exit 1" >.husky/pre-commit
expect 1 "git commit -m foo"

# Uninstall
npx --no-install husky uninstall
expect 1 "git config core.hooksPath"
9 changes: 5 additions & 4 deletions test/2_in-sub-dir.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
. "$(dirname -- "$0")/functions.sh"
#!/bin/sh
. test/functions.sh
setup
install

# Test custom dir support
mkdir sub
npx --no-install husky install sub/husky
npx --no-install husky add sub/husky/pre-commit "echo \"pre-commit\" && exit 1"
npx --no-install husky sub/husky
echo "echo \"pre-commit\" && exit 1" >sub/husky/pre-commit

# Test core.hooksPath
expect_hooksPath_to_be "sub/husky"
expect_hooksPath_to_be "sub/husky/_"

# Test pre-commit
git add package.json
11 changes: 6 additions & 5 deletions test/3_from-sub-dir.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
. "$(dirname -- "$0")/functions.sh"
#!/bin/sh
. test/functions.sh
setup

# Skip test for npm 6
@@ -12,10 +13,10 @@ npm --version | grep "^6\." && exit 0
mkdir sub
cd sub
npm install ../../husky.tgz
cat > package.json << EOL
cat >package.json <<EOL
{
"scripts": {
"prepare": "cd .. && husky install sub/.husky"
"prepare": "cd .. && husky sub/.husky"
}
}
EOL
@@ -24,10 +25,10 @@ EOL
npm run prepare

# Add hook
npx --no-install husky add .husky/pre-commit "echo \"pre-commit hook\" && exit 1"
echo "echo \"pre-commit hook\" && exit 1" >.husky/pre-commit

# Test core.hooksPath
expect_hooksPath_to_be "sub/.husky"
expect_hooksPath_to_be "sub/.husky/_"

# Test pre-commit
git add package.json
8 changes: 6 additions & 2 deletions test/4_not-git-dir.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
. "$(dirname -- "$0")/functions.sh"
#!/bin/sh
. test/functions.sh
setup
install

# Should not fail
rm -rf .git
expect 0 "npx --no-install husky install"
expect 0 "npx --no-install husky"

mkdir .git
expect 0 "npx --no-install husky"
11 changes: 11 additions & 0 deletions test/5_git_command_not_found.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/sh
. test/functions.sh
setup
install

cat >index.mjs <<EOL
process.env.PATH = ''
import h from 'husky'
console.log(h())
EOL
expect 0 "node index.mjs"
17 changes: 0 additions & 17 deletions test/5_set-add.sh

This file was deleted.

14 changes: 14 additions & 0 deletions test/6_command_not_found.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/bin/sh
. test/functions.sh
setup
install

npx --no-install husky

# Test core.hooksPath
expect_hooksPath_to_be ".husky/_"

# Test pre-commit with 127 exit code
git add package.json
echo "exit 127" >.husky/pre-commit
expect 1 "git commit -m foo"
10 changes: 0 additions & 10 deletions test/6_git_command_not_found.sh

This file was deleted.

13 changes: 0 additions & 13 deletions test/7_command_not_found.sh

This file was deleted.

10 changes: 10 additions & 0 deletions test/7_time.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/sh
. test/functions.sh
setup
install

npx --no-install husky

git add package.json
echo "echo pre-commit" >.husky/pre-commit
time git commit -m foo
7 changes: 3 additions & 4 deletions test/functions.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
set -eu

setup() {
name="$(basename -- $0)"
name="$(basename -- "$0")"
testDir="/tmp/husky-test-$name"
echo
echo "-------------------"
@@ -21,8 +21,7 @@ setup() {
git config user.name "test"

# Init package.json
npm_config_loglevel="error"
npm init -y 1>/dev/null
npm_config_loglevel="error" npm init -y 1>/dev/null
}

install() {
@@ -40,7 +39,7 @@ expect() {
}

expect_hooksPath_to_be() {
readonly hooksPath=`git config core.hooksPath`
hooksPath=$(git config core.hooksPath)
if [ "$hooksPath" != "$1" ]; then
error "core.hooksPath should be $1, was $hooksPath"
fi
13 changes: 0 additions & 13 deletions tsconfig.json

This file was deleted.