はじめに
Git Hooksを活用することで、コミット前のコード品質チェックやコミットメッセージの規約強制を自動化できます。本記事では、Husky v9を使った最新のセットアップ方法を解説します。
パッケージのインストール
# Husky v9
npm install -D husky
# lint-staged(ステージファイルのみチェック)
npm install -D lint-staged
# commitlint(コミットメッセージ検証)
npm install -D @commitlint/cli @commitlint/config-conventional
Husky v9セットアップ
初期化
npx husky init
このコマンドで以下が実行されます:
.husky/ディレクトリの作成.husky/pre-commitファイルの作成package.jsonにprepareスクリプトを追加
pre-commitフック
# .husky/pre-commit
npx lint-staged
commit-msgフック
# .husky/commit-msgを作成
echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg
pre-pushフック
# .husky/pre-pushを作成
echo 'npm run typecheck && npm run test' > .husky/pre-push
lint-staged設定
package.jsonに設定
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"format:check": "prettier --check .",
"typecheck": "tsc --noEmit",
"test": "vitest run",
"prepare": "husky"
},
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix --max-warnings=0",
"prettier --write"
],
"*.{js,jsx,mjs,cjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,scss,md,mdx}": [
"prettier --write"
]
}
}
別ファイルで設定(.lintstagedrc.js)
// .lintstagedrc.js
const path = require('path');
const buildEslintCommand = (filenames) =>
`next lint --fix --file ${filenames
.map((f) => path.relative(process.cwd(), f))
.join(' --file ')}`;
module.exports = {
'*.{ts,tsx}': [
buildEslintCommand,
'prettier --write',
],
'*.{js,jsx,mjs}': [
'eslint --fix',
'prettier --write',
],
'*.{json,css,scss,md}': [
'prettier --write',
],
};
commitlint設定
commitlint.config.js
// commitlint.config.js
module.exports = {
extends: ['@commitlint/config-conventional'],
rules: {
// type(スコープ): 説明 の形式
'type-enum': [
2,
'always',
[
'feat', // 新機能
'fix', // バグ修正
'docs', // ドキュメント
'style', // フォーマット(コードの動作に影響しない)
'refactor', // リファクタリング
'perf', // パフォーマンス改善
'test', // テスト追加・修正
'build', // ビルド関連
'ci', // CI設定
'chore', // その他の変更
'revert', // コミットの取り消し
],
],
'type-case': [2, 'always', 'lower-case'],
'subject-case': [0], // 日本語対応のため無効化
'subject-empty': [2, 'never'],
'subject-max-length': [2, 'always', 100],
'body-max-line-length': [0], // 本文の長さ制限なし
},
};
有効なコミットメッセージ例
# OK
git commit -m "feat: ユーザー登録機能を追加"
git commit -m "fix(auth): ログインエラーを修正"
git commit -m "docs: READMEを更新"
git commit -m "refactor: コンポーネントを分割"
# NG
git commit -m "新機能追加" # typeがない
git commit -m "FEAT: 機能追加" # 大文字
git commit -m "feat:" # 説明がない
型チェックの追加
pre-pushで型チェック
# .husky/pre-push
npm run typecheck
npm run test
並列実行で高速化
# .husky/pre-push(並列実行)
npm run typecheck & npm run test & wait
GitHub Actions連携
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint
run: pnpm lint
- name: Check formatting
run: pnpm format:check
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: TypeScript type check
run: pnpm typecheck
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run tests
run: pnpm test
build:
runs-on: ubuntu-latest
needs: [lint, typecheck, test]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm build
PRタイトルの検証
# .github/workflows/pr-title.yml
name: PR Title Check
on:
pull_request:
types: [opened, edited, synchronize]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v5
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
requireScope: false
subjectPattern: ^.{1,100}$
トラブルシューティング
Huskyが動作しない場合
# Huskyを再インストール
rm -rf .husky
npx husky init
# pre-commitを再設定
echo 'npx lint-staged' > .husky/pre-commit
Windows環境での問題
# Git Bash使用時にスクリプトが実行されない場合
git config core.hooksPath .husky
フックをスキップする場合
# 緊急時のみ使用(非推奨)
git commit --no-verify -m "fix: 緊急修正"
git push --no-verify
CI環境でHuskyを無効化
{
"scripts": {
"prepare": "husky || true"
}
}
スクリプトのカスタマイズ
カスタムバリデーション
# .husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 機密情報のチェック
if git diff --cached --name-only | xargs grep -l -E "(API_KEY|SECRET|PASSWORD)" 2>/dev/null; then
echo "Error: 機密情報が含まれている可能性があります"
exit 1
fi
# lint-staged実行
npx lint-staged
ブランチ名の検証
# .husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
branch=$(git rev-parse --abbrev-ref HEAD)
# ブランチ名のパターンチェック
if ! echo "$branch" | grep -qE "^(main|develop|feature\/|fix\/|hotfix\/)"; then
echo "Error: ブランチ名が規約に従っていません"
echo "使用可能: main, develop, feature/*, fix/*, hotfix/*"
exit 1
fi
npm run typecheck
npm run test
まとめ
| フック | 用途 | 実行タイミング |
|---|---|---|
| pre-commit | lint-staged | コミット前 |
| commit-msg | commitlint | コミットメッセージ入力後 |
| pre-push | 型チェック・テスト | プッシュ前 |