儲存庫自動化設定
在任何儲存庫中建立完整的 conventional-commits + release-please + CI 自動化堆疊。適用於空白儲存庫和已有內容的儲存庫。
概述
左移: 儘早發現問題。在 PR 階段發現錯誤的 commit 訊息只需幾秒鐘;同樣的訊息污染了發行說明則會損害可信度。將驗證上游移動 — 在 PR 標題強制執行 commit 格式,在 CI 中驗證配置,在問題進入 main 分支之前將其捕獲。
越快越安全: 較小的批次和更頻繁的發布可以降低風險。包含 3 個變更的發布比包含 30 個變更的發布更容易除錯。自動化發布可以建立對發布流程本身的信心。
此技能設定了強制機制:PR 標題驗證可確保每次合併到 main 分支都會產生一個有意義、可解析的 commit。Release-please 將這些 commit 轉換為帶有變更記錄的自動化發布。CI 在合併前捕獲配置和程式碼問題。
使用時機
- 正在開始一個新的儲存庫,需要從第一天起就進行發布自動化
- 現有儲存庫沒有自動版本控制或變更記錄產生
- 從手動發布流程或其他發布工具遷移
- 儲存庫需要 PR 標題驗證和自動標籤
不應使用
- 儲存庫已配置 release-please 或同等工具(請先檢查)
- 單一儲存庫使用不相容的發布工具(例如,Lerna 與獨立版本控制已正常運作)
- 儲存庫是分支,上游控制發布流程
流程
步驟 1:收集配置
在產生任何檔案之前,向使用者詢問下列值:
| 參數 | 預設值 | 說明 |
|---|---|---|
PROJECT_NAME | (必填) | 用於文件的人類可讀專案名稱 |
INITIAL_VERSION | 0.1.0 | 起始語意版本號 |
RELEASE_TYPE | simple | simple (單一套件) 或 node / python / 等 |
REPO_LAYOUT | single | single (一個套件在根目錄) 或 monorepo (多個套件) |
PACKAGE_PATHS | . | 對於 monorepo:逗號分隔的路徑,如 packages/api,packages/ui |
DEPENDABOT_ECOSYSTEMS | github-actions | 逗號分隔:github-actions、npm、pip、cargo 等 |
DEFAULT_BRANCH | main | 觸發發布的分支 |
自動偵測正確的 RELEASE_TYPE:
- 存在
package.json→ 建議node - 存在
setup.py或pyproject.toml→ 建議python - 存在
Cargo.toml→ 建議rust - 以上皆非 → 建議
simple
步驟 2:偵測現有檔案
在建立任何內容之前,請檢查衝突:
for f in .github/workflows/pull-request.yml .github/workflows/main-branch.yml \
.github/release-please-config.json .release-please-manifest.json \
.github/dependabot.yml .github/pull_request_template.md docs/DEVELOPMENT.md; do
[[ -f "$f" ]] && echo "EXISTS: $f"
done
如果檔案存在,請顯示將會變更的差異,並在覆寫前詢問。請不要默默地覆寫 — 現有檔案可能包含專案特定的自訂設定。
步驟 3:建立配置檔案
按此順序建立檔案(依賴關係由上而下):
3a. .github/release-please-config.json
單一套件:
{
"release-type": "{{RELEASE_TYPE}}",
"bump-minor-pre-major": true,
"bump-patch-for-minor-pre-major": false,
"initial-version": "{{INITIAL_VERSION}}",
"packages": {
".": {}
}
}
Monorepo: 將 packages 替換為每個路徑一個條目:
{
"packages": {
"packages/api": { "release-type": "node" },
"packages/ui": { "release-type": "node" }
}
}
每個 monorepo 套件都有自己的 release-type — 如果不同,請逐套件詢問使用者。
3b. .release-please-manifest.json
單一套件:
{
".": "{{INITIAL_VERSION}}"
}
Monorepo: 每個套件路徑一個條目,各自擁有自己的版本。
3c. .github/workflows/pull-request.yml
name: Pull Request Validation
on:
pull_request:
types: [opened, edited, synchronize, reopened]
permissions:
pull-requests: write
contents: read
statuses: write
jobs:
validate-pr-title:
name: Validate PR Title
runs-on: ubuntu-latest
steps:
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
chore
refactor
test
ci
requireScope: false
subjectPattern: ^[a-z].+$
subjectPatternError: |
The subject "{subject}" found in the pull request title "{title}"
didn't match the configured pattern. Please ensure that the subject
starts with a lowercase character (per conventional commits).
auto-label:
name: Auto Label PR
runs-on: ubuntu-latest
steps:
- name: Label based on PR title
uses: actions/github-script@v8
with:
script: |
const title = context.payload.pull_request.title;
const labelMap = {
'feat': 'enhancement',
'fix': 'bug',
'docs': 'documentation',
'chore': 'chore',
'refactor': 'refactor',
'test': 'test',
'ci': 'ci'
};
for (const [type, label] of Object.entries(labelMap)) {
if (title.startsWith(`${type}:`) || title.startsWith(`${type}(`)) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
labels: [label]
});
break;
}
}
no-ai-attribution:
name: No AI Attribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Scan commit messages and PR body
env:
PR_BODY: ${{ github.event.pull_request.body }}
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# Trailer/footer forms only: a commit that merely mentions Claude or
# Anthropic in prose is fine. Human Co-authored-by trailers are fine.
PAT='^[[:space:]]*Co-authored-by:.*(Claude|Anthropic|noreply@anthropic)|Generated with \[?(Claude|Anthropic)'
fail=0
if git log --format='%B' "$BASE_SHA..$HEAD_SHA" | grep -Eiq "$PAT"; then
echo "::error::AI attribution found in a commit message on this branch"
fail=1
fi
if printf '%s' "$PR_BODY" | grep -Eiq "$PAT"; then
echo "::error::AI attribution found in the PR description"
fail=1
fi
[ "$fail" -eq 0 ] && echo "No AI attribution found."
exit $fail
ci-checks:
name: CI Checks
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v6
- name: Validate YAML syntax
run: |
sudo apt-get update && sudo apt-get install -y yamllint
find . -name "*.yml" -o -name "*.yaml" | while read file; do
echo "Checking $file"
yamllint -d "{extends: default, rules: {line-length: disable}}" "$file" || true
done
No AI Attribution 從第一天起就啟用封鎖 — 它沒有誤報的驗證範圍(它比對的是尾部/頁腳形式,而非散文),因此無需經過下方的畢業路徑。
CI 檢查畢業路徑:
- 使用
continue-on-error: true部署(資訊性,非封鎖性) - 驗證檢查通過 3-5 個實際 PR
- 移除
continue-on-error並新增至分支保護的必要檢查 - 隨著程式碼庫增長,新增專案特定的檢查(lint、測試、建置)
任何關卡都不能跳過。 如果 lint 失敗,請修正 lint — 不要停用規則。如果測試失敗,請修正程式碼 — 不要跳過測試。當 CI 失敗時,請將失敗輸出提供給您的 agent 並在再次推送前修正。
3d. .github/workflows/main-branch.yml
name: Release Automation
on:
push:
branches:
- {{DEFAULT_BRANCH}}
permissions:
contents: write
pull-requests: write
jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
- uses: googleapis/release-please-action@v4
id: release
with:
config-file: .github/release-please-config.json
manifest-file: .release-please-manifest.json
update-major-tag:
name: Update Major Version Tag
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.release-please.outputs.tag_name }}
- name: Update major version tag
env:
TAG_NAME: ${{ needs.release-please.outputs.tag_name }}
run: |
MAJOR_TAG=$(echo "$TAG_NAME" | sed 's/^\(v[0-9]*\).*/\1/')
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -fa "$MAJOR_TAG" -m "Update $MAJOR_TAG tag to $TAG_NAME"
git push origin "$MAJOR_TAG" --force
3e. .github/dependabot.yml
為每個生態系統產生一個 updates 條目:
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
labels:
- "dependencies"
commit-message:
prefix: "chore"
include: "scope"
commit-message 配置可確保 Dependabot 的 PR 自動符合 Conventional Commits 格式(例如:chore(deps): bump actions/checkout from v5 to v6)。
3f. .github/pull_request_template.md
## 變更內容
[詳細描述變更 - 這會進入變更記錄]
## 原因
[變更的背景和動機]
## 檢查清單
- [ ] PR 標題遵循 Conventional Commits 格式 (`type: description`)
- [ ] 說明解釋了什麼和為什麼(不僅僅是什麼)
- [ ] 程式碼、日誌或版本控制中沒有機密
- [ ] 測試在本地通過
「原因」部分至關重要 — 只說明什麼變更的 PR 描述與 diff 重複。為什麼提供了讓未來 git-blame 變得有用的背景。
3g. .gitignore (如果缺失)
檢查 .gitignore 是否存在。如果不存在,請建立適合專案的檔案:
# 依賴
node_modules/
# 建置輸出
dist/
build/
.next/
# 環境
.env
.env.local
*.pem
# IDE
.idea/
# 作業系統
.DS_Store
Thumbs.db
根據偵測到的堆疊進行調整。永遠不要提交 node_modules/、.env 或建置產物。
3h. docs/DEVELOPMENT.md
產生一份開發指南,涵蓋以下內容:
- 分支工作流程 — 基於主幹的開發:短期功能分支(1-3 天),快速合併回來。分支命名:
feature/<desc>、fix/<desc>、chore/<desc>。 - Commit 紀律 — 在分支內自由提交(任何格式)。只有 PR 標題重要 — 它會成為 squash merge 到 main 的 commit 訊息,並出現在變更記錄中。
- PR 標題格式 — 類型表:
feat、fix、docs、chore、refactor、test、ci。重大變更:feat!:或主體中的BREAKING CHANGE:。 - 變更規模 — 每個 PR 目標約 100 行。~300 行可接受(適用於單一邏輯變更)。~1000+ 行必須拆分。將重構與功能開發分開 — 它們是不同的變更。
- PR 描述 — 第一行:簡短、祈使、獨立。主體:變更的內容和原因。反模式:「修復錯誤」、「更新檔案」、「第一階段」。
- 自動化檢查 — PR 標題驗證(必填,阻止合併)、自動標籤(資訊性)、CI 檢查(初始非封鎖,逐漸升級為必填)。
- 發布流程 — release-please 建立發布 PR → 合併 → GitHub 發布 + 標籤 + 變更記錄。版本進展:pre-1.0(
feat→ minor,fix→ patch)vs post-1.0(feat→ minor,fix→ patch,BREAKING CHANGE→ major)。 - 疑難排解 — 常見問題:PR 標題驗證失敗(編輯標題)、CI 檢查失敗、合併被阻止(檢查必要的狀態)。
在標題中使用 {{PROJECT_NAME}}。
步驟 4:分支保護建議
在檔案建立後,將這些作為手動步驟呈現(不會自動套用):
建議的 GitHub 分支保護設定 for {{DEFAULT_BRANCH}}:
✓ 合併前需要 PR
✓ 需要狀態檢查:「Validate PR Title」
✓ 要求分支保持最新狀態
✓ 僅允許 Squash 合併
✓ 要求線性歷史
為什麼使用 Squash 合併: 分支歷史(WIP commit、修正、實驗)會保留在 PR 中。Main 獲得一個乾淨、有意義、線性的日誌,其中每個 commit 都是一個 Conventional Commit。這使得 release-please 和 git log --oneline 變得有用。
步驟 5:啟用工作流程權限
Release-please 需要透過 GitHub Actions 建立 pull request 的權限。這在新儲存庫中預設為停用,並會導致發布自動化工作流程失敗,顯示以下錯誤:
GitHub Actions is not permitted to create or approve pull requests.
指示使用者啟用此設定:
GitHub repo → Settings → Actions → General → Workflow permissions:
✓ 讀取和寫入權限(release-please 推送變更記錄 commit 所需)
✓ 允許 GitHub Actions 建立和核准 Pull Request(發布 PR 所需)
必須在首次推送到 {{DEFAULT_BRANCH}} 之前完成此步驟,否則 release-please 將在每次推送時失敗,直到啟用此設定。
步驟 6:驗證
# 驗證 YAML 語法
yamllint .github/workflows/*.yml .github/dependabot.yml
# 驗證 JSON
python3 -c "import json; ...
[為翻譯而截斷]"