サーバーサイドエンジニアの田実です!
ヤプリのフロントエンドの開発では、huskyを使ってコミット前にlint-stagedを実行し、eslintやprettierでフォーマットしています。
huskyがどのようにして動いているのか興味があったので調べてみました。確認したhuskyのバージョンは v7.0.4
です。
husky installでやっていること
husky install
は .husky/_
ディレクトリと .husky/_/.gitignore
.husky/_/husky.sh
ファイルを作成します。
// Create .husky/_ fs.mkdirSync(p.join(dir, '_'), { recursive: true }) // Create .husky/_/.gitignore fs.writeFileSync(p.join(dir, '_/.gitignore'), '*') // Copy husky.sh to .husky/_/husky.sh fs.copyFileSync(p.join(__dirname, '../husky.sh'), p.join(dir, '_/husky.sh'))
その後、git config --core.hooksPath .husky
を実行し、hooksPathに .husky
ディレクトリを設定します。
// Configure repo const { error } = git(['config', 'core.hooksPath', dir])
github.com
これにより、.husky
ディレクトリ内のフックスクリプトが効くようになります。
この処理をnpm prepareに設定することで、 npm install
で自動的に husky install
が実行されることになり、リポジトリで予め指定したフックスクリプトが効くようになります。
husky add/setで設定されるフックスクリプトについて
以下の任意のフックスクリプトを作成できます。
$ npx husky add .husky/pre-commit "{command}"
作成されるフックスクリプトは指定したコマンドをラップしたスクリプトになっています。
#!/bin/sh . "$(dirname "$0")/_/husky.sh" ${cmd}
.husky/_/husky.sh
は husky install
したときにインストールされるスクリプトです。
環境変数のHUSKYを0に設定するとフックスクリプトが無効になったり(必ずexit 0を返す)、.huskyrcの読み込みを行っています。
if [ "$HUSKY" = "0" ]; then debug "HUSKY env variable is set to 0, skipping hook" exit 0 fi if [ -f ~/.huskyrc ]; then debug "sourcing ~/.huskyrc" . ~/.huskyrc fi
その後、husky_skip_init=1を設定してフックスクリプトを再帰的に呼び出し、呼び出したスクリプトのexitCodeで終了します。
export readonly husky_skip_init=1 sh -e "$0" "$@" # 元のスクリプト(例えば.husky/pre-commit)が実行される exitCode="$?" // ... exit $exitCode
husky_skip_init=1を設定して再帰呼び出しするとhusky.shの処理自体がスキップされ、もともと設定したコマンドのみが実行されます。
husky.shではなくフックスクリプトに直接分岐入れて処理すれば良さそうと思ったのですが、huskyのアップデートでラップスクリプトの処理が変わったときにhusky.shだけを更新すれば良いので、これはこれで便利な作りだなと思いました。
まとめ
npmパッケージのhuskyの仕組みを紹介しました!
仕組み自体はとてもシンプルなため、Node.jsだけではなく他の言語にも応用が効きそうです。*1 また、huskyのようにフックスクリプトをラップして使いたいケースもそんなに無さそうなので、以下の記事のようにnpm prepareでhooksPathを設定するスクリプトを入れるだけでも良さそうだなと思いました…w
*1:例えばmake installのときにフックを設定するなど