huskyでgit commitやgit pushの前にコマンドを実行して開発自動化を行う

## この記事について
[husky](https://typicode.github.io/husky/#/)というライブラリを使用して、`git commit`時や`git push`時にコマンドを実行する方法をお伝えします。
`git commit`時にフォーマッタを適用させたり、`git push`時にテストを実行したり、開発自動化を進める時などに有効です。

## 対象読者
* gitを使用しており、開発自動化を進めたい方
* huskyの使い方を学びたい方

## huskyとは
* gitで特定のアクションを行った際に、コマンドを実行してタスクを行う（これを`git hooks`とも言います）ための仕組みを提供するライブラリです（Node.js環境があれば利用可能）
* huskyを使用することで、`git push`した時にテストコードを実行させるなどが可能となります
* [lint-staged](https://github.com/okonet/lint-staged#readme)と組み合わせることで「ステージングしているファイル」や「コミットしたファイル」に対してのみコマンドを実行させる、ということが可能となります

## huskyの使い方
### 環境
以下の環境で動作確認を行いました。

|名前|バージョン|
|---|---|
|windows10||
|Node.js|12.13.0|
|npm|6.12.0|
|yarn|1.21.1|
|husky|4.2.1|

### インストール
* `npm`を使用する場合

```
npm install -D husky
```

* `yarn`を使用する場合

```
yarn add -D husky
```

### `package.json`への記述
`.husky`ディレクトリを作成し、その配下のファイルにスクリプトを記述する方法（[参考](https://typicode.github.io/husky/#/?id=custom-directory)）もありますが、ここでは`package.json`にhuskyの設定を記述していきます。
（`package.json`内で不要な記述は省略しております）

```json
{
  "devDependencies": {
    "husky": "^4.2.1",
  },
  "husky": {
    "hooks": {
      "pre-commit": "git commit時に実行したいコマンド",
      "pre-push": "git push時に実行したいコマンド"
    }
  }
}
```

#### 例：`git commit`時にeslintとprettierを適用させたい
（[`eslint`](https://eslint.org/docs/user-guide/getting-started)と[`prettier`](https://prettier.io/docs/en/install.html)はインストール済みであることを前提とします）

`package.json`の記述
（`git commit`時にルートディレクトリ配下のファイルに対して`prettier`と`eslint`による修正を行います）

```json
{
  "devDependencies": {
    "eslint": "^7.24.0",
    "eslint-config-prettier": "^6.11.0",
    "husky": "^4.2.1",
    "prettier": "^2.0.5",
  },
  "husky": {
    "hooks": {
      "pre-commit": "prettier --write . && eslint --fix ."
    }
  }
}
```

#### 例：`git push`時にJestのテストを実行させたい
`package.json`の記述

```json
{
  "devDependencies": {
    "jest": "^27.0.4",
    "husky": "^4.2.1",
  },
  "husky": {
    "hooks": {
      "pre-push": "jest"
    }
  }
}
```

### `lint-staged`との組み合わせ
`lint-staged`と組み合わせることで、「`git commit`時にはステージング中のファイルに対してのみ」「`git push`時にはcommitしたファイルに対してのみ」コマンドを実行することができます。

`package.json`の記述
`"pre-commit": "lint-staged --allow-empty"`の箇所では、単に`"pre-commit": "lint-staged"`と記述した場合、以下のエラーが出てしまう場合があるため空コミットを許容するようにしています。
[参考](https://github.com/okonet/lint-staged#migration)

```
Prevented an empty commit!
```

また、`lint-staged`のバージョン10以降ではコマンド実行後の`git add`は不要（自動で`git add`される）ため記述しておりません。
以下の流れで処理されます。

1. コミット時に`lint-staged --allow-empty`コマンドが実行される
1. コミットしたファイルが`css,html,js`いずれかのファイル形式であれば`prettier --write`を実行してprettierのフォーマット適用
1. コミットするファイルが`js`のファイル形式であれば`eslint --fix`を実行してeslint適用

```json
{
  "devDependencies": {
    "eslint": "^7.24.0",
    "eslint-config-prettier": "^6.11.0",
    "husky": "^4.2.1",
    "lint-staged": "^10.0.7",
    "prettier": "^2.0.5"
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged --allow-empty"
    }
  },
  "lint-staged": {
    "**/*.{css,html,js}": [
      "prettier --write"
    ],
    "**/*.js": [
      "eslint --fix"
    ]
  }
}
```

エラーが無い場合、`git commit`が行われます。
エラーがある場合、以下のようなメッセージがターミナル上に表示されて`git commit`は行われません。

```
154:5   warning  Unexpected console statement no-console
158:17  error    'event' is defined but never used no-unused-vars

✖ 2 problems (1 errors, 1 warnings)

husky > pre-commit hook failed (add --no-verify to bypass)
```

## 参考
[husky の pre-commit が（また）動かない](https://qiita.com/sprout2000/items/29e8a637dda259bab26d)
[【JavaScript】コミットする前にlint-stagedでeslintのチェックをする](https://kic-yuuki.hatenablog.com/entry/2019/05/27/090515)