AI

lefthookの使い方完全ガイド【インストールから応用まで】

lefthookの使い方完全ガイド【インストールから応用まで】

開発チームでコードの品質を均一に保つことは、簡単ではありません。個々の開発者が異なるエディタ設定を使ったり、リンターやフォーマッターの実行を忘れたりすることが頻繁に起こります。その結果、コードレビューで細かな修正が指摘されたり、CI/CDパイプラインでテストが失敗したりする事態が発生します。

このような手戻りは、開発効率を著しく低下させます。しかし、もしコミットやプッシュのタイミングで、自動的にコードチェックやテストが実行されたらどうでしょうか。CIが落ちる前にローカルで問題を検出し、コードレビューでの手戻りを大幅に削減できます。

本記事では、Git hooksを強力に管理し、開発体験を向上させるツール「lefthook」について、その導入から実践的な使い方までを詳しく解説します。

lefthookとは

lefthookは、Git hooksをシンプルかつ高速に管理するためのツールです。Go言語で書かれており、Node.js、Ruby、Pythonなど、さまざまなプロジェクトで利用できます。

Git hooksとは、git commitgit pushといった特定のGitイベントの前後で、任意のスクリプトを自動実行する仕組みです。例えば、コミット前にコードのフォーマットを自動修正したり、プッシュ前にテストを実行して品質を保証したりできます。

lefthookは、従来のGit hooks管理ツールと比較して、以下の点で優れています。

  • 高速性: Go言語で実装されており、複数の処理を並列で実行できます。これにより、フックの実行にかかる時間を大幅に短縮します。
  • シンプルさ: 依存関係のない単一のバイナリファイルとして動作します。環境に左右されず、どのようなプロジェクトでも簡単に導入が可能です。
  • 強力な制御: 実行するコマンド、対象ファイル、実行パスなどを柔軟に設定できます。プロジェクトの要件に応じた細かいカスタマイズが可能です。

特にJavaScript/TypeScript開発でよく使われるhuskyと比較すると、lefthookはその高速性とシンプルさで優位性があります。Evil Martians社によって開発され、開発者の生産性向上に貢献しています。

インストール方法

lefthookは、様々なパッケージマネージャーを通じて簡単にインストールできます。ここでは主要なインストール方法を紹介します。

Node.jsプロジェクト (npm)

多くのフロントエンドプロジェクトやNode.jsバックエンドプロジェクトで利用されます。

npm install lefthook --save-dev

Go言語プロジェクト

Goの開発環境が整っている場合、Goのツールとしてインストールできます。

go install github.com/evilmartians/lefthook/v2@v2.1.9

または、Goのツールとして取得します。

go get -tool github.com/evilmartians/lefthook/v2@v2.1.9

Rubyプロジェクト (gem)

Rubyの開発プロジェクトで利用する場合に便利です。

gem install lefthook

Pythonプロジェクト (pipx)

Pythonの開発プロジェクトで利用する場合に便利です。

pipx install lefthook

macOS (Homebrew)

macOSユーザーであれば、Homebrewを使って簡単にインストールできます。

brew install lefthook

Linux (APT)

Debian/Ubuntu系のLinuxディストリビューションでは、APTパッケージマネージャーを利用できます。

sudo apt install lefthook

Windows (Winget)

Windowsユーザーは、Wingetパッケージマネージャーを利用できます。

winget install lefthook

インストール確認

インストールが完了したら、以下のコマンドでバージョンを確認できます。

lefthook version

バージョン情報が表示されれば、正しくインストールされています。

基本的な使い方

lefthookの基本的な使い方は非常にシンプルです。設定ファイルを作成し、Git hooksを有効化するだけです。

1. 設定ファイル lefthook.yml の作成

プロジェクトのルートディレクトリにlefthook.ymlという名前のファイルを作成します。このファイルに、実行したいGit hooksとその内容を記述します。

例えば、pre-commitフックで簡単なメッセージを表示する設定は以下のようになります。

# lefthook.yml
pre-commit:
  commands:
    hello:
      run: echo "Hello from lefthook pre-commit hook!"

