diff --git a/.gitea/workflows/changelog_and_release.yaml b/.gitea/workflows/changelog_and_release.yaml deleted file mode 100644 index 7cd660e..0000000 --- a/.gitea/workflows/changelog_and_release.yaml +++ /dev/null @@ -1,921 +0,0 @@ -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}\n" - NEW_ENTRY="${NEW_ENTRY} \"${name}\"\n" - NEW_ENTRY="${NEW_ENTRY}\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 ~ //) 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" diff --git a/.gitea/workflows/changelog_and_release.yml b/.gitea/workflows/changelog_and_release.yml new file mode 100644 index 0000000..10b3b8c --- /dev/null +++ b/.gitea/workflows/changelog_and_release.yml @@ -0,0 +1,735 @@ +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" diff --git a/.gitea/workflows/immortal_wrt.yaml b/.gitea/workflows/immortal_wrt.yml similarity index 100% rename from .gitea/workflows/immortal_wrt.yaml rename to .gitea/workflows/immortal_wrt.yml