























@@ -2,6 +2,7 @@
22import { spawnSync } from "node:child_process";
33import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
44import path from "node:path";
5+import { pathToFileURL } from "node:url";
5667interface Options {
78beta: string;
@@ -101,6 +102,8 @@ function shellQuote(value: string): string {
101102return `'${value.replace(/'/g, "'\\''")}'`;
102103}
103104105+const TELEGRAM_BETA_WORKFLOW_FILE = "npm-telegram-beta-e2e.yml";
106+104107function resolveBetaVersion(beta: string): string {
105108const value = beta.trim().replace(/^openclaw@/, "");
106109if (/^\d{4}\.\d+\.\d+-beta\.\d+$/u.test(value)) {
@@ -160,13 +163,92 @@ function ghJson(repo: string, pathSuffix: string): unknown {
160163return JSON.parse(run("gh", ["api", `repos/${repo}/${pathSuffix}`], { capture: true }));
161164}
162165163-function dispatchTelegram(options: Options, packageSpec: string): string {
166+export function parseWorkflowRunIdFromOutput(output: string): string | undefined {
167+return /\/actions\/runs\/(\d+)/u.exec(output)?.[1];
168+}
169+170+type WorkflowRunListEntry = {
171+createdAt?: string;
172+databaseId?: number | string;
173+};
174+175+function normalizeRunId(value: unknown): string | undefined {
176+if (typeof value === "number" && Number.isFinite(value)) {
177+return String(value);
178+}
179+if (typeof value === "string" && value.trim()) {
180+return value.trim();
181+}
182+return undefined;
183+}
184+185+export function selectNewestDispatchedRunId(params: {
186+beforeIds: ReadonlySet<string>;
187+runs: readonly WorkflowRunListEntry[];
188+}): string | undefined {
189+return params.runs
190+.filter((entry) => {
191+const id = normalizeRunId(entry.databaseId);
192+return id !== undefined && !params.beforeIds.has(id);
193+})
194+.toSorted((a, b) => (b.createdAt ?? "").localeCompare(a.createdAt ?? ""))
195+.map((entry) => normalizeRunId(entry.databaseId))
196+.find((id): id is string => id !== undefined);
197+}
198+199+function listWorkflowDispatchRuns(repo: string, workflow: string): WorkflowRunListEntry[] {
200+return JSON.parse(
201+run(
202+"gh",
203+[
204+"run",
205+"list",
206+"--repo",
207+repo,
208+"--workflow",
209+workflow,
210+"--event",
211+"workflow_dispatch",
212+"--limit",
213+"50",
214+"--json",
215+"databaseId,createdAt",
216+],
217+{ capture: true },
218+),
219+) as WorkflowRunListEntry[];
220+}
221+222+async function findDispatchedWorkflowRunId(params: {
223+beforeIds: ReadonlySet<string>;
224+repo: string;
225+workflow: string;
226+}): Promise<string> {
227+for (let attempt = 0; attempt < 60; attempt++) {
228+const runId = selectNewestDispatchedRunId({
229+beforeIds: params.beforeIds,
230+runs: listWorkflowDispatchRuns(params.repo, params.workflow),
231+});
232+if (runId) {
233+return runId;
234+}
235+await new Promise((resolve) => setTimeout(resolve, 5_000));
236+}
237+throw new Error(`could not find dispatched run for ${params.workflow}`);
238+}
239+240+async function dispatchTelegram(options: Options, packageSpec: string): Promise<string> {
241+const beforeIds = new Set(
242+listWorkflowDispatchRuns(options.repo, TELEGRAM_BETA_WORKFLOW_FILE)
243+.map((entry) => normalizeRunId(entry.databaseId))
244+.filter((id): id is string => id !== undefined),
245+);
164246const output = run(
165247"gh",
166248[
167249"workflow",
168250"run",
169-"NPM Telegram Beta E2E",
251+TELEGRAM_BETA_WORKFLOW_FILE,
170252"--repo",
171253options.repo,
172254"--ref",
@@ -180,11 +262,15 @@ function dispatchTelegram(options: Options, packageSpec: string): string {
180262],
181263{ capture: true },
182264);
183-const runId = /\/actions\/runs\/(\d+)/u.exec(output)?.[1];
184-if (!runId) {
185-throw new Error(`could not parse workflow run id from gh output:\n${output}`);
265+const runId = parseWorkflowRunIdFromOutput(output);
266+if (runId) {
267+return runId;
186268}
187-return runId;
269+return await findDispatchedWorkflowRunId({
270+ beforeIds,
271+repo: options.repo,
272+workflow: TELEGRAM_BETA_WORKFLOW_FILE,
273+});
188274}
189275190276async function pollRun(repo: string, runId: string): Promise<void> {
@@ -266,7 +352,7 @@ async function main(): Promise<void> {
266352}
267353268354if (!options.skipTelegram) {
269-const runId = dispatchTelegram(options, packageSpec);
355+const runId = await dispatchTelegram(options, packageSpec);
270356await pollRun(options.repo, runId);
271357const artifactDir = downloadTelegramArtifact(options.repo, runId);
272358const report = findFile(artifactDir, "telegram-qa-report.md");
@@ -277,7 +363,9 @@ async function main(): Promise<void> {
277363}
278364}
279365280-await main().catch((error: unknown) => {
281-console.error(error instanceof Error ? error.message : String(error));
282-process.exit(1);
283-});
366+if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
367+await main().catch((error: unknown) => {
368+console.error(error instanceof Error ? error.message : String(error));
369+process.exit(1);
370+});
371+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。