[ node.js + Git ] 〇〇するまえに✖︎✖︎する

## 先に結論だけ

[Git](https://git-scm.com/)と[npmスクリプト](https://docs.npmjs.com/cli/v9/using-npm/scripts)には、コマンドの実行前後に割り込む仕組みが用意されています。この仕組みをLinterやテスティングフレームワークと組み合わせると、ヒューマンエラーと作業工数を減らせます。

## はじめに

この記事はGitとnpmスクリプトにおける、コマンドに割り込む仕組みについて記録、共有するためのものです。

### 対象とする読者

- Gitを使ったことがある。
- node.jsを使ったことがある。
- 開発フローでのヒューマンエラーを減らしたい。

### 想定する環境

- git version 2.40.1
- npm 9.5.0

:::note warn
対象となるパッケージのメジャーバージョンが変わると、記事の内容がそのまま適用できないかもしれません。記事を読む前に、お手元の環境をご確認ください。
:::

## Git

Gitには、[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)という仕組みがあります。

### Gitフックとは

Gitフックとは、`git`コマンドの前後に任意のコマンドを割り込ませるための仕組みです。一般的なプロジェクトでは`.git/hooks`ディレクトリにフック用スクリプトを配置します。

### node.jsでフックを作成/管理する

GitフックはGit管理外の`.git`ディレクトリに配置されるため、リポジトリの参加者には共有されません。Gitフックを共有するには、別のディレクトリにスクリプトを配置しなければいけません。

また、Gitフックの実行環境はシェルです。そのため、開発メンバーの環境によってはフックが動作しないことがあります。

https://github.com/typicode/husky

https://github.com/toplenboren/simple-git-hooks

これらの問題を解消するため、いくつかのnpmモジュールが公開されています。この記事では、huskyを利用してフックを作成/管理します。

### preフック

`pre-`で始まるフックは、対応する`git`コマンドの前に実行されます。preフックが正常に終了しなければ、続くコマンドは実行前にキャンセルされます。

#### pre-commit

`pre-commit`フックは、ブランチへコミットする前に実行されます。

##### commit前にlint/コードフォーマッターを自動実行する

コミット前にlintやコードフォーマッターを自動実行することで、表記揺れをなくしコードの品質を維持できます。例として、`prettier`と`pretty-quick`を使って[コードフォーマッターを自動実行する方法](https://prettier.io/docs/en/precommit.html#option-2-pretty-quickhttpsgithubcomazzpretty-quick)を紹介します。

_▼.husky/pre-commit_

```shell
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx pretty-quick --staged
```

#### pre-push

`pre-push`フックは、リモートリポジトリへpushする前に実行されます。

##### push前にテストを自動実行する

push時に、テストを通過することを強制します。

_▼.husky/pre-push_

```js
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm test
```

testコマンドが正常に終了しないと、pushは中断されます。

##### デフォルトブランチへのpushを禁止する

ブランチの切り替えを忘れて、mainブランチへpushしてしまうことがあります。このようなヒューマンエラーを防ぐため、特定の名前のブランチへのpushを禁止します。

https://qiita.com/Syuparn/items/9e107e0aaf1a61b260b5#masterdevelopmain%E3%81%B8%E3%81%AE%E3%83%97%E3%83%83%E3%82%B7%E3%83%A5%E3%82%92%E7%A6%81%E6%AD%A2

具体的な実装方法については、上記の記事を参照してください。みなさまのプロジェクトのブランチ運用ルールに合わせて、ブランチ名などの条件を変更してください。

### GitHubのブランチ保護ルール

GitHubにはブランチ保護ルールという機能があります。この機能を使うと、特定のブランチに対する操作を制限できます。

https://docs.github.com/ja/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/managing-a-branch-protection-rule

テストの自動実行やpushの禁止は、Gitフックとブランチ保護ルールの両方で実現できます。どちらを採用するかは開発環境 / 実行環境やメンバーの構成によって変化しますので、プロジェクトごとに検討が必要です。

## node.js

node.jsのnpmコマンドにも、Gitフックとよく似た割り込み機能があります。npmコマンドの割り込みは、`package.json`の`scripts`フィールドで定義します。

### pre/post修飾子

https://docs.npmjs.com/cli/v9/using-npm/scripts#pre--post-scripts

_▼package.json_
```json
{
  "scripts": {
    "precompress": "{{ executes BEFORE the `compress` script }}",
    "compress": "{{ run command to compress files }}",
    "postcompress": "{{ executes AFTER `compress` script }}"
  }
}
```

npmスクリプトの名前に`pre`または`post`を付けると、対応するコマンドの前後に処理が割り込まれます。自作したスクリプトと組み込みのコマンドの両方に対応しています。

### preversion

例として、[`npm version`コマンド](https://docs.npmjs.com/cli/v9/commands/npm-version?v=true)の前に割り込む`preversion`スクリプトを作成します。

_▼package.json_
```json
{
  "scripts": {
    "preversion": "npm test"
  }
}
```

テストに成功しなければ、versionコマンドは中断され、タグ付けもされません。

### npmコマンドとGitフックが競合する場合

`npm version`コマンドは、内部的にGitのコミットとタグ作成を実行します。そのため、versionコマンドを実行すると同時にGitフックも実行されます。

https://docs.npmjs.com/cli/v9/commands/npm-version#commit-hooks

preversionコマンドとpre-commitフックが競合する場合、versionコマンドのオプションでpre-commitフックを無効化できます。

```shell
npm version --no-commit-hooks patch
npm version --commit-hooks false patch
```

この2つのオプションは同じ意味になります。npmは`no-`接頭辞を付けたbooleanオプションを`false`として処理します。

## 個人的な感想

フックの導入はヒューマンエラーの削減に大きな効果があります。継続的なメンテナンスが必要なプロジェクトでは積極的に導入しましょう。

一方、動作環境に影響されるためフック自体のメンテナンスが必要になります。この点を踏まえて、ローカルでフックを動作させるか、動作環境に差異が出ないGitHub ActionsのようなCI/CD環境を利用するかを検討する必要があります。

以上、ありがとうございました。