Compare commits
No commits in common. "d5a7d5506ec0f7d128c60f908bbd17198308c587" and "8971cbffc3023891852a14d9917c8b9b2b7f8272" have entirely different histories.
d5a7d5506e
...
8971cbffc3
|
|
@ -0,0 +1,921 @@
|
|||
name: 📦 Tag Release Automation
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-9]*"
|
||||
|
||||
jobs:
|
||||
changelog-and-release:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: ⚙️ Configuration
|
||||
id: config
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "⚙️ Loading workflow configuration"
|
||||
echo "======================================"
|
||||
|
||||
# ========================================
|
||||
# 📝 自定义配置区域 - 在此修改所有配置
|
||||
# ========================================
|
||||
|
||||
# 📌 语义版本号规范 (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内容是叠加而非覆盖
|
||||
|
||||
# Gitea 服务器地址(用于生成头像链接)
|
||||
GITEA_SERVER="https://git.mytsl.cn"
|
||||
|
||||
# CHANGELOG 配置
|
||||
CHANGELOG_SECTION_TITLE="### :pencil: What's Changed"
|
||||
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_TITLE="{version}" # 可用变量: {version}
|
||||
|
||||
# Pre-release 配置
|
||||
# - "false": 正式版本(会被标记为 latest)
|
||||
# - "true": 预发布版本(会被标记为 pre-release)
|
||||
# - "auto": 自动判断(根据 tag 名称中是否包含 alpha/beta/rc)
|
||||
RELEASE_PRERELEASE_MODE="auto"
|
||||
|
||||
# Draft 配置(是否创建为草稿)
|
||||
RELEASE_IS_DRAFT="false" # true 或 false
|
||||
|
||||
# 需要忽略的提交模式(支持正则表达式)
|
||||
IGNORE_PATTERNS=(
|
||||
"^Merge "
|
||||
"^:memo: Auto update CHANGELOG"
|
||||
"\[skip ci\]"
|
||||
"\[ci skip\]"
|
||||
"^Bump version"
|
||||
"^Release v"
|
||||
"^chore: update config"
|
||||
"^style: "
|
||||
"^chore: format"
|
||||
"^chore: lint"
|
||||
"^Initial commit"
|
||||
)
|
||||
|
||||
# Git 提交消息格式
|
||||
COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]"
|
||||
|
||||
# 额外要上传到 Release 的文件(空格分隔)
|
||||
ADDITIONAL_RELEASE_FILES="" # 例如: "README.md LICENSE docs/guide.pdf"
|
||||
|
||||
# ========================================
|
||||
# 以下为自动处理,无需修改
|
||||
# ========================================
|
||||
|
||||
VERSION="${{ github.ref_name }}"
|
||||
|
||||
# 处理 CHANGELOG 版本号
|
||||
if [[ "$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="${RELEASE_TITLE//\{version\}/$VERSION}"
|
||||
|
||||
# 处理提交消息
|
||||
COMMIT_MSG="${COMMIT_MESSAGE//\{version\}/$VERSION}"
|
||||
|
||||
# 智能判断 pre-release
|
||||
if [[ "$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="$RELEASE_PRERELEASE_MODE"
|
||||
PRERELEASE_DETECTED="$RELEASE_PRERELEASE_MODE (manually configured)"
|
||||
fi
|
||||
|
||||
# 导出配置到环境变量
|
||||
echo "GITEA_SERVER=$GITEA_SERVER" >> $GITHUB_ENV
|
||||
echo "CHANGELOG_SECTION_TITLE=$CHANGELOG_SECTION_TITLE" >> $GITHUB_ENV
|
||||
echo "CHANGELOG_CONTRIBUTORS_TITLE=$CHANGELOG_CONTRIBUTORS_TITLE" >> $GITHUB_ENV
|
||||
echo "CHANGELOG_VERSION=$CHANGELOG_VERSION" >> $GITHUB_ENV
|
||||
echo "RELEASE_TITLE=$RELEASE_TITLE" >> $GITHUB_ENV
|
||||
echo "RELEASE_IS_PRERELEASE=$RELEASE_IS_PRERELEASE" >> $GITHUB_ENV
|
||||
echo "RELEASE_IS_DRAFT=$RELEASE_IS_DRAFT" >> $GITHUB_ENV
|
||||
echo "COMMIT_MSG=$COMMIT_MSG" >> $GITHUB_ENV
|
||||
echo "ADDITIONAL_RELEASE_FILES=$ADDITIONAL_RELEASE_FILES" >> $GITHUB_ENV
|
||||
|
||||
# 导出忽略模式(转换为 JSON 数组格式便于后续处理)
|
||||
IGNORE_JSON="["
|
||||
for pattern in "${IGNORE_PATTERNS[@]}"; do
|
||||
IGNORE_JSON="${IGNORE_JSON}\"${pattern}\","
|
||||
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: $GITEA_SERVER"
|
||||
echo " 📄 CHANGELOG Title: $CHANGELOG_SECTION_TITLE"
|
||||
echo " 🚀 Release Title: $RELEASE_TITLE"
|
||||
echo " 🏷️ Pre-release: $PRERELEASE_DETECTED"
|
||||
echo " 📄 Draft: $RELEASE_IS_DRAFT"
|
||||
echo " 💬 Commit Message: $COMMIT_MSG"
|
||||
echo " 📎 Additional Files: ${ADDITIONAL_RELEASE_FILES:-none}"
|
||||
echo ""
|
||||
echo "✅ Configuration loaded successfully"
|
||||
echo ""
|
||||
|
||||
- name: 📂 Checkout repository
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🚀 Starting repository checkout"
|
||||
echo "======================================"
|
||||
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
REPO_DIR="/home/workspace/$REPO_NAME"
|
||||
|
||||
echo "📁 Repository: $REPO_NAME"
|
||||
echo "📍 Target directory: $REPO_DIR"
|
||||
|
||||
if [ -d "$REPO_DIR/.git" ]; then
|
||||
echo "✓ Repository exists, updating..."
|
||||
cd "$REPO_DIR"
|
||||
git clean -fdx
|
||||
git reset --hard
|
||||
git fetch --all --tags --force
|
||||
echo "✓ Repository updated"
|
||||
else
|
||||
echo "📥 Cloning repository..."
|
||||
mkdir -p /home/workspace
|
||||
git clone https://oauth2:${{ secrets.WORKFLOW }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git "$REPO_DIR"
|
||||
cd "$REPO_DIR"
|
||||
echo "✓ Repository cloned"
|
||||
fi
|
||||
|
||||
echo "🏷️ Checking out tag: ${{ github.ref_name }}"
|
||||
git checkout -f ${{ github.ref_name }}
|
||||
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||
|
||||
echo "✅ Checkout completed"
|
||||
echo ""
|
||||
|
||||
- name: 🔍 Check if bot commit
|
||||
id: check_bot
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🤖 Checking commit author"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
LAST_AUTHOR=$(git log -1 --pretty=format:'%an')
|
||||
|
||||
echo "👤 Last commit author: $LAST_AUTHOR"
|
||||
|
||||
if [[ "$LAST_AUTHOR" == "github-actions[bot]" ]]; then
|
||||
echo "⚠️ Bot commit detected - skipping to prevent loop"
|
||||
echo "is_bot_commit=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "✓ Non-bot commit - continuing workflow"
|
||||
echo "is_bot_commit=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
echo ""
|
||||
|
||||
- name: 📋 Check if version exists in CHANGELOG
|
||||
if: steps.check_bot.outputs.is_bot_commit == 'false'
|
||||
id: check_version
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🔎 Checking CHANGELOG for existing version"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
|
||||
|
||||
echo "🏷️ CHANGELOG version to check: $CHANGELOG_VERSION"
|
||||
|
||||
git checkout main || git checkout master
|
||||
git pull origin main || git pull origin master || true
|
||||
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
echo "✓ CHANGELOG.md found"
|
||||
if grep -q "## :bookmark: ${CHANGELOG_VERSION}" CHANGELOG.md; then
|
||||
echo "ℹ️ Version ${CHANGELOG_VERSION} already exists in CHANGELOG"
|
||||
echo "📝 Will append new commits to existing entry (content accumulation mode)"
|
||||
echo "version_exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "✓ Version ${CHANGELOG_VERSION} not found - will generate new entry"
|
||||
echo "version_exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ CHANGELOG.md does not exist - will create new file"
|
||||
echo "version_exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
git checkout ${{ github.ref_name }}
|
||||
echo ""
|
||||
|
||||
- name: 📝 Generate CHANGELOG
|
||||
if: steps.check_bot.outputs.is_bot_commit == 'false'
|
||||
id: changelog
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📝 Generating CHANGELOG"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
VERSION="${{ github.ref_name }}"
|
||||
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
|
||||
VERSION_EXISTS="${{ steps.check_version.outputs.version_exists }}"
|
||||
PREVIOUS_TAG=$(git tag --sort=-creatordate | sed -n '2p')
|
||||
|
||||
echo "🏷️ Tag version: $VERSION"
|
||||
echo "📝 CHANGELOG version: $CHANGELOG_VERSION"
|
||||
echo "🏷️ Previous version: ${PREVIOUS_TAG:-none (first release)}"
|
||||
|
||||
if [ "$VERSION_EXISTS" = "true" ]; then
|
||||
echo "ℹ️ Updating existing CHANGELOG entry"
|
||||
else
|
||||
echo "ℹ️ Creating new CHANGELOG entry"
|
||||
fi
|
||||
|
||||
# 从环境变量加载忽略模式
|
||||
IGNORE_PATTERNS=(
|
||||
"^Merge "
|
||||
"^:memo: Auto update CHANGELOG"
|
||||
"\[skip ci\]"
|
||||
"\[ci skip\]"
|
||||
"^Bump version"
|
||||
"^Release v"
|
||||
"^chore: update config"
|
||||
"^style: "
|
||||
"^chore: format"
|
||||
"^chore: lint"
|
||||
)
|
||||
|
||||
echo "🔍 Scanning commits..."
|
||||
TEMP_COMMITS=$(mktemp)
|
||||
|
||||
if [ -z "$PREVIOUS_TAG" ]; then
|
||||
COMMIT_RANGE="HEAD"
|
||||
else
|
||||
COMMIT_RANGE="${PREVIOUS_TAG}..${VERSION}"
|
||||
fi
|
||||
|
||||
git log $COMMIT_RANGE --no-merges --reverse --format="COMMIT_START%n%h%n%an%n%s%nBODY_START%n%b%nBODY_END" > "$TEMP_COMMITS"
|
||||
|
||||
declare -a COMMITS_HASH
|
||||
declare -a COMMITS_AUTHOR
|
||||
declare -a COMMITS_MESSAGE
|
||||
declare -a COMMITS_BODY
|
||||
|
||||
COMMIT_INDEX=0
|
||||
CURRENT_HASH=""
|
||||
CURRENT_AUTHOR=""
|
||||
CURRENT_MESSAGE=""
|
||||
CURRENT_BODY=""
|
||||
IN_BODY=false
|
||||
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" == "COMMIT_START" ]]; then
|
||||
if [ -n "$CURRENT_HASH" ]; then
|
||||
SHOULD_SKIP=false
|
||||
for pattern in "${IGNORE_PATTERNS[@]}"; do
|
||||
if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then
|
||||
SHOULD_SKIP=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$SHOULD_SKIP" = false ]; then
|
||||
COMMITS_HASH[$COMMIT_INDEX]="$CURRENT_HASH"
|
||||
COMMITS_AUTHOR[$COMMIT_INDEX]="$CURRENT_AUTHOR"
|
||||
COMMITS_MESSAGE[$COMMIT_INDEX]="$CURRENT_MESSAGE"
|
||||
COMMITS_BODY[$COMMIT_INDEX]="$CURRENT_BODY"
|
||||
COMMIT_INDEX=$((COMMIT_INDEX + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
CURRENT_HASH=""
|
||||
CURRENT_AUTHOR=""
|
||||
CURRENT_MESSAGE=""
|
||||
CURRENT_BODY=""
|
||||
IN_BODY=false
|
||||
STATE="HASH"
|
||||
|
||||
elif [[ "$line" == "BODY_START" ]]; then
|
||||
IN_BODY=true
|
||||
CURRENT_BODY=""
|
||||
elif [[ "$line" == "BODY_END" ]]; then
|
||||
IN_BODY=false
|
||||
elif [ "$IN_BODY" = true ]; then
|
||||
if [ -n "$CURRENT_BODY" ]; then
|
||||
CURRENT_BODY="${CURRENT_BODY}\n${line}"
|
||||
else
|
||||
CURRENT_BODY="$line"
|
||||
fi
|
||||
else
|
||||
if [ -z "$CURRENT_HASH" ]; then
|
||||
CURRENT_HASH="$line"
|
||||
elif [ -z "$CURRENT_AUTHOR" ]; then
|
||||
CURRENT_AUTHOR="$line"
|
||||
elif [ -z "$CURRENT_MESSAGE" ]; then
|
||||
CURRENT_MESSAGE="$line"
|
||||
fi
|
||||
fi
|
||||
done < "$TEMP_COMMITS"
|
||||
|
||||
if [ -n "$CURRENT_HASH" ]; then
|
||||
SHOULD_SKIP=false
|
||||
for pattern in "${IGNORE_PATTERNS[@]}"; do
|
||||
if echo "$CURRENT_MESSAGE" | grep -qiE "$pattern"; then
|
||||
SHOULD_SKIP=true
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$SHOULD_SKIP" = false ]; then
|
||||
COMMITS_HASH[$COMMIT_INDEX]="$CURRENT_HASH"
|
||||
COMMITS_AUTHOR[$COMMIT_INDEX]="$CURRENT_AUTHOR"
|
||||
COMMITS_MESSAGE[$COMMIT_INDEX]="$CURRENT_MESSAGE"
|
||||
COMMITS_BODY[$COMMIT_INDEX]="$CURRENT_BODY"
|
||||
COMMIT_INDEX=$((COMMIT_INDEX + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$TEMP_COMMITS"
|
||||
|
||||
if [ $COMMIT_INDEX -eq 0 ]; then
|
||||
echo "⚠️ No valid commits found to add"
|
||||
echo "changelog_updated=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✓ Found ${COMMIT_INDEX} valid commits"
|
||||
echo ""
|
||||
echo "📄 Building CHANGELOG entry..."
|
||||
|
||||
# 使用配置的标题
|
||||
NEW_ENTRY="## :bookmark: ${CHANGELOG_VERSION}\n\n"
|
||||
NEW_ENTRY="${NEW_ENTRY}${CHANGELOG_SECTION_TITLE}\n\n"
|
||||
|
||||
REPO_URL="${{ github.server_url }}/${{ github.repository }}"
|
||||
|
||||
for ((i=0; i<$COMMIT_INDEX; i++)); do
|
||||
hash="${COMMITS_HASH[$i]}"
|
||||
author="${COMMITS_AUTHOR[$i]}"
|
||||
message="${COMMITS_MESSAGE[$i]}"
|
||||
body="${COMMITS_BODY[$i]}"
|
||||
|
||||
echo " [$((i+1))/$COMMIT_INDEX] ${hash:0:7}: ${message:0:60}..."
|
||||
|
||||
COMMIT_LINK="([${hash}](${REPO_URL}/commit/${hash}))"
|
||||
|
||||
if [ -n "$body" ]; then
|
||||
FULL_MESSAGE="${message}\n${body}"
|
||||
LINE_COUNT=$(echo -e "$FULL_MESSAGE" | sed '/^$/d' | wc -l)
|
||||
|
||||
CURRENT_LINE=1
|
||||
FORMATTED_MESSAGE=""
|
||||
|
||||
while IFS= read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
if [ $CURRENT_LINE -eq $LINE_COUNT ]; then
|
||||
FORMATTED_MESSAGE="${FORMATTED_MESSAGE}${line} ${COMMIT_LINK}"
|
||||
if [ -n "$author" ]; then
|
||||
FORMATTED_MESSAGE="${FORMATTED_MESSAGE} by @${author}"
|
||||
fi
|
||||
else
|
||||
FORMATTED_MESSAGE="${FORMATTED_MESSAGE}${line} \n"
|
||||
fi
|
||||
CURRENT_LINE=$((CURRENT_LINE + 1))
|
||||
done <<< "$(echo -e "$FULL_MESSAGE" | sed '/^$/d')"
|
||||
|
||||
NEW_ENTRY="${NEW_ENTRY}- ${FORMATTED_MESSAGE}\n"
|
||||
else
|
||||
if [ -n "$author" ]; then
|
||||
NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK} by @${author}\n"
|
||||
else
|
||||
NEW_ENTRY="${NEW_ENTRY}- ${message} ${COMMIT_LINK}\n"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "👥 Collecting contributors..."
|
||||
|
||||
if [ -z "$PREVIOUS_TAG" ]; then
|
||||
CONTRIBUTORS=$(git log --pretty=format:"%an" --no-merges | grep -v "github-actions\[bot\]" | grep -v "dependabot\[bot\]" | grep -v "renovate\[bot\]" | sort -u)
|
||||
else
|
||||
CONTRIBUTORS=$(git log ${PREVIOUS_TAG}..${VERSION} --pretty=format:"%an" --no-merges | grep -v "github-actions\[bot\]" | grep -v "dependabot\[bot\]" | grep -v "renovate\[bot\]" | sort -u)
|
||||
fi
|
||||
|
||||
if [ -n "$CONTRIBUTORS" ]; then
|
||||
CONTRIBUTOR_COUNT=$(echo "$CONTRIBUTORS" | wc -l)
|
||||
echo "✓ Found ${CONTRIBUTOR_COUNT} contributors"
|
||||
|
||||
# 使用配置的标题和服务器地址
|
||||
NEW_ENTRY="${NEW_ENTRY}\n${CHANGELOG_CONTRIBUTORS_TITLE}\n\n"
|
||||
|
||||
while IFS= read -r name; do
|
||||
NEW_ENTRY="${NEW_ENTRY}<a href=\"${GITEA_SERVER}/${name}\">\n"
|
||||
NEW_ENTRY="${NEW_ENTRY} <img src=\"${GITEA_SERVER}/${name}.png\" alt=\"${name}\" width=\"35\" height=\"35\" style=\"border-radius: 50%;\" onerror=\"this.src='${GITEA_SERVER}/assets/img/avatar_default.png'\" />\n"
|
||||
NEW_ENTRY="${NEW_ENTRY}</a>\n"
|
||||
done <<< "$CONTRIBUTORS"
|
||||
fi
|
||||
|
||||
NEW_ENTRY="${NEW_ENTRY}\n---\n"
|
||||
|
||||
echo ""
|
||||
echo "💾 Writing to CHANGELOG.md..."
|
||||
git checkout main || git checkout master
|
||||
|
||||
if [ -f CHANGELOG.md ]; then
|
||||
if [ "$VERSION_EXISTS" = "true" ]; then
|
||||
# 版本已存在,叠加新的条目(不是替换)
|
||||
echo "🔄 Appending new commits to existing entry for $CHANGELOG_VERSION"
|
||||
|
||||
# 提取现有版本区域的内容
|
||||
TEMP_OLD_CONTENT=$(mktemp)
|
||||
TEMP_NEW_FILE=$(mktemp)
|
||||
|
||||
# 提取旧版本区域的所有内容
|
||||
IN_VERSION=false
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^##[[:space:]]:bookmark:[[:space:]]${CHANGELOG_VERSION}$ ]]; then
|
||||
IN_VERSION=true
|
||||
elif [[ "$line" =~ ^##[[:space:]]:bookmark: ]] && [ "$IN_VERSION" = true ]; then
|
||||
IN_VERSION=false
|
||||
fi
|
||||
|
||||
if [ "$IN_VERSION" = true ]; then
|
||||
echo "$line" >> "$TEMP_OLD_CONTENT"
|
||||
fi
|
||||
done < CHANGELOG.md
|
||||
|
||||
# 提取旧的提交记录(在 What's Changed 和 Contributors 之间的内容)
|
||||
OLD_COMMITS=""
|
||||
if [ -f "$TEMP_OLD_CONTENT" ]; then
|
||||
OLD_COMMITS=$(awk '
|
||||
/^### :pencil: What'"'"'s Changed/,/^### :busts_in_silhouette: Contributors/ {
|
||||
if ($0 !~ /^### :pencil: What/ && $0 !~ /^### :busts_in_silhouette:/) {
|
||||
if ($0 ~ /^-/) print $0
|
||||
}
|
||||
}
|
||||
' "$TEMP_OLD_CONTENT")
|
||||
fi
|
||||
|
||||
# 提取旧的贡献者(在 Contributors 部分的内容)
|
||||
OLD_CONTRIBUTORS=""
|
||||
if [ -f "$TEMP_OLD_CONTENT" ]; then
|
||||
OLD_CONTRIBUTORS=$(awk '
|
||||
/^### :busts_in_silhouette: Contributors/,/^---/ {
|
||||
if ($0 ~ /<a href=/) {
|
||||
getline; img=$0
|
||||
getline; close_tag=$0
|
||||
print prev ORS img ORS close_tag
|
||||
prev=""
|
||||
}
|
||||
prev=$0
|
||||
}
|
||||
' "$TEMP_OLD_CONTENT")
|
||||
fi
|
||||
|
||||
# 构建新的完整条目(叠加模式)
|
||||
MERGED_ENTRY="## :bookmark: ${CHANGELOG_VERSION}\n\n"
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${CHANGELOG_SECTION_TITLE}\n\n"
|
||||
|
||||
# 先添加旧的提交(如果有)
|
||||
if [ -n "$OLD_COMMITS" ]; then
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${OLD_COMMITS}\n"
|
||||
fi
|
||||
|
||||
# 再添加新的提交(从NEW_ENTRY中提取)
|
||||
NEW_COMMITS=$(echo -e "$NEW_ENTRY" | awk '
|
||||
/^### :pencil: What/,/^### :busts_in_silhouette:/ {
|
||||
if ($0 !~ /^### :pencil: What/ && $0 !~ /^### :busts_in_silhouette:/) {
|
||||
if ($0 ~ /^-/) print $0
|
||||
}
|
||||
}
|
||||
')
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${NEW_COMMITS}\n"
|
||||
|
||||
# 合并贡献者(去重)
|
||||
MERGED_ENTRY="${MERGED_ENTRY}\n${CHANGELOG_CONTRIBUTORS_TITLE}\n\n"
|
||||
|
||||
# 使用数组来去重贡献者
|
||||
declare -A SEEN_CONTRIBUTORS
|
||||
|
||||
# 处理旧的贡献者
|
||||
if [ -n "$OLD_CONTRIBUTORS" ]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ href=\"[^\"]+/([^\"]+)\" ]]; then
|
||||
username="${BASH_REMATCH[1]}"
|
||||
if [ -z "${SEEN_CONTRIBUTORS[$username]}" ]; then
|
||||
SEEN_CONTRIBUTORS[$username]=1
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${line}\n"
|
||||
read -r img_line
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${img_line}\n"
|
||||
read -r close_line
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${close_line}\n"
|
||||
fi
|
||||
fi
|
||||
done <<< "$OLD_CONTRIBUTORS"
|
||||
fi
|
||||
|
||||
# 处理新的贡献者
|
||||
NEW_CONTRIBUTORS=$(echo -e "$NEW_ENTRY" | awk '
|
||||
/^### :busts_in_silhouette: Contributors/,/^---/ {
|
||||
if ($0 ~ /<a href=/) print $0
|
||||
else if ($0 ~ /<img src=/) print $0
|
||||
else if ($0 ~ /<\/a>/) print $0
|
||||
}
|
||||
')
|
||||
|
||||
if [ -n "$NEW_CONTRIBUTORS" ]; then
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ href=\"[^\"]+/([^\"]+)\" ]]; then
|
||||
username="${BASH_REMATCH[1]}"
|
||||
if [ -z "${SEEN_CONTRIBUTORS[$username]}" ]; then
|
||||
SEEN_CONTRIBUTORS[$username]=1
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${line}\n"
|
||||
read -r img_line
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${img_line}\n"
|
||||
read -r close_line
|
||||
MERGED_ENTRY="${MERGED_ENTRY}${close_line}\n"
|
||||
fi
|
||||
fi
|
||||
done <<< "$NEW_CONTRIBUTORS"
|
||||
fi
|
||||
|
||||
MERGED_ENTRY="${MERGED_ENTRY}\n---\n"
|
||||
|
||||
# 替换CHANGELOG中的对应版本区域
|
||||
IN_VERSION=false
|
||||
while IFS= read -r line; do
|
||||
if [[ "$line" =~ ^##[[:space:]]:bookmark:[[:space:]]${CHANGELOG_VERSION}$ ]]; then
|
||||
echo -e "${MERGED_ENTRY}" >> "$TEMP_NEW_FILE"
|
||||
IN_VERSION=true
|
||||
elif [[ "$line" =~ ^##[[:space:]]:bookmark: ]] && [ "$IN_VERSION" = true ]; then
|
||||
IN_VERSION=false
|
||||
echo "$line" >> "$TEMP_NEW_FILE"
|
||||
elif [ "$IN_VERSION" = false ]; then
|
||||
echo "$line" >> "$TEMP_NEW_FILE"
|
||||
fi
|
||||
done < CHANGELOG.md
|
||||
|
||||
mv "$TEMP_NEW_FILE" CHANGELOG.md
|
||||
rm -f "$TEMP_OLD_CONTENT"
|
||||
echo "✓ Appended new commits to existing CHANGELOG entry"
|
||||
else
|
||||
# 版本不存在,添加新条目
|
||||
echo "➕ Adding new entry for $CHANGELOG_VERSION"
|
||||
|
||||
TEMP_FILE=$(mktemp)
|
||||
head -n 1 CHANGELOG.md > "$TEMP_FILE"
|
||||
echo "" >> "$TEMP_FILE"
|
||||
echo -e "${NEW_ENTRY}" >> "$TEMP_FILE"
|
||||
tail -n +3 CHANGELOG.md >> "$TEMP_FILE"
|
||||
mv "$TEMP_FILE" CHANGELOG.md
|
||||
echo "✓ Added new CHANGELOG entry"
|
||||
fi
|
||||
else
|
||||
echo "# CHANGELOG" > CHANGELOG.md
|
||||
echo "" >> CHANGELOG.md
|
||||
echo -e "${NEW_ENTRY}" >> CHANGELOG.md
|
||||
echo "✓ Created new CHANGELOG.md"
|
||||
fi
|
||||
|
||||
echo "changelog_updated=true" >> $GITHUB_OUTPUT
|
||||
echo "✅ CHANGELOG generation completed"
|
||||
echo ""
|
||||
|
||||
- name: 📤 Commit and push CHANGELOG
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📤 Committing and pushing CHANGELOG"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
|
||||
git add CHANGELOG.md
|
||||
|
||||
if git diff --staged --quiet; then
|
||||
echo "ℹ️ No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "📝 Creating commit..."
|
||||
echo "💬 Commit message: $COMMIT_MSG"
|
||||
git commit -m "$COMMIT_MSG"
|
||||
|
||||
echo "⬆️ Pushing to remote..."
|
||||
git push origin main
|
||||
|
||||
echo "✅ CHANGELOG pushed to main branch"
|
||||
echo ""
|
||||
|
||||
- name: 🗑️ Delete old release
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🔍 Checking for existing release"
|
||||
echo "======================================"
|
||||
|
||||
OLD_ID=$(curl -s -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/tags/${{ github.ref_name }}" \
|
||||
| python3 -c "import sys, json; data=json.load(sys.stdin); print(data.get('id', ''))" 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$OLD_ID" ]; then
|
||||
echo "🗑️ Found existing release (ID: $OLD_ID)"
|
||||
echo "🔄 Deleting old release..."
|
||||
curl -s -X DELETE -H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases/$OLD_ID"
|
||||
echo "✓ Old release deleted"
|
||||
sleep 2
|
||||
else
|
||||
echo "ℹ️ No existing release found"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
- name: 📋 Generate release notes
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📋 Generating release notes"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
python3 << 'PYSCRIPT'
|
||||
import re
|
||||
import os
|
||||
|
||||
tag = "${{ github.ref_name }}"
|
||||
changelog_version = "${{ env.CHANGELOG_VERSION }}"
|
||||
print(f"🏷️ Tag version: {tag}")
|
||||
print(f"📝 CHANGELOG version: {changelog_version}")
|
||||
|
||||
changelog_path = 'CHANGELOG.md'
|
||||
|
||||
if not os.path.exists(changelog_path):
|
||||
print("❌ ERROR: CHANGELOG.md not found")
|
||||
exit(1)
|
||||
|
||||
print("📖 Reading CHANGELOG.md...")
|
||||
with open(changelog_path, 'r', encoding='utf-8') as f:
|
||||
changelog = f.read()
|
||||
|
||||
print(f"🔍 Searching for version entry: {changelog_version}")
|
||||
pattern = rf'##\s+:bookmark:\s+{re.escape(changelog_version)}\s*\n(.*?)(?=^##\s+:bookmark:|\Z)'
|
||||
match = re.search(pattern, changelog, re.DOTALL | re.MULTILINE)
|
||||
|
||||
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 ""
|
||||
|
||||
- name: 🚀 Create release
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🚀 Creating GitHub/Gitea release"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
echo "📦 Preparing release payload..."
|
||||
echo "📋 Release title: $RELEASE_TITLE"
|
||||
echo "🏷️ Pre-release: $RELEASE_IS_PRERELEASE"
|
||||
echo "📄 Draft: $RELEASE_IS_DRAFT"
|
||||
|
||||
# 转换 bash 布尔值为 Python 布尔值
|
||||
if [ "$RELEASE_IS_DRAFT" = "true" ]; then
|
||||
PY_DRAFT="True"
|
||||
else
|
||||
PY_DRAFT="False"
|
||||
fi
|
||||
|
||||
if [ "$RELEASE_IS_PRERELEASE" = "true" ]; then
|
||||
PY_PRERELEASE="True"
|
||||
else
|
||||
PY_PRERELEASE="False"
|
||||
fi
|
||||
|
||||
echo "🔄 Converting: draft=$RELEASE_IS_DRAFT → $PY_DRAFT, prerelease=$RELEASE_IS_PRERELEASE → $PY_PRERELEASE"
|
||||
|
||||
python3 << EOF
|
||||
import json
|
||||
|
||||
with open('/tmp/release-body.txt', 'r') as f:
|
||||
body = f.read()
|
||||
|
||||
payload = {
|
||||
"tag_name": "${{ github.ref_name }}",
|
||||
"name": "$RELEASE_TITLE",
|
||||
"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 "🌐 Sending API request..."
|
||||
HTTP_CODE=$(curl -s -o /tmp/release_response.json -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITEA_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d @/tmp/payload.json \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases")
|
||||
|
||||
echo "📡 API Response (HTTP $HTTP_CODE):"
|
||||
echo "---"
|
||||
cat /tmp/release_response.json | python3 -m json.tool 2>/dev/null || cat /tmp/release_response.json
|
||||
echo "---"
|
||||
|
||||
if [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "❌ ERROR: Failed to create release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Release created successfully"
|
||||
echo ""
|
||||
|
||||
- name: 📎 Upload CHANGELOG attachment
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📎 Uploading release attachments"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
echo "🔍 Extracting 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("❌ ERROR: No 'id' field in response", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(data['id'])
|
||||
except Exception as e:
|
||||
print(f"❌ ERROR: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PYSCRIPT
|
||||
)
|
||||
|
||||
if [ -z "$RELEASE_ID" ]; then
|
||||
echo "❌ ERROR: Failed to get release ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Release ID: $RELEASE_ID"
|
||||
echo ""
|
||||
|
||||
# 上传额外的文件
|
||||
if [ -n "$ADDITIONAL_RELEASE_FILES" ]; then
|
||||
echo ""
|
||||
echo "📦 Uploading additional files..."
|
||||
FILE_INDEX=2
|
||||
TOTAL_FILES=$((1 + $(echo "$ADDITIONAL_RELEASE_FILES" | wc -w)))
|
||||
|
||||
for file in $ADDITIONAL_RELEASE_FILES; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "📤 [$FILE_INDEX/$TOTAL_FILES] Uploading $(basename $file)..."
|
||||
HTTP_CODE=$(curl -s -o /tmp/upload_response_${FILE_INDEX}.json -w "%{http_code}" -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITEA_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) uploaded successfully"
|
||||
else
|
||||
echo "⚠️ WARNING: $(basename $file) upload failed (HTTP $HTTP_CODE)"
|
||||
fi
|
||||
FILE_INDEX=$((FILE_INDEX + 1))
|
||||
else
|
||||
echo "⚠️ WARNING: File not found: $file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ All attachments processed"
|
||||
echo ""
|
||||
|
||||
- name: 📊 Workflow summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📊 Generating workflow summary"
|
||||
echo "======================================"
|
||||
|
||||
echo "## 📦 Workflow Execution Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**🏷️ Tag:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**📂 Repository:** \`${{ github.repository }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**🕒 Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### ⚙️ Configuration" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Gitea Server:** \`$GITEA_SERVER\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Release Title:** \`$RELEASE_TITLE\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "$RELEASE_IS_PRERELEASE" = "true" ]; then
|
||||
echo "- **Release Type:** 🏷️ Pre-release" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "- **Release Type:** ✅ Latest (Stable)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "$RELEASE_IS_DRAFT" = "true" ]; then
|
||||
echo "- **Draft:** 📄 Yes" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
|
||||
echo "⏭️ **Status:** Skipped (bot commit detected)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The workflow was skipped to prevent infinite loops." >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
|
||||
echo "⏭️ **Status:** Skipped (version already exists)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Version \`${{ github.ref_name }}\` already exists in CHANGELOG.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
||||
echo "✅ **Status:** Success" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Completed Actions" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ CHANGELOG.md updated" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ Changes pushed to main branch" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ Release created" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ CHANGELOG.md attached to release" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -n "$ADDITIONAL_RELEASE_FILES" ]; then
|
||||
echo "- ✅ Additional files uploaded" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ **Status:** No updates" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No valid commits found between tags." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Workflow summary generated"
|
||||
|
|
@ -1,735 +0,0 @@
|
|||
name: 📦 Tag Release Automation
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-9]*"
|
||||
|
||||
# ========================================
|
||||
# 📝 全局配置区域 - 在此修改所有配置
|
||||
# ========================================
|
||||
env:
|
||||
# ========================================
|
||||
# 🔐 认证配置
|
||||
# ========================================
|
||||
|
||||
# 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 }}
|
||||
|
||||
# ========================================
|
||||
# 🌐 服务器配置
|
||||
# ========================================
|
||||
|
||||
# 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 提交消息模板
|
||||
# 可用变量: {version} - 将被替换为实际的版本号
|
||||
# [skip ci] 标记防止触发无限循环
|
||||
COMMIT_MESSAGE: ":memo: Auto update CHANGELOG for {version} [skip ci]"
|
||||
|
||||
# ========================================
|
||||
# 🚫 提交过滤配置
|
||||
# ========================================
|
||||
|
||||
# 需要忽略的提交模式(支持正则表达式)
|
||||
# 这些提交不会被添加到 CHANGELOG 中
|
||||
# 使用 | 分隔多个模式
|
||||
IGNORE_PATTERNS: >-
|
||||
^Merge |
|
||||
^:memo: Auto update CHANGELOG|
|
||||
\[skip ci\]|
|
||||
\[ci skip\]|
|
||||
^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
|
||||
steps:
|
||||
- name: ⚙️ Configuration
|
||||
id: config
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "⚙️ Loading workflow configuration"
|
||||
echo "======================================"
|
||||
|
||||
# 从环境变量读取配置
|
||||
VERSION="${{ github.ref_name }}"
|
||||
|
||||
# 处理 CHANGELOG 版本号
|
||||
if [[ "$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="${RELEASE_TITLE//\{version\}/$VERSION}"
|
||||
|
||||
# 处理提交消息
|
||||
COMMIT_MSG="${COMMIT_MESSAGE//\{version\}/$VERSION}"
|
||||
|
||||
# 智能判断 pre-release
|
||||
if [[ "$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="$RELEASE_PRERELEASE_MODE"
|
||||
PRERELEASE_DETECTED="$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 <<< "$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: $GITEA_SERVER"
|
||||
echo " 📄 CHANGELOG Title: $CHANGELOG_SECTION_TITLE"
|
||||
echo " 🚀 Release Title: $RELEASE_TITLE_PROCESSED"
|
||||
echo " 🏷️ Pre-release: $PRERELEASE_DETECTED"
|
||||
echo " 📄 Draft: $RELEASE_IS_DRAFT"
|
||||
echo " 💬 Commit Message: $COMMIT_MSG"
|
||||
echo " 📎 Additional Files: ${ADDITIONAL_RELEASE_FILES:-none}"
|
||||
echo ""
|
||||
echo "✅ Configuration loaded successfully"
|
||||
echo ""
|
||||
|
||||
- name: 📂 Checkout repository
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🚀 Starting repository checkout"
|
||||
echo "======================================"
|
||||
|
||||
REPO_NAME="${{ github.event.repository.name }}"
|
||||
REPO_DIR="/home/workspace/$REPO_NAME"
|
||||
|
||||
echo "📁 Repository: $REPO_NAME"
|
||||
echo "📍 Target directory: $REPO_DIR"
|
||||
|
||||
if [ -d "$REPO_DIR/.git" ]; then
|
||||
echo "✓ Repository exists, updating..."
|
||||
cd "$REPO_DIR"
|
||||
git clean -fdx
|
||||
git reset --hard
|
||||
git fetch --all --tags --force
|
||||
echo "✓ Repository updated"
|
||||
else
|
||||
echo "📥 Cloning repository..."
|
||||
mkdir -p /home/workspace
|
||||
git clone https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git "$REPO_DIR"
|
||||
cd "$REPO_DIR"
|
||||
echo "✓ Repository cloned"
|
||||
fi
|
||||
|
||||
echo "🏷️ Checking out tag: ${{ github.ref_name }}"
|
||||
git checkout -f ${{ github.ref_name }}
|
||||
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||
|
||||
echo "✅ Checkout completed"
|
||||
echo ""
|
||||
|
||||
- name: 🔍 Check if bot commit
|
||||
id: check_bot
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🤖 Checking commit author"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
LAST_AUTHOR=$(git log -1 --pretty=format:'%an')
|
||||
|
||||
echo "👤 Last commit author: $LAST_AUTHOR"
|
||||
|
||||
if [[ "$LAST_AUTHOR" == "github-actions[bot]" ]]; then
|
||||
echo "⚠️ Bot commit detected - skipping to prevent loop"
|
||||
echo "is_bot_commit=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✅ Not a bot commit"
|
||||
echo "is_bot_commit=false" >> $GITHUB_OUTPUT
|
||||
echo ""
|
||||
|
||||
- name: 🔍 Check version exists
|
||||
id: check_version
|
||||
if: steps.check_bot.outputs.is_bot_commit != 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🔍 Checking if version exists"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
if [ ! -f "CHANGELOG.md" ]; then
|
||||
echo "📝 CHANGELOG.md not found - will create new one"
|
||||
echo "version_exists=false" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CHANGELOG_VERSION="${{ env.CHANGELOG_VERSION }}"
|
||||
echo "🔎 Searching for version: $CHANGELOG_VERSION"
|
||||
|
||||
if grep -q "^## :bookmark: $CHANGELOG_VERSION" CHANGELOG.md; then
|
||||
echo "⚠️ Version $CHANGELOG_VERSION already exists in CHANGELOG"
|
||||
echo "version_exists=true" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "✅ Version $CHANGELOG_VERSION not found - will proceed"
|
||||
echo "version_exists=false" >> $GITHUB_OUTPUT
|
||||
echo ""
|
||||
|
||||
- name: 📝 Generate CHANGELOG
|
||||
id: changelog
|
||||
if: steps.check_bot.outputs.is_bot_commit != 'true' && steps.check_version.outputs.version_exists != 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📝 Generating CHANGELOG"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
# 获取前一个 tag
|
||||
CURRENT_TAG="${{ github.ref_name }}"
|
||||
echo "🏷️ Current tag: $CURRENT_TAG"
|
||||
|
||||
PREVIOUS_TAG=$(git tag --sort=-version:refname | grep -A 1 "^${CURRENT_TAG}$" | tail -n 1)
|
||||
|
||||
if [ -z "$PREVIOUS_TAG" ] || [ "$PREVIOUS_TAG" = "$CURRENT_TAG" ]; then
|
||||
echo "📌 No previous tag found, using initial commit"
|
||||
RANGE=$(git rev-list --max-parents=0 HEAD)..HEAD
|
||||
echo " Range: (initial)..HEAD"
|
||||
else
|
||||
echo "📌 Previous tag: $PREVIOUS_TAG"
|
||||
RANGE="${PREVIOUS_TAG}..${CURRENT_TAG}"
|
||||
echo " Range: $RANGE"
|
||||
fi
|
||||
|
||||
# 生成 CHANGELOG
|
||||
python3 << 'PYSCRIPT'
|
||||
import subprocess
|
||||
import re
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# 读取环境变量配置
|
||||
gitea_server = os.environ.get('GITEA_SERVER', '')
|
||||
section_title = os.environ.get('CHANGELOG_SECTION_TITLE', '### :pencil: What\'s Changed')
|
||||
contributors_title = os.environ.get('CHANGELOG_CONTRIBUTORS_TITLE', '### :busts_in_silhouette: Contributors')
|
||||
changelog_version = os.environ.get('CHANGELOG_VERSION', '')
|
||||
|
||||
# 读取忽略模式
|
||||
ignore_patterns_json = os.environ.get('IGNORE_PATTERNS_JSON', '[]')
|
||||
ignore_patterns = json.loads(ignore_patterns_json)
|
||||
|
||||
print(f"📋 Configuration:")
|
||||
print(f" Gitea Server: {gitea_server}")
|
||||
print(f" Section Title: {section_title}")
|
||||
print(f" Contributors Title: {contributors_title}")
|
||||
print(f" CHANGELOG Version: {changelog_version}")
|
||||
print(f" Ignore Patterns: {len(ignore_patterns)} patterns loaded")
|
||||
print()
|
||||
|
||||
# 获取 commit 范围
|
||||
git_range = os.environ.get('RANGE', 'HEAD')
|
||||
print(f"📊 Git range: {git_range}")
|
||||
|
||||
# 获取 commits
|
||||
result = subprocess.run(
|
||||
['git', 'log', git_range, '--pretty=format:%H|%an|%ae|%s'],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
|
||||
commits = []
|
||||
authors_dict = {}
|
||||
|
||||
print(f"📝 Processing commits...")
|
||||
for line in result.stdout.strip().split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
|
||||
parts = line.split('|', 3)
|
||||
if len(parts) != 4:
|
||||
continue
|
||||
|
||||
commit_hash, author_name, author_email, message = parts
|
||||
|
||||
# 检查是否应该忽略此提交
|
||||
should_ignore = False
|
||||
for pattern in ignore_patterns:
|
||||
if re.search(pattern, message):
|
||||
should_ignore = True
|
||||
print(f" ⏭️ Skipping: {message[:60]}... (matched: {pattern})")
|
||||
break
|
||||
|
||||
if should_ignore:
|
||||
continue
|
||||
|
||||
# 记录作者
|
||||
if author_email not in authors_dict:
|
||||
authors_dict[author_email] = author_name
|
||||
|
||||
commits.append({
|
||||
'hash': commit_hash[:7],
|
||||
'author': author_name,
|
||||
'email': author_email,
|
||||
'message': message
|
||||
})
|
||||
print(f" ✓ Added: {message[:60]}...")
|
||||
|
||||
print(f"\n✅ Total valid commits: {len(commits)}")
|
||||
print(f"✅ Total contributors: {len(authors_dict)}")
|
||||
|
||||
if not commits:
|
||||
print("⚠️ No valid commits found")
|
||||
print("changelog_updated=false", file=open(os.environ['GITHUB_OUTPUT'], 'a'))
|
||||
exit(0)
|
||||
|
||||
# 生成 CHANGELOG 内容
|
||||
print("\n📄 Generating CHANGELOG content...")
|
||||
|
||||
changelog_lines = [
|
||||
f"## :bookmark: {changelog_version}",
|
||||
"",
|
||||
f"**:calendar: Release Date:** {datetime.now().strftime('%Y-%m-%d')}",
|
||||
"",
|
||||
section_title,
|
||||
""
|
||||
]
|
||||
|
||||
for commit in commits:
|
||||
changelog_lines.append(f"- {commit['message']} ([`{commit['hash']}`](../../commit/{commit['hash']}))")
|
||||
|
||||
# 添加贡献者列表
|
||||
changelog_lines.extend([
|
||||
"",
|
||||
contributors_title,
|
||||
""
|
||||
])
|
||||
|
||||
for email, name in sorted(authors_dict.items(), key=lambda x: x[1].lower()):
|
||||
if gitea_server:
|
||||
avatar_url = f"{gitea_server}/avatars/{email.replace('@', '%40')}"
|
||||
changelog_lines.append(f"- [{name}]({avatar_url})")
|
||||
else:
|
||||
changelog_lines.append(f"- {name}")
|
||||
|
||||
changelog_content = '\n'.join(changelog_lines)
|
||||
|
||||
# 更新或创建 CHANGELOG.md
|
||||
print("\n📝 Updating CHANGELOG.md...")
|
||||
changelog_file = 'CHANGELOG.md'
|
||||
|
||||
if os.path.exists(changelog_file):
|
||||
with open(changelog_file, 'r', encoding='utf-8') as f:
|
||||
existing_content = f.read()
|
||||
|
||||
# 在文件开头插入新内容
|
||||
if existing_content.strip():
|
||||
new_content = f"{changelog_content}\n\n---\n\n{existing_content}"
|
||||
else:
|
||||
new_content = changelog_content
|
||||
else:
|
||||
print("📝 Creating new CHANGELOG.md")
|
||||
new_content = f"# Changelog\n\n{changelog_content}"
|
||||
|
||||
with open(changelog_file, 'w', encoding='utf-8') as f:
|
||||
f.write(new_content)
|
||||
|
||||
print("✅ CHANGELOG.md updated successfully")
|
||||
print(f" Lines added: {len(changelog_lines)}")
|
||||
print(f" Commits: {len(commits)}")
|
||||
print(f" Contributors: {len(authors_dict)}")
|
||||
|
||||
# 设置输出
|
||||
print("changelog_updated=true", file=open(os.environ['GITHUB_OUTPUT'], 'a'))
|
||||
PYSCRIPT
|
||||
|
||||
echo ""
|
||||
echo "✅ CHANGELOG generation completed"
|
||||
echo ""
|
||||
|
||||
- name: 📤 Commit and push CHANGELOG
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📤 Committing and pushing CHANGELOG"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
# 配置 Git
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# 切换到 main 分支
|
||||
echo "🔄 Switching to main branch..."
|
||||
git fetch origin main
|
||||
git checkout main
|
||||
git pull origin main
|
||||
|
||||
# 提交更改
|
||||
echo "📝 Committing changes..."
|
||||
git add CHANGELOG.md
|
||||
git commit -m "${{ env.COMMIT_MSG }}"
|
||||
|
||||
# 推送更改
|
||||
echo "📤 Pushing to remote..."
|
||||
git push https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git main
|
||||
|
||||
echo "✅ CHANGELOG pushed successfully"
|
||||
echo ""
|
||||
|
||||
- name: 📋 Prepare release notes
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📋 Preparing release notes"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
echo "📖 Extracting version content from CHANGELOG..."
|
||||
|
||||
python3 << 'PYSCRIPT'
|
||||
import re
|
||||
import os
|
||||
|
||||
changelog_version = os.environ.get('CHANGELOG_VERSION', '')
|
||||
print(f"🔍 Looking for version: {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 ""
|
||||
|
||||
- name: 🚀 Create release
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🚀 Creating GitHub/Gitea release"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
echo "📦 Preparing release payload..."
|
||||
echo "📋 Release title: ${{ env.RELEASE_TITLE_PROCESSED }}"
|
||||
echo "🏷️ Pre-release: ${{ env.RELEASE_IS_PRERELEASE }}"
|
||||
echo "📄 Draft: ${{ env.RELEASE_IS_DRAFT }}"
|
||||
|
||||
# 转换 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 "🔄 Converting: draft=${{ env.RELEASE_IS_DRAFT }} → $PY_DRAFT, prerelease=${{ env.RELEASE_IS_PRERELEASE }} → $PY_PRERELEASE"
|
||||
|
||||
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 "🌐 Sending API request..."
|
||||
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 "📡 API Response (HTTP $HTTP_CODE):"
|
||||
echo "---"
|
||||
cat /tmp/release_response.json | python3 -m json.tool 2>/dev/null || cat /tmp/release_response.json
|
||||
echo "---"
|
||||
|
||||
if [ "$HTTP_CODE" != "201" ] && [ "$HTTP_CODE" != "200" ]; then
|
||||
echo "❌ ERROR: Failed to create release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Release created successfully"
|
||||
echo ""
|
||||
|
||||
- name: 📎 Upload CHANGELOG attachment
|
||||
if: steps.changelog.outputs.changelog_updated == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📎 Uploading release attachments"
|
||||
echo "======================================"
|
||||
|
||||
cd ${{ env.REPO_DIR }}
|
||||
|
||||
echo "🔍 Extracting 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("❌ ERROR: No 'id' field in response", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(data['id'])
|
||||
except Exception as e:
|
||||
print(f"❌ ERROR: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
PYSCRIPT
|
||||
)
|
||||
|
||||
if [ -z "$RELEASE_ID" ]; then
|
||||
echo "❌ ERROR: Failed to get release ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Release ID: $RELEASE_ID"
|
||||
echo ""
|
||||
|
||||
# 上传额外的文件
|
||||
if [ -n "${{ env.ADDITIONAL_RELEASE_FILES }}" ]; then
|
||||
echo ""
|
||||
echo "📦 Uploading additional files..."
|
||||
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] Uploading $(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) uploaded successfully"
|
||||
else
|
||||
echo "⚠️ WARNING: $(basename $file) upload failed (HTTP $HTTP_CODE)"
|
||||
fi
|
||||
FILE_INDEX=$((FILE_INDEX + 1))
|
||||
else
|
||||
echo "⚠️ WARNING: File not found: $file"
|
||||
fi
|
||||
done
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ All attachments processed"
|
||||
echo ""
|
||||
|
||||
- name: 📊 Workflow summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📊 Generating workflow summary"
|
||||
echo "======================================"
|
||||
|
||||
echo "## 📦 Workflow Execution Summary" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**🏷️ Tag:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**📂 Repository:** \`${{ github.repository }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**🕒 Time:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### ⚙️ Configuration" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Gitea Server:** \`${{ env.GITEA_SERVER }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- **Release Title:** \`${{ env.RELEASE_TITLE_PROCESSED }}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ env.RELEASE_IS_PRERELEASE }}" = "true" ]; then
|
||||
echo "- **Release Type:** 🏷️ Pre-release" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "- **Release Type:** ✅ Latest (Stable)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "${{ env.RELEASE_IS_DRAFT }}" = "true" ]; then
|
||||
echo "- **Draft:** 📄 Yes" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
if [ "${{ steps.check_bot.outputs.is_bot_commit }}" = "true" ]; then
|
||||
echo "⏭️ **Status:** Skipped (bot commit detected)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The workflow was skipped to prevent infinite loops." >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check_version.outputs.version_exists }}" = "true" ]; then
|
||||
echo "⏭️ **Status:** Skipped (version already exists)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Version \`${{ github.ref_name }}\` already exists in CHANGELOG.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.changelog.outputs.changelog_updated }}" = "true" ]; then
|
||||
echo "✅ **Status:** Success" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### Completed Actions" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ CHANGELOG.md updated" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ Changes pushed to main branch" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ Release created" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- ✅ CHANGELOG.md attached to release" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -n "${{ env.ADDITIONAL_RELEASE_FILES }}" ]; then
|
||||
echo "- ✅ Additional files uploaded" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
else
|
||||
echo "ℹ️ **Status:** No updates" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No valid commits found between tags." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "✅ Workflow summary generated"
|
||||
|
|
@ -1,929 +0,0 @@
|
|||
name: Update Stats Badege
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, master]
|
||||
workflow_dispatch:
|
||||
|
||||
# ==========================================
|
||||
# 🔧 配置区域 - 根据你的项目修改
|
||||
# ==========================================
|
||||
env:
|
||||
# ===== Token 配置 =====
|
||||
# 优先使用 STATS_TOKEN,不存在则使用 GITHUB_TOKEN
|
||||
# 建议在 Settings -> Secrets 中配置 STATS_TOKEN 以获得更好的权限控制
|
||||
ACCESS_TOKEN: ${{ secrets.STATS_TOKEN }}
|
||||
|
||||
# ===== 工作区配置 =====
|
||||
# 完整克隆的工作目录
|
||||
WORKSPACE_DIR: '/home/workspace/'
|
||||
|
||||
# ===== 分支配置 =====
|
||||
# 徽章数据存储分支(可配置)
|
||||
BADGE_BRANCH: 'stats'
|
||||
# 徽章文件存储目录
|
||||
BADGE_DIR: 'badges'
|
||||
|
||||
# ===== 排除配置 =====
|
||||
# 全局排除的目录(这些目录会被完全忽略)
|
||||
EXCLUDE_DIRS: 'node_modules,dist,build,out,target,vendor,.venv,venv,__pycache__,.git,.github'
|
||||
|
||||
# 特殊包含规则(即使在排除目录中,也统计这些扩展名的文件)
|
||||
# 格式: 目录:扩展名列表
|
||||
# 例如: 'dist:js,css' 表示即使 dist 被排除,也统计其中的 .js 和 .css 文件
|
||||
SPECIAL_INCLUDES: ''
|
||||
# 示例: 'dist:js,css|vendor:go,mod'
|
||||
|
||||
# ===== 徽章颜色配置 =====
|
||||
COLOR_TOTAL: 'blue'
|
||||
COLOR_FILES: 'green'
|
||||
COLOR_DEFAULT: 'brightgreen'
|
||||
|
||||
# ===== 徽章样式配置 =====
|
||||
BADGE_STYLE: 'flat' # 可选: flat, flat-square, plastic, for-the-badge, social
|
||||
|
||||
# ===== 输出配置 =====
|
||||
# 是否生成详细报告
|
||||
GENERATE_DETAILED_REPORT: 'true'
|
||||
# 是否输出到 workflow summary
|
||||
OUTPUT_TO_SUMMARY: 'true'
|
||||
# 最小代码行数阈值(低于此值的语言不生成徽章)
|
||||
MIN_LINES_THRESHOLD: '10'
|
||||
|
||||
# ===== Git 配置 =====
|
||||
GIT_USER_NAME: 'github-actions[bot]'
|
||||
GIT_USER_EMAIL: 'github-actions[bot]@users.noreply.github.com'
|
||||
|
||||
# ==========================================
|
||||
# 🎨 语言分组配置
|
||||
# 格式: 组名:后缀列表:显示名称:颜色:图标(可选)
|
||||
# 图标使用 simple-icons 的名称,如 cplusplus, typescript 等
|
||||
# ==========================================
|
||||
LANGUAGE_GROUPS: |
|
||||
cpp:hpp,cpp,cxx,cc,h,c:C/C++:00599C:cplusplus
|
||||
python:py:Python:3572A5:python
|
||||
typescript:ts,tsx:TypeScript:3178c6:typescript
|
||||
javascript:js,jsx:JavaScript:F7DF1E:javascript
|
||||
java:java:Java:007396:java
|
||||
go:go:Go:00ADD8:go
|
||||
rust:rs:Rust:CE412B:rust
|
||||
shell:sh,bash:Shell:4EAA25:gnubash
|
||||
yaml:yml,yaml:YAML:CB171E
|
||||
tsf:tsl,tsf:TSF:9945FF
|
||||
|
||||
jobs:
|
||||
update-stats:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: 🔍 验证 Token 配置
|
||||
id: validate_token
|
||||
run: |
|
||||
echo "🔐 验证访问令牌..."
|
||||
|
||||
if [ -z "${{ env.ACCESS_TOKEN }}" ]; then
|
||||
echo "❌ 错误: 未配置访问令牌"
|
||||
echo "请在 Settings -> Secrets 中配置 STATS_TOKEN 或确保 GITHUB_TOKEN 可用"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 检测使用的是哪个 token
|
||||
if [ -n "${{ secrets.STATS_TOKEN }}" ]; then
|
||||
echo "✅ 使用自定义 STATS_TOKEN"
|
||||
echo "token_type=STATS_TOKEN" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "✅ 使用默认 GITHUB_TOKEN"
|
||||
echo "token_type=GITHUB_TOKEN" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
echo "🔗 仓库: ${{ github.repository }}"
|
||||
echo "🌿 分支: ${{ github.ref_name }}"
|
||||
echo "📝 提交: ${GITHUB_SHA:0:7}"
|
||||
|
||||
- name: 📥 克隆主仓库(完整模式)
|
||||
id: clone_main
|
||||
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 "🧹 清理已存在的目录..."
|
||||
rm -rf "$REPO_DIR"
|
||||
fi
|
||||
|
||||
# 创建工作目录
|
||||
mkdir -p "${{ env.WORKSPACE_DIR }}"
|
||||
|
||||
# 克隆仓库(使用 TOKEN)
|
||||
echo "📥 克隆仓库..."
|
||||
git clone \
|
||||
--depth 0 \
|
||||
--single-branch \
|
||||
--branch ${{ github.ref_name }} \
|
||||
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 "✅ 仓库克隆成功"
|
||||
echo ""
|
||||
|
||||
# 配置 Git 用户信息
|
||||
echo "⚙️ 配置 Git 用户信息..."
|
||||
git config user.name "${{ env.GIT_USER_NAME }}"
|
||||
git config user.email "${{ env.GIT_USER_EMAIL }}"
|
||||
|
||||
# 显示仓库信息
|
||||
echo "📊 仓库信息:"
|
||||
echo " - 当前分支: $(git branch --show-current)"
|
||||
echo " - 最新提交: $(git log -1 --oneline)"
|
||||
echo " - 远程地址: $(git remote get-url origin | sed 's/oauth2:.*@/oauth2:***@/')"
|
||||
echo ""
|
||||
|
||||
# 导出环境变量
|
||||
echo "REPO_DIR=$REPO_DIR" >> $GITHUB_ENV
|
||||
echo "REPO_NAME=$REPO_NAME" >> $GITHUB_ENV
|
||||
|
||||
echo "✅ 主仓库准备完成"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 🔧 准备统计分支
|
||||
id: prepare_stats_branch
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🔧 准备统计分支"
|
||||
echo "======================================"
|
||||
|
||||
cd "${{ env.REPO_DIR }}"
|
||||
|
||||
# 获取所有远程分支
|
||||
git fetch origin --prune
|
||||
|
||||
# 检查统计分支是否存在
|
||||
if git ls-remote --heads origin ${{ env.BADGE_BRANCH }} | grep -q ${{ env.BADGE_BRANCH }}; then
|
||||
echo "✅ 统计分支 '${{ env.BADGE_BRANCH }}' 已存在"
|
||||
|
||||
# 检出统计分支
|
||||
git fetch origin ${{ env.BADGE_BRANCH }}:${{ env.BADGE_BRANCH }}
|
||||
git checkout ${{ env.BADGE_BRANCH }}
|
||||
|
||||
echo "📂 当前分支内容:"
|
||||
ls -la
|
||||
|
||||
# 返回主分支
|
||||
git checkout ${{ github.ref_name }}
|
||||
|
||||
echo "branch_exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "🆕 统计分支 '${{ env.BADGE_BRANCH }}' 不存在,将创建"
|
||||
echo "branch_exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 🆕 创建统计分支(如需要)
|
||||
if: steps.prepare_stats_branch.outputs.branch_exists == 'false'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🆕 创建新的统计分支"
|
||||
echo "======================================"
|
||||
|
||||
cd "${{ env.REPO_DIR }}"
|
||||
|
||||
# 创建孤立分支
|
||||
git checkout --orphan ${{ env.BADGE_BRANCH }}
|
||||
|
||||
# 清空所有文件
|
||||
git rm -rf . 2>/dev/null || true
|
||||
rm -rf * .* 2>/dev/null || true
|
||||
|
||||
# 创建初始 README
|
||||
cat > README.md << 'EOF'
|
||||
# 📊 代码统计徽章数据
|
||||
|
||||
> 此分支由 GitHub Actions 自动生成和维护
|
||||
>
|
||||
> ⚠️ **请勿手动修改此分支的内容!**
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
badges/
|
||||
├── total-lines.json # 总代码行数徽章
|
||||
├── total-files.json # 总文件数徽章
|
||||
├── {language}-lines.json # 各语言代码行数徽章
|
||||
├── {language}-files.json # 各语言文件数徽章
|
||||
└── README.md # 详细统计报告
|
||||
```
|
||||
|
||||
## 🔄 更新机制
|
||||
|
||||
- ✅ 每次 push 到主分支时自动更新
|
||||
- ✅ 每天 UTC 02:00 定时更新
|
||||
- ✅ 可通过 workflow_dispatch 手动触发
|
||||
|
||||
## 📊 数据来源
|
||||
|
||||
统计数据由 [cloc](https://github.com/AlDanial/cloc) 工具生成,包含以下信息:
|
||||
|
||||
- 代码行数(不含注释和空行)
|
||||
- 文件数量
|
||||
- 各编程语言占比
|
||||
|
||||
## 🎨 徽章使用
|
||||
|
||||
请查看 `badges/README.md` 获取详细的徽章使用说明。
|
||||
|
||||
---
|
||||
|
||||
*🤖 自动生成于: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*
|
||||
EOF
|
||||
|
||||
# 添加并提交
|
||||
git add README.md
|
||||
git commit -m "chore: 初始化统计分支 ${{ env.BADGE_BRANCH }}"
|
||||
|
||||
# 推送到远程
|
||||
echo "📤 推送到远程..."
|
||||
git push https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git ${{ env.BADGE_BRANCH }}
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 统计分支创建成功"
|
||||
else
|
||||
echo "❌ 推送失败"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 返回主分支
|
||||
git checkout ${{ github.ref_name }}
|
||||
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 🔍 检查并安装依赖
|
||||
id: check_deps
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🔍 检查必要的工具"
|
||||
echo "======================================"
|
||||
|
||||
MISSING_TOOLS=()
|
||||
|
||||
# 检查 cloc
|
||||
if ! command -v cloc &> /dev/null; then
|
||||
echo "⚠️ cloc 未安装"
|
||||
MISSING_TOOLS+=("cloc")
|
||||
else
|
||||
echo "✅ cloc 已安装: $(cloc --version | head -n1)"
|
||||
fi
|
||||
|
||||
# 检查 jq
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "⚠️ jq 未安装"
|
||||
MISSING_TOOLS+=("jq")
|
||||
else
|
||||
echo "✅ jq 已安装: $(jq --version)"
|
||||
fi
|
||||
|
||||
# 检查 bc
|
||||
if ! command -v bc &> /dev/null; then
|
||||
echo "⚠️ bc 未安装"
|
||||
MISSING_TOOLS+=("bc")
|
||||
else
|
||||
echo "✅ bc 已安装"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# 如果有缺失的工具,进行安装
|
||||
if [ ${#MISSING_TOOLS[@]} -gt 0 ]; then
|
||||
echo "📦 安装缺失的工具: ${MISSING_TOOLS[*]}"
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y ${MISSING_TOOLS[@]}
|
||||
echo "✅ 工具安装完成"
|
||||
echo "installed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "✅ 所有必要工具已就绪"
|
||||
echo "installed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📋 最终工具版本:"
|
||||
echo " - cloc: $(cloc --version | head -n1)"
|
||||
echo " - jq: $(jq --version)"
|
||||
echo " - bc: $(bc --version | head -n1)"
|
||||
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 📊 统计总代码行数
|
||||
id: total
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📊 统计总代码行数"
|
||||
echo "======================================"
|
||||
|
||||
cd "${{ env.REPO_DIR }}"
|
||||
|
||||
# 构建排除参数
|
||||
EXCLUDE_ARG=""
|
||||
if [ -n "${{ env.EXCLUDE_DIRS }}" ]; then
|
||||
EXCLUDE_ARG="--exclude-dir=$(echo "${{ env.EXCLUDE_DIRS }}" | sed 's/ //g')"
|
||||
fi
|
||||
|
||||
echo "🔍 统计参数:"
|
||||
echo " - 目录: $(pwd)"
|
||||
echo " - 排除: ${{ env.EXCLUDE_DIRS }}"
|
||||
echo ""
|
||||
|
||||
# 执行统计
|
||||
echo "⏳ 正在统计..."
|
||||
cloc . $EXCLUDE_ARG --json > /tmp/total_stats.json || {
|
||||
echo "⚠️ cloc 执行失败,使用备用方案"
|
||||
echo '{"SUM":{"code":0},"header":{"n_files":0}}' > /tmp/total_stats.json
|
||||
}
|
||||
|
||||
TOTAL_CODE=$(jq '.SUM.code // 0' /tmp/total_stats.json)
|
||||
TOTAL_FILES=$(jq '.header.n_files // 0' /tmp/total_stats.json)
|
||||
|
||||
# 格式化数字(添加千位分隔符)
|
||||
format_num() {
|
||||
printf "%'d" "$1" 2>/dev/null || echo "$1"
|
||||
}
|
||||
|
||||
echo "total_code=$TOTAL_CODE" >> $GITHUB_OUTPUT
|
||||
echo "total_files=$TOTAL_FILES" >> $GITHUB_OUTPUT
|
||||
echo "formatted_code=$(format_num $TOTAL_CODE)" >> $GITHUB_OUTPUT
|
||||
echo "formatted_files=$(format_num $TOTAL_FILES)" >> $GITHUB_OUTPUT
|
||||
|
||||
echo ""
|
||||
echo "📈 统计结果:"
|
||||
echo " - 总代码行数: $(format_num $TOTAL_CODE) 行"
|
||||
echo " - 总文件数: $(format_num $TOTAL_FILES) 个"
|
||||
|
||||
# 保存原始数据供后续使用
|
||||
cat /tmp/total_stats.json | jq '.' > /tmp/total_stats_backup.json
|
||||
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 📊 按语言分组统计
|
||||
id: languages
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📊 按语言分组统计"
|
||||
echo "======================================"
|
||||
|
||||
cd "${{ env.REPO_DIR }}"
|
||||
|
||||
# 构建排除参数
|
||||
EXCLUDE_ARG=""
|
||||
if [ -n "${{ env.EXCLUDE_DIRS }}" ]; then
|
||||
EXCLUDE_ARG="--exclude-dir=$(echo "${{ env.EXCLUDE_DIRS }}" | sed 's/ //g')"
|
||||
fi
|
||||
|
||||
# 创建临时目录
|
||||
mkdir -p /tmp/lang_stats
|
||||
rm -f /tmp/lang_summary.txt
|
||||
|
||||
# 格式化数字函数
|
||||
format_num() {
|
||||
printf "%'d" "$1" 2>/dev/null || echo "$1"
|
||||
}
|
||||
|
||||
# 读取语言分组配置并统计
|
||||
LANG_COUNT=0
|
||||
MIN_THRESHOLD=${{ env.MIN_LINES_THRESHOLD }}
|
||||
|
||||
echo "⚙️ 配置信息:"
|
||||
echo " - 最小阈值: $MIN_THRESHOLD 行"
|
||||
echo ""
|
||||
|
||||
echo "${{ env.LANGUAGE_GROUPS }}" | while IFS= read -r line; do
|
||||
[ -z "$line" ] && continue
|
||||
|
||||
# 解析配置: 组名:后缀:显示名:颜色:图标(可选)
|
||||
LANG_ID=$(echo "$line" | cut -d':' -f1 | tr -d ' ')
|
||||
EXTENSIONS=$(echo "$line" | cut -d':' -f2 | tr -d ' ')
|
||||
DISPLAY_NAME=$(echo "$line" | cut -d':' -f3 | tr -d ' ')
|
||||
COLOR=$(echo "$line" | cut -d':' -f4 | tr -d ' ')
|
||||
ICON=$(echo "$line" | cut -d':' -f5 | tr -d ' ')
|
||||
|
||||
[ -z "$LANG_ID" ] && continue
|
||||
|
||||
echo "📝 统计 $DISPLAY_NAME ($EXTENSIONS)..."
|
||||
|
||||
# 统计该语言组
|
||||
cloc . $EXCLUDE_ARG \
|
||||
--include-ext="$EXTENSIONS" \
|
||||
--json > /tmp/lang_stats/${LANG_ID}.json 2>/dev/null || {
|
||||
echo " ⚠️ 统计失败,跳过"
|
||||
continue
|
||||
}
|
||||
|
||||
CODE_LINES=$(jq '.SUM.code // 0' /tmp/lang_stats/${LANG_ID}.json)
|
||||
FILE_COUNT=$(jq '.header.n_files // 0' /tmp/lang_stats/${LANG_ID}.json)
|
||||
|
||||
# 如果代码行数低于阈值,跳过
|
||||
if [ "$CODE_LINES" -lt "$MIN_THRESHOLD" ]; then
|
||||
echo " ⏭️ 代码行数 ($CODE_LINES) 低于阈值,跳过"
|
||||
continue
|
||||
fi
|
||||
|
||||
FORMATTED_LINES=$(format_num $CODE_LINES)
|
||||
|
||||
echo " ✅ ${DISPLAY_NAME}: $FORMATTED_LINES 行 ($FILE_COUNT 文件)"
|
||||
|
||||
# 保存数据(添加图标字段)
|
||||
echo "${LANG_ID}|${DISPLAY_NAME}|${CODE_LINES}|${FORMATTED_LINES}|${FILE_COUNT}|${COLOR}|${ICON}" >> /tmp/lang_summary.txt
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# 统计有效语言数量
|
||||
if [ -f /tmp/lang_summary.txt ]; then
|
||||
VALID_LANG_COUNT=$(wc -l < /tmp/lang_summary.txt)
|
||||
echo "language_count=$VALID_LANG_COUNT" >> $GITHUB_OUTPUT
|
||||
echo "✅ 语言统计完成: $VALID_LANG_COUNT 种语言"
|
||||
else
|
||||
echo "language_count=0" >> $GITHUB_OUTPUT
|
||||
echo "⚠️ 未找到符合条件的语言"
|
||||
fi
|
||||
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 🎨 生成徽章数据
|
||||
id: generate_badges
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "🎨 生成徽章数据"
|
||||
echo "======================================"
|
||||
|
||||
cd "${{ env.REPO_DIR }}"
|
||||
|
||||
# 切换到统计分支
|
||||
echo "🔄 切换到统计分支..."
|
||||
git fetch origin ${{ env.BADGE_BRANCH }}:${{ env.BADGE_BRANCH }}
|
||||
git checkout ${{ env.BADGE_BRANCH }}
|
||||
|
||||
# 创建徽章目录
|
||||
mkdir -p ${{ env.BADGE_DIR }}
|
||||
|
||||
# 清理旧的徽章文件
|
||||
echo "🧹 清理旧徽章..."
|
||||
rm -f ${{ env.BADGE_DIR }}/*.json
|
||||
|
||||
GENERATED_COUNT=0
|
||||
|
||||
echo ""
|
||||
echo "📝 生成徽章文件..."
|
||||
|
||||
# 1. 总代码行数徽章
|
||||
cat > ${{ env.BADGE_DIR }}/total-lines.json << EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "总代码",
|
||||
"message": "${{ steps.total.outputs.formatted_code }} 行",
|
||||
"color": "${{ env.COLOR_TOTAL }}",
|
||||
"style": "${{ env.BADGE_STYLE }}"
|
||||
}
|
||||
EOF
|
||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
||||
echo " ✅ total-lines.json"
|
||||
|
||||
# 2. 总文件数徽章
|
||||
cat > ${{ env.BADGE_DIR }}/total-files.json << EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "文件数",
|
||||
"message": "${{ steps.total.outputs.formatted_files }} 个",
|
||||
"color": "${{ env.COLOR_FILES }}",
|
||||
"style": "${{ env.BADGE_STYLE }}"
|
||||
}
|
||||
EOF
|
||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
||||
echo " ✅ total-files.json"
|
||||
|
||||
# 3. 为每个语言生成徽章
|
||||
if [ -f /tmp/lang_summary.txt ]; then
|
||||
while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
||||
[ -z "$lang_id" ] && continue
|
||||
|
||||
# 使用默认颜色(如果未指定)
|
||||
[ -z "$color" ] && color="${{ env.COLOR_DEFAULT }}"
|
||||
|
||||
# 语言代码行数徽章
|
||||
cat > ${{ env.BADGE_DIR }}/${lang_id}-lines.json << EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "${display_name}",
|
||||
"message": "${formatted_lines} 行",
|
||||
"color": "${color}",
|
||||
"style": "${{ env.BADGE_STYLE }}"
|
||||
}
|
||||
EOF
|
||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
||||
|
||||
# 语言文件数徽章
|
||||
cat > ${{ env.BADGE_DIR }}/${lang_id}-files.json << EOF
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"label": "${display_name} 文件",
|
||||
"message": "${file_count} 个",
|
||||
"color": "${color}",
|
||||
"style": "${{ env.BADGE_STYLE }}"
|
||||
}
|
||||
EOF
|
||||
GENERATED_COUNT=$((GENERATED_COUNT + 1))
|
||||
|
||||
echo " ✅ ${lang_id}-lines.json, ${lang_id}-files.json"
|
||||
done < /tmp/lang_summary.txt
|
||||
fi
|
||||
|
||||
echo "generated_count=$GENERATED_COUNT" >> $GITHUB_OUTPUT
|
||||
|
||||
echo ""
|
||||
echo "📊 生成统计:"
|
||||
echo " - 总计: $GENERATED_COUNT 个徽章文件"
|
||||
echo ""
|
||||
echo "📂 徽章文件列表:"
|
||||
ls -lh ${{ env.BADGE_DIR }}/*.json 2>/dev/null || echo " ⚠️ 未找到徽章文件"
|
||||
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 📄 生成统计报告
|
||||
if: env.GENERATE_DETAILED_REPORT == 'true'
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📄 生成详细统计报告"
|
||||
echo "======================================"
|
||||
|
||||
cd "${{ env.REPO_DIR }}"
|
||||
|
||||
TOTAL_CODE=${{ steps.total.outputs.total_code }}
|
||||
REPO_NAME="${{ env.REPO_NAME }}"
|
||||
REPO_OWNER="${{ github.repository_owner }}"
|
||||
|
||||
echo "📝 生成 README.md..."
|
||||
|
||||
# 生成 Markdown 报告
|
||||
cat > ${{ env.BADGE_DIR }}/README.md << EOFMD
|
||||
# 📊 ${REPO_NAME} 代码统计报告
|
||||
|
||||
> 最后更新: $(date '+%Y-%m-%d %H:%M:%S %Z')
|
||||
>
|
||||
> 分支: \`${{ github.ref_name }}\` | 提交: [\`${GITHUB_SHA:0:7}\`](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA})
|
||||
|
||||
## 📈 总览
|
||||
|
||||
| 指标 | 数值 |
|
||||
|------|------|
|
||||
| 💻 **总代码行数** | ${{ steps.total.outputs.formatted_code }} |
|
||||
| 📁 **总文件数** | ${{ steps.total.outputs.formatted_files }} |
|
||||
| 🌐 **语言种类** | ${{ steps.languages.outputs.language_count }} |
|
||||
| 🎨 **徽章数量** | ${{ steps.generate_badges.outputs.generated_count }} |
|
||||
|
||||
## 🎨 各语言统计
|
||||
|
||||
EOFMD
|
||||
|
||||
# 检查是否有语言数据
|
||||
if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then
|
||||
cat >> ${{ env.BADGE_DIR }}/README.md << 'EOFMD'
|
||||
| 语言 | 代码行数 | 文件数 | 占比 | 进度条 |
|
||||
|------|----------|--------|------|--------|
|
||||
EOFMD
|
||||
|
||||
# 添加各语言数据(按代码行数排序)
|
||||
sort -t'|' -k3 -nr /tmp/lang_summary.txt | while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
||||
[ -z "$lang_id" ] && continue
|
||||
|
||||
# 计算百分比
|
||||
if [ "$TOTAL_CODE" -gt 0 ]; then
|
||||
PERCENT=$(echo "scale=1; $code_lines * 100 / $TOTAL_CODE" | bc)
|
||||
else
|
||||
PERCENT="0.0"
|
||||
fi
|
||||
|
||||
# 生成进度条(每2%一个方块)
|
||||
BAR_LENGTH=$(echo "scale=0; $PERCENT / 2" | bc)
|
||||
BAR_LENGTH=${BAR_LENGTH:-0}
|
||||
if [ "$BAR_LENGTH" -gt 0 ]; then
|
||||
BAR=$(printf '█%.0s' $(seq 1 $BAR_LENGTH))
|
||||
else
|
||||
BAR="░"
|
||||
fi
|
||||
|
||||
# 添加图标(如果有)
|
||||
ICON_DISPLAY=""
|
||||
if [ -n "$icon" ]; then
|
||||
ICON_DISPLAY="<img src=\"https://cdn.simpleicons.org/${icon}/${color}\" width=\"16\" height=\"16\" alt=\"${display_name}\"/> "
|
||||
fi
|
||||
|
||||
echo "| ${ICON_DISPLAY}**${display_name}** | ${formatted_lines} | ${file_count} | ${PERCENT}% | ${BAR} |" >> ${{ env.BADGE_DIR }}/README.md
|
||||
done
|
||||
else
|
||||
echo "" >> ${{ env.BADGE_DIR }}/README.md
|
||||
echo "> ⚠️ 未找到符合统计条件的语言文件" >> ${{ env.BADGE_DIR }}/README.md
|
||||
fi
|
||||
|
||||
# 添加使用说明
|
||||
cat >> ${{ env.BADGE_DIR }}/README.md << EOFMD
|
||||
|
||||
---
|
||||
|
||||
## 📖 徽章使用指南
|
||||
|
||||
### 🎯 总览徽章
|
||||
|
||||
\`\`\`markdown
|
||||

|
||||

|
||||
\`\`\`
|
||||
|
||||
**效果预览:**
|
||||
|
||||

|
||||

|
||||
|
||||
### 🌐 各语言徽章
|
||||
|
||||
EOFMD
|
||||
|
||||
# 添加各语言徽章使用示例
|
||||
if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then
|
||||
while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
||||
[ -z "$lang_id" ] && continue
|
||||
cat >> ${{ env.BADGE_DIR }}/README.md << EOFLANG
|
||||
**${display_name}:**
|
||||
|
||||
\`\`\`markdown
|
||||

|
||||

|
||||
\`\`\`
|
||||
|
||||

|
||||

|
||||
|
||||
EOFLANG
|
||||
done < /tmp/lang_summary.txt
|
||||
fi
|
||||
|
||||
# 添加配置说明
|
||||
cat >> ${{ env.BADGE_DIR }}/README.md << 'EOFMD'
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
### Token 配置
|
||||
|
||||
- 当前使用: **${{ steps.validate_token.outputs.token_type }}**
|
||||
- 推荐配置自定义 `STATS_TOKEN` 以获得更好的权限控制
|
||||
|
||||
### 排除规则
|
||||
|
||||
当前排除的目录:
|
||||
\`\`\`
|
||||
${{ env.EXCLUDE_DIRS }}
|
||||
\`\`\`
|
||||
|
||||
### 阈值设置
|
||||
|
||||
- 最小代码行数阈值: **${{ env.MIN_LINES_THRESHOLD }}** 行
|
||||
- 低于此阈值的语言将不会生成徽章
|
||||
|
||||
### 徽章样式
|
||||
|
||||
- 当前样式: **${{ env.BADGE_STYLE }}**
|
||||
- 可选样式: flat, flat-square, plastic, for-the-badge, social
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
*📊 由 [GitHub Actions](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions) 自动生成和更新*
|
||||
|
||||
*🤖 生成时间: $(date -u '+%Y-%m-%d %H:%M:%S UTC')*
|
||||
|
||||
</div>
|
||||
EOFMD
|
||||
|
||||
echo "✅ 详细统计报告已生成"
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 📤 提交并推送到统计分支
|
||||
id: commit
|
||||
run: |
|
||||
echo "======================================"
|
||||
echo "📤 提交并推送更新"
|
||||
echo "======================================"
|
||||
|
||||
cd "${{ env.REPO_DIR }}"
|
||||
|
||||
# 确保在统计分支
|
||||
git checkout ${{ env.BADGE_BRANCH }}
|
||||
|
||||
# 配置 Git 用户信息
|
||||
git config user.name "${{ env.GIT_USER_NAME }}"
|
||||
git config user.email "${{ env.GIT_USER_EMAIL }}"
|
||||
|
||||
# 添加所有更改
|
||||
git add ${{ env.BADGE_DIR }}/
|
||||
|
||||
# 检查是否有变更
|
||||
if git diff --staged --quiet; then
|
||||
echo "📌 没有变更,跳过提交"
|
||||
echo "changed=false" >> $GITHUB_OUTPUT
|
||||
echo ""
|
||||
else
|
||||
echo "📝 准备提交..."
|
||||
|
||||
# 生成提交信息
|
||||
COMMIT_MSG="chore: 更新代码统计 [$(date '+%Y-%m-%d %H:%M')]
|
||||
|
||||
📊 统计摘要:
|
||||
- 总代码: ${{ steps.total.outputs.formatted_code }} 行
|
||||
- 总文件: ${{ steps.total.outputs.formatted_files }} 个
|
||||
- 语言数: ${{ steps.languages.outputs.language_count }} 种
|
||||
- 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个
|
||||
|
||||
🔗 触发提交: ${GITHUB_SHA:0:7}
|
||||
🤖 由 GitHub Actions 自动生成"
|
||||
|
||||
git commit -m "$COMMIT_MSG"
|
||||
|
||||
echo "📤 推送到远程 ${{ env.BADGE_BRANCH }} 分支..."
|
||||
|
||||
# 推送到远程(使用 TOKEN)
|
||||
git push https://oauth2:${{ env.ACCESS_TOKEN }}@${GITHUB_SERVER_URL#https://}/${{ github.repository }}.git ${{ env.BADGE_BRANCH }}
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ 推送成功"
|
||||
echo "changed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ 推送失败"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "======================================"
|
||||
echo ""
|
||||
|
||||
- name: 📊 生成 Workflow Summary
|
||||
if: env.OUTPUT_TO_SUMMARY == 'true' && success()
|
||||
run: |
|
||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFSUMMARY'
|
||||
# 📊 代码统计完成
|
||||
|
||||
## ✅ 执行结果
|
||||
|
||||
| 项目 | 结果 |
|
||||
|------|------|
|
||||
| 🔐 Token 类型 | **${{ steps.validate_token.outputs.token_type }}** |
|
||||
| 🔧 依赖检查 | ${{ steps.check_deps.outputs.installed == 'true' && '✅ 已安装缺失工具' || '✅ 所有工具就绪' }} |
|
||||
| 📊 总代码行数 | **${{ steps.total.outputs.formatted_code }}** 行 |
|
||||
| 📁 总文件数 | **${{ steps.total.outputs.formatted_files }}** 个 |
|
||||
| 🌐 语言种类 | **${{ steps.languages.outputs.language_count }}** 种 |
|
||||
| 🎨 生成徽章 | **${{ steps.generate_badges.outputs.generated_count }}** 个 |
|
||||
| 📤 提交状态 | ${{ steps.commit.outputs.changed == 'true' && '✅ 已更新' || '📌 无变更' }} |
|
||||
|
||||
## 🔗 快速链接
|
||||
|
||||
- 📊 [查看详细统计报告](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md)
|
||||
- 🎨 [浏览徽章文件](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }})
|
||||
- 🔧 [查看 Workflow 配置](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/blob/${{ github.ref_name }}/.github/workflows/update_stats_badge.yaml)
|
||||
|
||||
## 📝 语言分布
|
||||
|
||||
EOFSUMMARY
|
||||
|
||||
# 添加语言分布表格
|
||||
if [ -f /tmp/lang_summary.txt ] && [ -s /tmp/lang_summary.txt ]; then
|
||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFTABLE'
|
||||
| 语言 | 代码行数 | 文件数 | 占比 |
|
||||
|------|----------|--------|------|
|
||||
EOFTABLE
|
||||
|
||||
TOTAL_CODE=${{ steps.total.outputs.total_code }}
|
||||
sort -t'|' -k3 -nr /tmp/lang_summary.txt | head -10 | while IFS='|' read -r lang_id display_name code_lines formatted_lines file_count color icon; do
|
||||
[ -z "$lang_id" ] && continue
|
||||
|
||||
if [ "$TOTAL_CODE" -gt 0 ]; then
|
||||
PERCENT=$(echo "scale=1; $code_lines * 100 / $TOTAL_CODE" | bc)
|
||||
else
|
||||
PERCENT="0.0"
|
||||
fi
|
||||
|
||||
echo "| **${display_name}** | ${formatted_lines} | ${file_count} | ${PERCENT}% |" >> $GITHUB_STEP_SUMMARY
|
||||
done
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "> ⚠️ 未找到符合条件的语言文件" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# 添加工作区信息
|
||||
cat >> $GITHUB_STEP_SUMMARY << 'EOFWS'
|
||||
|
||||
## 📂 工作区信息
|
||||
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 📁 仓库名称 | `${{ env.REPO_NAME }}` |
|
||||
| 📍 工作目录 | `${{ env.REPO_DIR }}` |
|
||||
| 🌿 统计分支 | `${{ env.BADGE_BRANCH }}` |
|
||||
| 📂 徽章目录 | `${{ env.BADGE_DIR }}` |
|
||||
|
||||
EOFWS
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "*⏱️ 执行时间: $(date '+%Y-%m-%d %H:%M:%S %Z')*" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: 📋 输出使用说明
|
||||
if: success()
|
||||
run: |
|
||||
echo "================================================"
|
||||
echo "✅ 代码统计任务执行完成!"
|
||||
echo "================================================"
|
||||
echo ""
|
||||
|
||||
echo "🔐 Token 信息:"
|
||||
echo " - 使用: ${{ steps.validate_token.outputs.token_type }}"
|
||||
echo ""
|
||||
|
||||
# 根据实际结果输出
|
||||
if [ "${{ steps.commit.outputs.changed }}" == "true" ]; then
|
||||
echo "📊 统计结果:"
|
||||
echo " - 总代码: ${{ steps.total.outputs.formatted_code }} 行"
|
||||
echo " - 总文件: ${{ steps.total.outputs.formatted_files }} 个"
|
||||
echo " - 语言数: ${{ steps.languages.outputs.language_count }} 种"
|
||||
echo " - 徽章数: ${{ steps.generate_badges.outputs.generated_count }} 个"
|
||||
echo ""
|
||||
echo "📤 已推送更新到分支: ${{ env.BADGE_BRANCH }}"
|
||||
else
|
||||
echo "📌 统计数据无变更,未产生新的提交"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "📂 工作区信息:"
|
||||
echo " - 仓库目录: ${{ env.REPO_DIR }}"
|
||||
echo " - 统计分支: ${{ env.BADGE_BRANCH }}"
|
||||
echo ""
|
||||
echo "🔗 查看详细报告:"
|
||||
echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}/README.md"
|
||||
echo ""
|
||||
echo "🎨 徽章目录:"
|
||||
echo " ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/src/branch/${{ env.BADGE_BRANCH }}/${{ env.BADGE_DIR }}"
|
||||
echo ""
|
||||
echo "================================================"
|
||||
|
||||
# 如果有警告或特殊情况,也输出
|
||||
if [ "${{ steps.check_deps.outputs.installed }}" == "true" ]; then
|
||||
echo ""
|
||||
echo "ℹ️ 本次运行安装了缺失的依赖工具"
|
||||
fi
|
||||
|
||||
if [ "${{ steps.languages.outputs.language_count }}" == "0" ]; then
|
||||
echo ""
|
||||
echo "⚠️ 警告: 未找到符合条件的语言文件"
|
||||
echo " 请检查 LANGUAGE_GROUPS 配置和 MIN_LINES_THRESHOLD 设置"
|
||||
fi
|
||||
|
||||
# 清理工作区(可选)
|
||||
if [ "${{ env.CLEANUP_WORKSPACE }}" == "true" ]; then
|
||||
echo ""
|
||||
echo "🧹 清理工作区..."
|
||||
rm -rf "${{ env.WORKSPACE_DIR }}"
|
||||
echo "✅ 清理完成"
|
||||
fi
|
||||
|
||||
- name: 🧹 清理工作区
|
||||
if: always()
|
||||
run: |
|
||||
echo "🧹 清理临时文件..."
|
||||
rm -rf /tmp/lang_stats /tmp/lang_summary.txt /tmp/total_stats*.json
|
||||
echo "✅ 清理完成"
|
||||
|
|
@ -39,11 +39,11 @@
|
|||
|
||||
包含内容:
|
||||
|
||||
- 🔄 `changelog_and_release.yml` - 自动更新 CHANGELOG和自动创建Release
|
||||
- 💡 工作流配置说明
|
||||
- 🔧 如何使用和定制
|
||||
- 🔄 案例:`changelog_and_release.yml` - 自动更新 CHANGELOG 和自动创建 Release
|
||||
|
||||
👉 **实用的 Actions 工作流,可直接复制使用**
|
||||
👉 **两个实用的 Actions 工作流,可直接复制使用**
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
883
WORKFLOW.md
883
WORKFLOW.md
|
|
@ -1,163 +1,880 @@
|
|||
# Workflow
|
||||
# 📦 Tag Release Automation
|
||||
|
||||
本目录包含 Gitea Actions 的自动化工作流示例,展示如何使用 Gitea Actions 实现 CI/CD 自动化。
|
||||
自动生成 CHANGELOG 并创建 Release 的一体化工作流,**支持语义版本号(SemVer)和内容叠加**。
|
||||
|
||||
---
|
||||
|
||||
## 📂 文件结构
|
||||
## 📂 文件位置
|
||||
|
||||
```txt
|
||||
.gitea/workflows/
|
||||
├── changelog-and-release.yml # CHANGELOG 生成 + Release 创建
|
||||
├── update_stats_badge.yml # 代码行数的统计
|
||||
└── ... # 更多 workflow 待添加
|
||||
└── changelog-and-release.yaml # CHANGELOG 生成 + Release 创建
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 功能列表
|
||||
## 🚀 快速开始
|
||||
|
||||
### ✅ 已实现
|
||||
### 1️⃣ 配置 Gitea Token
|
||||
|
||||
#### 1. 📦 自动发布工作流 (`changelog-and-release.yml`)
|
||||
#### 生成 Token
|
||||
|
||||
**功能**:在推送 tag 时自动生成 CHANGELOG 并创建 Release
|
||||
1. 登录 Gitea → 右上角头像 → **设置 (Settings)** → **应用 (Applications)**
|
||||
2. 点击 **生成新令牌 (Generate New Token)**
|
||||
3. 配置权限:
|
||||
- ✅ **repo** - 完整的仓库访问权限(必需)
|
||||
4. 复制生成的 Token(⚠️ 只显示一次)
|
||||
|
||||
**特性**:
|
||||
#### 添加 Secret
|
||||
|
||||
- 🏷️ 自动识别语义版本号(SemVer)
|
||||
- 📝 智能生成 CHANGELOG 条目
|
||||
- 🔄 支持多个开发版本内容叠加
|
||||
- 👥 自动提取贡献者列表
|
||||
- 🚀 创建 GitHub/Gitea Release
|
||||
- 📎 上传附件到 Release
|
||||
- 🏷️ 自动识别 Pre-release 标记
|
||||
**推荐方式:仓库级别配置**
|
||||
|
||||
**触发方式**:
|
||||
1. 进入仓库 → **设置 (Settings)** → **Actions** → **Secrets**
|
||||
2. 点击 **新建 Secret (New Secret)**
|
||||
3. 填写:
|
||||
- **名称**:`GITEA_TOKEN`
|
||||
- **值**:粘贴刚才的 token
|
||||
4. 点击 **保存**
|
||||
|
||||
💡 **提示**:如果有多个仓库需要使用,可以在个人设置中添加全局 Secret
|
||||
|
||||
### 2️⃣ 部署 Workflow
|
||||
|
||||
```bash
|
||||
# 创建目录
|
||||
mkdir -p .gitea/workflows
|
||||
|
||||
# 复制文件(将文件重命名为 changelog-and-release.yaml)
|
||||
cp changelog-and-release.yaml .gitea/workflows/
|
||||
|
||||
# 提交
|
||||
git add .gitea/workflows/
|
||||
git commit -m "ci: add changelog and release automation"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### 3️⃣ 自定义配置
|
||||
|
||||
打开 `changelog-and-release.yaml`,找到 **⚙️ Configuration** 步骤(约第 10-60 行),所有配置都在这里:
|
||||
|
||||
```yaml
|
||||
- name: ⚙️ Configuration
|
||||
id: config
|
||||
run: |
|
||||
# ========================================
|
||||
# 📝 自定义配置区域 - 在此修改所有配置
|
||||
# ========================================
|
||||
|
||||
# 📌 语义版本号规范 (SemVer)
|
||||
# 格式: MAJOR.MINOR.PATCH[-prerelease]
|
||||
#
|
||||
# 示例: 1.0.0, 1.1.0, 1.1.1, 1.0.0-beta1, 2.0.0-rc1
|
||||
#
|
||||
# ⚠️ 重要规则:
|
||||
# 1. 开发版本以 .0 开始 (如 1.0.0-beta1, 不是 1.0.1-beta1)
|
||||
# 2. 多个开发版本会累加到同一 CHANGELOG 区域 (推荐使用 strip 模式)
|
||||
# 3. CHANGELOG 内容是叠加而非覆盖
|
||||
#
|
||||
# 📖 详细规范请参考: CONVENTIONS.md
|
||||
|
||||
# Gitea 服务器地址(用于生成头像链接)
|
||||
GITEA_SERVER="https://git.mytsl.cn"
|
||||
|
||||
# CHANGELOG 配置
|
||||
CHANGELOG_SECTION_TITLE="### :pencil: What's Changed"
|
||||
CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors"
|
||||
|
||||
# CHANGELOG 版本号处理方式
|
||||
# - "full": 使用完整 tag 名称(1.0.0-beta → ## :bookmark: 1.0.0-beta)
|
||||
# - "strip": 去除 pre-release 后缀(1.0.0-beta1 → ## :bookmark: 1.0.0)
|
||||
# ⚠️ 推荐使用 "strip" 模式以支持多个开发版本叠加到同一CHANGELOG区域
|
||||
CHANGELOG_VERSION_MODE="strip"
|
||||
|
||||
# Release 配置
|
||||
RELEASE_TITLE="{version}" # 可用变量: {version}
|
||||
|
||||
# Pre-release 配置
|
||||
# - "false": 正式版本(会被标记为 latest)
|
||||
# - "true": 预发布版本(会被标记为 pre-release)
|
||||
# - "auto": 自动判断(根据 tag 名称中是否包含 alpha/beta/rc)
|
||||
RELEASE_PRERELEASE_MODE="auto"
|
||||
|
||||
# Draft 配置(是否创建为草稿)
|
||||
RELEASE_IS_DRAFT="false" # true 或 false
|
||||
|
||||
# 需要忽略的提交模式
|
||||
IGNORE_PATTERNS=(
|
||||
"^Merge "
|
||||
"^:memo: Auto update CHANGELOG"
|
||||
"\[skip ci\]"
|
||||
"\[ci skip\]"
|
||||
"^style: "
|
||||
"^chore: format"
|
||||
)
|
||||
|
||||
# Git 提交消息格式
|
||||
COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]"
|
||||
|
||||
# 额外要上传到 Release 的文件
|
||||
ADDITIONAL_RELEASE_FILES="" # 例如: "README.md LICENSE"
|
||||
```
|
||||
|
||||
#### 常见配置示例
|
||||
|
||||
**修改 Gitea 服务器地址**:
|
||||
|
||||
```bash
|
||||
GITEA_SERVER="https://your-gitea.com"
|
||||
```
|
||||
|
||||
**修改 CHANGELOG 标题为中文**:
|
||||
|
||||
```bash
|
||||
CHANGELOG_SECTION_TITLE="### :pencil: 变更内容"
|
||||
CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: 贡献者"
|
||||
```
|
||||
|
||||
**配置 CHANGELOG 版本号处理方式**:
|
||||
|
||||
```bash
|
||||
# ⭐ 推荐:strip 模式(支持内容叠加)
|
||||
CHANGELOG_VERSION_MODE="strip"
|
||||
# 所有相同前缀的版本共享同一个 CHANGELOG 区域,内容累积
|
||||
# 1.0.0-beta1 → ## :bookmark: 1.0.0
|
||||
# 1.0.0-beta2 → ## :bookmark: 1.0.0 (追加内容)
|
||||
# 1.0.0 → ## :bookmark: 1.0.0 (追加内容)
|
||||
|
||||
# 方式 2: full 模式(每个 tag 独立条目)
|
||||
CHANGELOG_VERSION_MODE="full"
|
||||
# 每个 tag 都有独立的 CHANGELOG 条目
|
||||
# 1.0.0-beta1 → ## :bookmark: 1.0.0-beta1
|
||||
# 1.0.0-beta2 → ## :bookmark: 1.0.0-beta2
|
||||
# 1.0.0 → ## :bookmark: 1.0.0
|
||||
```
|
||||
|
||||
**推荐使用 `strip` 模式的原因**:
|
||||
|
||||
1. ✅ **支持内容叠加** - 多个开发版本的提交累积在一起
|
||||
2. ✅ **避免重复** - 所有版本的改动集中在一个区域
|
||||
3. ✅ **完整历史** - 正式发布时 CHANGELOG 已包含所有开发阶段的提交
|
||||
4. ✅ **符合最佳实践** - 大多数开源项目的做法
|
||||
|
||||
**strip 模式工作流程示例**:
|
||||
|
||||
```bash
|
||||
# 1. 推送第一个 beta 版本
|
||||
git push origin 1.0.0-beta1
|
||||
# → CHANGELOG: ## :bookmark: 1.0.0(创建新区域,添加 beta1 的提交)
|
||||
# → Release: 1.0.0-beta1 (Pre-release 🏷️)
|
||||
|
||||
# 2. 推送第二个 beta 版本
|
||||
git push origin 1.0.0-beta2
|
||||
# → CHANGELOG: ## :bookmark: 1.0.0(叠加 beta2 的提交到现有区域)
|
||||
# → Release: 1.0.0-beta2 (Pre-release 🏷️)
|
||||
|
||||
# 3. 推送 rc 版本
|
||||
git push origin 1.0.0-rc1
|
||||
# → CHANGELOG: ## :bookmark: 1.0.0(继续叠加 rc1 的提交)
|
||||
# → Release: 1.0.0-rc1 (Pre-release 🏷️)
|
||||
|
||||
# 4. 推送正式版本
|
||||
git push origin 1.0.0
|
||||
# → CHANGELOG: ## :bookmark: 1.0.0(叠加正式版的提交,现在包含完整历史)
|
||||
# → Release: 1.0.0 (Latest ✅)
|
||||
```
|
||||
|
||||
**生成的 CHANGELOG 示例**(叠加模式):
|
||||
|
||||
```markdown
|
||||
## :bookmark: 1.0.0
|
||||
|
||||
### :pencil: What's Changed
|
||||
|
||||
- :sparkles: Add user authentication (来自 beta1)
|
||||
- :sparkles: Add profile page (来自 beta1)
|
||||
- :bug: Fix login redirect (来自 beta2)
|
||||
- :sparkles: Add password reset (来自 beta2)
|
||||
- :memo: Update documentation (来自 rc1)
|
||||
- :bug: Fix validation error (来自正式版)
|
||||
|
||||
### :busts_in_silhouette: Contributors
|
||||
|
||||
[所有版本的贡献者,自动去重]
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
**修改 Release 标题**:
|
||||
|
||||
```bash
|
||||
RELEASE_TITLE="{version}" # → 1.0.0
|
||||
RELEASE_TITLE="v{version}" # → v1.0.0
|
||||
RELEASE_TITLE="Release {version}" # → Release 1.0.0
|
||||
RELEASE_TITLE="🎉 {version}" # → 🎉 1.0.0
|
||||
```
|
||||
|
||||
**配置 Pre-release 标记**:
|
||||
|
||||
```bash
|
||||
# 方式 1: 自动判断(推荐)
|
||||
RELEASE_PRERELEASE_MODE="auto"
|
||||
# tag 包含 alpha/beta/rc/pre/preview/dev/test → Pre-release 🏷️
|
||||
# 其他 tag → Latest (Stable) ✅
|
||||
|
||||
# 方式 2: 始终标记为正式版本
|
||||
RELEASE_PRERELEASE_MODE="false"
|
||||
# 所有 Release 都标记为 Latest ✅
|
||||
|
||||
# 方式 3: 始终标记为预发布版本
|
||||
RELEASE_PRERELEASE_MODE="true"
|
||||
# 所有 Release 都标记为 Pre-release 🏷️
|
||||
```
|
||||
|
||||
**示例效果**:
|
||||
|
||||
```bash
|
||||
# auto 模式下:
|
||||
1.0.0 → Latest ✅
|
||||
1.0.0-beta1 → Pre-release 🏷️
|
||||
1.0.0-rc1 → Pre-release 🏷️
|
||||
1.0.0-alpha1 → Pre-release 🏷️
|
||||
1.0.0.dev → Pre-release 🏷️
|
||||
```
|
||||
|
||||
**创建为草稿**:
|
||||
|
||||
```bash
|
||||
RELEASE_IS_DRAFT="true" # Release 创建后不会立即发布,需要手动发布
|
||||
```
|
||||
|
||||
**添加更多忽略的提交类型**:
|
||||
|
||||
```bash
|
||||
IGNORE_PATTERNS=(
|
||||
"^Merge "
|
||||
"^:memo: Auto update CHANGELOG"
|
||||
"\[skip ci\]"
|
||||
"^test: " # 添加:忽略测试
|
||||
"^docs: " # 添加:忽略文档
|
||||
"^refactor: " # 添加:忽略重构
|
||||
)
|
||||
```
|
||||
|
||||
**上传额外的文件到 Release**:
|
||||
|
||||
```bash
|
||||
ADDITIONAL_RELEASE_FILES="README.md LICENSE docs/guide.pdf"
|
||||
```
|
||||
|
||||
**自定义提交消息**:
|
||||
|
||||
```bash
|
||||
COMMIT_MESSAGE="docs: update CHANGELOG for {version}"
|
||||
COMMIT_MESSAGE="chore(release): update CHANGELOG [{version}]"
|
||||
```
|
||||
|
||||
### 4️⃣ 创建 Release
|
||||
|
||||
#### 完整开发流程示例
|
||||
|
||||
```bash
|
||||
# === 第一阶段:Alpha 内部测试 ===
|
||||
git commit -m ":sparkles: Add user authentication"
|
||||
git tag 1.0.0-alpha1
|
||||
git push origin 1.0.0-alpha1
|
||||
|
||||
git commit -m ":sparkles: Add user profile"
|
||||
git tag 1.0.0-alpha2
|
||||
git push origin 1.0.0-alpha2
|
||||
|
||||
# === 第二阶段:Beta 公开测试 ===
|
||||
git commit -m ":bug: Fix login redirect"
|
||||
git tag 1.0.0-beta1
|
||||
git push origin 1.0.0-beta1
|
||||
|
||||
git commit -m ":sparkles: Add password reset"
|
||||
git tag 1.0.0-beta2
|
||||
git push origin 1.0.0-beta2
|
||||
|
||||
# === 第三阶段:RC 发布候选 ===
|
||||
git commit -m ":memo: Update documentation"
|
||||
git tag 1.0.0-rc1
|
||||
git push origin 1.0.0-rc1
|
||||
|
||||
# === 第四阶段:正式发布 ===
|
||||
git tag 1.0.0
|
||||
git push origin 1.0.0
|
||||
|
||||
# === 后续版本 ===
|
||||
# 新增功能(向后兼容)
|
||||
git commit -m ":sparkles: Add email notifications"
|
||||
git tag 1.1.0
|
||||
git push origin 1.1.0
|
||||
|
||||
# Bug 修复
|
||||
git commit -m ":bug: Fix email sending"
|
||||
git tag 1.1.1
|
||||
git push origin 1.1.1
|
||||
|
||||
# 破坏性变更
|
||||
git commit -m ":boom: Change API format"
|
||||
git tag 2.0.0
|
||||
git push origin 2.0.0
|
||||
```
|
||||
|
||||
**文件**:[changelog-and-release.yml](.gitea/workflows/changelog_and_release.yml)
|
||||
🎉 完成!工作流会自动:
|
||||
|
||||
💡 **详细配置**:查看 `changelog-and-release.yml` 文件顶部的 `env` 区域,所有配置项都有详细注释说明
|
||||
1. ✅ 加载自定义配置
|
||||
2. ✅ 生成/叠加 CHANGELOG 条目
|
||||
3. ✅ 合并并去重贡献者
|
||||
4. ✅ 提交并推送到 main 分支
|
||||
5. ✅ 创建 Release(自动识别 Pre-release)
|
||||
6. ✅ 上传 CHANGELOG.md 及额外文件
|
||||
|
||||
---
|
||||
|
||||
#### 2. 📊 代码统计徽章工作流 (`update-stats-badge.yml`)
|
||||
## 📋 工作原理
|
||||
|
||||
**功能**:自动统计代码行数并生成徽章数据
|
||||
### 触发条件
|
||||
|
||||
**特性**:
|
||||
|
||||
- 📈 统计总代码行数和文件数
|
||||
- 🌐 按语言分组统计
|
||||
- 🎨 自动生成徽章 JSON 数据
|
||||
- 📊 生成统计摘要(JSON 格式)
|
||||
- 🚫 灵活的目录排除配置
|
||||
|
||||
**触发方式**:
|
||||
**推送数字开头的 tag**:
|
||||
|
||||
```bash
|
||||
# 推送到主分支时自动触发
|
||||
git push origin main
|
||||
# ✅ 会触发
|
||||
git push origin 1.0.0
|
||||
git push origin 1.1.0-beta1
|
||||
git push origin 2.0.0-rc1
|
||||
|
||||
# 手动触发
|
||||
# 仓库 → Actions → Update Code Statistics Badge → Run workflow
|
||||
# ❌ 不会触发(不是数字开头)
|
||||
git push origin v1.0.0
|
||||
git push origin release-1.0
|
||||
```
|
||||
|
||||
**配置文件**:[update-stats-badge.yml](.gitea/workflows/update_stats_badge.yaml)
|
||||
### 执行流程
|
||||
|
||||
**markdown引用格式**: ``
|
||||
```txt
|
||||
Tag Push (1.0.0-beta1)
|
||||
↓
|
||||
⚙️ 加载配置参数
|
||||
↓
|
||||
📂 检出代码
|
||||
↓
|
||||
🤖 检查是否为 bot 提交
|
||||
↓
|
||||
📋 检查版本是否已存在(1.0.0)
|
||||
↓ 已存在 → 叠加模式
|
||||
↓ 不存在 → 创建新条目
|
||||
📝 生成 CHANGELOG 条目
|
||||
├─ 提取旧的提交记录(如果存在)
|
||||
├─ 提取旧的贡献者(如果存在)
|
||||
├─ 追加新的提交记录
|
||||
├─ 合并并去重贡献者
|
||||
└─ 生成完整的条目
|
||||
↓
|
||||
📤 提交并推送
|
||||
↓
|
||||
🗑️ 删除旧 Release
|
||||
↓
|
||||
🚀 创建新 Release
|
||||
├─ 使用配置的标题格式
|
||||
├─ 自动识别 Pre-release
|
||||
└─ 上传配置的附件列表
|
||||
↓
|
||||
✅ 完成
|
||||
```
|
||||
|
||||
💡 **详细配置**:查看 `update-stats-badge.yml` 文件顶部的 `env` 区域,包含语言分组、颜色、排除目录等配置
|
||||
### 版本号处理逻辑
|
||||
|
||||
#### Strip 模式(推荐)
|
||||
|
||||
```txt
|
||||
Tag: 1.0.0-beta1
|
||||
↓ strip 处理
|
||||
CHANGELOG Version: 1.0.0
|
||||
↓
|
||||
检查 1.0.0 是否存在?
|
||||
├─ 不存在 → 创建新区域
|
||||
└─ 已存在 → 叠加内容
|
||||
|
||||
Tag: 1.0.0-beta2
|
||||
↓ strip 处理
|
||||
CHANGELOG Version: 1.0.0
|
||||
↓
|
||||
检查 1.0.0 是否存在?
|
||||
└─ 已存在 → 叠加内容(保留 beta1 的提交)
|
||||
|
||||
Tag: 1.0.0
|
||||
↓ strip 处理
|
||||
CHANGELOG Version: 1.0.0
|
||||
↓
|
||||
检查 1.0.0 是否存在?
|
||||
└─ 已存在 → 叠加内容(保留所有之前的提交)
|
||||
```
|
||||
|
||||
### 配置加载日志示例
|
||||
|
||||
执行时会显示配置摘要:
|
||||
|
||||
```txt
|
||||
======================================
|
||||
⚙️ Loading workflow configuration
|
||||
======================================
|
||||
|
||||
📋 Configuration Summary:
|
||||
🏷️ Tag version: 1.0.0-beta1
|
||||
📝 CHANGELOG version: 1.0.0
|
||||
🌐 Gitea Server: https://git.mytsl.cn
|
||||
📄 CHANGELOG Title: ### :pencil: What's Changed
|
||||
🚀 Release Title: 1.0.0-beta1
|
||||
🏷️ Pre-release: yes (auto-detected: 1.0.0-beta1 contains pre-release keyword)
|
||||
📄 Draft: false
|
||||
💬 Commit Message: :memo: Auto update CHANGELOG for 1.0.0-beta1 [skip ci]
|
||||
📎 Additional Files: README.md LICENSE
|
||||
|
||||
✅ Configuration loaded successfully
|
||||
```
|
||||
|
||||
### 生成的内容示例
|
||||
|
||||
#### CHANGELOG.md(叠加模式)
|
||||
|
||||
```markdown
|
||||
## :bookmark: 1.0.0
|
||||
|
||||
### :pencil: What's Changed
|
||||
|
||||
- :sparkles: Add user authentication ([a1b2c3d](链接)) by @developer1
|
||||
- :sparkles: Add user profile ([e4f5g6h](链接)) by @developer1
|
||||
- :bug: Fix login redirect ([i7j8k9l](链接)) by @developer2
|
||||
- :sparkles: Add password reset ([m0n1o2p](链接)) by @developer2
|
||||
|
||||
### :busts_in_silhouette: Contributors
|
||||
|
||||
<a href="https://git.mytsl.cn/developer1">
|
||||
<img src="https://git.mytsl.cn/developer1.png" alt="developer1" width="35" height="35" />
|
||||
</a>
|
||||
<a href="https://git.mytsl.cn/developer2">
|
||||
<img src="https://git.mytsl.cn/developer2.png" alt="developer2" width="35" height="35" />
|
||||
</a>
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
#### Release
|
||||
|
||||
- **标题**:根据 `RELEASE_TITLE` 生成(例如:`1.0.0-beta1` 或 `v1.0.0-beta1`)
|
||||
- **标记**:
|
||||
- `RELEASE_PRERELEASE_MODE="auto"` → 根据 tag 名称自动判断
|
||||
- `RELEASE_PRERELEASE_MODE="false"` → Latest ✅
|
||||
- `RELEASE_PRERELEASE_MODE="true"` → Pre-release 🏷️
|
||||
- **内容**:CHANGELOG 中该版本的完整内容(包含所有叠加的提交)
|
||||
- **附件**:`CHANGELOG.md` + `ADDITIONAL_RELEASE_FILES` 中的文件
|
||||
|
||||
**Latest Release 示例**:
|
||||
|
||||
```txt
|
||||
✅ Latest
|
||||
|
||||
1.0.0 ← RELEASE_TITLE
|
||||
|
||||
### :pencil: What's Changed
|
||||
- :sparkles: Add user authentication ([a1b2c3d](链接))
|
||||
- :sparkles: Add user profile ([e4f5g6h](链接))
|
||||
- :bug: Fix login redirect ([i7j8k9l](链接))
|
||||
- :sparkles: Add password reset ([m0n1o2p](链接))
|
||||
|
||||
### :busts_in_silhouette: Contributors
|
||||
[所有贡献者头像]
|
||||
```
|
||||
|
||||
**Pre-release 示例**:
|
||||
|
||||
```txt
|
||||
🏷️ Pre-release
|
||||
|
||||
1.0.0-beta1 ← RELEASE_TITLE
|
||||
|
||||
### :pencil: What's Changed
|
||||
- :sparkles: Add user authentication ([a1b2c3d](链接))
|
||||
- :sparkles: Add user profile ([e4f5g6h](链接))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔜 待扩展
|
||||
## 🎨 配置示例库
|
||||
|
||||
更多自动化场景:
|
||||
### 示例 1:标准 SemVer 配置(推荐)
|
||||
|
||||
- 🧪 **自动测试** - 推送代码时运行单元测试
|
||||
- 🏗️ **自动构建** - 构建并发布 Docker 镜像
|
||||
- 📊 **代码质量** - 运行代码检查和静态分析
|
||||
- 🌐 **自动部署** - 部署到生产环境
|
||||
- 📧 **通知集成** - 发送构建结果到 Slack/邮件
|
||||
- 🔐 **安全扫描** - 依赖漏洞扫描
|
||||
```bash
|
||||
GITEA_SERVER="https://git.example.com"
|
||||
CHANGELOG_SECTION_TITLE="### :pencil: What's Changed"
|
||||
CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors"
|
||||
CHANGELOG_VERSION_MODE="strip" # 支持内容叠加
|
||||
RELEASE_TITLE="{version}"
|
||||
RELEASE_PRERELEASE_MODE="auto" # 自动识别 beta/rc 等
|
||||
COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]"
|
||||
```
|
||||
|
||||
### 示例 2:中文环境
|
||||
|
||||
```bash
|
||||
GITEA_SERVER="https://git.example.com"
|
||||
CHANGELOG_SECTION_TITLE="### :pencil: 变更内容"
|
||||
CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: 贡献者"
|
||||
CHANGELOG_VERSION_MODE="strip"
|
||||
RELEASE_TITLE="版本 {version}"
|
||||
RELEASE_PRERELEASE_MODE="auto"
|
||||
COMMIT_MESSAGE="文档: 更新 CHANGELOG [{version}] [skip ci]"
|
||||
```
|
||||
|
||||
### 示例 3:详细记录
|
||||
|
||||
```bash
|
||||
GITEA_SERVER="https://git.example.com"
|
||||
CHANGELOG_SECTION_TITLE="### :clipboard: What's Changed in This Version"
|
||||
CHANGELOG_CONTRIBUTORS_TITLE="### :heart: Special Thanks To"
|
||||
CHANGELOG_VERSION_MODE="strip"
|
||||
RELEASE_TITLE="Release {version}"
|
||||
RELEASE_PRERELEASE_MODE="auto"
|
||||
RELEASE_IS_DRAFT="false"
|
||||
COMMIT_MESSAGE=":memo: Automated CHANGELOG update for version {version} [skip ci]"
|
||||
ADDITIONAL_RELEASE_FILES="README.md LICENSE CHANGELOG.md docs/MIGRATION.md"
|
||||
IGNORE_PATTERNS=(
|
||||
"^Merge "
|
||||
"^:memo: Auto update CHANGELOG"
|
||||
"\[skip ci\]"
|
||||
"^Bump version"
|
||||
"^style: "
|
||||
"^chore: format"
|
||||
"^chore: lint"
|
||||
"^test: "
|
||||
"^docs: update readme"
|
||||
)
|
||||
```
|
||||
|
||||
### 示例 4:开发分支(始终 Pre-release)
|
||||
|
||||
```bash
|
||||
GITEA_SERVER="https://git.example.com"
|
||||
CHANGELOG_SECTION_TITLE="### :test_tube: Development Changes"
|
||||
CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors"
|
||||
CHANGELOG_VERSION_MODE="strip"
|
||||
RELEASE_TITLE="Dev Build {version}"
|
||||
RELEASE_PRERELEASE_MODE="true" # 始终标记为 pre-release
|
||||
COMMIT_MESSAGE=":test_tube: Update CHANGELOG for dev build {version} [skip ci]"
|
||||
```
|
||||
|
||||
### 示例 5:草稿模式
|
||||
|
||||
```bash
|
||||
GITEA_SERVER="https://git.example.com"
|
||||
CHANGELOG_SECTION_TITLE="### :pencil: What's Changed"
|
||||
CHANGELOG_CONTRIBUTORS_TITLE="### :busts_in_silhouette: Contributors"
|
||||
CHANGELOG_VERSION_MODE="strip"
|
||||
RELEASE_TITLE="{version}"
|
||||
RELEASE_PRERELEASE_MODE="auto"
|
||||
RELEASE_IS_DRAFT="true" # 创建为草稿
|
||||
COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 版本号最佳实践
|
||||
|
||||
### 快速参考
|
||||
|
||||
**版本演进示例**:
|
||||
|
||||
```txt
|
||||
0.1.0 → 0.2.0 → 1.0.0 (首个稳定版) → 1.1.0 (新功能) → 1.1.1 (Bug修复) → 2.0.0 (破坏性变更)
|
||||
```
|
||||
|
||||
**何时递增版本号**:
|
||||
|
||||
- **MAJOR**:不兼容的 API 修改、破坏性变更
|
||||
- **MINOR**:新增功能但保持向后兼容
|
||||
- **PATCH**:Bug 修复、安全补丁
|
||||
|
||||
**Pre-release 版本命名**:
|
||||
|
||||
```bash
|
||||
1.0.0-alpha1 # 内部测试,可能不稳定
|
||||
1.0.0-beta1 # 公开测试,功能基本完成
|
||||
1.0.0-rc1 # 发布候选,准备正式发布
|
||||
```
|
||||
|
||||
**版本号检查清单**:
|
||||
|
||||
- [ ] 格式:`MAJOR.MINOR.PATCH` (不要 `v` 前缀)
|
||||
- [ ] 开发版本以 `.0` 开始 (如 `1.0.0-beta1`)
|
||||
- [ ] Pre-release 后缀完整 (`beta1` 不是 `b1`)
|
||||
- [ ] `CHANGELOG_VERSION_MODE` 设置为 `"strip"`
|
||||
|
||||
📖 **详细说明请参考**:[CONVENTIONS.md](./CONVENTIONS.md) - 包含完整的 SemVer 规范、更新规则和示例
|
||||
|
||||
---
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q1: 如何配置 Token?
|
||||
### Q1: 为什么推荐使用 strip 模式?
|
||||
|
||||
**步骤**:
|
||||
**原因**:
|
||||
|
||||
1. Gitea → 设置 → 应用 → 生成新令牌
|
||||
2. 权限:勾选 **repo**
|
||||
3. 仓库 → Settings → Actions → Secrets → 添加 Secret
|
||||
4. 名称:`WORKFLOW`,值:你的 Token
|
||||
1. ✅ **支持内容叠加** - 多个开发版本的提交会累积在同一个 CHANGELOG 区域
|
||||
2. ✅ **完整的开发历史** - 正式发布时,CHANGELOG 已包含所有开发阶段的提交
|
||||
3. ✅ **避免重复** - 不需要为每个 beta/rc 版本创建独立的 CHANGELOG 条目
|
||||
4. ✅ **便于阅读** - 所有相关的变更集中在一起
|
||||
|
||||
💡 **提示**:一个 Token 即可完成所有操作(Git + API)
|
||||
**对比**:
|
||||
|
||||
```bash
|
||||
# Strip 模式
|
||||
1.0.0-beta1, 1.0.0-beta2, 1.0.0 → 所有提交在 ## :bookmark: 1.0.0
|
||||
|
||||
# Full 模式
|
||||
1.0.0-beta1 → ## :bookmark: 1.0.0-beta1
|
||||
1.0.0-beta2 → ## :bookmark: 1.0.0-beta2
|
||||
1.0.0 → ## :bookmark: 1.0.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Q2: `changelog_and_release` 没有触发?
|
||||
### Q2: 如何修改配置?
|
||||
|
||||
**所有配置都在第一个步骤中**,只需编辑 `changelog-and-release.yaml` 的 **⚙️ Configuration** 步骤即可。
|
||||
|
||||
修改后提交推送:
|
||||
|
||||
```bash
|
||||
git add .gitea/workflows/changelog-and-release.yaml
|
||||
git commit -m "ci: update workflow configuration"
|
||||
git push
|
||||
```
|
||||
|
||||
下次推送 tag 时会使用新配置。
|
||||
|
||||
---
|
||||
|
||||
### Q3: 版本号格式错误会怎样?
|
||||
|
||||
**常见错误**:
|
||||
|
||||
```bash
|
||||
❌ v1.0.0 # 不应该有 v 前缀
|
||||
❌ 1.0.1-beta1 # 开发版本应该从 .0 开始
|
||||
❌ 1.0.0beta1 # 缺少连字符
|
||||
❌ 1.0.0-b1 # 后缀应该完整写 beta1
|
||||
```
|
||||
|
||||
**影响**:
|
||||
|
||||
- 工作流仍会执行
|
||||
- 但可能无法正确识别为 Pre-release
|
||||
- CHANGELOG 版本号可能不符合预期
|
||||
|
||||
---
|
||||
|
||||
### Q4: 如何查看 CHANGELOG 是否正确叠加?
|
||||
|
||||
**检查步骤**:
|
||||
|
||||
1. 查看 CHANGELOG.md 文件
|
||||
2. 找到对应的版本区域(如 `## :bookmark: 1.0.0`)
|
||||
3. 确认包含所有 beta/rc 版本的提交
|
||||
|
||||
**示例**:
|
||||
|
||||
```markdown
|
||||
## :bookmark: 1.0.0
|
||||
|
||||
### :pencil: What's Changed
|
||||
|
||||
- 提交 1(来自 1.0.0-beta1)
|
||||
- 提交 2(来自 1.0.0-beta1)
|
||||
- 提交 3(来自 1.0.0-beta2)
|
||||
- 提交 4(来自 1.0.0-rc1)
|
||||
- 提交 5(来自 1.0.0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Q5: 贡献者重复怎么办?
|
||||
|
||||
**不用担心!** 工作流会自动去重贡献者。
|
||||
|
||||
如果同一个人在多个版本都有贡献,他们的头像只会出现一次。
|
||||
|
||||
---
|
||||
|
||||
### Q6: Workflow 没有触发?
|
||||
|
||||
**检查清单**:
|
||||
|
||||
- [ ] Workflow 文件在 `.gitea/workflows/` 目录
|
||||
- [ ] 文件名正确(如 `changelog-and-release.yml`)
|
||||
- [ ] Token 已正确配置(Secret 名称为 `WORKFLOW`)
|
||||
- [ ] Tag 格式正确(数字开头,如 `1.0.0`)
|
||||
- [ ] Gitea Actions 已启用
|
||||
- [ ] Runner 正常运行
|
||||
- [ ] Workflow 文件在 `.gitea/workflows/` 目录下
|
||||
- [ ] 文件名为 `changelog-and-release.yaml`(或 `.yml`)
|
||||
- [ ] Tag 格式正确(数字开头,如 `1.0.0` 或 `1.0.0-beta1`)
|
||||
- [ ] Runner 已部署并正常运行
|
||||
- [ ] 仓库已启用 Actions
|
||||
- [ ] GITEA_TOKEN 已正确配置
|
||||
|
||||
---
|
||||
|
||||
### Q3: `changelog_and_release` 版本号格式有要求吗?
|
||||
### Q7: 提示 "GITEA_TOKEN not found"?
|
||||
|
||||
推荐使用**语义化版本号(SemVer)**:
|
||||
**原因**:Token 未正确配置
|
||||
|
||||
**解决方法**:
|
||||
|
||||
1. 确认已添加名为 `GITEA_TOKEN` 的 Secret
|
||||
2. 确认 Token 具有 `repo` 权限
|
||||
3. 尝试重新生成并配置 Token
|
||||
|
||||
---
|
||||
|
||||
### Q8: CHANGELOG 没有更新?
|
||||
|
||||
**可能原因**:
|
||||
|
||||
1. 没有新的有效提交
|
||||
2. 所有提交都匹配了 `IGNORE_PATTERNS` 中的规则
|
||||
3. 版本已存在且是 bot 提交
|
||||
|
||||
**检查方法**:
|
||||
|
||||
```bash
|
||||
✅ 推荐格式:
|
||||
1.0.0 # 正式版本
|
||||
1.0.0-beta1 # 预发布版本
|
||||
# 查看两个 tag 之间的提交
|
||||
git log $(git describe --tags --abbrev=0 @^)..@ --oneline --no-merges
|
||||
|
||||
❌ 不推荐:
|
||||
v1.0.0 # 不要使用 v 前缀
|
||||
1.0.0-b1 # 后缀要完整
|
||||
# 检查 workflow 日志中的 "Found X valid commits"
|
||||
```
|
||||
|
||||
详细规范请参考 workflow 文件中的注释说明。
|
||||
---
|
||||
|
||||
### Q9: 如何跳过自动化?
|
||||
|
||||
在 commit 消息中添加 `[skip ci]`:
|
||||
|
||||
```bash
|
||||
git commit -m "docs: update readme [skip ci]"
|
||||
```
|
||||
|
||||
**注意**:Tag 推送无法跳过,因为 workflow 在 tag 推送时触发
|
||||
|
||||
---
|
||||
|
||||
### Q4: 如何查看执行日志?
|
||||
### Q10: 什么时候使用 full 模式?
|
||||
|
||||
仓库 → Actions → 点击对应的 workflow 运行记录 → 查看详细日志
|
||||
**使用场景**:
|
||||
|
||||
- 你希望每个 tag 都有独立的 CHANGELOG 条目
|
||||
- 不需要累积开发版本的提交
|
||||
- 每个版本的变更需要完全独立
|
||||
|
||||
**大多数情况下,推荐使用 strip 模式。**
|
||||
|
||||
---
|
||||
|
||||
### Q5: Token 权限不足怎么办?
|
||||
### Q11: Release 标记(Latest/Pre-release)是如何工作的?
|
||||
|
||||
确保 Token 具有以下权限:
|
||||
**GitHub/Gitea 的自动标记规则**:
|
||||
|
||||
- ✅ **repo** - 完整仓库访问权限
|
||||
- 用于读取代码
|
||||
- 用于推送提交
|
||||
- 用于创建 Release
|
||||
1. **Latest(最新稳定版)**:
|
||||
|
||||
如果还是失败,尝试重新生成 Token。
|
||||
- 自动标记给最新的非 pre-release 版本
|
||||
- 显示绿色的 "Latest" 标签 ✅
|
||||
|
||||
2. **Pre-release(预发布版)**:
|
||||
- 通过 API 设置 `prerelease: true`
|
||||
- 显示橙色的 "Pre-release" 标签 🏷️
|
||||
- 不会被标记为 Latest
|
||||
|
||||
**auto 模式识别的关键词**:
|
||||
|
||||
- `alpha`, `beta`, `rc`, `pre`, `preview`, `dev`, `test`
|
||||
|
||||
**示例**:
|
||||
|
||||
```bash
|
||||
1.0.0 → Latest ✅
|
||||
1.0.0-beta1 → Pre-release 🏷️
|
||||
1.0.0-rc1 → Pre-release 🏷️
|
||||
1.1.0 → Latest ✅ (1.0.0 变为旧版本)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🤝 贡献
|
||||
## 🔍 调试技巧
|
||||
|
||||
欢迎提交更多实用的 Workflow 示例!
|
||||
### 查看配置加载情况
|
||||
|
||||
每次执行都会在日志开头显示配置摘要:
|
||||
|
||||
```
|
||||
======================================
|
||||
⚙️ Loading workflow configuration
|
||||
======================================
|
||||
|
||||
📋 Configuration Summary:
|
||||
🏷️ Tag version: 1.0.0-beta1
|
||||
📝 CHANGELOG version: 1.0.0
|
||||
🌐 Gitea Server: https://git.mytsl.cn
|
||||
📝 CHANGELOG Title: ### :pencil: What's Changed
|
||||
🚀 Release Title: 1.0.0-beta1
|
||||
🏷️ Pre-release: yes (auto-detected)
|
||||
💬 Commit Message: :memo: Auto update CHANGELOG for 1.0.0-beta1 [skip ci]
|
||||
📎 Additional Files: none
|
||||
```
|
||||
|
||||
### 本地测试配置
|
||||
|
||||
提取 Configuration 步骤中的 bash 代码,在本地运行测试:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# 模拟环境变量
|
||||
export github_ref_name="1.0.0-beta1"
|
||||
|
||||
# 复制你的配置
|
||||
GITEA_SERVER="https://git.mytsl.cn"
|
||||
CHANGELOG_VERSION_MODE="strip"
|
||||
RELEASE_TITLE="{version}"
|
||||
COMMIT_MESSAGE=":memo: Auto update CHANGELOG for {version} [skip ci]"
|
||||
|
||||
# 测试版本处理
|
||||
VERSION="$github_ref_name"
|
||||
if [[ "$CHANGELOG_VERSION_MODE" == "strip" ]]; then
|
||||
CHANGELOG_VERSION=$(echo "$VERSION" | sed -E 's/-(alpha|beta|rc|pre|preview|dev|test)[0-9]*$//')
|
||||
echo "Tag: $VERSION → CHANGELOG: $CHANGELOG_VERSION"
|
||||
fi
|
||||
|
||||
# 测试变量替换
|
||||
RELEASE_TITLE="${RELEASE_TITLE//\{version\}/$VERSION}"
|
||||
COMMIT_MSG="${COMMIT_MESSAGE//\{version\}/$VERSION}"
|
||||
|
||||
echo "Release Title: $RELEASE_TITLE"
|
||||
echo "Commit Message: $COMMIT_MSG"
|
||||
```
|
||||
|
||||
### 验证版本号格式
|
||||
|
||||
```bash
|
||||
# 测试版本号是否符合规范
|
||||
version="1.0.0-beta1"
|
||||
|
||||
# 检查是否数字开头
|
||||
if [[ "$version" =~ ^[0-9] ]]; then
|
||||
echo "✅ 格式正确"
|
||||
else
|
||||
echo "❌ 版本号必须以数字开头"
|
||||
fi
|
||||
|
||||
# 检查是否包含 pre-release 标识
|
||||
if [[ "$version" =~ (alpha|beta|rc|pre|preview|dev|test) ]]; then
|
||||
echo "🏷️ 将被识别为 Pre-release"
|
||||
else
|
||||
echo "✅ 将被识别为 Latest"
|
||||
fi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可 MIT
|
||||
|
|
|
|||
Loading…
Reference in New Issue