diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24dc100..f27e3ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,26 +8,23 @@ on: permissions: contents: write +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + jobs: - release: + prepare-meta: runs-on: windows-latest environment: 软件发布 env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GLM_KEY: ${{ secrets.GLM_KEY }} - TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} - TELEGRAM_CHAT_IDS: ${{ vars.TELEGRAM_CHAT_IDS }} - TELEGRAM_RELEASE_COVER_URL: ${{ vars.TELEGRAM_RELEASE_COVER_URL }} FORCE_UPDATE_MIN_VERSION: ${{ vars.FORCE_UPDATE_MIN_VERSION }} FORCE_UPDATE_BLOCKED_VERSIONS: ${{ vars.FORCE_UPDATE_BLOCKED_VERSIONS }} FORCE_UPDATE_TITLE: ${{ vars.FORCE_UPDATE_TITLE }} FORCE_UPDATE_MESSAGE: ${{ vars.FORCE_UPDATE_MESSAGE }} FORCE_UPDATE_RELEASE_NOTES: ${{ vars.FORCE_UPDATE_RELEASE_NOTES }} - R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} - R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} - R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} - R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + outputs: + version: ${{ steps.version.outputs.version }} + tag: ${{ steps.version.outputs.tag }} steps: - name: Checkout uses: actions/checkout@v5 @@ -59,35 +56,154 @@ jobs: - name: Install dependencies run: npm ci + - name: Generate force update manifest + run: npm run build:force-update-manifest + + - name: Generate release context + env: + RELEASE_TAG: ${{ steps.version.outputs.tag }} + run: npm run build:release-context + + - name: Upload release metadata + uses: actions/upload-artifact@v4 + with: + name: release-meta + path: | + release/force-update.json + release/release-context.json + if-no-files-found: error + + build-windows: + runs-on: windows-latest + environment: 软件发布 + needs: prepare-meta + outputs: + version: ${{ needs.prepare-meta.outputs.version }} + tag: ${{ needs.prepare-meta.outputs.tag }} + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22.12.0 + cache: npm + + - name: Install dependencies + run: npm ci + - name: Rebuild native modules run: npx electron-rebuild - name: Build app run: npm run build - - name: Generate force update manifest - run: npm run build:force-update-manifest + - name: Validate build artifacts + shell: pwsh + run: | + $version = "${{ needs.prepare-meta.outputs.version }}" + $installer = "release/CipherTalk-$version-Setup.exe" + if (-not (Test-Path $installer)) { + Write-Error "Installer not found: $installer" + exit 1 + } + if (-not (Test-Path "release/latest.yml")) { + Write-Error "latest.yml not found" + exit 1 + } - - name: Generate release context - env: - RELEASE_TAG: ${{ github.ref_name }} - run: npm run build:release-context + - name: Upload release binaries + uses: actions/upload-artifact@v4 + with: + name: release-binaries + path: | + release/CipherTalk-${{ needs.prepare-meta.outputs.version }}-Setup.exe + release/latest.yml + release/*.blockmap + if-no-files-found: error + + generate-release-body: + runs-on: windows-latest + environment: 软件发布 + needs: prepare-meta + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GLM_KEY: ${{ secrets.GLM_KEY }} + FORCE_UPDATE_MIN_VERSION: ${{ vars.FORCE_UPDATE_MIN_VERSION }} + FORCE_UPDATE_BLOCKED_VERSIONS: ${{ vars.FORCE_UPDATE_BLOCKED_VERSIONS }} + FORCE_UPDATE_TITLE: ${{ vars.FORCE_UPDATE_TITLE }} + FORCE_UPDATE_MESSAGE: ${{ vars.FORCE_UPDATE_MESSAGE }} + FORCE_UPDATE_RELEASE_NOTES: ${{ vars.FORCE_UPDATE_RELEASE_NOTES }} + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22.12.0 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Download release metadata + uses: actions/download-artifact@v4 + with: + name: release-meta + path: release - name: Generate AI release body run: npm run build:release-body - - name: Ensure AWS CLI + - name: Validate release body shell: pwsh run: | - if (-not (Get-Command aws -ErrorAction SilentlyContinue)) { - choco install awscli -y + if (-not (Test-Path "release/release-body.md")) { + Write-Error "release-body.md not found" + exit 1 } - aws --version - - name: Validate release files + - name: Upload release body + uses: actions/upload-artifact@v4 + with: + name: release-body + path: release/release-body.md + if-no-files-found: error + + publish-github-release: + runs-on: windows-latest + environment: 软件发布 + needs: + - prepare-meta + - build-windows + - generate-release-body + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Download release metadata + uses: actions/download-artifact@v4 + with: + name: release-meta + path: release + + - name: Download release binaries + uses: actions/download-artifact@v4 + with: + name: release-binaries + path: release + + - name: Download release body + uses: actions/download-artifact@v4 + with: + name: release-body + path: release + + - name: Validate release package shell: pwsh run: | - $version = "${{ steps.version.outputs.version }}" + $version = "${{ needs.prepare-meta.outputs.version }}" $installer = "release/CipherTalk-$version-Setup.exe" if (-not (Test-Path $installer)) { Write-Error "Installer not found: $installer" @@ -101,10 +217,6 @@ jobs: Write-Error "force-update.json not found" exit 1 } - if (-not (Test-Path "release/release-context.json")) { - Write-Error "release-context.json not found" - exit 1 - } if (-not (Test-Path "release/release-body.md")) { Write-Error "release-body.md not found" exit 1 @@ -113,15 +225,47 @@ jobs: - name: Create or update GitHub Release uses: softprops/action-gh-release@v2.5.0 with: - tag_name: ${{ github.ref_name }} + tag_name: ${{ needs.prepare-meta.outputs.tag }} body_path: release/release-body.md fail_on_unmatched_files: false files: | - release/CipherTalk-${{ steps.version.outputs.version }}-Setup.exe + release/CipherTalk-${{ needs.prepare-meta.outputs.version }}-Setup.exe release/latest.yml release/force-update.json release/*.blockmap + mirror-r2: + runs-on: windows-latest + environment: 软件发布 + needs: + - prepare-meta + - build-windows + env: + R2_ACCOUNT_ID: ${{ secrets.R2_ACCOUNT_ID }} + R2_BUCKET_NAME: ${{ secrets.R2_BUCKET_NAME }} + R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + steps: + - name: Download release metadata + uses: actions/download-artifact@v4 + with: + name: release-meta + path: release + + - name: Download release binaries + uses: actions/download-artifact@v4 + with: + name: release-binaries + path: release + + - name: Ensure AWS CLI + shell: pwsh + run: | + if (-not (Get-Command aws -ErrorAction SilentlyContinue)) { + choco install awscli -y + } + aws --version + - name: Upload mirrored files to R2 shell: pwsh run: | @@ -135,7 +279,7 @@ jobs: $env:AWS_DEFAULT_REGION = "auto" $endpoint = "https://$($env:R2_ACCOUNT_ID).r2.cloudflarestorage.com" $bucket = "s3://$($env:R2_BUCKET_NAME)" - $version = "${{ steps.version.outputs.version }}" + $version = "${{ needs.prepare-meta.outputs.version }}" $currentInstaller = "CipherTalk-$version-Setup.exe" $existingInstallers = aws s3 ls $bucket --endpoint-url $endpoint | ForEach-Object { @@ -155,18 +299,50 @@ jobs: aws s3 cp "release/latest.yml" "$bucket/latest.yml" --endpoint-url $endpoint aws s3 cp "release/force-update.json" "$bucket/force-update.json" --endpoint-url $endpoint + notify-telegram-success: + runs-on: windows-latest + environment: 软件发布 + needs: + - prepare-meta + - generate-release-body + - publish-github-release + env: + TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} + TELEGRAM_CHAT_IDS: ${{ vars.TELEGRAM_CHAT_IDS }} + TELEGRAM_RELEASE_COVER_URL: ${{ vars.TELEGRAM_RELEASE_COVER_URL }} + RELEASE_VERSION: ${{ needs.prepare-meta.outputs.version }} + TELEGRAM_NOTIFY_MODE: success + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 22.12.0 + cache: npm + + - name: Download release metadata + uses: actions/download-artifact@v4 + with: + name: release-meta + path: release + + - name: Download release body + uses: actions/download-artifact@v4 + with: + name: release-body + path: release + - name: Notify Telegram success - env: - RELEASE_VERSION: ${{ steps.version.outputs.version }} - TELEGRAM_NOTIFY_MODE: success run: npm run notify:telegram + continue-on-error: true notify-failure: runs-on: windows-latest environment: 软件发布 if: failure() env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_IDS: ${{ vars.TELEGRAM_CHAT_IDS }} TELEGRAM_RELEASE_COVER_URL: ${{ vars.TELEGRAM_RELEASE_COVER_URL }} @@ -184,3 +360,4 @@ jobs: - name: Notify Telegram failure run: npm run notify:telegram + continue-on-error: true diff --git a/scripts/README.md b/scripts/README.md index 8fb5541..02f32c5 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -21,22 +21,47 @@ git push origin v2.2.14 `.github/workflows/release.yml` 会在 `v*` 标签触发后执行: -1. 安装依赖 -2. 重新编译原生模块 -3. 执行 `npm run build` -4. 生成 `release/force-update.json` -5. 生成 `release/release-context.json` -6. 自动生成 `release/release-body.md`(AI 失败时自动降级为模板版) -7. 创建/更新 GitHub Release -8. 上传以下文件到 GitHub Release: +当前工作流已拆成串并行 job: + +- `prepare-meta` +- `build-windows` +- `generate-release-body` +- `publish-github-release` +- `mirror-r2` +- `notify-telegram-success` +- `notify-failure` + +其中: + +1. `prepare-meta` 生成 `force-update.json` 和 `release-context.json` +2. `build-windows` 负责构建安装包和 `latest.yml` +3. `generate-release-body` 负责 AI / 模板版发布说明 +4. `publish-github-release` 汇总产物并创建 GitHub Release +5. `mirror-r2` 与 `notify-telegram-success` 在发布成功后并行执行 + +GitHub Release 上传内容: + +- 安装包 +- `latest.yml` +- `force-update.json` +- 若存在则上传 `.blockmap` + +Cloudflare R2 同步内容: + +- 安装包 +- `latest.yml` +- `force-update.json` + +Telegram 通知: + +- 成功时发送 AI 摘要通知 +- 失败时发送失败通知 + +GitHub Release 资产包括: - 安装包 - `latest.yml` - `force-update.json` - 若存在则上传 `.blockmap` -9. 将以下文件同步到 Cloudflare R2: - - 安装包 - - `latest.yml` - - `force-update.json` 10. 向 Telegram 频道/群发送发布通知(AI 摘要 + 强制更新提醒) ## 版本要求 diff --git a/scripts/generate-release-body.js b/scripts/generate-release-body.js index c7e9964..cf821a1 100644 --- a/scripts/generate-release-body.js +++ b/scripts/generate-release-body.js @@ -9,14 +9,12 @@ const glmKey = process.env.GLM_KEY || '' const PRIMARY_AUTHOR_LOGINS = new Set(['ILoveBingLu']) const PRIMARY_AUTHOR_NAMES = new Set(['ILoveBingLu', 'BingLu', 'ILoveBinglu']) -const PRIMARY_AUTHOR_EMAILS = new Set(['aiqiji74@gmail.com', 'aiqji74@gmail.com']) function isPrimaryAuthor(person) { if (!person) return false const login = String(person.authorLogin || '').trim() const name = String(person.authorName || '').trim() - const email = String(person.authorEmail || '').trim().toLowerCase() - return PRIMARY_AUTHOR_LOGINS.has(login) || PRIMARY_AUTHOR_NAMES.has(name) || PRIMARY_AUTHOR_EMAILS.has(email) + return PRIMARY_AUTHOR_LOGINS.has(login) || PRIMARY_AUTHOR_NAMES.has(name) } function classifyCommit(subject) { diff --git a/scripts/send-telegram-release.js b/scripts/send-telegram-release.js index 37dcde8..d3f232c 100644 --- a/scripts/send-telegram-release.js +++ b/scripts/send-telegram-release.js @@ -66,11 +66,19 @@ function buildSuccessMessage(context, releaseBody) { const thanks = [] const primaryLogins = new Set(['ILoveBingLu']) + const primaryNames = new Set(['ILoveBingLu', 'BingLu', 'ILoveBinglu']) for (const pr of context?.pullRequests || []) { if (pr?.authorLogin && !primaryLogins.has(pr.authorLogin)) { thanks.push(`🙏 感谢 @${pr.authorLogin} 提交 PR #${pr.number}`) } } + for (const commit of context?.commits || []) { + const hasPrRef = /#(\d+)/.test(commit.subject || '') + const authorName = String(commit.authorName || '').trim() + if (!hasPrRef && authorName && !primaryNames.has(authorName)) { + thanks.push(`🙏 感谢 ${authorName} 提交改动《${commit.subject}》`) + } + } const lines = [ `🚀 CipherTalk v${escapeHtml(version)} 已发布`,