lint-staged/huskyを使ってコミット時やプッシュ時にコマンドを自動で発火するようにしよう！

今回はコミットやプッシュ時に自動でコマンドを叩いてくれるlint-staged/huskyについて紹介します。

# 開発環境
- MacBook Pro (13-inch, M1, 2020) macOS Monterey 12.1
- Yarn 1.22.18
- lint-staged 12.4.1
- husky 7.0.4

#  lint-stagedおよびhuskyとは

## huskyとは
lint-stagedを説明する上でhuskyは外せないもののため、先にこちらについて説明します。
huskyは[jsonplaceholder](https://jsonplaceholder.typicode.com/)や[JSON Server](https://github.com/typicode/json-server)の作者で知られるtypicode氏が作成したライブラリになります。

https://typicode.github.io/husky/#/

> You can use it to lint your commit messages, run tests, lint code, etc... when you commit or push.

公式でこんな記載がある通り、Gitでコミットやプッシュと同時にlintやテストの実行を行ってくれる便利ツールです。これらの実行で使用するファイルはGit hooks（Gitフック）というGit標準機能が活用されており、すべてのフックがhusky上で使用可能となっています。ただGitフックの種類は数が多く本筋が逸れるため今回は説明しません。詳細は以下のサイトをご確認ください。

https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF

後述する例でも出しますが、基本的には「pre-commit（コミット前）」/「pre-push（プッシュ前）」だけ覚えておけば問題ありません。

## lint-stagedとは
Gitにステージされている指定した形式のファイルに対してESLintといったlintを走らせてくれるツールです。lintと名がついていますがPrettierといったフォーマッターを走らせることも可能です。

https://github.com/okonet/lint-staged

# 実際に試してみる

実際に作成したリポジトリはこちらです。[create-next-app](https://nextjs.org/docs/api-reference/create-next-app
)で作成したNext.jsアプリを使用しました。

https://github.com/miily8310s/husky-tutorial

## lint-stagedおよびhuskyのインストール
※以下の方法では、事前にESLintやPrettierといったコード管理ライブラリがプロジェクトフォルダにインストール＆設定されている必要があります。

lint-stagedを一から設定するのは大変なので、こちらのコマンドでインストール＆設定します。このコマンドを使うとhuskyも同時に設定されます。

```bash
npx mrm@2 lint-staged
```

すると以下の変更がかかりました。

```diff_json: package.json
{
  "scripts": {
    "lint": "next lint",
+   "prepare": "husky install"
  },
  ...
  "devDependencies": {
    ...
+   "husky": ">=6",
+   "lint-staged": ">=10",
    "typescript": "4.6.3"
  },
+  "lint-staged": {
+    "*.js": "eslint --cache --fix"
+  }
}

```

`.husky`フォルダも追加されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/919803/a8992711-22df-79b0-747b-0384434da361.png)

## コミット時にESLintを走らせる
`.husky`フォルダにいる`pre-commit`ファイルを確認しましょう。
最終行に書かれているコマンドがコミット時に発火するコマンドとなります。
`husky.sh`と書いているところはhuskyを動作するためのものなので編集してはいけません。

```sh: pre-commit
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
```

では`npx lint-staged`はどこに定義されているのでしょうか？

先程変更がかかった`package.json`の内容をもう一度確認してみると気になる記載が。。。はい、ここです。

`js`ファイルの変更をコミットすると、`eslint --cache --fix`のコマンドが発火されます。

```diff_json: package.json
{
   ...
+  "lint-staged": {
+    "*.js": "eslint --cache --fix"
+  }
}
```

ただしこのままではTypeScriptで作成したファイルでは適用されません。ですので本箇所を次のように変更します。

```diff_json: package.json
{
   ...
+  "lint-staged": {
+    "*.{js,jsx,ts,tsx}": "eslint --cache --fix"
+  }
}
```

lint-stagedではglobパターンを使って、ファイル形式や条件にそってコマンドを発火することができます。globについての詳細は以下の記事をご確認ください。

https://atmarkit.itmedia.co.jp/ait/articles/1708/29/news025.html

また、lint-stagedのREADMEでもたくさんの記載例が掲載されているので参考にしてみてください。

では実際にtsxファイルを編集してコミットしESLintが動くことを確認してみましょう。

```diff_tsx: pages/index.tsx
import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import styles from "../styles/Home.module.css";

+ const hogehoge = "";
```

今回はすぐ消えてスクショできなかったのですが、コミット時に`eslint --cache --fix`が走りました。
以下のメッセージが出てくると、lint-stagedの動作は成功しコミットされます。
```
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/919803/7acbe677-14dd-fdcc-62dc-a58a7b49f421.png)

なお、lint-stagedで失敗した場合はコミットはされません。

## プッシュ時にユニットテストを走らせる

※今回の例は`tsx`ファイルのユニットテストを使うため、JestやTesting Libraryの設定が入っていること前提で話を進めます。

package.jsonに以下のユニットテストを走らせるためのscriptsを追加しておきます。

```diff_json: package.json
{
  "scripts": {
    ...
    "lint": "next lint",
+   "test": "jest",
    "prepare": "husky install"
  },
  ...
}
```

今回のユニットテストコードはこちらを用意しました。

```tsx: __tests__/index.test.tsx
import { render, screen } from "@testing-library/react";
import Home from "@/pages/index";

describe("Home", () => {
  it("renders a heading", () => {
    render(<Home />);

    const heading = screen.getByRole("heading", {
      name: /welcome to next\.js!/i,
    });

    expect(heading).toBeInTheDocument();
  });
});
```

では準備ができたところでhuskyにpre-pushフックを追加しましょう。

```bash
npx husky add .husky/pre-push "yarn test"
```

そしてプッシュするとユニットテストが発動するようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/919803/44039841-238e-b85a-b453-b2a6550bf05a.png)

フックの追加の仕方については以下のサイトをご確認ください。huskyはバージョン7になってから大幅な変更が入ったため、ネットに転がっている頃の記事だと古い情報が紹介されている可能性が高いです。
ですので、公式サイトを確認することをオススメします。
https://typicode.github.io/husky/#/?id=create-a-hook

# 終わりに
lint-staged/huskyは公式で様々な設定レシピが記載されているので、ぜひ設定してより良い開発環境にしていきましょう！！

最後にhusky公式で紹介されているOSSライブラリの設定例をいくつか貼り付けておきます。先行者の設定例を見るのも大切です。

- typescript-eslint

https://github.com/typescript-eslint/typescript-eslint/tree/main/.husky

- Angular

https://github.com/angular/angular/tree/master/.husky