957 lines
35 KiB
YAML
957 lines
35 KiB
YAML
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 "✅ 清理完成" |