Lefthook Git Hooks 管理
Lefthook 是由 Evil Martians 開發的 Git hooks 管理工具,用單一設定檔就能在 commit、push、merge 等時機自動執行 lint、format、測試等任務。相較 Husky + lint-staged 的組合,Lefthook 原生支援平行執行、檔案過濾、本地覆寫,且不限語言(Go / Ruby / Node / Python 皆可)。
為什麼用 Lefthook?
| 特性 | Lefthook | Husky + lint-staged |
|---|---|---|
| 設定檔 | 單一 lefthook.yml | .husky/ + lint-staged 設定 |
| 平行執行 | 內建 parallel: true | 需額外處理 |
| 檔案過濾 | 內建 glob / exclude | 依賴 lint-staged |
| 跨語言 | 是(單一二進位) | 主要綁 Node.js 生態 |
| 本地覆寫 | lefthook-local.yml | 較不便 |
常見使用情境:
- pre-commit:只對 staged 檔案跑 eslint、prettier、stylelint
- pre-push:推送前跑測試或安全掃描
- post-merge:
git pull後自動pnpm install - commit-msg:驗證 commit message 格式(commitlint)
安裝
依專案環境選擇安裝方式:
# Node.js 專案(最常見)
pnpm add -D lefthook
# 或 npm install lefthook --save-dev
# Homebrew(macOS)
brew install lefthook
# Go
go install github.com/evilmartians/lefthook/v2@latest
安裝後,在專案根目錄執行一次:
npx lefthook install
這會把設定寫入 .git/hooks/,之後每次 git commit 等操作就會觸發對應 hook。
建議在 package.json 加上 prepare script,讓團隊成員 pnpm install 後自動安裝 hooks:
{
"scripts": {
"prepare": "lefthook install"
}
}
基本流程
# 1. 建立或編輯設定檔
vim lefthook.yml
# 2. 安裝 hooks 到 .git/hooks/
lefthook install
# 3. 驗證設定是否正確
lefthook validate
# 4. 手動測試某個 hook(不必真的 commit)
lefthook run pre-commit
# 5. 暫時跳過 lefthook(緊急 commit 時)
LEFTHOOK=0 git commit -m "hotfix"
設定檔結構
設定檔可命名為 lefthook.yml、lefthook.yaml 或 .lefthook.yml 等,專案內只保留一份,避免衝突。官方文件:Configuration
Hook 名稱
對應 Git 內建 hooks,例如 pre-commit、pre-push、commit-msg、post-merge、post-checkout。
commands 與 jobs
兩種寫法功能相近,新版文件較推 jobs,舊專案常見 commands:
# commands 寫法(以名稱為 key)
pre-commit:
commands:
lint:
run: eslint {staged_files}
glob: "*.{js,ts}"
# jobs 寫法(陣列,可加 name、tags)
pre-commit:
jobs:
- name: lint
run: eslint {staged_files}
glob: "*.{js,ts}"
常用欄位
| 欄位 | 說明 |
|---|---|
run | 要執行的 shell 指令 |
glob | 只對符合副檔名的 staged 檔案執行 |
exclude | 排除特定路徑或 pattern |
parallel | true 時同 hook 內多個任務平行跑 |
files | 自訂檔案來源,如 git ls-files -m |
script + runner | 執行腳本檔,如 runner: node |
tags | 標記任務,可用 exclude_tags 在本地略過 |
stage_fixed | 自動 git add 被 fix 過的檔案 |
rc | hook 執行前載入的 shell script(補 PATH 等) |
檔案佔位符
| 佔位符 | 說明 |
|---|---|
{staged_files} | 目前 staged 且符合 glob 的檔案 |
{all_files} | 所有符合 glob 的 staged 檔案(含 exclude 邏輯) |
{files} | 搭配自訂 files 指令使用 |
{push_files} | pre-push 時變更的檔案 |
本地覆寫:lefthook-local.yml
個人開發若不想跑某些 hook,可建立 lefthook-local.yml(建議加入 .gitignore):
# lefthook-local.yml
pre-push:
exclude_tags:
- frontend
jobs:
- name: audit packages
skip: true
也可直接複製 lefthook-local.yml.example → lefthook-local.yml 做自訂 PATH 設定。
入門範例
最小 pre-commit(lint + format)
pre-commit:
parallel: true
jobs:
- run: yarn eslint --fix {staged_files}
glob: "*.{js,ts,jsx,tsx}"
stage_fixed: true
- run: yarn prettier --write {staged_files}
glob: "*.{json,md,yml}"
stage_fixed: true
pre-push 依 tags 分組
pre-push:
jobs:
- name: packages audit
tags:
- frontend
- security
run: yarn audit
- name: gems audit
tags:
- backend
- security
run: bundle audit
多語言 monorepo
pre-commit:
parallel: true
jobs:
- run: yarn eslint {staged_files}
glob: "*.{js,ts,jsx,tsx}"
- name: rubocop
glob: "*.rb"
exclude:
- config/application.rb
- config/routes.rb
run: bundle exec rubocop --force-exclusion {all_files}
- name: govet
files: git ls-files -m
glob: "*.go"
run: go vet {files}
- script: "hello.js"
runner: node
- script: "hello.go"
runner: go run
實戰:Monorepo 前端專案設定
以下是實際在用的 lefthook.yml,以 pnpm monorepo + Vue 為主,commit 前自動格式化與 lint,merge 後自動安裝依賴。
完整設定
# EXAMPLE USAGE:
#
# Refer for explanation to following link:
# https://lefthook.dev/configuration/
#
# pre-push:
# jobs:
# - name: packages audit
# tags:
# - frontend
# - security
# run: yarn audit
#
# - name: gems audit
# tags:
# - backend
# - security
# run: bundle audit
#
# pre-commit:
# parallel: true
# jobs:
# - run: yarn eslint {staged_files}
# glob: "*.{js,ts,jsx,tsx}"
#
# - name: rubocop
# glob: "*.rb"
# exclude:
# - config/application.rb
# - config/routes.rb
# run: bundle exec rubocop --force-exclusion {all_files}
#
# - name: govet
# files: git ls-files -m
# glob: "*.go"
# run: go vet {files}
#
# - script: "hello.js"
# runner: node
#
# - script: "hello.go"
# runner: go run
# Hooks get a minimal PATH; scripts/lefthook-rc.sh loads common Node managers when present.
# For custom setups, copy lefthook-local.yml.example → lefthook-local.yml (gitignored).
rc: scripts/lefthook-rc.sh
pre-commit:
parallel: true
commands:
code-workspace:
run: corepack pnpm vsh code-workspace --auto-commit
lint-vue:
run: corepack pnpm oxfmt {staged_files} && corepack pnpm oxlint --fix --no-error-on-unmatched-pattern {staged_files} && corepack pnpm eslint --cache --fix {staged_files} && corepack pnpm stylelint --fix --allow-empty-input {staged_files}
glob: '*.vue'
lint-js:
run: corepack pnpm oxfmt {staged_files} && corepack pnpm oxlint --fix --no-error-on-unmatched-pattern {staged_files} && corepack pnpm eslint --cache --fix {staged_files}
glob: '*.{js,jsx,ts,tsx}'
exclude:
- 'apps/portal/src/protos-ts/**'
lint-style:
run: corepack pnpm oxfmt {staged_files} && corepack pnpm stylelint --fix --allow-empty-input {staged_files}
glob: '*.{scss,less,styl,html,vue,css}'
lint-package:
run: corepack pnpm oxfmt {staged_files}
glob: 'package.json'
lint-json:
run: corepack pnpm oxfmt {staged_files}
glob: '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}'
post-merge:
commands:
install:
run: corepack pnpm install
# commit-msg:
# commands:
# commitlint:
# run: pnpm exec commitlint --edit $1
設定逐項說明
rc: scripts/lefthook-rc.sh
Git hook 執行時的 PATH 很精簡,常常找不到 node、pnpm、nvm 等。rc 會在每個 hook 執行前 source 這支 script,把常見 Node 版本管理器的路徑載入。若你有特殊環境,可複製 lefthook-local.yml.example 做本地調整。
pre-commit(parallel: true)
所有 command 會同時執行,縮短 commit 等待時間。各 command 依 glob 只處理自己負責的 staged 檔案,互不影響。
| Command | 負責檔案 | 做了什麼 |
|---|---|---|
code-workspace | 無 glob(每次都跑) | vsh code-workspace --auto-commit:維護 VS Code workspace 設定並自動 commit |
lint-vue | *.vue | oxfmt → oxlint --fix → eslint --fix → stylelint --fix |
lint-js | *.{js,jsx,ts,tsx} | oxfmt → oxlint --fix → eslint --fix;排除自動產生的 protos-ts |
lint-style | *.{scss,less,styl,html,vue,css} | oxfmt → stylelint --fix |
lint-package | package.json | oxfmt 格式化 |
lint-json | 其他 json / rc 檔 | oxfmt;glob 用進階 pattern 排除 package.json 和 browserslist rc |
幾個設計重點:
corepack pnpm:透過 corepack 確保使用專案指定的 pnpm 版本,避免團隊環境不一致。- 工具鏈順序:先
oxfmt(格式化)→oxlint(快速 lint)→eslint(深度檢查)→stylelint(樣式),前面 fix 過的內容後面工具會接著檢查。 --no-error-on-unmatched-pattern:沒有符合檔案時不報錯,避免空跑失敗。--allow-empty-input:stylelint 在沒有輸入檔時不會 exit 1。exclude: apps/portal/src/protos-ts/**:protobuf 產生的 TS 不跑 eslint,避免大量自動產生碼被誤改。
post-merge
git pull 或 git merge 完成後自動執行 corepack pnpm install,確保依賴與 lockfile 同步。切換有不同 package.json 的分支時特別實用。
註解掉的 commit-msg
若團隊要強制 Conventional Commits,可取消註解,用 commitlint 驗證 $1(commit message 檔案路徑):
commit-msg:
commands:
commitlint:
run: pnpm exec commitlint --edit $1
檔案頂部註解範例
註解區塊是官方風格的參考模板,展示 pre-push + jobs + tags、多語言 lint、以及 script / runner 寫法。實際專案目前未啟用,但可作為擴充起點(例如加上 pre-push 跑 audit 或測試)。
這份設定的執行時序
與 Husky 的對照
若你目前使用 husky,遷移思路如下:
pnpm add -D lefthook,移除husky與lint-staged(若不再需要)- 把
.husky/pre-commit裡的指令改寫進lefthook.yml lefthook install,刪除或停用.husky/目錄package.json的prepare從husky install改為lefthook install
Husky 適合簡單、純 Node 的 hook;專案變大、要多語言或多任務平行時,Lefthook 通常更好維護。
常用 CLI 速查
lefthook install # 安裝 / 更新 git hooks
lefthook run pre-commit # 手動跑 pre-commit
lefthook validate # 驗證 yml 語法
lefthook dump # 輸出合併後的完整設定(含 extends / remote)
LEFTHOOK=0 git commit # 跳過所有 lefthook hooks