diff --git a/.github/workflows/cut-patch-release.yml b/.github/workflows/cut-patch-release.yml index f18c44797cd..a96e50ee283 100644 --- a/.github/workflows/cut-patch-release.yml +++ b/.github/workflows/cut-patch-release.yml @@ -200,3 +200,23 @@ jobs: --color 0E8A16 \ --description "Backport this fix to release/v$VERSION" \ --force + + # Post an immediate "branch cut" card so the team is notified the moment the + # patch branch exists, without waiting ~20-40 min for notify-release-feishu to + # finish the prerelease build. The download card still follows from that build. + # feishu-notice.ts only imports node:crypto, so the setup-node above suffices. + - name: Notify Feishu that the branch was cut + if: steps.guard.outputs.published == 'true' + env: + FEISHU_WEBHOOK: ${{ secrets.FEISHU_RELEASE_WEBHOOK }} + FEISHU_SIGN_SECRET: ${{ secrets.FEISHU_RELEASE_SIGN_SECRET }} + NOTICE_TITLE: "🌿 已切出 release 分支 v${{ steps.ver.outputs.version }}(小版本 patch)" + NOTICE_TEMPLATE: "green" + NOTICE_BODY: | + **分支**:[`release/v${{ steps.ver.outputs.version }}`](${{ github.server_url }}/${{ github.repository }}/tree/release/v${{ steps.ver.outputs.version }})(从 main 切出,小版本 / patch) + + **Backport label**:`backport release/v${{ steps.ver.outputs.version }}` —— 要回灌到这个版本的修复,给 PR 打这个 label。 + + Prerelease 打包已开始(约 20–40 分钟);完成后会再发一张带各平台下载链接的卡片。 + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: node --experimental-strip-types tools/release/src/notifications/feishu-notice.ts diff --git a/.github/workflows/cut-release.yml b/.github/workflows/cut-release.yml index 34a2d4a7154..c1aafad5eda 100644 --- a/.github/workflows/cut-release.yml +++ b/.github/workflows/cut-release.yml @@ -101,3 +101,23 @@ jobs: --color 0E8A16 \ --description "Backport this fix to release/v$VERSION" \ --force + + # Post an immediate "branch cut" card so the team is notified the moment the + # release branch exists, without waiting ~20-40 min for notify-release-feishu + # to finish the prerelease build. The download card still follows from that + # build. feishu-notice.ts only imports node:crypto, so the setup-node above + # is all it needs. + - name: Notify Feishu that the branch was cut + env: + FEISHU_WEBHOOK: ${{ secrets.FEISHU_RELEASE_WEBHOOK }} + FEISHU_SIGN_SECRET: ${{ secrets.FEISHU_RELEASE_SIGN_SECRET }} + NOTICE_TITLE: "🌿 已切出 release 分支 v${{ steps.ver.outputs.version }}(大版本 minor)" + NOTICE_TEMPLATE: "green" + NOTICE_BODY: | + **分支**:[`release/v${{ steps.ver.outputs.version }}`](${{ github.server_url }}/${{ github.repository }}/tree/release/v${{ steps.ver.outputs.version }})(从 main 切出,大版本 / minor) + + **Backport label**:`backport release/v${{ steps.ver.outputs.version }}` —— 要回灌到这个版本的修复,给 PR 打这个 label。 + + Prerelease 打包已开始(约 20–40 分钟);完成后会再发一张带各平台下载链接的卡片。 + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: node --experimental-strip-types tools/release/src/notifications/feishu-notice.ts diff --git a/e2e/tests/packaged-smoke-workflow.test.ts b/e2e/tests/packaged-smoke-workflow.test.ts index 500aa6adbbc..38aa598ca80 100644 --- a/e2e/tests/packaged-smoke-workflow.test.ts +++ b/e2e/tests/packaged-smoke-workflow.test.ts @@ -44,6 +44,7 @@ const packagedPackageJsonPath = join(workspaceRoot, "apps", "packaged", "package const scopesScriptPath = join(workspaceRoot, "scripts", "scopes.ts"); const runnersScriptPath = join(workspaceRoot, ".github", "scripts", "runners.py"); const notifyDailyFeishuWorkflowPath = join(workspaceRoot, ".github", "workflows", "notify-daily-feishu.yml"); +const cutReleaseWorkflowPath = join(workspaceRoot, ".github", "workflows", "cut-release.yml"); const cutPatchReleaseWorkflowPath = join(workspaceRoot, ".github", "workflows", "cut-patch-release.yml"); const feishuNoticeScriptPath = join(workspaceRoot, "tools", "release", "src", "notifications", "feishu-notice.ts"); const landingPageDailyFeishuWorkflowPath = join(workspaceRoot, ".github", "workflows", "landing-page-daily-feishu.yml"); @@ -1401,6 +1402,38 @@ process.stdin.on("end", () => { expect(notice).toContain('required("NOTICE_BODY")'); }); + it("[P2] posts an immediate branch-cut Feishu card naming the backport label on both cut workflows", async () => { + // Cutting a release branch triggers a ~20-40 min prerelease build before the + // download card lands, so both cut workflows post an eager "branch cut" notice + // the moment the branch exists. Each must: reuse feishu-notice.ts, run AFTER the + // branch push + label creation, and name the exact backport label so the team + // knows which label to apply for backports. + const [minorCut, patchCut] = await Promise.all([ + readFile(cutReleaseWorkflowPath, "utf8"), + readFile(cutPatchReleaseWorkflowPath, "utf8"), + ]); + + for (const [label, workflow, kind] of [ + ["cut-release (minor)", minorCut, "大版本 minor"], + ["cut-patch-release (patch)", patchCut, "小版本 patch"], + ] as const) { + const step = sectionBetween(workflow, "- name: Notify Feishu that the branch was cut", "\n run:"); + // Same standalone notifier + the shared release webhook/secret. + expect(workflow, label).toContain("run: node --experimental-strip-types tools/release/src/notifications/feishu-notice.ts"); + expect(step, label).toContain("FEISHU_WEBHOOK: ${{ secrets.FEISHU_RELEASE_WEBHOOK }}"); + // Names the version's backport label in the card body. + expect(step, label).toContain("backport release/v${{ steps.ver.outputs.version }}"); + // Distinguishes major vs minor cut. + expect(step, label).toContain(kind); + // The eager card must come AFTER the branch is actually cut + pushed. + expect(workflow.indexOf("- name: Notify Feishu that the branch was cut"), label) + .toBeGreaterThan(workflow.indexOf('git push origin "$BRANCH"')); + } + // The patch workflow's eager card only fires on the happy (published) path. + const patchNotice = sectionBetween(patchCut, "- name: Notify Feishu that the branch was cut", "\n run:"); + expect(patchNotice).toContain("if: steps.guard.outputs.published == 'true'"); + }); + it("[P2] sends the daily landing PR summary to Feishu with staging deployment status", async () => { const [workflow, productionWorkflow, script] = await Promise.all([ readFile(landingPageDailyFeishuWorkflowPath, "utf8"),