Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

huskyの仕組みを調べてみた

サーバーサイドエンジニアの田実です!

ヤプリのフロントエンドの開発では、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'))

github.com

その後、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}

github.com

.husky/_/husky.shhusky 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

github.com

その後、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

efcl.info

*1:例えばmake installのときにフックを設定するなど