





















@@ -18,6 +18,11 @@ function formatSeconds(value) {
1818return value === null ? "" : `${value}s`;
1919}
202021+function parseRunList(raw) {
22+const parsed = JSON.parse(raw);
23+return Array.isArray(parsed) ? parsed : [];
24+}
25+2126function collectRunTimingContext(run) {
2227const created = parseTime(run.createdAt);
2328const updated = parseTime(run.updatedAt);
@@ -64,6 +69,17 @@ export function summarizeRunTimings(run, limit = 15) {
6469};
6570}
667172+export function selectLatestMainPushCiRun(runs, headSha = null) {
73+const pushRuns = runs.filter((run) => run.event === "push");
74+if (headSha) {
75+const matchingRun = pushRuns.find((run) => run.headSha === headSha);
76+if (matchingRun) {
77+return matchingRun;
78+}
79+}
80+return pushRuns[0] ?? null;
81+}
82+6783function getLatestCiRunId() {
6884const raw = execFileSync(
6985"gh",
@@ -78,6 +94,40 @@ function getLatestCiRunId() {
7894return String(runId);
7995}
809697+function getRemoteMainSha() {
98+const raw = execFileSync("git", ["ls-remote", "origin", "main"], { encoding: "utf8" }).trim();
99+const [sha] = raw.split(/\s+/u);
100+if (!sha) {
101+throw new Error("Could not resolve origin/main");
102+}
103+return sha;
104+}
105+106+function getLatestMainPushCiRunId() {
107+const headSha = getRemoteMainSha();
108+const raw = execFileSync(
109+"gh",
110+[
111+"run",
112+"list",
113+"--branch",
114+"main",
115+"--workflow",
116+"CI",
117+"--limit",
118+"20",
119+"--json",
120+"databaseId,headSha,event,status,conclusion",
121+],
122+{ encoding: "utf8" },
123+);
124+const run = selectLatestMainPushCiRun(parseRunList(raw), headSha);
125+if (!run?.databaseId) {
126+throw new Error(`No push CI run found for origin/main ${headSha.slice(0, 10)}`);
127+}
128+return String(run.databaseId);
129+}
130+81131function listRecentSuccessfulCiRuns(limit) {
82132const raw = execFileSync(
83133"gh",
@@ -161,11 +211,15 @@ function printSection(title, jobs, metric) {
161211}
162212}
163213164-async function main() {
165-const args = process.argv.slice(2);
214+export function parseRunTimingArgs(args) {
166215const recentIndex = args.indexOf("--recent");
167216const limitIndex = args.indexOf("--limit");
168217const ignoredArgIndexes = new Set();
218+for (const [index, arg] of args.entries()) {
219+if (arg === "--" || arg === "--latest-main") {
220+ignoredArgIndexes.add(index);
221+}
222+}
169223if (limitIndex !== -1) {
170224ignoredArgIndexes.add(limitIndex);
171225ignoredArgIndexes.add(limitIndex + 1);
@@ -176,8 +230,21 @@ async function main() {
176230}
177231const limit =
178232limitIndex === -1 ? 15 : Math.max(1, Number.parseInt(args[limitIndex + 1] ?? "", 10) || 15);
179-if (recentIndex !== -1) {
180-const recentLimit = Math.max(1, Number.parseInt(args[recentIndex + 1] ?? "", 10) || 10);
233+const recentLimit =
234+recentIndex === -1 ? null : Math.max(1, Number.parseInt(args[recentIndex + 1] ?? "", 10) || 10);
235+return {
236+explicitRunId: args.find((_arg, index) => !ignoredArgIndexes.has(index)),
237+ limit,
238+ recentLimit,
239+useLatestMain: args.includes("--latest-main"),
240+};
241+}
242+243+async function main() {
244+const { explicitRunId, limit, recentLimit, useLatestMain } = parseRunTimingArgs(
245+process.argv.slice(2),
246+);
247+if (recentLimit !== null) {
181248for (const run of listRecentSuccessfulCiRuns(recentLimit)) {
182249const summary = summarizeJobs(loadRun(run.databaseId));
183250console.log(
@@ -197,7 +264,7 @@ async function main() {
197264}
198265return;
199266}
200-const runId = args.find((_arg, index) => !ignoredArgIndexes.has(index)) ?? getLatestCiRunId();
267+const runId = explicitRunId ?? (useLatestMain ? getLatestMainPushCiRunId() : getLatestCiRunId());
201268const summary = summarizeRunTimings(loadRun(runId), limit);
202269203270console.log(
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。