この例では、pre-commitというフックの中にhelloという名前のコマンドを定義しています。runキーには、実際に実行したいシェルコマンドを指定します。

より一般的には、複数の処理を定義するためにjobsセクションを使用します。jobsを使うと、各処理にnameを付けて管理しやすくなります。

# lefthook.yml
pre-commit:
  jobs:
    lint:
      name: Run ESLint
      run: npm run lint
    test:
      name: Run tests
      run: npm test

この設定では、コミット前にESLintを実行し、その後テストを実行します。

2. Git hooksの有効化

lefthook.ymlを作成したら、以下のコマンドを実行してGit hooksをプロジェクトにインストールします。

lefthook install

このコマンドは、.git/hooksディレクトリ内にlefthookが制御するスクリプトを生成します。一度実行すれば、次回からは自動的にlefthookがフックを処理します。

3. Git操作でフックを実行

lefthook installが完了すれば、あとは通常通りGit操作を行うだけです。

例えば、ファイルをステージングしてコミットを試みます。

git add .
git commit -m "feat: add initial lefthook setup"

このコミット操作時に、lefthook.ymlで定義したpre-commitフックが自動的に実行されます。上記の例であれば、「Hello from lefthook pre-commit hook!」というメッセージが表示され、npm run lintnpm testが実行されます。

フックの実行中にエラーが発生した場合、コミットは中断されます。これにより、問題のあるコードがコミットされるのを防ぎます。

4. 特定のフックを手動で実行する

lefthook runコマンドを使用すると、特定のフックを手動で実行できます。これは、設定のテストやデバッグに便利です。

lefthook run pre-commit

このコマンドは、pre-commitフックに定義されているすべてのジョブを実行します。

5. フックの実行をスキップする

一時的にGit hooksの実行をスキップしたい場合は、Gitの--no-verifyオプションを使用します。

git commit -m "fix: temporary commit" --no-verify

このオプションを付与すると、Git hooksは一切実行されずにコミットが完了します。緊急時やデバッグ時に活用できます。

便利な使い方・応用例 3選

lefthookの真価は、その柔軟な設定と強力な機能にあります。ここでは、実際の開発シーンで役立つ応用例を3つ紹介します。

5.1. 並列実行とファイルフィルタリングで開発を加速する

大規模なプロジェクトでは、コミット前に複数のリンター、フォーマッター、テストを実行すると時間がかかります。lefthookはこれらの処理を並列で実行し、さらに変更されたファイルのみを対象とすることで、フックの実行時間を大幅に短縮できます。

例えば、React/TypeScriptプロジェクトで、コミット前にESLint、Prettier、Vitestを並列で実行する設定を考えます。

# lefthook.yml (例: React/TypeScriptプロジェクト)
pre-commit:
  parallel: true # 並列実行を有効化
  jobs:
    eslint:
      name: Lint JavaScript/TypeScript
      glob: "*.{js,jsx,ts,tsx}" # JS/TSファイルに限定
      run: npm run eslint -- {staged_files} # ステージされたファイルのみを対象
    prettier:
      name: Format Code
      glob: "*.{js,jsx,ts,tsx,json,css,scss,md}" # 対象ファイルの種類を拡張
      run: npm run prettier --write {staged_files} # ステージされたファイルをフォーマット
    test:
      name: Run Vitest affected tests
      glob: "*.{ts,tsx}" # TS/TSXファイルに限定
      run: npm run test:affected -- {staged_files} # 変更に関連するテストのみ実行

解説:

  • parallel: true: pre-commitフック内のjobsを並列で実行します。これにより、ESLint、Prettier、Vitestが同時に動作し、合計実行時間を短縮します。
  • glob: 特定のファイルパターンに一致するファイルのみを対象とします。例えば、*.{js,jsx,ts,tsx}はJavaScriptとTypeScriptファイルに限定します。
  • {staged_files}: Gitのステージングエリアにある変更ファイルリストをコマンドに渡します。これにより、プロジェクト全体のファイルをスキャンするのではなく、実際に変更されたファイルのみをチェックするため、非常に効率的です。
  • npm run eslint -- {staged_files}: package.jsonで定義されたeslintスクリプトを呼び出し、引数としてステージングされたファイルを渡します。

