T-CREATOR

ESLint と Husky を使ったコミット前チェック自動化

ESLint と Husky を使ったコミット前チェック自動化

「コミット前にコードをチェックし忘れて、後からレビューで大量の指摘を受けた」「チームメンバーによってコードの品質にばらつきがある」「CI で失敗して初めて問題に気づく」といった経験はありませんか?

開発チームでコードの品質を保つためには、できるだけ早い段階で問題を発見し、修正することが重要です。そこで威力を発揮するのが、Git hooks を活用した自動化システムです。Husky と ESLint を組み合わせることで、コミット前に自動的にコード品質をチェックし、問題のあるコードがリポジトリに入り込むことを防げます。

本記事では、Husky を使った Git hooks の設定方法から、ESLint との連携、さらにはチーム開発での実践的な運用方法まで、コミット前チェック自動化の全体像を詳しく解説いたします。効率的で確実な品質保証システムを構築していきましょう。

Husky の基本セットアップ

Husky は、Git hooks を簡単に管理できるツールです。コミットやプッシュなどの Git の操作をトリガーとして、自動的にスクリプトを実行できるようになります。

Git hooks の仕組みと役割

Git hooks は、Git の特定の操作が実行されるタイミングで自動的に呼び出されるスクリプトの仕組みです。開発フローの各段階で品質チェックを自動化できます。

主要な Git hooks とその役割

#フック名実行タイミング主な用途
1pre-commitコミット前コード品質チェック、フォーマット
2commit-msgコミットメッセージ作成後メッセージ形式の検証
3pre-pushプッシュ前最終テスト、型チェック
4post-mergeマージ後依存関係の更新確認

従来の Git hooks の課題

bash# 従来の方法(.git/hooks/pre-commit)
#!/bin/sh
# 実行権限の設定が必要
chmod +x .git/hooks/pre-commit

# チーム間での共有が困難
# バージョン管理対象外のため、個別設定が必要

Husky による解決

Husky を使用することで、これらの課題を解決できます:

  • バージョン管理: hook スクリプトをプロジェクトファイルとして管理
  • チーム共有: すべての開発者で同じ設定を自動適用
  • 簡単な設定: 複雑な権限設定や手動インストールが不要

Yarn を使った Husky のインストール

プロジェクトに Husky を導入する手順を詳しく見ていきましょう。

基本パッケージのインストール

bash# Huskyの本体とlint-stagedをインストール
yarn add --dev husky lint-staged

# TypeScriptプロジェクトの場合は型定義も追加
yarn add --dev @types/node

# ESLintが未導入の場合は併せてインストール
yarn add --dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Husky の初期化

bash# Huskyの初期設定を実行
yarn husky install

# package.jsonにpostinstallスクリプトを追加(チーム開発用)
yarn pkg set scripts.postinstall="husky install"

初期化後のファイル構造

csharpproject-root/
├── .husky/
│   ├── _/
│   │   ├── .gitignore
│   │   └── husky.sh
│   └── .gitignore
├── package.json
└── yarn.lock

初期設定とフォルダ構造

Husky が作成するファイル構造と、各ファイルの役割を理解しましょう。

Husky ディレクトリの構造

bash.husky/
├── _/                    # Husky内部ファイル
│   ├── .gitignore       # 内部ファイルの除外設定
│   └── husky.sh         # Husky実行スクリプト
├── pre-commit           # pre-commitフック(手動作成)
├── commit-msg           # commit-msgフック(手動作成)
├── pre-push            # pre-pushフック(手動作成)
└── .gitignore          # Huskyファイルのgit管理設定

package.json の設定例

json{
  "name": "my-project",
  "scripts": {
    "postinstall": "husky install",
    "lint": "eslint . --ext .ts,.tsx,.js,.jsx",
    "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
    "type-check": "tsc --noEmit"
  },
  "devDependencies": {
    "husky": "^8.0.3",
    "lint-staged": "^14.0.1",
    "eslint": "^8.50.0",
    "@typescript-eslint/parser": "^6.7.0",
    "@typescript-eslint/eslint-plugin": "^6.7.0"
  },
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": ["eslint --fix", "git add"]
  }
}

初回セットアップの確認

bash# Huskyが正常にインストールされているか確認
ls -la .husky/

# Git hooksディレクトリの確認
ls -la .git/hooks/

# Huskyのバージョン確認
yarn husky --version

ESLint との連携設定

