actions-template/.gitea/workflows/changelog_and_release.yml

957 lines
35 KiB
YAML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: 📦 自动发布
on:
push:
tags:
- "[0-9]*"
# ==========================================
# 🔧 配置区域 - 根据你的项目修改
# ==========================================
env:
# ===== Token 配置 =====
# Gitea/GitHub 访问令牌
# 用途:
# 1. Git 操作:克隆仓库、推送提交
# 2. API 调用:创建 Release、上传附件
# 权限要求:
# - repo (仓库完整访问权限)
# - write:packages (可选,如需上传包)
# 配置位置:仓库 Settings > Secrets > Actions
# Secret 名称WORKFLOW (或其他自定义名称)
#
# 💡 提示:同一个 token 可用于所有操作
# 如需分离权限,可配置两个不同的 token
# - GIT_TOKEN: 用于 git clone/push
# - API_TOKEN: 用于 API 调用
ACCESS_TOKEN: ${{ secrets.WORKFLOW }}
# ===== 工作区配置 =====
# 完整克隆的工作目录 - 自建的runner可以复用工作区
WORKSPACE_DIR: "/home/workspace"
# ===== 分支配置 =====
# 主分支名称(用于推送 CHANGELOG 更新)
# 如果留空会自动检测main 或 master
MAIN_BRANCH: ""
# ===== 服务器配置 =====
# Gitea 服务器地址(用于生成头像链接和 API 调用)
GITEA_SERVER: "https://git.mytsl.cn"
# ===== CHANGELOG 配置 =====
# CHANGELOG 变更列表标题
CHANGELOG_SECTION_TITLE: "### :pencil: What's Changed"
# CHANGELOG 贡献者列表标题
CHANGELOG_CONTRIBUTORS_TITLE: "### :busts_in_silhouette: Contributors"
# CHANGELOG 版本号处理方式
# - "full": 使用完整 tag 名称2025.10.31-1-beta → ## :bookmark: 2025.10.31-1-beta
# - "strip": 去除 pre-release 后缀1.0.0-beta1 → ## :bookmark: 1.0.0
# ⚠️ 推荐使用 "strip" 模式以支持多个开发版本叠加到同一CHANGELOG区域
CHANGELOG_VERSION_MODE: "strip"
# ===== RELEASE 配置 =====
# Release 标题模板
# 可用变量: {version} - 将被替换为实际的版本号
RELEASE_TITLE: "{version}"
# Pre-release 配置
# - "false": 正式版本(会被标记为 latest
# - "true": 预发布版本(会被标记为 pre-release
# - "auto": 自动判断(根据 tag 名称中是否包含 alpha/beta/rc
RELEASE_PRERELEASE_MODE: "auto"
# Draft 配置(是否创建为草稿)
# - "true": 创建为草稿,不会立即发布
# - "false": 立即发布 Release
RELEASE_IS_DRAFT: "false"
# 额外要上传到 Release 的文件(空格分隔)
# 例如: "README.md LICENSE docs/guide.pdf"
ADDITIONAL_RELEASE_FILES: ""
# ===== Git 提交配置 =====
# Git 用户配置
GIT_USER_NAME: "github-actions[bot]"
GIT_USER_EMAIL: "github-actions[bot]@users.noreply.github.com"
# Git 提交消息模板
# 可用变量: {version} - 将被替换为实际的版本号
# [skip ci] 标记防止触发无限循环
COMMIT_MESSAGE: ":memo: Auto update CHANGELOG for {version} [skip ci]"
# ===== 提交过滤配置 =====
# 需要忽略的提交模式(支持正则表达式)
# 这些提交不会被添加到 CHANGELOG 中
# 使用 | 分隔多个模式
#
# ⚠️ 重要说明:
# - 使用 ^ 表示行首匹配,$ 表示行尾匹配
# - [skip ci] 和 [ci skip] 仅匹配行尾,避免误过滤其他提交
# - 如果要完全匹配某个字符串,使用 ^...$
IGNORE_PATTERNS: >-
^Merge |
^:memo: Auto update CHANGELOG|
\[skip ci\]\s*$|
\[ci skip\]\s*$|
^Bump version|
^Release v|
^chore: update config$|
^style: |
^chore: format|
^chore: lint|
^Initial commit$
# ========================================
# 📌 版本号规范说明 (SemVer)
# ========================================
#
# 格式: MAJOR.MINOR.PATCH[-prerelease]
#
# 开发版本示例:
# - 1.0.0-beta1, 1.0.0-beta2, 1.0.0-beta10 等
# - 1.2.0-alpha1, 1.2.0-rc1 等
#
# 正式版本示例:
# - 1.0.0 (首个稳定版PATCH必须从0开始)
# - 1.1.0 (新增功能,向后兼容)
# - 1.1.1 (Bug修复)
# - 2.0.0 (破坏性变更,不向后兼容)
#
# ⚠️ 重要规则:
# 1. 开发版本都以 .0-beta[N] 或 .0-alpha[N] 结尾
# 2. 正式发布必须从 .0 开始(如 1.0.0, 1.1.0, 2.0.0
# 3. 多个开发版本(如1.0.0-beta1, 1.0.0-beta2)会累加到同一个CHANGELOG区域(1.0.0)
# 4. CHANGELOG内容是叠加而非覆盖
jobs:
changelog-and-release:
runs-on: ubuntu-22.04
permissions:
contents: write
steps:
- name: ⚙️ 加载配置
id: config
run: |
echo "======================================"
echo "⚙️ 加载工作流配置"
echo "======================================"
# 从环境变量读取配置
VERSION="${{ github.ref_name }}"
# 处理 CHANGELOG 版本号
if [[ "${{ env.CHANGELOG_VERSION_MODE }}" == "strip" ]]; then
# 去除 pre-release 后缀(-beta, -rc1, -alpha 等)
CHANGELOG_VERSION=$(echo "$VERSION" | sed -E 's/-(alpha|beta|rc|pre|preview|dev|test)[0-9]*$//')
echo "📝 CHANGELOG version mode: strip"
echo " Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION"
else
# 使用完整 tag 名称
CHANGELOG_VERSION="$VERSION"
echo "📝 CHANGELOG version mode: full"
echo " Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION"
fi
# 处理 Release 标题
RELEASE_TITLE_PROCESSED="${{ env.RELEASE_TITLE }}"
RELEASE_TITLE_PROCESSED="${RELEASE_TITLE_PROCESSED//\{version\}/$VERSION}"
# 处理提交消息
COMMIT_MSG="${{ env.COMMIT_MESSAGE }}"
COMMIT_MSG="${COMMIT_MSG//\{version\}/$VERSION}"
# 智能判断 pre-release
if [[ "${{ env.RELEASE_PRERELEASE_MODE }}" == "auto" ]]; then
if [[ "$VERSION" =~ (alpha|beta|rc|pre|preview|dev|test) ]]; then
RELEASE_IS_PRERELEASE="true"
PRERELEASE_DETECTED="yes (auto-detected: $VERSION contains pre-release keyword)"
else
RELEASE_IS_PRERELEASE="false"
PRERELEASE_DETECTED="no (auto-detected: stable release)"
fi
else
RELEASE_IS_PRERELEASE="${{ env.RELEASE_PRERELEASE_MODE }}"
PRERELEASE_DETECTED="${{ env.RELEASE_PRERELEASE_MODE }} (manually configured)"
fi
# 导出处理后的配置到 GitHub 环境变量
echo "CHANGELOG_VERSION=$CHANGELOG_VERSION" >> $GITHUB_ENV
echo "RELEASE_TITLE_PROCESSED=$RELEASE_TITLE_PROCESSED" >> $GITHUB_ENV
echo "RELEASE_IS_PRERELEASE=$RELEASE_IS_PRERELEASE" >> $GITHUB_ENV
echo "COMMIT_MSG=$COMMIT_MSG" >> $GITHUB_ENV
# 处理忽略模式(转换为数组)
IFS='|' read -ra PATTERNS_ARRAY <<< "${{ env.IGNORE_PATTERNS }}"
IGNORE_JSON="["
for pattern in "${PATTERNS_ARRAY[@]}"; do
# 去除首尾空格
pattern=$(echo "$pattern" | xargs)
if [ -n "$pattern" ]; then
IGNORE_JSON="${IGNORE_JSON}\"${pattern}\","
fi
done
IGNORE_JSON="${IGNORE_JSON%,}]"
echo "IGNORE_PATTERNS_JSON=$IGNORE_JSON" >> $GITHUB_ENV
# 显示配置摘要
echo ""
echo "📋 Configuration Summary:"
echo " 🏷️ Tag version: $VERSION"
echo " 📝 CHANGELOG version: $CHANGELOG_VERSION"
echo " 🌐 Gitea Server: ${{ env.GITEA_SERVER }}"
echo " 📄 CHANGELOG Title: ${{ env.CHANGELOG_SECTION_TITLE }}"
echo " 🚀 Release Title: $RELEASE_TITLE_PROCESSED"
echo " 🏷️ Pre-release: $PRERELEASE_DETECTED"
echo " 📄 Draft: ${{ env.RELEASE_IS_DRAFT }}"
echo " 💬 Commit Message: $COMMIT_MSG"
echo " 📎 Additional Files: ${ADDITIONAL_RELEASE_FILES:-none}"
echo ""
echo "✅ Configuration loaded successfully"
echo "======================================"
echo ""
- name: 📥 克隆仓库
id: clone
run: |
echo "======================================"
echo "🚀 开始准备仓库"
echo "======================================"
REPO_NAME="${{ github.event.repository.name }}"
REPO_DIR="${{ env.WORKSPACE_DIR }}/$REPO_NAME"
echo "📁 仓库名称: $REPO_NAME"
echo "📍 目标目录: $REPO_DIR"
echo "🌐 服务器: ${GITHUB_SERVER_URL}"
echo ""
# 检查仓库状态
if [ -d "$REPO_DIR" ]; then
echo "📂 目录已存在,检查 Git 仓库状态..."
if [ -d "$REPO_DIR/.git" ]; then
# 目录存在且是有效的 Git 仓库
echo "✓ 发现有效的 Git 仓库,执行增量更新..."
cd "$REPO_DIR"
# 清理工作区
git clean -fdx
git reset --hard
# 获取最新代码
echo "📥 拉取最新代码和标签..."
git fetch --all --tags --force
echo "✓ 仓库已更新"
else
# 目录存在但不是 Git 仓库(可能之前运行失败)
echo "⚠️ 目录存在但不是有效的 Git 仓库"
echo "🧹 清理损坏的目录..."
rm -rf "$REPO_DIR"
echo "✓ 已清理"
# 重新克隆
echo "📥 克隆仓库..."
mkdir -p "${{ env.WORKSPACE_DIR }}"
git clone \
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
"$REPO_DIR"
if [ $? -ne 0 ]; then
echo "❌ 克隆失败"
exit 1
fi
cd "$REPO_DIR"
echo "✓ 仓库已克隆"
fi
else
# 目录不存在,首次克隆
echo "📥 克隆仓库(首次)..."
mkdir -p "${{ env.WORKSPACE_DIR }}"
git clone \
https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git \
"$REPO_DIR"
if [ $? -eq 0 ]; then
cd "$REPO_DIR"
echo "✓ 仓库已克隆"
else
echo "❌ 克隆失败"
exit 1
fi
fi
# 检测主分支名称
echo ""
echo "🔍 检测主分支名称..."
if [ -n "${{ env.MAIN_BRANCH }}" ]; then
MAIN_BRANCH="${{ env.MAIN_BRANCH }}"
echo "✓ 使用配置的主分支: $MAIN_BRANCH"
else
# 自动检测 main 或 master
if git show-ref --verify --quiet refs/remotes/origin/main; then
MAIN_BRANCH="main"
echo "✓ 自动检测到主分支: main"
elif git show-ref --verify --quiet refs/remotes/origin/master; then
MAIN_BRANCH="master"
echo "✓ 自动检测到主分支: master"
else
echo "❌ 错误: 无法检测主分支main 或 master"
exit 1
fi
fi
# 切换到主分支
echo ""
echo "🌿 切换到主分支: $MAIN_BRANCH"
git checkout $MAIN_BRANCH
git pull origin $MAIN_BRANCH
echo "✓ 已切换到主分支并更新到最新"
# 导出到环境变量供后续步骤使用
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
echo "MAIN_BRANCH=$MAIN_BRANCH" >> $GITHUB_ENV
echo ""
echo "✅ 仓库准备完成"
echo "======================================"
echo ""
- name: 🔍 检查 Bot 提交
id: check_bot
run: |
echo "======================================"
echo "🤖 检查是否为 Bot 提交"
echo "======================================"
cd ${{ env.REPO_DIR }}
COMMIT_MSG=$(git log -1 --pretty=%B ${{ github.ref_name }})
echo "📝 Tag commit message:"
echo "$COMMIT_MSG"
echo ""
# 检查是否包含 [skip ci] 或由 bot 创建
if echo "$COMMIT_MSG" | grep -qE '\[skip ci\]|\[ci skip\]'; then
echo "⏭️ Bot commit detected (contains [skip ci])"
echo "is_bot_commit=true" >> $GITHUB_OUTPUT
else
echo "✅ Not a bot commit"
echo "is_bot_commit=false" >> $GITHUB_OUTPUT
fi
echo "======================================"
echo ""
- name: 🔍 检查版本是否已存在
id: check_version
if: steps.check_bot.outputs.is_bot_commit != 'true'
run: |
echo "======================================"
echo "🔍 检查 CHANGELOG 版本"
echo "======================================"
cd ${{ env.REPO_DIR }}
if [ ! -f "CHANGELOG.md" ]; then
echo "📝 CHANGELOG.md 不存在,将创建新文件"
echo "version_exists=false" >> $GITHUB_OUTPUT
else
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
echo "📝 检查版本: $CHANGELOG_VERSION"
if grep -q "## :bookmark: $CHANGELOG_VERSION" CHANGELOG.md; then
echo "⚠️ 版本 $CHANGELOG_VERSION 已存在于 CHANGELOG.md"
echo "version_exists=true" >> $GITHUB_OUTPUT
else
echo "✅ 版本 $CHANGELOG_VERSION 不存在,可以继续"
echo "version_exists=false" >> $GITHUB_OUTPUT
fi
fi
echo "======================================"
echo ""
- name: 📝 生成 CHANGELOG
id: changelog
if: steps.check_bot.outputs.is_bot_commit != 'true' && steps.check_version.outputs.version_exists != 'true'
run: |
echo "======================================"
echo "📝 生成 CHANGELOG"
echo "======================================"
cd ${{ env.REPO_DIR }}
VERSION="${{ github.ref_name }}"
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
echo "🏷️ Tag: $VERSION"
echo "📝 CHANGELOG Version: $CHANGELOG_VERSION"
echo ""
# 查找前一个 tag
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -v "^${VERSION}$" | head -n 1)
if [ -z "$PREVIOUS_TAG" ]; then
echo " No previous tag found, using all commits"
COMMIT_RANGE=""
else
echo "📍 Previous tag: $PREVIOUS_TAG"
COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}"
fi
# 获取提交记录
echo ""
echo "📋 获取提交记录..."
if [ -z "$COMMIT_RANGE" ]; then
git log --pretty=format:'%H|%s' > /tmp/commits.txt
else
git log --pretty=format:'%H|%s' "$COMMIT_RANGE" > /tmp/commits.txt
fi
TOTAL_COMMITS=$(wc -l < /tmp/commits.txt)
echo "✓ 找到 $TOTAL_COMMITS 个提交"
# Python 脚本生成 CHANGELOG
python3 << 'PYSCRIPT'
import re
import json
import sys
from datetime import datetime
# 读取配置
changelog_version = "${{ env.CHANGELOG_VERSION }}"
gitea_server = "${{ env.GITEA_SERVER }}"
repo = "${{ github.repository }}"
section_title = "${{ env.CHANGELOG_SECTION_TITLE }}"
contributors_title = "${{ env.CHANGELOG_CONTRIBUTORS_TITLE }}"
# 读取忽略模式
ignore_patterns = json.loads('${{ env.IGNORE_PATTERNS_JSON }}')
print(f"🔍 Loaded {len(ignore_patterns)} ignore patterns:")
for pattern in ignore_patterns:
print(f" - {pattern}")
print()
# 读取提交
with open('/tmp/commits.txt', 'r', encoding='utf-8') as f:
lines = f.readlines()
commits = []
skipped_commits = []
for line in lines:
line = line.strip()
if not line or '|' not in line:
continue
commit_hash, message = line.split('|', 1)
# 检查是否应该忽略
should_ignore = False
matched_pattern = None
for pattern in ignore_patterns:
if re.search(pattern, message):
should_ignore = True
matched_pattern = pattern
break
if should_ignore:
skipped_commits.append((message, matched_pattern))
else:
commits.append({'hash': commit_hash, 'message': message})
# 输出统计
print(f"📊 Commit Statistics:")
print(f" Total: {len(lines)}")
print(f" Valid: {len(commits)}")
print(f" Skipped: {len(skipped_commits)}")
print()
if skipped_commits:
print("⏭️ Skipped Commits:")
for msg, pattern in skipped_commits:
print(f" ⏭️ Skipping: {msg[:80]}... (matched: {pattern})")
print()
if not commits:
print("⚠️ No valid commits found")
with open('/tmp/changelog_updated.txt', 'w') as f:
f.write('false')
sys.exit(0)
# 收集贡献者
contributors = set()
for commit in commits:
# 从提交消息中提取可能的用户名(如果使用了 @username 格式)
# 这里简化处理,实际可以通过 git log --format='%an|%ae' 获取
pass
# 生成 CHANGELOG 内容
changelog_content = f"## :bookmark: {changelog_version}\n\n"
changelog_content += f"*Released on {datetime.now().strftime('%Y-%m-%d')}*\n\n"
changelog_content += f"{section_title}\n\n"
for commit in commits:
short_hash = commit['hash'][:7]
message = commit['message']
changelog_content += f"- {message} ([`{short_hash}`](https://github.com/{repo}/commit/{commit['hash']}))\n"
# 如果有贡献者信息,可以在这里添加
# changelog_content += f"\n{contributors_title}\n\n"
changelog_content += "\n---\n\n"
# 更新或创建 CHANGELOG.md
changelog_file = 'CHANGELOG.md'
try:
with open(changelog_file, 'r', encoding='utf-8') as f:
existing_content = f.read()
except FileNotFoundError:
existing_content = "# :memo: CHANGELOG\n\n"
# 检查版本是否已存在
if f"## :bookmark: {changelog_version}" in existing_content:
print(f" Version {changelog_version} already exists, appending commits")
# 找到版本区块,在其中追加新的提交
pattern = rf'(## :bookmark: {re.escape(changelog_version)}.*?\n{re.escape(section_title)}\n\n)(.*?)(\n---|\n## :bookmark: |\Z)'
match = re.search(pattern, existing_content, re.DOTALL)
if match:
existing_commits = match.group(2).strip()
new_commits = changelog_content.split(f"{section_title}\n\n")[1].split("\n---")[0].strip()
# 合并提交,去重
all_commits_text = existing_commits + "\n" + new_commits
# 简单去重(基于完整行)
unique_lines = []
seen = set()
for line in all_commits_text.split('\n'):
if line.strip() and line not in seen:
unique_lines.append(line)
seen.add(line)
merged_commits = '\n'.join(unique_lines)
# 重新组装版本区块
updated_section = match.group(1) + merged_commits + "\n\n---\n"
new_content = existing_content[:match.start()] + updated_section + existing_content[match.end():]
else:
print("⚠️ Could not find version section, prepending new content")
new_content = existing_content.replace(
"# :memo: CHANGELOG\n\n",
f"# :memo: CHANGELOG\n\n{changelog_content}"
)
else:
# 插入新版本
new_content = existing_content.replace(
"# :memo: CHANGELOG\n\n",
f"# :memo: CHANGELOG\n\n{changelog_content}"
)
with open(changelog_file, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"✅ CHANGELOG updated successfully")
print(f" Added {len(commits)} commits to version {changelog_version}")
with open('/tmp/changelog_updated.txt', 'w') as f:
f.write('true')
PYSCRIPT
UPDATED=$(cat /tmp/changelog_updated.txt)
echo "changelog_updated=$UPDATED" >> $GITHUB_OUTPUT
if [ "$UPDATED" = "true" ]; then
echo ""
echo "✅ CHANGELOG 生成完成"
else
echo ""
echo " 没有有效的提交,跳过 CHANGELOG 更新"
fi
echo "======================================"
echo ""
- name: 📤 提交 CHANGELOG 到主分支
id: commit
if: steps.changelog.outputs.changelog_updated == 'true'
run: |
echo "======================================"
echo "📤 提交 CHANGELOG 更新"
echo "======================================"
cd ${{ env.REPO_DIR }}
# 配置 Git 用户信息
git config user.name "${{ env.GIT_USER_NAME }}"
git config user.email "${{ env.GIT_USER_EMAIL }}"
# 添加 CHANGELOG.md
git add CHANGELOG.md
# 检查是否有变更
if git diff --staged --quiet; then
echo "📌 没有变更,跳过提交"
else
echo "📝 提交 CHANGELOG 更新..."
git commit -m "${{ env.COMMIT_MSG }}"
echo "📤 推送到远程 ${{ env.MAIN_BRANCH }} 分支..."
git push https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git ${{ env.MAIN_BRANCH }}
if [ $? -eq 0 ]; then
echo "✅ 推送成功"
else
echo "❌ 推送失败"
exit 1
fi
fi
echo "======================================"
echo ""
- name: 📄 提取 Release Notes
id: extract_notes
if: steps.changelog.outputs.changelog_updated == 'true'
run: |
echo "======================================"
echo "📄 提取 Release Notes"
echo "======================================"
cd ${{ env.REPO_DIR }}
# 确保使用最新的 CHANGELOG
git pull origin ${{ env.MAIN_BRANCH }}
python3 << 'PYSCRIPT'
import re
changelog_version = "${{ env.CHANGELOG_VERSION }}"
with open('CHANGELOG.md', 'r', encoding='utf-8') as f:
content = f.read()
# 匹配版本区块
pattern = rf'## :bookmark: {re.escape(changelog_version)}\s*\n(.*?)(?=\n---\n|\n## :bookmark: |\Z)'
match = re.search(pattern, content, re.DOTALL)
if match:
version_content = match.group(1).strip()
# 清理多余的空行
version_content = re.sub(r'\n\s*\n\s*\n+', '\n\n', version_content).strip()
# 移除可能的分隔线
version_content = re.sub(r'^-{3,}\s*\n', '', version_content)
print(f"✓ Found changelog for version {changelog_version}")
print(f"📊 Content length: {len(version_content)} characters")
else:
version_content = "⚠️ No changelog found for this version"
print(f"⚠️ WARNING: Version {changelog_version} not found in CHANGELOG")
with open('/tmp/release-body.txt', 'w', encoding='utf-8') as f:
f.write(version_content)
print("✅ Release notes generated successfully")
PYSCRIPT
echo "======================================"
echo ""
- name: 🚀 创建 Release
id: create_release
if: steps.changelog.outputs.changelog_updated == 'true'
run: |
echo "======================================"
echo "🚀 创建 GitHub/Gitea Release"
echo "======================================"
cd ${{ env.REPO_DIR }}
echo "📦 准备 Release 数据..."
echo "📋 Release 标题: ${{ env.RELEASE_TITLE_PROCESSED }}"
echo "🏷️ Pre-release: ${{ env.RELEASE_IS_PRERELEASE }}"
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
import json
with open('/tmp/release-body.txt', 'r') as f:
body = f.read()
payload = {
"tag_name": "${{ github.ref_name }}",
"name": "${{ env.RELEASE_TITLE_PROCESSED }}",
"body": body,
"draft": $PY_DRAFT,
"prerelease": $PY_PRERELEASE
}
with open('/tmp/payload.json', 'w', encoding='utf-8') as f:
json.dump(payload, f, ensure_ascii=False, indent=2)
print("✓ Payload prepared")
EOF
echo "🌐 发送 API 请求..."
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \
-H "Authorization: token ${{ env.ACCESS_TOKEN }}" \
-H "Content-Type: application/json" \
-d @/tmp/payload.json \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases")
echo ""
echo "📡 API 响应 (HTTP $HTTP_CODE):"
echo "---"
cat /tmp/release_response.json | python3 -m json.tool 2>/dev/null || cat /tmp/release_response.json
echo "---"
echo ""
if [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "200" ]; then
echo "❌ 错误: 创建 Release 失败"
exit 1
fi
echo "✅ Release 创建成功"
echo "======================================"
echo ""
- name: 📎 上传附件
id: upload_assets
if: steps.changelog.outputs.changelog_updated == 'true'
run: |
echo "======================================"
echo "📎 上传 Release 附件"
echo "======================================"
cd ${{ env.REPO_DIR }}
echo "🔍 提取 Release ID..."
RELEASE_ID=$(python3 << 'PYSCRIPT'
import json
import sys
try:
with open('/tmp/release_response.json', 'r') as f:
data = json.load(f)
if 'id' not in data:
print("❌ 错误: 响应中没有 'id' 字段", file=sys.stderr)
sys.exit(1)
print(data['id'])
except Exception as e:
print(f"❌ 错误: {e}", file=sys.stderr)
sys.exit(1)
PYSCRIPT
)
if [ -z "$RELEASE_ID" ]; then
echo "❌ 错误: 无法获取 Release ID"
exit 1
fi
echo "✓ Release ID: $RELEASE_ID"
echo ""
# 上传 CHANGELOG.md
echo "📤 上传 CHANGELOG.md..."
HTTP_CODE=$(curl -s -o /tmp/upload_response_changelog.json -w "%{http_code}" -X POST \
-H "Authorization: token ${{ env.ACCESS_TOKEN }}" \
-F "attachment=@CHANGELOG.md" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=CHANGELOG.md")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "✅ CHANGELOG.md 上传成功"
else
echo "⚠️ 警告: CHANGELOG.md 上传失败 (HTTP $HTTP_CODE)"
fi
# 上传额外的文件
if [ -n "${{ env.ADDITIONAL_RELEASE_FILES }}" ]; then
echo ""
echo "📦 上传额外文件..."
FILE_INDEX=2
TOTAL_FILES=$((1 + $(echo "${{ env.ADDITIONAL_RELEASE_FILES }}" | wc -w)))
for file in ${{ env.ADDITIONAL_RELEASE_FILES }}; do
if [ -f "$file" ]; then
echo "📤 [$FILE_INDEX/$TOTAL_FILES] 上传 $(basename $file)..."
HTTP_CODE=$(curl -s -o /tmp/upload_response_${FILE_INDEX}.json -w "%{http_code}" -X POST \
-H "Authorization: token ${{ env.ACCESS_TOKEN }}" \
-F "attachment=@${file}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?name=$(basename $file)")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "✅ $(basename $file) 上传成功"
else
echo "⚠️ 警告: $(basename $file) 上传失败 (HTTP $HTTP_CODE)"
fi
FILE_INDEX=$((FILE_INDEX + 1))
else
echo "⚠️ 警告: 文件不存在: $file"
fi
done
fi
echo ""
echo "✅ 所有附件处理完成"
echo "======================================"
echo ""
- name: 📊 生成 Workflow Summary
if: always()
run: |
echo "======================================"
echo "📊 生成 Workflow Summary"
echo "======================================"
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
# 📦 Tag Release 工作流执行报告
## ✅ 执行结果
| 项目 | 结果 |
|------|------|
| 🏷️ Tag 版本 | **`${{ github.ref_name }}`** |
| 📝 CHANGELOG 版本 | **`${{ env.CHANGELOG_VERSION }}`** |
| 🌿 主分支 | **`${{ env.MAIN_BRANCH }}`** |
| 🌐 Gitea 服务器 | **`${{ env.GITEA_SERVER }}`** |
EOFSUMMARY
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
cat >> $GITHUB_STEP_SUMMARY << 'EOFBOT'
| 📋 执行状态 | ⏭️ 已跳过 (Bot 提交) |
---
⏭️ **工作流已跳过**
检测到此提交由 Bot 创建(包含 `[skip ci]` 标记),为防止无限循环,已跳过执行。
EOFBOT
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
cat >> $GITHUB_STEP_SUMMARY << 'EOFEXIST'
| 📋 执行状态 | ⏭️ 已跳过 (版本已存在) |
---
⏭️ **工作流已跳过**
版本 `${{ env.CHANGELOG_VERSION }}` 已存在于 CHANGELOG.md 中。
EOFEXIST
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUCCESS'
| 📋 执行状态 | ✅ 执行成功 |
---
### ✅ 完成的操作
- ✅ CHANGELOG.md 已更新
- ✅ 更改已推送到主分支 (`${{ env.MAIN_BRANCH }}`)
- ✅ Release 已创建
- ✅ CHANGELOG.md 已作为附件上传
EOFSUCCESS
if [ -n "${{ env.ADDITIONAL_RELEASE_FILES }}" ]; then
echo "- ✅ 额外文件已上传" >> $GITHUB_STEP_SUMMARY
fi
else
cat >> $GITHUB_STEP_SUMMARY << 'EOFNOCOMMIT'
| 📋 执行状态 | 无更新 |
---
**无有效提交**
在标签之间未找到有效的提交记录。
EOFNOCOMMIT
fi
cat >> $GITHUB_STEP_SUMMARY << 'EOFEND'
---
## 🔗 快速链接
- 📝 [查看 CHANGELOG](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/CHANGELOG.md)
- 🚀 [查看 Releases](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases)
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${{ env.MAIN_BRANCH }}/.github/workflows/changelog_and_release.yml)
---
<div align="center">
*📊 由 [GitHub Actions](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions) 自动生成和更新*
EOFEND
echo "*🤖 生成时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "</div>" >> $GITHUB_STEP_SUMMARY
echo ""
echo "✅ Workflow summary 生成完成"
echo "======================================"
echo ""
- name: 💡 输出使用说明
if: success()
run: |
echo "======================================"
echo "✅ Tag Release 工作流执行完成!"
echo "======================================"
echo ""
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
echo "⏭️ 已跳过: Bot 提交检测"
echo " 原因: 检测到 [skip ci] 标记,防止无限循环"
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
echo "⏭️ 已跳过: 版本已存在"
echo " 版本: ${{ env.CHANGELOG_VERSION }}"
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
echo "📊 执行结果:"
echo " - Tag: ${{ github.ref_name }}"
echo " - CHANGELOG 版本: ${{ env.CHANGELOG_VERSION }}"
echo " - 主分支: ${{ env.MAIN_BRANCH }}"
echo " - Release 标题: ${{ env.RELEASE_TITLE_PROCESSED }}"
echo " - Pre-release: ${{ env.RELEASE_IS_PRERELEASE }}"
echo ""
echo "✅ 已完成的操作:"
echo " - CHANGELOG.md 已更新并推送到 ${{ env.MAIN_BRANCH }} 分支"
echo " - Release 已创建"
echo " - CHANGELOG.md 已上传为附件"
else
echo " 无有效提交,未生成 CHANGELOG"
fi
echo ""
echo "🔗 快速链接:"
echo " CHANGELOG: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.MAIN_BRANCH }}/CHANGELOG.md"
echo " Releases: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases"
echo ""
echo "======================================"
- name: 🧹 清理工作区
if: always()
run: |
echo "🧹 清理临时文件..."
rm -rf /tmp/commits.txt /tmp/changelog_updated.txt /tmp/release-body.txt /tmp/payload.json /tmp/release_response.json /tmp/upload_response_*.json
echo "✅ 清理完成"