この設定により、コミット前に必要な品質チェックが高速かつ効率的に実行され、開発者はよりスムーズに作業を進められます。

5.2. モノレポ構成でのサブディレクトリ実行

モノレポ(monorepo)構成のプロジェクトでは、フロントエンド、バックエンド、共通ライブラリなどが一つのリポジトリにまとめられています。この場合、特定のディレクトリ内のファイル変更に対してのみフックを実行したいことがあります。

lefthookのrootオプションを使用すると、コマンドを実行するルートディレクトリを指定できます。

例えば、frontend/backend/というサブディレクトリを持つモノレポ構成で、それぞれのディレクトリに特化したフックを設定します。

# lefthook.yml (例: モノレポ)
pre-commit:
  jobs:
    lint-frontend:
      name: Lint Frontend
      root: "frontend/" # frontendディレクトリで実行
      glob: "*.{js,jsx,ts,tsx}"
      run: npm run lint -- {staged_files}
    lint-backend:
      name: Lint Backend
      root: "backend/" # backendディレクトリで実行
      glob: "*.rb"
      run: bundle exec rubocop -- {staged_files}

解説:

  • root: "frontend/": lint-frontendジョブは、リポジトリのルートではなく、frontend/ディレクトリをカレントディレクトリとしてコマンドを実行します。
  • npm run lint: frontendディレクトリ内のpackage.jsonに定義されたlintスクリプトが実行されます。
  • 同様に、lint-backendジョブはbackend/ディレクトリ内でRubyのRuboCopを実行します。

これにより、モノレポ内の異なるプロジェクトが互いに影響を与えることなく、それぞれの開発言語やフレームワークに最適化されたフックを適用できます。

5.3. ローカル設定とタグによる柔軟なフック管理

チーム全体で共通のGit hooks設定を適用しつつ、個々の開発者の環境や特定の作業内容に応じて、一部のフックをスキップしたい場合があります。lefthookはlefthook-local.ymlファイルとtags機能を提供し、この課題を解決します。

まず、lefthook.ymlでタグを定義します。

# lefthook.yml
pre-push:
  jobs:
    full-test:
      name: Run all tests
      tags: [frontend, backend, ci] # 複数のタグを付与
      run: npm run test:ci # 時間のかかる全てのテストを実行
    security-audit:
      name: Audit dependencies
      tags: [backend, security] # バックエンドのセキュリティチェック
      run: bundle audit
    deploy-check:
      name: Check deploy readiness
      tags: [ci] # CIでのみ実行したいチェック
      run: ./scripts/deploy_check.sh

次に、個人の環境で特定のフックやジョブをスキップするためにlefthook-local.ymlを作成します。このファイルは.gitignoreに追加して、リポジトリにコミットされないようにします。

# lefthook-local.yml (例: フロントエンド開発者がバックエンドの重いチェックをスキップ)
pre-push:
  exclude_tags:
    - backend # 'backend'タグを持つすべてのジョブをスキップ
  jobs:
    full-test:
      skip: true # 'full-test'ジョブを完全にスキップ

解説:

  • lefthook-local.ymlは、lefthook.ymlの設定を上書きまたは拡張するために使用されます。
  • exclude_tags: [backend]は、pre-pushフック内でbackendタグを持つすべてのジョブ(この場合security-audit)の実行をスキップします。
  • jobs.full-test.skip: trueは、特定のジョブfull-testの実行を完全にスキップします。

この機能により、チーム全体で強力な品質ゲートを維持しつつ、個々の開発者は特定の状況下で(例えば、フロントエンドのみを開発している際にバックエンドの重いチェックを一時的に無効にするなど)柔軟に対応できます。

他ツールとの組み合わせ

lefthookは、様々な開発ツールと組み合わせて利用することで、その真価を発揮します。ここでは、相性の良いツールとその組み合わせ方を紹介します。