Husky と ESLint を連携させることで、コミット前に自動的にコード品質をチェックできるようになります。効率的で確実なチェック体制を構築しましょう。

pre-commit フックの設定

pre-commit フックは、git commit実行時に自動的に呼び出されるスクリプトです。ESLint によるコード検証を組み込みましょう。

基本的な pre-commit フックの作成

bash# pre-commitフックファイルを作成
yarn husky add .husky/pre-commit "yarn lint-staged"

.husky/pre-commit ファイルの内容

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

# lint-stagedを実行(変更されたファイルのみチェック)
yarn lint-staged

ESLint 実行の設定パターン

bash# パターン1: 全ファイルをチェック(小規模プロジェクト向け)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

yarn lint
if [ $? -ne 0 ]; then
  echo "ESLintエラーが検出されました。修正してからコミットしてください。"
  exit 1
fi

# パターン2: 段階的チェック(推奨)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "🔍 コード品質をチェック中..."

# 1. 変更ファイルのみESLintチェック
yarn lint-staged

# 2. TypeScript型チェック(軽量)
echo "📝 TypeScript型チェック中..."
yarn type-check --incremental

echo "✅ すべてのチェックが完了しました"

エラーハンドリングの強化

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

# 色付きメッセージの設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

echo "${YELLOW}🔍 コミット前チェックを開始...${NC}"

# ESLintチェック
if ! yarn lint-staged; then
  echo "${RED}❌ ESLintエラーが検出されました${NC}"
  echo "${YELLOW}修正方法:${NC}"
  echo "  yarn lint:fix  # 自動修正を実行"
  echo "  yarn lint      # エラー詳細を確認"
  exit 1
fi

echo "${GREEN}✅ コード品質チェック完了${NC}"

lint-staged との組み合わせ

lint-staged は、Git でステージングされた(git addされた)ファイルのみに対してコマンドを実行するツールです。大規模プロジェクトでのパフォーマンス向上に欠かせません。

lint-staged の基本設定

json{
  "lint-staged": {
    // TypeScript/JavaScriptファイル
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix", // 自動修正可能なエラーを修正
      "prettier --write" // コードフォーマットを統一
    ],

    // JSONファイル
    "*.json": ["prettier --write"],

    // CSSファイル
    "*.{css,scss,sass}": [
      "stylelint --fix",
      "prettier --write"
    ],

    // Markdownファイル
    "*.md": ["prettier --write"]
  }
}

高度な lint-staged 設定

json{
  "lint-staged": {
    // ファイルタイプ別の詳細設定
    "src/**/*.{ts,tsx}": [
      "eslint --fix --max-warnings 0",  // 警告もエラー扱い
      "jest --bail --findRelatedTests"   // 関連テストを実行
    ],

    // 特定ディレクトリでの設定分岐
    "app/**/*.{ts,tsx}": [
      "eslint --config .eslintrc.app.js --fix"
    ],

    "pages/**/*.{ts,tsx}": [
      "eslint --config .eslintrc.pages.js --fix"
    ],

    // 条件付き実行
    "*.{ts,tsx}": (filenames) => [
      `eslint --fix ${filenames.join(' ')}`,
      `tsc --noEmit --incremental`,
      // 10ファイル以上の場合は全テストを実行
      filenames.length > 10 ? 'yarn test' : `jest --findRelatedTests ${filenames.join(' ')}`
    ]
  }
}

package.json スクリプトとの連携

json{
  "scripts": {
    // 基本的なlintコマンド
    "lint": "eslint . --ext .ts,.tsx,.js,.jsx",
    "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",

    // lint-staged用の細分化されたコマンド
    "lint:staged:js": "eslint --fix",
    "lint:staged:css": "stylelint --fix",
    "lint:staged:format": "prettier --write",

    // デバッグ用コマンド
    "lint-staged:debug": "lint-staged --debug",
    "husky:test": "yarn lint-staged --dry-run"
  }
}

段階的チェックの実装

開発効率とコード品質のバランスを取るため、段階的なチェック体制を構築しましょう。

3 段階チェック戦略

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

echo "🚀 段階的品質チェックを開始..."

# Stage 1: 高速チェック(必須)
echo "📋 Stage 1: 基本チェック"
if ! yarn lint-staged; then
  echo "❌ ESLintエラーが検出されました"
  exit 1
fi

