mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
fix release smoke gating and notifications (#6709)
This commit is contained in:
@@ -11,8 +11,8 @@ name: cut-patch-release
|
||||
# — i.e. release-stable ran `gh release edit --draft=false --latest`). If it is
|
||||
# not published yet, do NOT cut a branch or launch a prerelease publication;
|
||||
# instead post a Feishu notice that the patch cut was skipped, and stop. The
|
||||
# independent Windows canary still runs first so every release-cut attempt
|
||||
# leaves a current product-smoke signal.
|
||||
# independent scheduled Windows canary remains an advisory product-smoke
|
||||
# signal and does not block this branch cut or its prerelease package.
|
||||
# 3. On the happy path it is otherwise identical to cut-release: create
|
||||
# release/vX.Y.(Z+1), bump apps/packaged/package.json, push with the App token
|
||||
# (which triggers notify-release-feishu -> prerelease build + Feishu card), and
|
||||
@@ -41,7 +41,6 @@ on:
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read # branch/label writes still go through the App token below
|
||||
|
||||
concurrency:
|
||||
@@ -49,13 +48,7 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
prerelease_win_smoke:
|
||||
if: github.repository == 'nexu-io/open-design'
|
||||
uses: ./.github/workflows/main-prerelease-win-smoke.yml
|
||||
secrets: inherit
|
||||
|
||||
cut:
|
||||
needs: prerelease_win_smoke
|
||||
if: github.repository == 'nexu-io/open-design'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -67,8 +60,8 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Cut the exact main commit that passed the Windows packaged smoke.
|
||||
ref: ${{ needs.prerelease_win_smoke.outputs.commit }}
|
||||
# Smoke is advisory; release cuts always use the current main tip.
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app.outputs.token }}
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
name: cut-release
|
||||
# Every Tuesday 09:00 Asia/Shanghai, cut the next minor release branch from main:
|
||||
# 1. Build + smoke main's Windows prerelease package and capture the passing SHA
|
||||
# 2. Compute version (highest release/vX.Y.Z -> bump minor -> X.(Y+1).0; e.g. 0.11.0 -> 0.12.0)
|
||||
# 3. Create release/vX.Y.Z from that SHA, bump apps/packaged/package.json, push
|
||||
# 4. Create the "backport release/vX.Y.Z" label
|
||||
# 1. Compute version (highest release/vX.Y.Z -> bump minor -> X.(Y+1).0; e.g. 0.11.0 -> 0.12.0)
|
||||
# 2. Create release/vX.Y.Z from main, bump apps/packaged/package.json, push
|
||||
# 3. Create the "backport release/vX.Y.Z" label
|
||||
# The independent main prerelease Windows canary remains an advisory signal: its
|
||||
# smoke result is notified separately and never blocks a branch cut or package.
|
||||
# The push uses the App token (not GITHUB_TOKEN), so it triggers notify-release-feishu
|
||||
# -> builds a nightly package and posts the Feishu card.
|
||||
|
||||
@@ -18,17 +19,10 @@ on:
|
||||
default: ''
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
contents: read # branch/label writes still go through the App token below
|
||||
|
||||
jobs:
|
||||
prerelease_win_smoke:
|
||||
if: github.repository == 'nexu-io/open-design'
|
||||
uses: ./.github/workflows/main-prerelease-win-smoke.yml
|
||||
secrets: inherit
|
||||
|
||||
cut:
|
||||
needs: prerelease_win_smoke
|
||||
if: github.repository == 'nexu-io/open-design'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -40,8 +34,8 @@ jobs:
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# Cut the exact main commit that passed the Windows packaged smoke.
|
||||
ref: ${{ needs.prerelease_win_smoke.outputs.commit }}
|
||||
# Smoke is advisory; release cuts always use the current main tip.
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
token: ${{ steps.app.outputs.token }}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
workflow_call:
|
||||
outputs:
|
||||
commit:
|
||||
description: "The exact commit that passed the Windows packaged smoke."
|
||||
description: "The exact commit that was exercised by the Windows packaged smoke."
|
||||
value: ${{ jobs.smoke.outputs.commit }}
|
||||
|
||||
permissions:
|
||||
@@ -256,6 +256,6 @@ jobs:
|
||||
|
||||
**目标 ref**: `main`
|
||||
|
||||
release cut 会被阻止,直到同一条 smoke 通过。
|
||||
该 smoke 是独立质量信号,不阻塞 release cut 或 prerelease 打包 / 发布。
|
||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
run: node --experimental-strip-types tools/release/src/notifications/feishu-notice.ts
|
||||
|
||||
@@ -42,7 +42,7 @@ on:
|
||||
type: string
|
||||
default: ""
|
||||
win_x64_smoke_mode:
|
||||
description: "Windows x64 packaged smoke coverage (skip/core/full). Empty uses the per-branch default: skip on release/v0.18.0 to bypass the known packaged-win smoke flake that gates publish, otherwise release-prerelease's own default."
|
||||
description: "Windows x64 packaged smoke coverage (skip/core/full). Empty uses core. Smoke failures are reported on the download card but do not block publication."
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
@@ -85,22 +85,17 @@ jobs:
|
||||
# (This replaces a temporary `test` default on release/v0.18.0 that
|
||||
# existed only while prod's resource-hub storage was unconfigured.)
|
||||
amr_profile: ${{ inputs.amr_profile || 'prod' }}
|
||||
# Windows packaged smoke is a hard publish gate (publish needs build_win
|
||||
# success) but flaked on release/v0.18.0 ("did not reach main app shell"),
|
||||
# skip-blocking EVERY platform's publish + the Feishu card. TEMPORARY:
|
||||
# skip it on release/v0.18.0 so the workspace-team prerelease can actually
|
||||
# ship its mac/win/linux packages + post its card. Manual dispatch overrides.
|
||||
# The root cause is fixed on main (#6481); remove this branch default once
|
||||
# release/v0.18.0 no longer builds.
|
||||
#
|
||||
# The final arm must name a real profile, NOT ''. A workflow_call
|
||||
# Windows packaged smoke is advisory to publication: a failed smoke is
|
||||
# recorded in the report and annotated on the full download card without
|
||||
# blocking platform or metadata publication. The final arm must name a
|
||||
# real profile, NOT ''. A workflow_call
|
||||
# `default:` applies only when an input is omitted, so forwarding an empty
|
||||
# string defeats the callee's declared `core` default; the empty value
|
||||
# then reaches the spec, reads as "not core", and selects the updater path
|
||||
# — which demands a fixture only a genuine `full` request wires up, so the
|
||||
# job dies before the smoke starts. That is how release/v0.18.1's first
|
||||
# prerelease failed on its branch-cut commit.
|
||||
win_x64_smoke_mode: ${{ inputs.win_x64_smoke_mode || (github.ref_name == 'release/v0.18.0' && 'skip') || 'core' }}
|
||||
win_x64_smoke_mode: ${{ inputs.win_x64_smoke_mode || 'core' }}
|
||||
|
||||
notify:
|
||||
name: Notify Feishu (release)
|
||||
@@ -153,6 +148,8 @@ jobs:
|
||||
PREVIOUS_COMMIT: ${{ needs.build.outputs.previous_commit }}
|
||||
CHANGELOG_FILE: ${{ steps.changelog.outputs.file }}
|
||||
BUILD_STATE: ${{ needs.build.result }}
|
||||
MAC_ARM64_SMOKE_RESULT: ${{ needs.build.outputs.mac_arm64_smoke_result }}
|
||||
WIN_X64_SMOKE_RESULT: ${{ needs.build.outputs.win_x64_smoke_result }}
|
||||
MAC_ARM64_URL: ${{ needs.build.outputs.mac_arm64_url }}
|
||||
MAC_INTEL_URL: ${{ needs.build.outputs.mac_intel_url }}
|
||||
WIN_URL: ${{ needs.build.outputs.win_url }}
|
||||
|
||||
@@ -84,6 +84,12 @@ on:
|
||||
previous_commit:
|
||||
description: "Commit SHA of the previously published prerelease; empty on cold start."
|
||||
value: ${{ jobs.metadata.outputs.previous_commit }}
|
||||
mac_arm64_smoke_result:
|
||||
description: "macOS arm64 packaged smoke outcome; advisory to publication."
|
||||
value: ${{ jobs.build_mac.outputs.smoke_result }}
|
||||
win_x64_smoke_result:
|
||||
description: "Windows x64 packaged smoke outcome; advisory to publication."
|
||||
value: ${{ jobs.build_win.outputs.smoke_result }}
|
||||
version_metadata_url:
|
||||
description: "Public R2 URL of this build's version metadata.json."
|
||||
value: ${{ jobs.publish.outputs.version_metadata_url }}
|
||||
@@ -241,6 +247,8 @@ jobs:
|
||||
name: Build prerelease mac arm64
|
||||
needs: [metadata, verify]
|
||||
runs-on: macos-14
|
||||
outputs:
|
||||
smoke_result: ${{ steps.mac_smoke.outcome }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_COMMIT: ${{ needs.metadata.outputs.commit }}
|
||||
@@ -505,6 +513,8 @@ jobs:
|
||||
${{ runner.temp }}/mac-framework-diagnostics.txt
|
||||
if-no-files-found: warn
|
||||
- name: Smoke prerelease mac packaged runtime
|
||||
id: mac_smoke
|
||||
continue-on-error: true
|
||||
working-directory: e2e
|
||||
env:
|
||||
OD_PACKAGED_E2E_BUILD_JSON_PATH: ${{ runner.temp }}/release-build/mac_arm64/build.json
|
||||
@@ -887,6 +897,8 @@ jobs:
|
||||
name: Build prerelease win x64
|
||||
needs: [metadata, verify]
|
||||
runs-on: windows-latest
|
||||
outputs:
|
||||
smoke_result: ${{ steps.win_smoke.outcome }}
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
RELEASE_COMMIT: ${{ needs.metadata.outputs.commit }}
|
||||
@@ -1018,6 +1030,7 @@ jobs:
|
||||
"deletedFailedCacheKey=$matchedKey count=$($caches.Count)"
|
||||
- name: Build prerelease win_x64 update fixture
|
||||
if: ${{ inputs.win_x64_smoke_mode == 'full' && inputs.win_x64_update_metadata_url == '' && inputs.win_x64_update_target_version == '' }}
|
||||
continue-on-error: true
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -1031,7 +1044,9 @@ jobs:
|
||||
$updateBuild = $updateOutput | ConvertFrom-Json
|
||||
pnpm.cmd exec tools-pack win validate-payload --namespace release-prerelease-win --payload-path $updateBuild.payloadPath --expected-version $updateVersion --json
|
||||
- name: Smoke prerelease windows packaged runtime
|
||||
id: win_smoke
|
||||
if: ${{ inputs.win_x64_smoke_mode != 'skip' }}
|
||||
continue-on-error: true
|
||||
working-directory: e2e
|
||||
env:
|
||||
OD_PACKAGED_E2E_BUILD_JSON_PATH: ${{ runner.temp }}\release-build\win_x64\build.json
|
||||
|
||||
@@ -75,8 +75,10 @@ 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 notifyReleaseFeishuWorkflowPath = join(workspaceRoot, ".github", "workflows", "notify-release-feishu.yml");
|
||||
const cutReleaseWorkflowPath = join(workspaceRoot, ".github", "workflows", "cut-release.yml");
|
||||
const cutPatchReleaseWorkflowPath = join(workspaceRoot, ".github", "workflows", "cut-patch-release.yml");
|
||||
const feishuCardScriptPath = join(workspaceRoot, "tools", "release", "src", "notifications", "feishu.ts");
|
||||
const feishuNoticeScriptPath = join(workspaceRoot, "tools", "release", "src", "notifications", "feishu-notice.ts");
|
||||
const landingPageDailyFeishuWorkflowPath = join(workspaceRoot, ".github", "workflows", "landing-page-daily-feishu.yml");
|
||||
const landingPageCiWorkflowPath = join(workspaceRoot, ".github", "workflows", "landing-page-ci.yml");
|
||||
@@ -323,6 +325,52 @@ process.exit(1);
|
||||
await writeFile(ghCmdPath, `@echo off\r\n"${process.execPath}" "%~dp0gh" %*\r\n`);
|
||||
}
|
||||
|
||||
async function renderFeishuBuildCard(env: Record<string, string>): Promise<Record<string, unknown>> {
|
||||
let payload: Record<string, unknown> | undefined;
|
||||
const server = createServer((request, response) => {
|
||||
void (async () => {
|
||||
let raw = "";
|
||||
for await (const chunk of request) raw += chunk.toString();
|
||||
payload = JSON.parse(raw) as Record<string, unknown>;
|
||||
response.setHeader("content-type", "application/json");
|
||||
response.end(JSON.stringify({ code: 0 }));
|
||||
})();
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.once("error", reject);
|
||||
server.listen(0, "127.0.0.1", () => {
|
||||
server.off("error", reject);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const address = server.address();
|
||||
if (address == null || typeof address === "string") {
|
||||
throw new Error("Feishu card fixture did not bind to a TCP port");
|
||||
}
|
||||
|
||||
try {
|
||||
await execFileAsync(process.execPath, ["--experimental-strip-types", feishuCardScriptPath], {
|
||||
cwd: workspaceRoot,
|
||||
env: {
|
||||
...process.env,
|
||||
BUILD_STATE: "success",
|
||||
CHANNEL_LABEL: "Prerelease",
|
||||
FEISHU_WEBHOOK: `http://127.0.0.1:${address.port}`,
|
||||
VERSION: "0.19.0-prerelease.1",
|
||||
...env,
|
||||
},
|
||||
});
|
||||
} finally {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
server.close((error) => (error == null ? resolve() : reject(error)));
|
||||
});
|
||||
}
|
||||
|
||||
if (payload == null) throw new Error("Feishu card fixture received no payload");
|
||||
return payload;
|
||||
}
|
||||
|
||||
describe("packaged smoke workflow", () => {
|
||||
it("[P2] keeps packaged smoke outside the main CI gate", async () => {
|
||||
const workflow = await readFile(ciWorkflowPath, "utf8");
|
||||
@@ -1888,7 +1936,7 @@ process.stdin.on("end", () => {
|
||||
expect(workflow).toContain("tools/release/src/notifications/feishu-notice.ts");
|
||||
});
|
||||
|
||||
it("[P1] runs a metadata-independent prerelease Windows smoke before release cuts", async () => {
|
||||
it("[P1] keeps the metadata-independent prerelease Windows smoke advisory to release cuts", async () => {
|
||||
const [canary, minorCut, patchCut] = await Promise.all([
|
||||
readFile(mainPrereleaseWinSmokeWorkflowPath, "utf8"),
|
||||
readFile(cutReleaseWorkflowPath, "utf8"),
|
||||
@@ -1924,14 +1972,99 @@ process.stdin.on("end", () => {
|
||||
["cut-release", minorCut],
|
||||
["cut-patch-release", patchCut],
|
||||
] as const) {
|
||||
const smokeJob = sectionBetween(workflow, " prerelease_win_smoke:", "\n cut:");
|
||||
expect(smokeJob, label).toContain("uses: ./.github/workflows/main-prerelease-win-smoke.yml");
|
||||
expect(smokeJob, label).not.toContain("with:");
|
||||
expect(smokeJob, label).toContain("secrets: inherit");
|
||||
expect(workflow, label).toContain("permissions:\n actions: write\n contents: read");
|
||||
expect(workflow, label).toContain("needs: prerelease_win_smoke");
|
||||
expect(workflow, label).toContain("ref: ${{ needs.prerelease_win_smoke.outputs.commit }}");
|
||||
const cutJob = workflow.slice(workflow.indexOf(" cut:"));
|
||||
expect(workflow, label).not.toContain("prerelease_win_smoke");
|
||||
expect(workflow, label).not.toContain("uses: ./.github/workflows/main-prerelease-win-smoke.yml");
|
||||
expect(workflow, label).toContain("permissions:\n contents: read");
|
||||
expect(cutJob, label).not.toContain("needs:");
|
||||
expect(cutJob, label).toContain("ref: main");
|
||||
}
|
||||
|
||||
expect(canary).toContain("该 smoke 是独立质量信号,不阻塞 release cut 或 prerelease 打包 / 发布。");
|
||||
expect(canary).not.toContain("release cut 会被阻止");
|
||||
});
|
||||
|
||||
it("[P1] keeps prerelease smoke failures advisory and annotates the download card", async () => {
|
||||
const [prerelease, notify, feishuCard] = await Promise.all([
|
||||
readFile(releasePrereleaseWorkflowPath, "utf8"),
|
||||
readFile(notifyReleaseFeishuWorkflowPath, "utf8"),
|
||||
readFile(feishuCardScriptPath, "utf8"),
|
||||
]);
|
||||
|
||||
const workflowCall = sectionBetween(prerelease, " workflow_call:", "permissions:");
|
||||
expect(workflowCall).toContain("mac_arm64_smoke_result:");
|
||||
expect(workflowCall).toContain("value: ${{ jobs.build_mac.outputs.smoke_result }}");
|
||||
expect(workflowCall).toContain("win_x64_smoke_result:");
|
||||
expect(workflowCall).toContain("value: ${{ jobs.build_win.outputs.smoke_result }}");
|
||||
|
||||
const macJob = sectionBetween(prerelease, " build_mac:", " build_mac_intel:");
|
||||
const macSmoke = sectionBetween(
|
||||
macJob,
|
||||
" - name: Smoke prerelease mac packaged runtime",
|
||||
" - name: Write mac_arm64 release report",
|
||||
);
|
||||
expect(macJob).toContain("outputs:\n smoke_result: ${{ steps.mac_smoke.outcome }}");
|
||||
expect(macSmoke).toContain("id: mac_smoke");
|
||||
expect(macSmoke).toContain("continue-on-error: true");
|
||||
|
||||
const winJob = sectionBetween(prerelease, " build_win:", " build_linux:");
|
||||
const winSmokeFixture = sectionBetween(
|
||||
winJob,
|
||||
" - name: Build prerelease win_x64 update fixture",
|
||||
" - name: Smoke prerelease windows packaged runtime",
|
||||
);
|
||||
const winSmoke = sectionBetween(
|
||||
winJob,
|
||||
" - name: Smoke prerelease windows packaged runtime",
|
||||
" - name: Write win_x64 release report",
|
||||
);
|
||||
expect(winJob).toContain("outputs:\n smoke_result: ${{ steps.win_smoke.outcome }}");
|
||||
expect(winSmokeFixture).toContain("continue-on-error: true");
|
||||
expect(winSmoke).toContain("id: win_smoke");
|
||||
expect(winSmoke).toContain("continue-on-error: true");
|
||||
expect(winJob.indexOf("Smoke prerelease windows packaged runtime")).toBeLessThan(
|
||||
winJob.indexOf("Publish windows prerelease platform"),
|
||||
);
|
||||
|
||||
const notifyJob = notify.slice(notify.indexOf(" notify:"));
|
||||
expect(notifyJob).toContain("MAC_ARM64_SMOKE_RESULT: ${{ needs.build.outputs.mac_arm64_smoke_result }}");
|
||||
expect(notifyJob).toContain("WIN_X64_SMOKE_RESULT: ${{ needs.build.outputs.win_x64_smoke_result }}");
|
||||
expect(notifyJob).toContain("MAC_ARM64_URL: ${{ needs.build.outputs.mac_arm64_url }}");
|
||||
expect(notifyJob).toContain("WIN_URL: ${{ needs.build.outputs.win_url }}");
|
||||
expect(notifyJob).toContain("tools/release/src/notifications/feishu.ts");
|
||||
expect(notifyJob).not.toContain("tools/release/src/notifications/feishu-notice.ts");
|
||||
|
||||
expect(feishuCard).toContain('optional("MAC_ARM64_SMOKE_RESULT")');
|
||||
expect(feishuCard).toContain('optional("WIN_X64_SMOKE_RESULT")');
|
||||
expect(feishuCard).toContain("Windows x64 smoke 失败");
|
||||
expect(feishuCard).toContain("macOS arm64 smoke 失败");
|
||||
expect(feishuCard).toContain("产物已继续发布,可通过下方链接下载");
|
||||
expect(feishuCard).toContain('template: smokeFailures.length > 0 ? "orange"');
|
||||
});
|
||||
|
||||
it("[P1] keeps download actions on a prerelease card with a failed Windows smoke", async () => {
|
||||
const payload = await renderFeishuBuildCard({
|
||||
MAC_ARM64_SMOKE_RESULT: "success",
|
||||
MAC_ARM64_URL: "https://releases.example/mac.dmg",
|
||||
WIN_X64_SMOKE_RESULT: "failure",
|
||||
WIN_URL: "https://releases.example/windows.exe",
|
||||
});
|
||||
const card = payload.card as {
|
||||
elements: Array<{ actions?: Array<{ url?: string }>; text?: { content?: string } }>;
|
||||
header: { template?: string; title?: { content?: string } };
|
||||
};
|
||||
|
||||
expect(card.header).toMatchObject({
|
||||
template: "orange",
|
||||
title: { content: expect.stringContaining("Windows x64 smoke 失败") },
|
||||
});
|
||||
expect(card.elements.map((element) => element.text?.content).filter(Boolean)).toContain(
|
||||
"**Smoke 告警**\n- Windows x64 smoke 失败\n\n产物已继续发布,可通过下方链接下载。",
|
||||
);
|
||||
expect(card.elements.flatMap((element) => element.actions ?? []).map((action) => action.url)).toEqual([
|
||||
"https://releases.example/mac.dmg",
|
||||
"https://releases.example/windows.exe",
|
||||
]);
|
||||
});
|
||||
|
||||
it("[P2] gates the Thursday patch cut on the Tuesday minor being published", async () => {
|
||||
@@ -1980,9 +2113,9 @@ process.stdin.on("end", () => {
|
||||
expect(workflow).toContain(`- name: ${step}\n if: steps.guard.outputs.published == 'true'`);
|
||||
}
|
||||
|
||||
// Happy path keeps cut-release's mechanics: cut the exact main commit that
|
||||
// passed the prerelease Windows canary, then push with the App token.
|
||||
expect(workflow).toContain("ref: ${{ needs.prerelease_win_smoke.outputs.commit }}");
|
||||
// Happy path keeps cut-release's mechanics: cut current main independently
|
||||
// from the advisory Windows canary, then push with the App token.
|
||||
expect(workflow).toContain("ref: main");
|
||||
expect(workflow).toContain("token: ${{ steps.app.outputs.token }}");
|
||||
expect(workflow).toContain('git push origin "$BRANCH"');
|
||||
// The version bump is a no-op whenever main already leads stable by this exact
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
// PREVIOUS_COMMIT changelog baseline (last published build); empty on cold start
|
||||
// CHANGELOG_FILE path to a file holding `git log` output (one commit per line)
|
||||
// BUILD_STATE build result (success | ...) — drives header color
|
||||
// MAC_ARM64_SMOKE_RESULT macOS arm64 packaged smoke outcome (success | failure | skipped)
|
||||
// WIN_X64_SMOKE_RESULT Windows x64 packaged smoke outcome (success | failure | skipped)
|
||||
// STREAM_LABEL human label for the trigger (e.g. "release 分支推送" / "每日定时")
|
||||
// REPO owner/name
|
||||
// RUN_URL link back to the GitHub Actions run
|
||||
@@ -52,6 +54,15 @@ const streamLabel = optional("STREAM_LABEL", "构建");
|
||||
const repo = optional("REPO");
|
||||
const runUrl = optional("RUN_URL");
|
||||
|
||||
const smokeFailures = buildState === "success"
|
||||
? [
|
||||
{ failureText: "macOS arm64 smoke 失败", result: optional("MAC_ARM64_SMOKE_RESULT") },
|
||||
{ failureText: "Windows x64 smoke 失败", result: optional("WIN_X64_SMOKE_RESULT") },
|
||||
]
|
||||
.filter((entry) => entry.result === "failure")
|
||||
.map((entry) => entry.failureText)
|
||||
: [];
|
||||
|
||||
const MAX_CHANGELOG_LINES = 30;
|
||||
|
||||
// Download buttons, in display order. Empty URLs are skipped, so the set of
|
||||
@@ -122,6 +133,15 @@ function buildCard(): FeishuCard {
|
||||
fields.push({ is_short: true, text: { tag: "lark_md", content: `**触发**\n${streamLabel}` } });
|
||||
|
||||
const elements: FeishuElement[] = [{ tag: "div", fields }];
|
||||
if (smokeFailures.length > 0) {
|
||||
elements.push({
|
||||
tag: "div",
|
||||
text: {
|
||||
tag: "lark_md",
|
||||
content: `**Smoke 告警**\n${smokeFailures.map((failure) => `- ${failure}`).join("\n")}\n\n产物已继续发布,可通过下方链接下载。`,
|
||||
},
|
||||
});
|
||||
}
|
||||
elements.push({
|
||||
tag: "div",
|
||||
text: { tag: "lark_md", content: `**自上个 ${channelLabel} 新增提交**\n${changelogMarkdown()}` },
|
||||
@@ -143,8 +163,13 @@ function buildCard(): FeishuCard {
|
||||
return {
|
||||
config: { wide_screen_mode: true },
|
||||
header: {
|
||||
template: headerTemplate(),
|
||||
title: { tag: "plain_text", content: `🚀 Open Design ${channelLabel} ${version}` },
|
||||
template: smokeFailures.length > 0 ? "orange" : headerTemplate(),
|
||||
title: {
|
||||
tag: "plain_text",
|
||||
content: smokeFailures.length > 0
|
||||
? `⚠️ Open Design ${channelLabel} ${version} · ${smokeFailures.join("、")}`
|
||||
: `🚀 Open Design ${channelLabel} ${version}`,
|
||||
},
|
||||
},
|
||||
elements,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user