コードフォーマッタ

  • Prettier (JavaScript/TypeScript/CSS/HTML/Markdown): コミット前に自動でコードを整形します。
    pre-commit:
      jobs:
        prettier:
          name: Format with Prettier
          glob: "*.{js,jsx,ts,tsx,json,css,scss,html,md}"
          run: npx prettier --write {staged_files}
    
  • Black (Python):
    pre-commit:
      jobs:
        black:
          name: Format with Black
          glob: "*.py"
          run: black {staged_files}
    
  • RuboCop (Ruby):
    pre-commit:
      jobs:
        rubocop:
          name: Lint with RuboCop
          glob: "*.rb"
          run: bundle exec rubocop --force-exclusion --autocorrect -- {staged_files}
    

リンター

  • ESLint (JavaScript/TypeScript): コードの品質とスタイルをチェックします。
    pre-commit:
      jobs:
        eslint:
          name: Lint with ESLint
          glob: "*.{js,jsx,ts,tsx}"
          run: npx eslint --fix {staged_files}
    
  • Stylelint (CSS/SCSS): スタイルシートのリンティングを行います。
    pre-commit:
      jobs:
        stylelint:
          name: Lint with Stylelint
          glob: "*.{css,scss}"
          run: npx stylelint --fix {staged_files}
    
  • Flake8 (Python):
    pre-commit:
      jobs:
        flake8:
          name: Lint with Flake8
          glob: "*.py"
          run: flake8 {staged_files}
    

テストフレームワーク

  • Jest / Vitest (JavaScript/TypeScript): 関連するテストのみを実行することで、コミット時間を短縮します。
    pre-commit:
      jobs:
        vitest:
          name: Run Vitest affected tests
          glob: "*.{ts,tsx}"
          run: npx vitest related --run {staged_files}
    

セキュリティツール

  • npm audit (Node.js): プロジェクトの依存関係の脆弱性をチェックします。
    pre-push:
      jobs:
        audit:
          name: Run npm audit
          run: npm audit --audit-level=moderate
    
  • bundle audit (Ruby): Gemの脆弱性をチェックします。
    pre-push:
      jobs:
        bundle-audit:
          name: Run bundle audit
          run: bundle audit check --update
    

これらのツールをlefthook.ymlに適切に設定することで、開発プロセス全体を通じてコード品質、一貫性、セキュリティを自動的に維持できます。

よくある設定・カスタマイズ

lefthookは、様々なGit hooksに対応し、高度なカスタマイズが可能です。ここでは、特に役立つ設定例を紹介します。

1. コミットメッセージのフォーマットチェック (commit-msgフック)

チームでコミットメッセージの規約(例: Conventional Commits)を設けている場合、commit-msgフックでその規約を強制できます。

# lefthook.yml
commit-msg:
  jobs:
    validate-message:
      name: Validate commit message
      # コミットメッセージファイルの内容を標準入力に渡すスクリプトを実行
      script: |
        #!/bin/bash
        # コミットメッセージを検証するロジックをここに記述
        # 例えば、Conventional Commitsのパターンにマッチするか確認
        if ! grep -qE '^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?: .{1,50}' "$1"; then
          echo "Error: Invalid commit message format."
          echo "Please follow Conventional Commits (e.g., 'feat: add new feature')."
          exit 1
        fi

解説:

  • commit-msgフックは、コミットメッセージが保存された一時ファイルのパスを引数として受け取ります。scriptオプションでそのファイルを読み込み、検証ロジックを実行します。
  • シェルスクリプトを直接記述することで、複雑なチェックも可能です。

2. マージ/チェックアウト後の依存関係の自動インストール (post-merge, post-checkoutフック)

ブランチを切り替えたり、最新の変更をプルしたりした際に、新しい依存関係が追加されていることがあります。post-mergepost-checkoutフックを使って、自動的に依存関係をインストールできます。

# lefthook.yml
post-merge:
  jobs:
    npm-install:
      name: Install NPM dependencies
      run: npm install

post-checkout:
  jobs:
    npm-install:
      name: Install NPM dependencies
      run: npm install

解説:

  • npm installコマンドを実行することで、package.jsonの変更に応じて必要なパッケージが自動的にインストールされます。Rubyのbundle installやPythonのpip install -r requirements.txtなどにも応用できます。

3. スクリプトファイルの実行