# Stage 2: 中程度チェック(推奨)
echo "📋 Stage 2: 型チェック"
if ! yarn type-check --incremental; then
  echo "❌ TypeScriptエラーが検出されました"
  exit 1
fi

# Stage 3: 詳細チェック(オプション)
if [ "$HUSKY_STRICT_MODE" = "true" ]; then
  echo "📋 Stage 3: 厳密チェック"
  if ! yarn test --related --passWithNoTests; then
    echo "❌ テストが失敗しました"
    exit 1
  fi
fi

echo "✅ すべてのチェックが完了しました"

環境別設定の切り替え

json{
  "scripts": {
    // 開発環境用(高速)
    "husky:dev": "cross-env HUSKY_MODE=dev husky install",

    // 本番環境用(厳密)
    "husky:prod": "cross-env HUSKY_MODE=prod husky install",

    // CI環境用(最適化)
    "husky:ci": "cross-env HUSKY_MODE=ci husky install"
  }
}

.husky/pre-commit(環境対応版)

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

# 環境変数に基づくチェックレベルの切り替え
case "${HUSKY_MODE:-dev}" in
  "dev")
    echo "🔧 開発モード: 基本チェックのみ"
    yarn lint-staged
    ;;
  "prod")
    echo "🛡️ 本番モード: 厳密チェック"
    yarn lint-staged && yarn type-check && yarn test --related
    ;;
  "ci")
    echo "🤖 CIモード: 最適化チェック"
    yarn lint-staged && yarn type-check --incremental
    ;;
  *)
    echo "⚡ デフォルトチェック"
    yarn lint-staged
    ;;
esac

パフォーマンス重視の設定

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

# 変更ファイル数に基づく動的チェック
CHANGED_FILES=$(git diff --cached --name-only | wc -l)

echo "📊 変更ファイル数: $CHANGED_FILES"

if [ "$CHANGED_FILES" -lt 5 ]; then
  # 少数ファイル変更時: 厳密チェック
  echo "🎯 厳密チェックモード"
  yarn lint-staged && yarn type-check
elif [ "$CHANGED_FILES" -lt 20 ]; then
  # 中程度変更時: 標準チェック
  echo "⚖️ 標準チェックモード"
  yarn lint-staged
else
  # 大量変更時: 高速チェック
  echo "⚡ 高速チェックモード"
  yarn lint-staged --concurrent false
fi

この設定により、変更の規模に応じて適切なレベルのチェックを実行し、開発効率を維持しながら品質を保つことができます。次のセクションでは、コミットメッセージの品質管理について詳しく解説いたします。

コミットメッセージの品質管理

コードの品質だけでなく、コミットメッセージの品質も重要です。一貫性のあるコミットメッセージは、プロジェクトの履歴を理解しやすくし、自動化ツールとの連携も円滑にします。

commitlint の導入

commitlint は、コミットメッセージの形式を検証するツールです。チーム全体で統一されたメッセージ形式を保つことができます。

commitlint のインストール

bash# commitlintと設定プリセットをインストール
yarn add --dev @commitlint/cli @commitlint/config-conventional

# 日本語対応の設定を使いたい場合
yarn add --dev @commitlint/config-conventional-ja

commitlint 設定ファイルの作成

javascript// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    // 日本語でのコミットメッセージを許可
    'subject-case': [0],
    'subject-max-length': [2, 'always', 100],

    // カスタムタイプの追加
    'type-enum': [
      2,
      'always',
      [
        'feat', // 新機能
        'fix', // バグ修正
        'docs', // ドキュメント
        'style', // フォーマット
        'refactor', // リファクタリング
        'test', // テスト
        'chore', // ビルド・設定
        'perf', // パフォーマンス改善
        'ci', // CI設定
        'revert', // コミット取り消し
        'wip', // 作業中(開発用)
      ],
    ],

    // スコープの設定(プロジェクトに応じて調整)
    'scope-enum': [
      2,
      'always',
      ['ui', 'api', 'auth', 'db', 'config', 'deps', 'ci'],
    ],
  },
};

commit-msg フックの設定

bash# commit-msgフックを追加
yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'

.husky/commit-msg ファイルの内容

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

# commitlintでメッセージ形式をチェック
yarn commitlint --edit $1

conventional commits の適用

Conventional Commits は、コミットメッセージの標準的な形式です。自動バージョニングや CHANGELOG 生成にも活用できます。

基本的なメッセージ形式

arduino<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

