♻️ 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:
@@ -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 }}"
|
||||
|
||||
Reference in New Issue
Block a user