From 24aa384d5225ac8cd586cab19bfa909bea715122 Mon Sep 17 00:00:00 2001 From: lefarcen <935902669@qq.com> Date: Thu, 9 Jul 2026 11:43:34 +0800 Subject: [PATCH] feat(release): post an immediate "branch cut" Feishu card on both release cuts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cutting a release branch (Tuesday minor via cut-release, Thursday patch via cut-patch-release) pushes to release/**, which triggers notify-release-feishu to build a full prerelease before it posts the download card — 20-40 minutes during which the team has no signal the cut even happened. Post an eager green "🌿 已切出 release 分支" card the moment the branch exists, from the cut workflow itself, reusing feishu-notice.ts (node:crypto only, so the existing setup-node suffices). The card names: - the branch and whether it is a 大版本 (minor) or 小版本 (patch), - the exact backport label (`backport release/vX.Y.Z`) so people know which label to apply for backports, - a note that the prerelease build is running and a download card will follow. The download card from notify-release-feishu is unchanged; each cut now yields an immediate ack card plus the eventual download card. The patch workflow's eager card only fires on the happy (published) path, alongside the existing skip card. Topology test asserts both workflows post the eager card after the branch push and name the backport label. --- .github/workflows/cut-patch-release.yml | 20 ++++++++++++++ .github/workflows/cut-release.yml | 20 ++++++++++++++ e2e/tests/packaged-smoke-workflow.test.ts | 33 +++++++++++++++++++++++ 3 files changed, 73 insertions(+) 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"),