具体的なコミットメッセージ例

bash# 新機能追加
feat(auth): ユーザー認証機能を追加

Google OAuth 2.0による認証機能を実装
- ログイン・ログアウト機能
- セッション管理
- 認証状態の永続化

Closes #123

# バグ修正
fix(ui): ダークモードでのボタン色を修正

ダークモード時にボタンの背景色が見えにくい問題を解決
hover時の色も調整

# 破壊的変更
feat(api)!: ユーザーAPIのレスポンス形式を変更

BREAKING CHANGE: user.name は user.fullName に変更されました

高度な commitlint 設定

javascript// commitlint.config.js(高度版)
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    // ヘッダーの最大長
    'header-max-length': [2, 'always', 100],

    // 本文の最大行長
    'body-max-line-length': [2, 'always', 100],

    // フッターの最大行長
    'footer-max-line-length': [2, 'always', 100],

    // 空行の必須化
    'body-leading-blank': [2, 'always'],
    'footer-leading-blank': [2, 'always'],

    // 日本語対応
    'subject-case': [0],
    'subject-full-stop': [0],

    // Issue番号の形式チェック
    'references-empty': [2, 'never'],

    // カスタムバリデーション
    'custom-rules': [2, 'always'],
  },

  // カスタムルールの定義
  plugins: [
    {
      rules: {
        'custom-rules': ({ header }) => {
          // 日本語が含まれる場合の特別な検証
          const hasJapanese =
            /[\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF]/.test(
              header
            );
          if (hasJapanese) {
            // 日本語の場合は句読点をチェック
            return [
              !header.endsWith('。'),
              '日本語のコミットメッセージは句読点で終わらないでください',
            ];
          }
          return [true];
        },
      },
    },
  ],
};

メッセージテンプレートの作成

開発者がより良いコミットメッセージを書けるよう、テンプレートを提供しましょう。

Git コミットテンプレートの設定

bash# .gitmessage ファイルを作成
cat > .gitmessage << 'EOF'
# <type>[optional scope]: <description>
#
# [optional body]
#
# [optional footer(s)]
#
# --- COMMIT END ---
# type: feat, fix, docs, style, refactor, test, chore, perf, ci, revert
# scope: 変更範囲(ui, api, auth, db, config, deps, ci など)
# description: 変更内容の簡潔な説明(50文字以内、日本語OK)
# body: 変更理由や詳細説明(オプション)
# footer: Issue番号や破壊的変更の説明(オプション)
#
# 例:
# feat(auth): ユーザー認証機能を追加
#
# Google OAuth 2.0による認証機能を実装
# - ログイン・ログアウト機能
# - セッション管理
# - 認証状態の永続化
#
# Closes #123
EOF

# テンプレートをGitに設定
git config commit.template .gitmessage

インタラクティブなコミットヘルパー

javascript// scripts/commit-helper.js
const inquirer = require('inquirer');
const { execSync } = require('child_process');

const types = [
  { name: '✨ feat: 新機能', value: 'feat' },
  { name: '🐛 fix: バグ修正', value: 'fix' },
  { name: '📚 docs: ドキュメント', value: 'docs' },
  { name: '💄 style: フォーマット', value: 'style' },
  {
    name: '♻️ refactor: リファクタリング',
    value: 'refactor',
  },
  { name: '✅ test: テスト', value: 'test' },
  { name: '🔧 chore: ビルド・設定', value: 'chore' },
  { name: '⚡ perf: パフォーマンス', value: 'perf' },
  { name: '👷 ci: CI設定', value: 'ci' },
  { name: '⏪ revert: 取り消し', value: 'revert' },
];

const scopes = [
  'ui',
  'api',
  'auth',
  'db',
  'config',
  'deps',
  'ci',
];