ワンライナーのコマンドでは表現しきれない複雑なロジックは、独立したスクリプトファイルとして定義し、それをlefthookから呼び出すことができます。

# lefthook.yml
pre-push:
  jobs:
    custom-check:
      name: Run custom pre-push check
      script: "./scripts/pre_push_check.sh" # スクリプトファイルのパス
      runner: bash # スクリプトを実行するランナーを指定 (bash, sh, zshなど)

解説:

  • scriptオプションにスクリプトファイルのパスを指定します。
  • runnerオプションで、そのスクリプトを実行するシェルを指定します。

4. 環境変数を使った設定

ジョブの実行時に環境変数を設定できます。これにより、コマンドの挙動を柔軟に制御できます。

# lefthook.yml
pre-commit:
  jobs:
    lint-with-env:
      name: Lint with custom environment
      env:
        NODE_ENV: test # NODE_ENV環境変数を設定
        DEBUG_MODE: "true"
      run: npm run lint

解説:

  • envセクションで定義された環境変数は、そのジョブのrunコマンド実行時にのみ有効になります。

5. CI/CD環境での利用

CI/CDパイプラインでは、Git hooksを意図的に無効にしたい場合があります。lefthookは、CI環境での挙動を制御するためのオプションを提供します。

# CI環境でlefthookを完全にスキップする (通常は必要ないが、参考として)
CI=true lefthook install # CI環境変数がtrueの場合、lefthookは自動的にスキップモードになる

また、特定のフックの実行を強制したい場合は、lefthook runコマンドを直接CIスクリプトに組み込むこともできます。

これらの設定やカスタマイズを活用することで、lefthookはプロジェクトのニーズに合わせた高度なGit hooks管理を実現します。

今日からできる実行プラン

lefthookをあなたのプロジェクトに導入するのは非常に簡単です。以下の3つのステップで、今日からGit hooksの恩恵を受け始めましょう。

Step 1: lefthookをプロジェクトにインストールする

まずは、あなたのプロジェクトにlefthookをインストールします。Node.jsプロジェクトであればNPM、RubyプロジェクトであればGemといったように、プロジェクトの主要言語に合わせた方法を選びましょう。

# Node.jsプロジェクトの場合
npm install lefthook --save-dev

# macOSの場合 (グローバルインストール)
brew install lefthook

Step 2: lefthook.ymlを作成し、最初のpre-commitフックを定義する

プロジェクトのルートディレクトリにlefthook.ymlファイルを作成します。最初の一歩として、コミット前にESLintを実行するpre-commitフックを設定してみましょう。

# lefthook.yml
pre-commit:
  jobs:
    eslint:
      name: Run ESLint on staged files
      glob: "*.{js,jsx,ts,tsx}" # JavaScript/TypeScriptファイルに限定
      run: npm run eslint -- {staged_files} # ステージされたファイルのみを対象

この設定例では、package.json"eslint": "eslint --fix"のようなスクリプトが定義されていることを前提としています。

Step 3: lefthook installを実行し、コミットを試す

lefthook.ymlを保存したら、Git hooksを有効化します。

lefthook install

これで準備は完了です。ファイルをいくつか変更し、ステージングしてコミットを試してみてください。

git add .
git commit -m "feat: add first lefthook pre-commit hook"

ESLintが実行され、問題があればコミットが中断されるはずです。問題がなければ、コミットが成功します。

チームへの展開

lefthookの導入をチーム全体に広げるには、package.jsonpostinstallスクリプトにlefthook installを追加するのが一般的です。

// package.json
{
  "name": "my-project",
  "version": "1.0.0",
  "scripts": {
    "postinstall": "lefthook install",
    "eslint": "eslint --fix ."
  },
  "devDependencies": {
    "lefthook": "^2.1.9",
    "eslint": "^8.0.0"
  }
}

これにより、チームメンバーがnpm install(またはyarn installなど)を実行するたびに、自動的にlefthookがセットアップされます。

lefthookは、あなたの開発ワークフローをより堅牢で効率的なものに変える強力なツールです。ぜひ今日から導入を検討し、その恩恵を実感してください。


参考文献

広告

-AI