問題:CI 失敗的等待循環
你:push → CI 排隊 5min → lint fail → 切回來改 → push → CI 排隊...
↑
這是 CI 失敗修 lint 的平均次數:2-3 次
遠端 CI 是為了保護共享分支,不是為了讓你知道漏了分號。漏分號這種事在 push 之前就能檢查。
git hook 的層次
| Hook | 時機 | 適合做什麼 |
|---|---|---|
pre-commit | git commit 執行前 | Lint、Format 檢查、Secret 掃描 |
commit-msg | commit message 寫完後 | Conventional Commits 格式驗證 |
pre-push | git push 執行前 | 跑測試、Typecheck(比 pre-commit 重,適合非每次 commit 都跑的任務) |
工具選擇
lint-staged + husky(JS 生態系最常見)
lint-staged 的核心優勢:只對 staged 的檔案跑 lint,不跑整個 repo——大 repo 每次 commit 跑全量 lint 會很慢。
npm install --save-dev husky lint-staged
npx husky initpackage.json:
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{js,jsx}": ["eslint --fix"],
"*.{md,json,yaml}": ["prettier --write"]
}
}.husky/pre-commit:
npx lint-stagedlefthook(多語言、更快)
lefthook 用 YAML 設定,支援並行執行多個 hook:
# lefthook.yml
pre-commit:
parallel: true
commands:
lint:
glob: "*.{ts,tsx,js}"
run: npx eslint {staged_files}
format:
glob: "*.{ts,tsx,js,json,md}"
run: npx prettier --check {staged_files}
typecheck:
run: npx tsc --noEmit
commit-msg:
commands:
conventional:
run: npx commitlint --edit {1}pre-commit.com(Python 生態系,跨語言)
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.0
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: detect-private-key # 防止 commit private key
- id: check-added-large-files # 防止 commit 大檔案
- id: end-of-file-fixerpip install pre-commit
pre-commit install # 安裝 hook 到 .git/hooks/Pre-push:跑測試
Pre-commit 跑 lint 夠快(< 5 秒),但跑完整測試可能要 30 秒以上,每次 commit 都跑會很煩。
建議把測試移到 pre-push,只在 push 前跑一次:
# .husky/pre-push
npm run test -- --passWithNoTests
npm run typecheck或用 lefthook:
pre-push:
commands:
tests:
run: npm test
typecheck:
run: npx tsc --noEmitSecret 掃描
這是最重要的 pre-commit check——防止把 API key、password、private key 推上去:
# 用 detect-secrets(Python)
pip install detect-secrets
detect-secrets scan > .secrets.baseline或在 pre-commit-config.yaml 加 detect-private-key hook。
重要:如果 secret 已經 commit 了,即使刪掉也在 git history 裡。發現後要立刻 revoke key,不能只刪檔案了事。
速度原則
Pre-commit 如果太慢,開發者會加 --no-verify 繞過——徹底失去守門的效果。
保持 pre-commit < 5 秒的做法:
- 只對 staged 檔案跑(lint-staged / lefthook 的
{staged_files}) - Lint 和 format 並行執行
- 把慢的 typecheck 移到 pre-push
- 測試只跑受影響的範圍(Jest 的
--findRelatedTests)