async function createCommitMessage() {
  const answers = await inquirer.prompt([
    {
      type: 'list',
      name: 'type',
      message: 'コミットタイプを選択:',
      choices: types,
    },
    {
      type: 'list',
      name: 'scope',
      message: 'スコープを選択(オプション):',
      choices: ['', ...scopes],
      default: '',
    },
    {
      type: 'input',
      name: 'description',
      message: '変更内容を入力:',
      validate: (input) =>
        input.length > 0 && input.length <= 50,
    },
    {
      type: 'input',
      name: 'body',
      message: '詳細説明(オプション):',
    },
    {
      type: 'input',
      name: 'footer',
      message: 'Issue番号など(オプション):',
    },
  ]);

  const { type, scope, description, body, footer } =
    answers;

  let message = `${type}`;
  if (scope) message += `(${scope})`;
  message += `: ${description}`;

  if (body) {
    message += `\n\n${body}`;
  }

  if (footer) {
    message += `\n\n${footer}`;
  }

  console.log('\n生成されたコミットメッセージ:');
  console.log('---');
  console.log(message);
  console.log('---');

  const { confirm } = await inquirer.prompt([
    {
      type: 'confirm',
      name: 'confirm',
      message: 'このメッセージでコミットしますか?',
    },
  ]);

  if (confirm) {
    execSync(`git commit -m "${message}"`, {
      stdio: 'inherit',
    });
  }
}

createCommitMessage().catch(console.error);

package.json にヘルパースクリプトを追加

json{
  "scripts": {
    "commit": "node scripts/commit-helper.js",
    "commit:quick": "git add . && yarn commit"
  },
  "devDependencies": {
    "inquirer": "^9.2.0"
  }
}

プッシュ前の最終チェック

pre-push フックを活用して、リモートリポジトリにプッシュする前の最終チェックを行いましょう。

pre-push フックの活用

pre-push フックは、git push実行前に自動実行されるスクリプトです。より包括的なチェックを実施できます。

基本的な pre-push フックの設定

bash# pre-pushフックを追加
yarn husky add .husky/pre-push "yarn test && yarn build"

.husky/pre-push ファイルの詳細設定

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

# 色付きメッセージの設定
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

echo "${BLUE}🚀 プッシュ前の最終チェックを開始...${NC}"

# 1. ブランチ名の検証
current_branch=$(git branch --show-current)
if [[ "$current_branch" =~ ^(master|main|develop)$ ]]; then
  echo "${YELLOW}⚠️ 保護されたブランチへの直接プッシュです${NC}"
  read -p "続行しますか? (y/N): " -n 1 -r
  echo
  if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "${RED}❌ プッシュがキャンセルされました${NC}"
    exit 1
  fi
fi

# 2. リモートとの差分チェック
echo "${YELLOW}📡 リモートとの差分をチェック中...${NC}"
git fetch origin $current_branch 2>/dev/null
if git diff HEAD origin/$current_branch --quiet; then
  echo "${GREEN}✅ リモートとの差分はありません${NC}"
else
  echo "${YELLOW}📊 リモートとの差分が検出されました${NC}"
fi

# 3. TypeScript型チェック
echo "${YELLOW}📝 TypeScript型チェック中...${NC}"
if ! yarn type-check; then
  echo "${RED}❌ TypeScriptエラーが検出されました${NC}"
  exit 1
fi

# 4. テスト実行
echo "${YELLOW}🧪 テストを実行中...${NC}"
if ! yarn test --passWithNoTests --coverage; then
  echo "${RED}❌ テストが失敗しました${NC}"
  exit 1
fi

# 5. ビルドチェック
echo "${YELLOW}🔨 ビルドチェック中...${NC}"
if ! yarn build; then
  echo "${RED}❌ ビルドが失敗しました${NC}"
  exit 1
fi

echo "${GREEN}✅ すべてのチェックが完了しました。プッシュを続行します${NC}"

TypeScript 型チェックの統合

TypeScript プロジェクトでは、実行時エラーを防ぐため型チェックを必須にしましょう。

効率的な型チェック設定

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

echo "📝 TypeScript型チェックを実行中..."

# インクリメンタル型チェック(高速)
if yarn type-check --incremental --tsBuildInfoFile .tsbuildinfo; then
  echo "✅ 型チェック完了"
else
  echo "❌ 型エラーが検出されました"
  echo ""
  echo "修正方法:"
  echo "  yarn type-check           # エラー詳細を確認"
  echo "  yarn type-check --watch   # ウォッチモードで修正"
  exit 1
fi

package.json の型チェックスクリプト

json{
  "scripts": {
    "type-check": "tsc --noEmit",
    "type-check:watch": "tsc --noEmit --watch",
    "type-check:incremental": "tsc --noEmit --incremental",
    "type-check:strict": "tsc --noEmit --strict"
  }
}

テスト実行の自動化

品質保証のため、プッシュ前にテストの実行を自動化しましょう。

効率的なテスト実行戦略

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

echo "🧪 テスト実行戦略を決定中..."

