先に結論だけ
Gitとnpmスクリプトには、コマンドの実行前後に割り込む仕組みが用意されています。この仕組みをLinterやテスティングフレームワークと組み合わせると、ヒューマンエラーと作業工数を減らせます。
はじめに
この記事はGitとnpmスクリプトにおける、コマンドに割り込む仕組みについて記録、共有するためのものです。
対象とする読者
- Gitを使ったことがある。
- node.jsを使ったことがある。
- 開発フローでのヒューマンエラーを減らしたい。
想定する環境
- git version 2.40.1
- npm 9.5.0
対象となるパッケージのメジャーバージョンが変わると、記事の内容がそのまま適用できないかもしれません。記事を読む前に、お手元の環境をご確認ください。
Git
Gitには、Gitフックという仕組みがあります。
Gitフックとは
Gitフックとは、git
コマンドの前後に任意のコマンドを割り込ませるための仕組みです。一般的なプロジェクトでは.git/hooks
ディレクトリにフック用スクリプトを配置します。
node.jsでフックを作成/管理する
GitフックはGit管理外の.git
ディレクトリに配置されるため、リポジトリの参加者には共有されません。Gitフックを共有するには、別のディレクトリにスクリプトを配置しなければいけません。
また、Gitフックの実行環境はシェルです。そのため、開発メンバーの環境によってはフックが動作しないことがあります。
これらの問題を解消するため、いくつかのnpmモジュールが公開されています。この記事では、huskyを利用してフックを作成/管理します。
preフック
pre-
で始まるフックは、対応するgit
コマンドの前に実行されます。preフックが正常に終了しなければ、続くコマンドは実行前にキャンセルされます。
pre-commit
pre-commit
フックは、ブランチへコミットする前に実行されます。
commit前にlint/コードフォーマッターを自動実行する
コミット前にlintやコードフォーマッターを自動実行することで、表記揺れをなくしコードの品質を維持できます。例として、prettier
とpretty-quick
を使ってコードフォーマッターを自動実行する方法を紹介します。
▼.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx pretty-quick --staged
pre-push
pre-push
フックは、リモートリポジトリへpushする前に実行されます。
push前にテストを自動実行する
push時に、テストを通過することを強制します。
▼.husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test
testコマンドが正常に終了しないと、pushは中断されます。
デフォルトブランチへのpushを禁止する
ブランチの切り替えを忘れて、mainブランチへpushしてしまうことがあります。このようなヒューマンエラーを防ぐため、特定の名前のブランチへのpushを禁止します。
具体的な実装方法については、上記の記事を参照してください。みなさまのプロジェクトのブランチ運用ルールに合わせて、ブランチ名などの条件を変更してください。
GitHubのブランチ保護ルール
GitHubにはブランチ保護ルールという機能があります。この機能を使うと、特定のブランチに対する操作を制限できます。
テストの自動実行やpushの禁止は、Gitフックとブランチ保護ルールの両方で実現できます。どちらを採用するかは開発環境 / 実行環境やメンバーの構成によって変化しますので、プロジェクトごとに検討が必要です。
node.js
node.jsのnpmコマンドにも、Gitフックとよく似た割り込み機能があります。npmコマンドの割り込みは、package.json
のscripts
フィールドで定義します。
pre/post修飾子
▼package.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
コマンドの前に割り込むpreversion
スクリプトを作成します。
▼package.json
{
"scripts": {
"preversion": "npm test"
}
}
テストに成功しなければ、versionコマンドは中断され、タグ付けもされません。
npmコマンドとGitフックが競合する場合
npm version
コマンドは、内部的にGitのコミットとタグ作成を実行します。そのため、versionコマンドを実行すると同時にGitフックも実行されます。
preversionコマンドとpre-commitフックが競合する場合、versionコマンドのオプションでpre-commitフックを無効化できます。
npm version --no-commit-hooks patch
npm version --commit-hooks false patch
この2つのオプションは同じ意味になります。npmはno-
接頭辞を付けたbooleanオプションをfalse
として処理します。
個人的な感想
フックの導入はヒューマンエラーの削減に大きな効果があります。継続的なメンテナンスが必要なプロジェクトでは積極的に導入しましょう。
一方、動作環境に影響されるためフック自体のメンテナンスが必要になります。この点を踏まえて、ローカルでフックを動作させるか、動作環境に差異が出ないGitHub ActionsのようなCI/CD環境を利用するかを検討する必要があります。
以上、ありがとうございました。