♻️ refactor(ci): 重构为 Prepare 上游 + workflow_run 下游消费者模型

- 新增 prepare.yml:统一检查/安装公共工具并集中 fetch 到共享 bare 仓库
- 统计与发布改为消费成功的 Prepare workflow_run,各自维护 stats/release worktree
- 四个 workflow name 统一为带 emoji 的中文,同步 workflow_run 触发引用
- 系统信息 workflow 保持独立直接触发
- 删除 tests/ 手动验证脚本
This commit is contained in:
csh
2026-07-20 16:21:06 +08:00
parent 610c2b3e74
commit 50efc4bbfa
10 changed files with 528 additions and 977 deletions
+185 -95
View File
@@ -1,9 +1,10 @@
name: 📦 自动发布
on:
push:
tags:
- "[0-9]*"
workflow_run:
workflows: ["🧰 准备环境"]
types:
- completed
concurrency:
group: release-${{ github.repository }}
@@ -16,7 +17,7 @@ env:
# ===== Token 配置 =====
# Gitea/GitHub 访问令牌
# 用途:
# 1. Git 操作:克隆仓库、推送提交
# 1. Git 操作:推送提交(仓库读取由 Prepare 完成)
# 2. API 调用:创建 Release、上传附件
# 权限要求:
# - repo (仓库完整访问权限)
@@ -26,7 +27,7 @@ env:
#
# 💡 提示:同一个 token 可用于所有操作
# 如需分离权限,可配置两个不同的 token:
# - GIT_TOKEN: 用于 git clone/push
# - GIT_TOKEN: 用于 git push
# - API_TOKEN: 用于 API 调用
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
GIT_CONFIG_COUNT: "3"
@@ -38,7 +39,7 @@ env:
GIT_CONFIG_VALUE_2: "never"
# ===== 工作区配置 =====
WORKSPACE_DIR: "/data/workspace"
WORKSPACE_ROOT: "/data/workspace"
WORKSPACE_SLOT: "release"
# ===== 分支配置 =====
@@ -134,10 +135,100 @@ env:
# 4. CHANGELOG内容是叠加而非覆盖
jobs:
route-release:
if: ${{ github.event.workflow_run.conclusion == 'success' && contains(github.event.workflow_run.path, '@refs/tags/') }}
runs-on: standard-ubuntu-22
permissions:
contents: read
outputs:
should_release: ${{ steps.route.outputs.should_release }}
version: ${{ steps.route.outputs.version }}
tag_ref: ${{ steps.route.outputs.tag_ref }}
source_sha: ${{ steps.route.outputs.source_sha }}
env:
UPSTREAM_PATH: ${{ github.event.workflow_run.path }}
HEAD_SHA: ${{ github.event.workflow_run.head_sha }}
steps:
- name: 🧭 恢复并验证 Tag
id: route
shell: bash
run: |
set -euo pipefail
if ! command -v git >/dev/null 2>&1; then
echo "Prepare 成功后仍缺少发布路由所需命令: git" >&2
exit 1
fi
echo "should_release=false" >> "$GITHUB_OUTPUT"
case "$HEAD_SHA" in
''|*[!0-9a-fA-F]*)
echo "无效的上游提交 SHA: $HEAD_SHA" >&2
exit 1
;;
esac
FULL_REF="${UPSTREAM_PATH##*@}"
case "$FULL_REF" in
refs/tags/*) ;;
*)
echo "workflow_run.path 未包含有效 Tag ref: $UPSTREAM_PATH" >&2
exit 1
;;
esac
VERSION="${FULL_REF#refs/tags/}"
case "$VERSION" in
[0-9]*) ;;
*)
echo "跳过非版本 Tag: $VERSION"
exit 0
;;
esac
if ! git check-ref-format "$FULL_REF"; then
echo "数字开头的 Tag ref 格式无效: $FULL_REF" >&2
exit 1
fi
REPO_NAME="${{ github.event.repository.name }}"
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
if [ ! -d "$REPOSITORY_DIR" ] || \
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
exit 1
fi
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
echo "共享仓库中不存在上游提交: $HEAD_SHA" >&2
exit 1
fi
if ! tag_sha=$(git --git-dir="$REPOSITORY_DIR" rev-parse "${FULL_REF}^{commit}" 2>/dev/null); then
echo "共享仓库中不存在可解析的 Tag: $FULL_REF" >&2
exit 1
fi
if [ "$tag_sha" != "$HEAD_SHA" ]; then
echo "Tag/SHA 不匹配: $FULL_REF -> $tag_sha,上游为 $HEAD_SHA" >&2
exit 1
fi
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag_ref=$FULL_REF" >> "$GITHUB_OUTPUT"
echo "source_sha=$HEAD_SHA" >> "$GITHUB_OUTPUT"
echo "发布路由通过: $VERSION @ $HEAD_SHA"
changelog-and-release:
runs-on: ubuntu-22.04
needs: route-release
if: ${{ needs.route-release.outputs.should_release == 'true' }}
runs-on: standard-ubuntu-22
permissions:
contents: write
env:
VERSION: ${{ needs.route-release.outputs.version }}
TAG_REF: ${{ needs.route-release.outputs.tag_ref }}
SOURCE_SHA: ${{ needs.route-release.outputs.source_sha }}
steps:
- name: ⚙️ 加载配置
@@ -148,7 +239,7 @@ jobs:
echo "======================================"
# 从环境变量读取配置
VERSION="${{ github.ref_name }}"
VERSION="${{ env.VERSION }}"
KEYWORDS="${{ env.PRERELEASE_KEYWORDS }}"
STRIP_REGEX="-($KEYWORDS)[0-9]*$" # 用于 sed 删除后缀
@@ -235,78 +326,91 @@ jobs:
echo "======================================"
echo ""
- name: 📥 准备隔离仓库
id: clone
- name: 📥 对齐发布 Worktree
id: prepare_worktree
shell: bash
run: |
set -euo pipefail
echo "======================================"
echo "🚀 开始准备仓库"
echo "======================================"
REPO_SLUG="${{ github.repository }}"
for command in git python3 curl flock realpath; do
if ! command -v "$command" >/dev/null 2>&1; then
echo "Prepare 成功后仍缺少发布所需命令: $command" >&2
exit 1
fi
done
REPO_NAME="${{ github.event.repository.name }}"
HEAD_SHA="${{ env.SOURCE_SHA }}"
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
WORKSPACE_DIR="${{ env.WORKSPACE_DIR }}"
WORKSPACE_SLOT="${{ env.WORKSPACE_SLOT }}"
REPO_DIR="${WORKSPACE_DIR}/${REPO_NAME}/${WORKSPACE_SLOT}"
REMOTE_URL="${GITHUB_SERVER_URL}/${REPO_SLUG}.git"
REMOTE_REF="refs/remotes/origin/${MAIN_BRANCH}"
REPOSITORY_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/repository.git"
WORKTREE_DIR="${WORKSPACE_ROOT}/${REPO_NAME}/worktrees/release"
WORKTREE_LOCK="${WORKSPACE_ROOT}/${REPO_NAME}/worktree-admin.lock"
MAIN_REMOTE_REF="refs/remotes/origin/${MAIN_BRANCH}"
if [ -z "$MAIN_BRANCH" ]; then
echo "❌ 错误: 未配置主分支"
exit 1
fi
case "$REMOTE_URL" in
https://*) ;;
*)
echo "❌ 拒绝通过非 HTTPS remote 使用 WORKFLOW token" >&2
case "$HEAD_SHA" in
''|*[!0-9a-fA-F]*)
echo "无效的路由提交 SHA: $HEAD_SHA" >&2
exit 1
;;
esac
if [ -e "$REPO_DIR" ] && [ ! -d "$REPO_DIR/.git" ]; then
echo "❌ Slot 已存在但不是 Git 仓库: $REPO_DIR" >&2
if [ -z "$MAIN_BRANCH" ]; then
echo "未配置 MAIN_BRANCH" >&2
exit 1
fi
if [ ! -d "$REPOSITORY_DIR" ] || \
[ "$(git --git-dir="$REPOSITORY_DIR" rev-parse --is-bare-repository 2>/dev/null)" != "true" ]; then
echo "Prepare 未创建有效共享 bare 仓库: $REPOSITORY_DIR" >&2
exit 1
fi
if ! git --git-dir="$REPOSITORY_DIR" cat-file -e "${HEAD_SHA}^{commit}"; then
echo "共享仓库中不存在发布提交: $HEAD_SHA" >&2
exit 1
fi
if ! git --git-dir="$REPOSITORY_DIR" show-ref --verify --quiet "$MAIN_REMOTE_REF"; then
echo "Prepare 未 fetch 配置的主分支: $MAIN_REMOTE_REF" >&2
exit 1
fi
echo "📁 仓库名称: $REPO_NAME"
echo "📦 工作区根目录: $WORKSPACE_DIR"
echo "🎯 Workflow 职责: $WORKSPACE_SLOT"
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
echo ""
if [ ! -f "$WORKTREE_DIR/.git" ]; then
exec 9>"$WORKTREE_LOCK"
flock 9
if [ ! -d "$REPO_DIR/.git" ]; then
mkdir -p "$(dirname "$REPO_DIR")"
git clone --no-checkout "$REMOTE_URL" "$REPO_DIR"
else
git -C "$REPO_DIR" remote set-url origin "$REMOTE_URL"
if [ ! -f "$WORKTREE_DIR/.git" ]; then
if [ -e "$WORKTREE_DIR" ]; then
echo "发布 Worktree 路径已存在但不是 linked worktree: $WORKTREE_DIR" >&2
exit 1
fi
mkdir -p "$(dirname "$WORKTREE_DIR")"
git --git-dir="$REPOSITORY_DIR" worktree add --detach "$WORKTREE_DIR" "$HEAD_SHA"
fi
flock -u 9
fi
git -C "$REPO_DIR" fetch \
--prune --prune-tags --tags --force origin \
'+refs/heads/*:refs/remotes/origin/*'
if ! git -C "$REPO_DIR" show-ref --verify --quiet "$REMOTE_REF"; then
echo "❌ 配置的远程分支不存在: $MAIN_BRANCH" >&2
common_dir=$(git -C "$WORKTREE_DIR" rev-parse --path-format=absolute --git-common-dir)
if [ "$common_dir" != "$(realpath "$REPOSITORY_DIR")" ]; then
echo "发布 Worktree 不属于共享仓库: $WORKTREE_DIR" >&2
exit 1
fi
git -C "$REPO_DIR" checkout --force -B "$MAIN_BRANCH" "$REMOTE_REF"
git -C "$REPO_DIR" reset --hard "$REMOTE_REF"
git -C "$REPO_DIR" clean -ffdx
echo "✓ 仓库目录: $REPO_DIR"
echo ""
git -C "$WORKTREE_DIR" checkout --detach --force "$HEAD_SHA"
git -C "$WORKTREE_DIR" reset --hard "$HEAD_SHA"
git -C "$WORKTREE_DIR" clean -ffdx
cd "$REPO_DIR"
echo "✓ 主分支 '$MAIN_BRANCH' 已准备并清理"
actual_sha=$(git -C "$WORKTREE_DIR" rev-parse HEAD)
if [ "$actual_sha" != "$HEAD_SHA" ]; then
echo "发布 Worktree 提交不匹配: 期望 $HEAD_SHA,实际 $actual_sha" >&2
exit 1
fi
# 导出到环境变量供后续步骤使用
echo "REPO_DIR=$REPO_DIR" >> "$GITHUB_ENV"
git -C "$WORKTREE_DIR" config user.name "${{ env.GIT_USER_NAME }}"
git -C "$WORKTREE_DIR" config user.email "${{ env.GIT_USER_EMAIL }}"
echo "REPOSITORY_DIR=$REPOSITORY_DIR" >> "$GITHUB_ENV"
echo "REPO_DIR=$WORKTREE_DIR" >> "$GITHUB_ENV"
echo "REPO_NAME=$REPO_NAME" >> "$GITHUB_ENV"
echo "WORKSPACE_SLOT=$WORKSPACE_SLOT" >> "$GITHUB_ENV"
echo "MAIN_BRANCH=$MAIN_BRANCH" >> "$GITHUB_ENV"
echo ""
echo "✅ 仓库准备完成"
echo "======================================"
echo ""
echo "MAIN_REMOTE_REF=$MAIN_REMOTE_REF" >> "$GITHUB_ENV"
echo "发布 Worktree: $WORKTREE_DIR @ $actual_sha"
- name: 🤖 检查 Bot 提交
id: check_bot
@@ -317,7 +421,7 @@ jobs:
cd ${{ env.REPO_DIR }}
COMMIT_MSG=$(git log -1 --pretty=%B ${{ github.ref_name }})
COMMIT_MSG=$(git log -1 --pretty=%B "${{ env.SOURCE_SHA }}")
echo "📝 Tag commit message:"
echo "$COMMIT_MSG"
echo ""
@@ -344,7 +448,7 @@ jobs:
cd ${{ env.REPO_DIR }}
VERSION="${{ github.ref_name }}"
VERSION="${{ env.VERSION }}"
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
echo "🏷️ Tag: $VERSION"
@@ -607,8 +711,12 @@ jobs:
echo ""
echo "💾 写入 CHANGELOG.md..."
# 切换到主分支
git checkout ${{ env.MAIN_BRANCH }}
# 从 Prepare 已 fetch 的远端引用准备主分支,再写入 CHANGELOG
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
MAIN_REMOTE_REF="${{ env.MAIN_REMOTE_REF }}"
git -C "${{ env.REPO_DIR }}" checkout --force -B "$MAIN_BRANCH" "$MAIN_REMOTE_REF"
git -C "${{ env.REPO_DIR }}" reset --hard "$MAIN_REMOTE_REF"
git -C "${{ env.REPO_DIR }}" clean -ffdx
# ============================================
# 更新或创建 CHANGELOG.md
@@ -819,9 +927,6 @@ jobs:
cd ${{ env.REPO_DIR }}
# 确保使用最新的 CHANGELOG
git -C "${{ env.REPO_DIR }}" pull --ff-only origin "${{ env.MAIN_BRANCH }}"
python3 << 'PYSCRIPT'
import re
@@ -871,41 +976,26 @@ jobs:
echo "📄 Draft: ${{ env.RELEASE_IS_DRAFT }}"
echo ""
# 转换 bash 布尔值为 Python 布尔值
if [ "${{ env.RELEASE_IS_DRAFT }}" = "true" ]; then
PY_DRAFT="True"
else
PY_DRAFT="False"
fi
if [ "${{ env.RELEASE_IS_PRERELEASE }}" = "true" ]; then
PY_PRERELEASE="True"
else
PY_PRERELEASE="False"
fi
echo "🔄 转换布尔值: draft=${{ env.RELEASE_IS_DRAFT }} → $PY_DRAFT, prerelease=${{ env.RELEASE_IS_PRERELEASE }} → $PY_PRERELEASE"
echo ""
python3 << EOF
python3 << 'PYSCRIPT'
import json
import os
with open('/tmp/release-body.txt', 'r') as f:
body = f.read()
with open('/tmp/release-body.txt', 'r', encoding='utf-8') as release_body_file:
body = release_body_file.read()
payload = {
"tag_name": "${{ github.ref_name }}",
"name": "${{ env.RELEASE_TITLE_PROCESSED }}",
"tag_name": os.environ["VERSION"],
"name": os.environ["RELEASE_TITLE_PROCESSED"],
"body": body,
"draft": $PY_DRAFT,
"prerelease": $PY_PRERELEASE
"draft": os.environ["RELEASE_IS_DRAFT"] == "true",
"prerelease": os.environ["RELEASE_IS_PRERELEASE"] == "true",
}
with open('/tmp/payload.json', 'w', encoding='utf-8') as f:
json.dump(payload, f, ensure_ascii=False, indent=2)
with open('/tmp/payload.json', 'w', encoding='utf-8') as payload_file:
json.dump(payload, payload_file, ensure_ascii=False, indent=2)
print("✓ Payload prepared")
EOF
PYSCRIPT
echo "🌐 发送 API 请求..."
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \
@@ -1010,7 +1100,7 @@ jobs:
| 项目 | 结果 |
|------|------|
| 🏷️ Tag 版本 | **`${{ github.ref_name }}`** |
| 🏷️ Tag 版本 | **`${{ env.VERSION }}`** |
| 📝 CHANGELOG 版本 | **`${{ env.CHANGELOG_VERSION }}`** |
| 🌿 主分支 | **`${{ env.MAIN_BRANCH }}`** |
| 🌐 Gitea 服务器 | **`${{ env.GITEA_SERVER }}`** |
@@ -1113,7 +1203,7 @@ jobs:
echo " 原因: 检测到 [skip ci] 标记,防止无限循环"
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
echo "📊 执行结果:"
echo " - Tag: ${{ github.ref_name }}"
echo " - Tag: ${{ env.VERSION }}"
echo " - CHANGELOG 版本: ${{ env.CHANGELOG_VERSION }}"
echo " - 主分支: ${{ env.MAIN_BRANCH }}"
echo " - Release 标题: ${{ env.RELEASE_TITLE_PROCESSED }}"