# 変更されたファイルを取得
changed_files=$(git diff --cached --name-only)
test_files=$(echo "$changed_files" | grep -E '\.(test|spec)\.(ts|tsx|js|jsx)$' || true)

if [ -n "$test_files" ]; then
  echo "🎯 変更されたテストファイルを実行"
  yarn test $test_files --passWithNoTests
elif [ -n "$changed_files" ]; then
  echo "🔍 関連テストを実行"
  yarn test --findRelatedTests $changed_files --passWithNoTests
else
  echo "📋 全テストを実行"
  yarn test --passWithNoTests
fi

テスト設定の最適化

json{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:related": "jest --findRelatedTests",
    "test:changed": "jest --onlyChanged",
    "test:ci": "jest --ci --coverage --watchAll=false"
  },
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node",
    "collectCoverageFrom": [
      "src/**/*.{ts,tsx}",
      "!src/**/*.d.ts",
      "!src/**/*.test.{ts,tsx}"
    ],
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

チーム開発での運用設定

大規模なチーム開発では、個人の設定だけでなく、チーム全体での統一された運用が重要です。

共有設定の管理方法

チーム全体で一貫した設定を維持するための方法を解説します。

設定ファイルの一元管理

perlproject-root/
├── .husky/                    # Git hooks設定
│   ├── pre-commit
│   ├── commit-msg
│   └── pre-push
├── .eslintrc.js              # ESLint設定
├── commitlint.config.js      # commitlint設定
├── prettier.config.js        # Prettier設定
├── package.json              # 依存関係とスクリプト
└── scripts/                  # 共有スクリプト
    ├── setup-husky.sh
    └── validate-environment.sh

環境構築自動化スクリプト

bash#!/bin/bash
# scripts/setup-husky.sh

echo "🛠️ 開発環境をセットアップ中..."

# Node.jsバージョンチェック
node_version=$(node -v | cut -d'v' -f2)
required_version="18.0.0"

if [ "$(printf '%s\n' "$required_version" "$node_version" | sort -V | head -n1)" != "$required_version" ]; then
  echo "❌ Node.js $required_version 以上が必要です(現在: $node_version)"
  exit 1
fi

# Yarnの確認
if ! command -v yarn &> /dev/null; then
  echo "❌ Yarnがインストールされていません"
  echo "インストール方法: npm install -g yarn"
  exit 1
fi

# 依存関係のインストール
echo "📦 依存関係をインストール中..."
yarn install

# Huskyの初期化
echo "🐕 Huskyを初期化中..."
yarn husky install

# Git設定の確認
echo "🔧 Git設定を確認中..."
if [ -z "$(git config user.name)" ] || [ -z "$(git config user.email)" ]; then
  echo "⚠️ Git設定が不完全です"
  echo "設定方法:"
  echo "  git config user.name 'Your Name'"
  echo "  git config user.email 'your.email@example.com'"
fi

# コミットテンプレートの設定
if [ -f ".gitmessage" ]; then
  git config commit.template .gitmessage
  echo "✅ コミットテンプレートを設定しました"
fi

echo "🎉 セットアップ完了!"
echo ""
echo "次のステップ:"
echo "  yarn lint     # コード品質をチェック"
echo "  yarn test     # テストを実行"
echo "  yarn commit   # インタラクティブコミット"

package.json でのセットアップ統合

json{
  "scripts": {
    "postinstall": "husky install",
    "setup": "chmod +x scripts/setup-husky.sh && ./scripts/setup-husky.sh",
    "validate": "yarn lint && yarn type-check && yarn test"
  }
}

CI との連携パターン

Husky との重複を避けつつ、CI で包括的なチェックを行う設定を構築しましょう。

GitHub Actions 設定例

yaml# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  lint-and-test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x, 20.x]

    steps:
      - uses: actions/checkout@v4
        with:
          # Huskyとの重複チェックを避けるため、履歴を取得
          fetch-depth: 0

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'yarn'

      - name: Install dependencies
        run: yarn install --frozen-lockfile

      - name: Validate commits (only on PR)
        if: github.event_name == 'pull_request'
        run: |
          yarn commitlint --from ${{ github.event.pull_request.base.sha }} --to HEAD

      - name: Run ESLint
        run: yarn lint

      - name: Run TypeScript check
        run: yarn type-check

      - name: Run tests
        run: yarn test:ci

      - name: Build application
        run: yarn build

      - name: Upload coverage reports
        uses: codecov/codecov-action@v3
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

