Lefthookを導入して安全なコミットを実現した話

# はじめに
記事投稿キャンペーンのテーマが「[今の開発組織でトライしたこと・トライしていること・トライしようとしていること](https://qiita.com/official-events/06e1e6af790c81a75b7f)」ということで、最近自分が提案してチームに取り入れた、Lefthookを使ったコミット前のコードチェックについて書き残したいと思います。

# 前提
- 以下の技術を使ってSPAを開発しています
  - Vite, React, TypeScript, Yarn
- Amazon WorkSpaces(Amazon Linux 2)を使っています
- Dockerを使ってコンテナ内で開発しています
  - コンテナにGitコマンドは入ってません
  - コンテナ内で開発→コンテナを出てコミットする流れです
- Bitbucket PipelinesによるCIでプルリク作成時に以下をチェックしています
  - ESLintによるlintでコードがルールに則っているか
  - Prettierによるformatでコードがルールに則っているか
  - Viteによるbuildでビルド可能か

# 抱えていた問題
前提に書いた通り、CIによるチェックは行われていましたが、この仕組みだけではプルリク作成後にしかエラーに気づかないことがチーム内での問題でした。コミットやプッシュ前にローカルでコマンドの実行を忘れてプッシュし、プルリク作成時にCIでエラーになってから修正するような流れが何度も発生していました。

# 解決方法
コミットやプッシュ前にローカルでコマンドの実行を忘れたままプルリク作成まで進めてしまうのが良くないと考え、[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`を使ってコミット時にコマンドを自動実行する仕組みをチームへ導入することにしました。

**チームへ導入すると言っても、推奨設定ファイルを共有しツールのインストールを推奨するだけになります。好みもありますのでメンバーに強制はしない方がいいと思っています。また、各々が簡単に実行を無視したり設定を自分用にカスタマイズできますので、全員に対して一貫した強制力を発揮することはできません。したがって、CIによるlintやbuildは現状のまま必要であると判断しました。**

# ツール選定
調べてみると、Gitフックの導入にあたって以下の2つのツールを使ったやり方が多く見つかりました。
- [Husky](https://github.com/typicode/husky?tab=readme-ov-file) (+ [lint-staged](https://github.com/lint-staged/lint-staged))
- [Lefthook](https://github.com/evilmartians/lefthook)

導入するツールは1つで済むなら1つで済ませたいのでLefthook寄りで調査を続けました。設定や導入周りについてのわかりやすさ、機能の充実度や柔軟性、おまけに実行速度も上回っているということで、Lefthookを試すことに決めました。

Lefthookについては公式の英語記事の日本語訳が公開されていますので、そちらを読んでいただくのがいいと思います。

https://techracho.bpsinc.jp/hachi8833/2019_10_16/79052

# Lefthookの導入
## インストール
[公式のインストールガイド](https://github.com/evilmartians/lefthook/blob/master/docs/install.md)を見ながら行いました。Amazon Linux 2でのインストール方法は書いてないのですが、「[Amazon Linux は何系のディストリビューションに該当するのか](https://www.acrovision.jp/service/aws/?p=653)」という記事を読んで、CentOSでのインストール方法で実施しました。

## 設定
### lefthook.yml
`lefthook.yml`を作って設定を書いていきます。

今回実現したいのは、前述の通り`pre-commit`を使ってコミット時にコマンドを実行し、エラーであればコミットされないことです。リーダーと相談した結果、実行するコマンドはとりあえず初期段階ではCIと同じような構成にしました。実際の設定から変えてありますが大体同じような感じです。

```yaml:lefthook.yml
pre-commit:
  parallel: true
  commands:
    lint:
      glob: "**/*.{ts,tsx}"
      run: docker compose run --rm app sh -c "yarn lint {staged_files} && yarn format {staged_files}"
    build:
      glob: "src/**/*"
      exclude: '\.test\..+$'
      run: docker compose run --rm app yarn build
```

`package.json`の`scripts`は以下の通りです。これも実際の設定から変えてあります。

```json:package.json
{
  "scripts": {
    "build": "tsc && vite build",
    "lint": "eslint --no-error-on-unmatched-pattern",
    "format": "prettier --check --no-error-on-unmatched-pattern",
  },
}
```

設定ファイルの書き方についてはドキュメントをご確認いただければと思います。

https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md

簡単に設定内容を説明しておきます。

 `parallel`にtrueを設定することによりコマンドが並列で実行されます。今回の場合は`commands`の`lint`と`build`が同時に実行されるということになります。

`exclude`は対象外となるファイルの条件です。対象外となるファイルのみがコミットされる場合は`run`に進みません。今回の場合だと、テストコードのみの修正であれば`build`は実行不要と判断し対象外としています。

`run`が実行内容になります。Docker Composeを使ってコンテナを立ち上げて、コンテナ内で開発を完結させているという前提のため、yarnコマンドをコンテナ内で実行しています。コンテナを新たに作成して使い捨てにしてますが、全て同じコンテナでコマンドを実行すべきか悩んでいるところです。

### lefthook install
`lefthook.yml`と`.git/hooks`を同期させるために、`.git`が存在するディレクトリで`lefthook install`を実行します。

`install`というコマンド名が誤解を招く気がするのですが、やってることは設定ファイルとの同期です。[ドキュメント](https://github.com/evilmartians/lefthook/blob/master/docs/usage.md#lefthook-install)による説明の引用とGPT-4による日本語訳を載せておきます。

> Run lefthook install to initialize a lefthook.yml config and/or synchronize .git/hooks/ with your configuration. This must be the first thing you do after cloning the repo with lefthook.yml config. For config options see our configuration documentation.

> lefthook.yml設定ファイルを初期化したり、.git/hooks/をあなたの設定と同期するために、lefthook installを実行してください。これは、lefthook.yml設定ファイルを含むリポジトリをクローンした後に最初に行う必要があります。設定オプションについては、当社の設定ドキュメントをご覧ください。

## 他メンバーへの展開
Gitで管理するのは設定ファイルのみになります。しかし設定ファイルだけでは機能しないため、Lefthookのインストールと`lefthook install`の実行を各々やってもらう必要があります。`lefthook.yml`の作成と同時にドキュメントに導入手順を追加し、プルリクと一緒にレビューしてもらいました。マージ後に関係者へ共有して完了です。

# 導入の効果
まだ導入から1ヶ月も経ってないですが、導入以降CIで失敗したのは1件のみで、それもLefthookを導入し忘れていただけだったので、今のところ効果はありそうです。メンバーからも振り返りでポジティブな評価をいただきました。

# おわりに
Lefthookは非常に好感触で、今後も使っていきたいと思えるツールでした。

今回解決に取り組んだ、lintやbuildの実行を忘れてコミットしてしまう問題は、仕組みを作っていないと起こってしまうことだと思います。一度自動実行の味を知ってしまったので、自分が関わる開発ではこの仕組みがなるべくほしいと思っていますし、なければ積極的に提案と導入をしていきたいと思います。

lintやbuildだけでなくtestの実行など、まだLefthookを使って自動化できることはいくつかありそうですので引き続き取り組んでいきたいです。

いい経験をさせていただいたチームに感謝しています。

ご覧いただきありがとうございました。

《2024-04-22 追記》
XのQiita公式アカウントに本記事を紹介していただきました！ありがとうございます！

https://x.com/Qiita/status/1782250474424639806