重複チェックの回避設定

bash# .husky/pre-push(CI連携版)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

# CI環境では実行をスキップ
if [ "$CI" = "true" ]; then
  echo "🤖 CI環境のため pre-push チェックをスキップ"
  exit 0
fi

# ローカル環境でのみ実行
echo "🔍 ローカル環境でのプッシュ前チェック"

# 軽量なチェックのみ実行(詳細はCIで)
yarn lint-staged && yarn type-check --incremental

トラブルシューティング

よくある問題とその解決方法をまとめました。

よくある問題と解決法

#問題原因解決方法
1Husky が動作しない権限不足chmod +x .husky​/​*
2lint-staged が遅い大量ファイル処理並列実行とファイル除外
3CI との重複実行設定重複環境変数での分岐
4コミットできない厳しすぎるルール段階的なルール適用

デバッグとログ出力

bash#!/usr/bin/env sh
# .husky/pre-commit(デバッグ版)
. "$(dirname -- "$0")/_/husky.sh"

# デバッグモードの有効化
if [ "$HUSKY_DEBUG" = "1" ]; then
  set -x  # コマンドトレースを有効化
fi

# ログファイルの設定
LOG_FILE=".husky/logs/pre-commit-$(date +%Y%m%d-%H%M%S).log"
mkdir -p .husky/logs

# ログ出力関数
log() {
  echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}

log "🔍 pre-commit チェック開始"

# 環境情報のログ出力
log "Node.js: $(node -v)"
log "Yarn: $(yarn -v)"
log "Git: $(git --version)"
log "ブランチ: $(git branch --show-current)"
log "変更ファイル数: $(git diff --cached --name-only | wc -l)"

# 実際のチェック実行
if yarn lint-staged; then
  log "✅ lint-staged 成功"
else
  log "❌ lint-staged 失敗"
  exit 1
fi

log "🎉 pre-commit チェック完了"

緊急時のバイパス方法

bash# 緊急時のコミット(品質チェックをスキップ)
git commit --no-verify -m "hotfix: 緊急修正"

# 緊急時のプッシュ(プッシュ前チェックをスキップ)
git push --no-verify

# 一時的なHusky無効化
HUSKY=0 git commit -m "一時的にHuskyを無効化"

パフォーマンス最適化

大規模プロジェクトで Husky と ESLint を効率的に動作させるための最適化手法を解説します。

大規模プロジェクトでの高速化

ファイル数が多い場合のパフォーマンス改善策を見ていきましょう。

ファイル除外の最適化

json{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix --max-warnings 0"
    ],
    // 大きなファイルを除外
    "!(**/*.min.js|**/dist/**|**/build/**|**/node_modules/**)"
  }
}

.eslintignore の活用

csharp# .eslintignore
# ビルド成果物
dist/
build/
out/

# 依存関係
node_modules/
.yarn/

# 一時ファイル
*.min.js
*.bundle.js
coverage/

# 自動生成ファイル
**/*.generated.*
**/generated/**

# 巨大なファイル
**/*.large.js

段階的チェックの実装

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

# 変更ファイル数による動的処理
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx|js|jsx)$' | wc -l)

echo "📊 ステージングファイル数: $STAGED_FILES"

if [ "$STAGED_FILES" -eq 0 ]; then
  echo "📭 チェック対象ファイルがありません"
  exit 0
elif [ "$STAGED_FILES" -le 5 ]; then
  echo "🎯 小規模変更: 詳細チェック"
  yarn lint-staged && yarn type-check
elif [ "$STAGED_FILES" -le 20 ]; then
  echo "⚖️ 中規模変更: 標準チェック"
  yarn lint-staged
else
  echo "⚡ 大規模変更: 高速チェック"
  yarn lint-staged --concurrent false
fi

インクリメンタルチェック

変更されたファイルのみを効率的にチェックする仕組みを構築しましょう。

TypeScript インクリメンタルチェック

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

# インクリメンタル型チェック用の設定
TSC_OPTIONS="--noEmit --incremental --tsBuildInfoFile .tsbuildinfo"

if [ -f ".tsbuildinfo" ]; then
  echo "🔄 インクリメンタル型チェック実行中..."
  yarn tsc $TSC_OPTIONS
else
  echo "🆕 初回型チェック実行中..."
  yarn tsc $TSC_OPTIONS
fi

ESLint キャッシュの活用

json{
  "scripts": {
    "lint": "eslint . --ext .ts,.tsx,.js,.jsx --cache --cache-location .eslintcache",
    "lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix --cache --cache-location .eslintcache"
  },
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "eslint --fix --cache --cache-location .eslintcache"
    ]
  }
}

並列実行の設定

複数のチェックを並列実行してパフォーマンスを向上させましょう。

並列実行スクリプト

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

echo "🚀 並列チェック開始..."

# 背景で実行するプロセス
yarn lint-staged &
LINT_PID=$!

yarn type-check --incremental &
TYPE_PID=$!

# プロセスの完了を待機
wait $LINT_PID
LINT_EXIT=$?

wait $TYPE_PID
TYPE_EXIT=$?

# 結果の確認
if [ $LINT_EXIT -eq 0 ] && [ $TYPE_EXIT -eq 0 ]; then
  echo "✅ すべてのチェックが完了"
  exit 0
else
  echo "❌ チェックが失敗しました"
  exit 1
fi

package.json での並列実行

json{
  "scripts": {
    "check:parallel": "run-p lint:check type-check test:quick",
    "lint:check": "eslint . --ext .ts,.tsx,.js,.jsx --cache",
    "type-check": "tsc --noEmit --incremental",
    "test:quick": "jest --onlyChanged --passWithNoTests"
  },
  "devDependencies": {
    "npm-run-all": "^4.1.5"
  }
}

高度な並列制御

javascript// scripts/parallel-checks.js
const { spawn } = require('child_process');
const os = require('os');

const checks = [
  {
    name: 'ESLint',
    command: 'yarn',
    args: ['lint-staged'],
  },
  {
    name: 'TypeScript',
    command: 'yarn',
    args: ['type-check', '--incremental'],
  },
  {
    name: 'Tests',
    command: 'yarn',
    args: ['test', '--onlyChanged', '--passWithNoTests'],
  },
];

// CPU数に基づく並列数の制御
const maxParallel = Math.min(
  checks.length,
  os.cpus().length
);

async function runChecks() {
  console.log(`🔄 ${maxParallel} 並列でチェック実行中...`);

  const results = await Promise.allSettled(
    checks
      .slice(0, maxParallel)
      .map((check) => runCommand(check))
  );

  const failed = results.filter(
    (result) => result.status === 'rejected'
  );

  if (failed.length > 0) {
    console.error('❌ チェックが失敗しました');
    process.exit(1);
  } else {
    console.log('✅ すべてのチェックが完了');
  }
}

function runCommand({ name, command, args }) {
  return new Promise((resolve, reject) => {
    console.log(`🔍 ${name} チェック開始...`);

    const child = spawn(command, args, {
      stdio: 'inherit',
    });

    child.on('close', (code) => {
      if (code === 0) {
        console.log(`✅ ${name} 完了`);
        resolve();
      } else {
        console.error(
          `❌ ${name} 失敗 (exit code: ${code})`
        );
        reject(new Error(`${name} failed`));
      }
    });
  });
}

runChecks().catch(console.error);

まとめ

Husky と ESLint を組み合わせたコミット前チェック自動化により、チーム開発での品質保証を大幅に改善できます。本記事では、基本的なセットアップから高度な運用まで、実践的な設定方法を詳しく解説いたしました。

導入効果の整理

  • 品質向上: 問題のあるコードのリポジトリ混入を防止
  • 効率化: 自動チェックによりレビュー負荷を軽減
  • 統一性: チーム全体で一貫したコーディング規約を維持
  • 早期発見: 開発段階での問題発見により修正コストを削減

成功のポイント

  1. 段階的導入: 基本設定から始めて徐々に厳格化
  2. パフォーマンス重視: 大規模プロジェクトでも快適な開発体験を維持
  3. チーム合意: ルールの意図を共有し、適切なエスケープハッチを用意
  4. 継続的改善: 開発者のフィードバックを基にした設定の最適化

今後の展望 AI 支援ツールとの連携や、より高度な静的解析ツールとの統合により、さらなる品質向上が期待できます。重要なのは、技術的な完璧さよりも、チーム全体が継続して使える実用的なシステムを構築することです。

適切に設定された Husky と ESLint の組み合わせは、開発チームの生産性向上と品質保証の両立を実現する強力なツールとなるでしょう。

関連リンク