@mutmutco/cli 4.5.123 → 4.5.125
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.cjs +696 -253
- package/dist/runbook-sweep.cjs +5536 -0
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -76,19 +76,19 @@ function hardExit(code) {
|
|
|
76
76
|
process.exit(code);
|
|
77
77
|
}
|
|
78
78
|
function flushStream(stream) {
|
|
79
|
-
return new Promise((
|
|
79
|
+
return new Promise((resolve8) => {
|
|
80
80
|
try {
|
|
81
|
-
stream.write("", () =>
|
|
81
|
+
stream.write("", () => resolve8());
|
|
82
82
|
} catch {
|
|
83
|
-
|
|
83
|
+
resolve8();
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
async function flushStdio(timeoutMs = STDIO_FLUSH_TIMEOUT_MS) {
|
|
88
88
|
await Promise.race([
|
|
89
89
|
Promise.all([flushStream(process.stdout), flushStream(process.stderr)]),
|
|
90
|
-
new Promise((
|
|
91
|
-
setTimeout(
|
|
90
|
+
new Promise((resolve8) => {
|
|
91
|
+
setTimeout(resolve8, timeoutMs).unref?.();
|
|
92
92
|
})
|
|
93
93
|
]);
|
|
94
94
|
}
|
|
@@ -96,7 +96,7 @@ async function cleanExit(code) {
|
|
|
96
96
|
process.exitCode = code;
|
|
97
97
|
await closeHttpPool();
|
|
98
98
|
await flushStdio();
|
|
99
|
-
await new Promise((
|
|
99
|
+
await new Promise((resolve8) => setImmediate(resolve8));
|
|
100
100
|
return void 0;
|
|
101
101
|
}
|
|
102
102
|
async function finishCliRun(watchdogMs = CLI_EXIT_WATCHDOG_MS) {
|
|
@@ -644,7 +644,7 @@ function rateLimitedReceipt(opts) {
|
|
|
644
644
|
};
|
|
645
645
|
}
|
|
646
646
|
async function runWithRateLimitBackoff(operation, opts) {
|
|
647
|
-
const sleep2 = opts.sleep ?? ((ms) => new Promise((
|
|
647
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
648
648
|
const now = opts.now ?? Date.now;
|
|
649
649
|
const log = opts.log ?? ((message2) => console.warn(message2));
|
|
650
650
|
const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
|
|
@@ -734,7 +734,7 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
|
734
734
|
const attempts = opts.attempts ?? 3;
|
|
735
735
|
const baseDelayMs = opts.baseDelayMs ?? 250;
|
|
736
736
|
const retryOn = opts.retryOn ?? ((res) => res.status >= 500);
|
|
737
|
-
const sleep2 = opts.sleep ?? ((ms) => new Promise((
|
|
737
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
738
738
|
let lastErr;
|
|
739
739
|
for (let i = 0; i < attempts; i++) {
|
|
740
740
|
const isLast = i === attempts - 1;
|
|
@@ -1057,8 +1057,8 @@ async function readStdin(opts = {}) {
|
|
|
1057
1057
|
})().catch(() => {
|
|
1058
1058
|
});
|
|
1059
1059
|
let timer;
|
|
1060
|
-
const timeout = new Promise((
|
|
1061
|
-
timer = setTimeout(
|
|
1060
|
+
const timeout = new Promise((resolve8) => {
|
|
1061
|
+
timer = setTimeout(resolve8, timeoutMs);
|
|
1062
1062
|
});
|
|
1063
1063
|
try {
|
|
1064
1064
|
await Promise.race([drain, timeout]);
|
|
@@ -1125,12 +1125,12 @@ function killProcessTree(pid) {
|
|
|
1125
1125
|
function execFileHard(file, args, options) {
|
|
1126
1126
|
const { timeout, step, ...rest } = options;
|
|
1127
1127
|
const started = Date.now();
|
|
1128
|
-
return new Promise((
|
|
1128
|
+
return new Promise((resolve8, reject) => {
|
|
1129
1129
|
const child2 = (0, import_node_child_process2.execFile)(file, args, { encoding: "utf8", windowsHide: true, ...rest, timeout: 0 }, (error, stdout, stderr) => {
|
|
1130
1130
|
clearTimeout(timer);
|
|
1131
1131
|
if (expired) return;
|
|
1132
1132
|
if (error) reject(error);
|
|
1133
|
-
else
|
|
1133
|
+
else resolve8({ stdout: String(stdout), stderr: String(stderr) });
|
|
1134
1134
|
});
|
|
1135
1135
|
let expired = false;
|
|
1136
1136
|
const timer = setTimeout(() => {
|
|
@@ -5119,8 +5119,8 @@ function survivingFlagArgs(argv, rejectedFlag, ownFlags) {
|
|
|
5119
5119
|
return out;
|
|
5120
5120
|
}
|
|
5121
5121
|
function formatPositionalTarget(commandPath3, argName, opts = {}) {
|
|
5122
|
-
const
|
|
5123
|
-
const target =
|
|
5122
|
+
const concrete2 = opts.flag && opts.argv ? targetFlagValueFromArgv(opts.flag, opts.argv) : void 0;
|
|
5123
|
+
const target = concrete2 ?? `<${argName}>`;
|
|
5124
5124
|
const kept = opts.flag && opts.argv && opts.ownFlags ? survivingFlagArgs(opts.argv, opts.flag, opts.ownFlags) : [];
|
|
5125
5125
|
return [`mmi-cli ${commandPath3}`, target, ...kept].join(" ");
|
|
5126
5126
|
}
|
|
@@ -5656,7 +5656,7 @@ function resolveVerbAliasShim(argv) {
|
|
|
5656
5656
|
|
|
5657
5657
|
// src/command-composition.ts
|
|
5658
5658
|
var import_node_os25 = require("node:os");
|
|
5659
|
-
var
|
|
5659
|
+
var import_node_path56 = require("node:path");
|
|
5660
5660
|
|
|
5661
5661
|
// src/board-read.ts
|
|
5662
5662
|
var import_node_fs14 = require("node:fs");
|
|
@@ -5789,6 +5789,8 @@ var APP_ELIGIBLE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
5789
5789
|
"read run view",
|
|
5790
5790
|
"read run list",
|
|
5791
5791
|
"read run watch",
|
|
5792
|
+
"read run log",
|
|
5793
|
+
// #8136: bounded jobs/logs REST read, same polling credential.
|
|
5792
5794
|
"read pr view",
|
|
5793
5795
|
"read pr checks",
|
|
5794
5796
|
"read issue view",
|
|
@@ -6733,7 +6735,7 @@ async function readRestPrSnapshotWithRetry(prNumber, repo, gh2 = defaultGhApi, o
|
|
|
6733
6735
|
const retries = options?.retries ?? PR_SNAPSHOT_READ_RETRIES;
|
|
6734
6736
|
const untilMs = options?.retryTransientUntilMs;
|
|
6735
6737
|
const delayMs = options?.delayMs ?? (untilMs !== void 0 ? PR_CHECKS_POLL_MS : PR_SNAPSHOT_READ_DELAY_MS);
|
|
6736
|
-
const sleep2 = options?.sleep ?? ((ms) => new Promise((
|
|
6738
|
+
const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
6737
6739
|
const now = options?.now ?? (() => Date.now());
|
|
6738
6740
|
let lastError = "no attempt completed";
|
|
6739
6741
|
let attempt = 0;
|
|
@@ -10108,18 +10110,18 @@ function isGhHttpNotFound(err) {
|
|
|
10108
10110
|
function isForkStyleHead(head) {
|
|
10109
10111
|
return head.includes(":");
|
|
10110
10112
|
}
|
|
10111
|
-
async function defaultReadRemoteHead(
|
|
10113
|
+
async function defaultReadRemoteHead(exec3, repo, head) {
|
|
10112
10114
|
try {
|
|
10113
|
-
await
|
|
10115
|
+
await exec3("gh", ["api", `repos/${repo}/git/ref/heads/${encodeURIComponent(head)}`], { timeout: 15e3 });
|
|
10114
10116
|
return "present";
|
|
10115
10117
|
} catch (e) {
|
|
10116
10118
|
if (isGhHttpNotFound(e)) return "absent";
|
|
10117
10119
|
return "unknown";
|
|
10118
10120
|
}
|
|
10119
10121
|
}
|
|
10120
|
-
async function defaultCompareRefs(
|
|
10122
|
+
async function defaultCompareRefs(exec3, repo, base, head) {
|
|
10121
10123
|
try {
|
|
10122
|
-
const { stdout } = await
|
|
10124
|
+
const { stdout } = await exec3(
|
|
10123
10125
|
"gh",
|
|
10124
10126
|
["api", `repos/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`],
|
|
10125
10127
|
{ timeout: 15e3 }
|
|
@@ -10130,11 +10132,11 @@ async function defaultCompareRefs(exec2, repo, base, head) {
|
|
|
10130
10132
|
return "unknown";
|
|
10131
10133
|
}
|
|
10132
10134
|
}
|
|
10133
|
-
async function publishAbsentPrHeadIfOwned(repo, head,
|
|
10135
|
+
async function publishAbsentPrHeadIfOwned(repo, head, exec3) {
|
|
10134
10136
|
if (isForkStyleHead(head)) return false;
|
|
10135
10137
|
let originRepo;
|
|
10136
10138
|
try {
|
|
10137
|
-
const { stdout } = await
|
|
10139
|
+
const { stdout } = await exec3("git", ["remote", "get-url", "--push", "origin"], { timeout: GIT_TIMEOUT_MS });
|
|
10138
10140
|
originRepo = repoFromRemoteUrl(stdout.trim());
|
|
10139
10141
|
} catch {
|
|
10140
10142
|
return false;
|
|
@@ -10142,20 +10144,20 @@ async function publishAbsentPrHeadIfOwned(repo, head, exec2) {
|
|
|
10142
10144
|
if (!originRepo || originRepo.toLowerCase() !== repo.toLowerCase()) return false;
|
|
10143
10145
|
let currentBranch2;
|
|
10144
10146
|
try {
|
|
10145
|
-
currentBranch2 = (await
|
|
10147
|
+
currentBranch2 = (await exec3("git", ["symbolic-ref", "--quiet", "--short", "HEAD"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
10146
10148
|
} catch {
|
|
10147
10149
|
return false;
|
|
10148
10150
|
}
|
|
10149
10151
|
if (!currentBranch2 || currentBranch2 !== head) return false;
|
|
10150
10152
|
let commit;
|
|
10151
10153
|
try {
|
|
10152
|
-
commit = (await
|
|
10154
|
+
commit = (await exec3("git", ["rev-parse", "--verify", "HEAD^{commit}"], { timeout: GIT_TIMEOUT_MS })).stdout.trim();
|
|
10153
10155
|
} catch {
|
|
10154
10156
|
return false;
|
|
10155
10157
|
}
|
|
10156
10158
|
if (!commit) return false;
|
|
10157
10159
|
try {
|
|
10158
|
-
await
|
|
10160
|
+
await exec3("git", ["push", "origin", `${commit}:refs/heads/${head}`], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10159
10161
|
return true;
|
|
10160
10162
|
} catch {
|
|
10161
10163
|
return false;
|
|
@@ -10285,22 +10287,22 @@ function upstreamFaultMessage(verb, stderr) {
|
|
|
10285
10287
|
const advice = mayRetryCreate(verb) ? "Retrying is the right action" : "The write may have completed server-side before the error \u2014 VERIFY before retrying, or a retry will duplicate it";
|
|
10286
10288
|
return `gh ${verb} create failed: GitHub's API is failing (server-side 5xx) \u2014 this is NOT a problem with your diff or arguments. GitHub request ID: ${requestId ?? "unavailable"}. ${advice}; note githubstatus.com may still read green while this is happening.`;
|
|
10287
10289
|
}
|
|
10288
|
-
async function resolveRepoForCreate(args,
|
|
10290
|
+
async function resolveRepoForCreate(args, exec3) {
|
|
10289
10291
|
const fromFlag = flagValue(args, "--repo");
|
|
10290
10292
|
if (fromFlag) return fromFlag;
|
|
10291
10293
|
try {
|
|
10292
|
-
const { stdout } = await
|
|
10294
|
+
const { stdout } = await exec3("git", ["remote", "get-url", "origin"], { timeout: 1e4 });
|
|
10293
10295
|
const fromOrigin = repoFromRemoteUrl(stdout.trim());
|
|
10294
10296
|
if (fromOrigin) return fromOrigin;
|
|
10295
10297
|
} catch {
|
|
10296
10298
|
}
|
|
10297
10299
|
return repoFromRemoteUrl(originUrlFromGitConfig() ?? "") || void 0;
|
|
10298
10300
|
}
|
|
10299
|
-
async function resolveHeadForCreate(args,
|
|
10301
|
+
async function resolveHeadForCreate(args, exec3) {
|
|
10300
10302
|
const fromFlag = flagValue(args, "--head");
|
|
10301
10303
|
if (fromFlag) return fromFlag;
|
|
10302
10304
|
try {
|
|
10303
|
-
const { stdout } = await
|
|
10305
|
+
const { stdout } = await exec3("git", ["rev-parse", "--abbrev-ref", "HEAD"], { timeout: 1e4 });
|
|
10304
10306
|
const head = stdout.trim();
|
|
10305
10307
|
return head && head !== "HEAD" ? head : void 0;
|
|
10306
10308
|
} catch {
|
|
@@ -10312,13 +10314,13 @@ async function resolveBodyForCreate(args, read) {
|
|
|
10312
10314
|
if (bodyFile !== void 0) return read(bodyFile, "utf8");
|
|
10313
10315
|
return flagValue(args, "--body") ?? "";
|
|
10314
10316
|
}
|
|
10315
|
-
async function defaultFindOpenPr(
|
|
10317
|
+
async function defaultFindOpenPr(exec3, input) {
|
|
10316
10318
|
const owner = input.repo.split("/")[0];
|
|
10317
10319
|
if (!owner) return void 0;
|
|
10318
10320
|
const qs = new URLSearchParams({ state: "open", head: `${owner}:${input.head}` });
|
|
10319
10321
|
if (input.base) qs.set("base", input.base);
|
|
10320
10322
|
try {
|
|
10321
|
-
const { stdout } = await
|
|
10323
|
+
const { stdout } = await exec3("gh", ["api", `repos/${input.repo}/pulls?${qs.toString()}`], { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10322
10324
|
const rows = JSON.parse(stdout);
|
|
10323
10325
|
const first = Array.isArray(rows) ? rows[0] : void 0;
|
|
10324
10326
|
if (typeof first?.number === "number" && typeof first.html_url === "string") {
|
|
@@ -10328,7 +10330,7 @@ async function defaultFindOpenPr(exec2, input) {
|
|
|
10328
10330
|
}
|
|
10329
10331
|
return void 0;
|
|
10330
10332
|
}
|
|
10331
|
-
async function defaultRestCreatePr(
|
|
10333
|
+
async function defaultRestCreatePr(exec3, input, deps = {}) {
|
|
10332
10334
|
const write = deps.write ?? import_promises.writeFile;
|
|
10333
10335
|
const remove2 = deps.remove ?? import_promises.unlink;
|
|
10334
10336
|
const ensureDir = deps.ensureDir ?? import_promises.mkdir;
|
|
@@ -10353,7 +10355,7 @@ async function defaultRestCreatePr(exec2, input, deps = {}) {
|
|
|
10353
10355
|
file
|
|
10354
10356
|
];
|
|
10355
10357
|
try {
|
|
10356
|
-
const { stdout } = await
|
|
10358
|
+
const { stdout } = await exec3("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10357
10359
|
const parsed = JSON.parse(stdout);
|
|
10358
10360
|
if (typeof parsed.number === "number" && typeof parsed.html_url === "string") {
|
|
10359
10361
|
return { number: parsed.number, url: parsed.html_url };
|
|
@@ -10365,7 +10367,7 @@ async function defaultRestCreatePr(exec2, input, deps = {}) {
|
|
|
10365
10367
|
const fromText = parseExistingPr(text);
|
|
10366
10368
|
if (fromText) return { ...fromText, existing: true };
|
|
10367
10369
|
if (/already exists/i.test(text)) {
|
|
10368
|
-
const found = await defaultFindOpenPr(
|
|
10370
|
+
const found = await defaultFindOpenPr(exec3, { repo: input.repo, head: input.head, base: input.base });
|
|
10369
10371
|
if (found) return found;
|
|
10370
10372
|
}
|
|
10371
10373
|
throw e;
|
|
@@ -10376,7 +10378,7 @@ async function defaultRestCreatePr(exec2, input, deps = {}) {
|
|
|
10376
10378
|
}
|
|
10377
10379
|
}
|
|
10378
10380
|
}
|
|
10379
|
-
async function defaultRestCreateIssue(
|
|
10381
|
+
async function defaultRestCreateIssue(exec3, input) {
|
|
10380
10382
|
const args = [
|
|
10381
10383
|
"api",
|
|
10382
10384
|
"--method",
|
|
@@ -10391,7 +10393,7 @@ async function defaultRestCreateIssue(exec2, input) {
|
|
|
10391
10393
|
args.push("-f", `labels[]=${label}`);
|
|
10392
10394
|
}
|
|
10393
10395
|
try {
|
|
10394
|
-
const { stdout } = await
|
|
10396
|
+
const { stdout } = await exec3("gh", args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10395
10397
|
const parsed = JSON.parse(stdout);
|
|
10396
10398
|
if (typeof parsed.number === "number" && typeof parsed.html_url === "string") {
|
|
10397
10399
|
return { number: parsed.number, url: parsed.html_url };
|
|
@@ -10410,9 +10412,9 @@ ${err.stdout ?? ""}`;
|
|
|
10410
10412
|
throw e;
|
|
10411
10413
|
}
|
|
10412
10414
|
}
|
|
10413
|
-
async function defaultReadRateLimit(
|
|
10415
|
+
async function defaultReadRateLimit(exec3) {
|
|
10414
10416
|
try {
|
|
10415
|
-
const { stdout } = await
|
|
10417
|
+
const { stdout } = await exec3("gh", ["api", "rate_limit"], { timeout: 15e3 });
|
|
10416
10418
|
return parseRateLimitPools(stdout);
|
|
10417
10419
|
} catch {
|
|
10418
10420
|
return {};
|
|
@@ -10585,17 +10587,17 @@ async function createPrViaRestFallback(args, swappedArgs, deps, knownPools) {
|
|
|
10585
10587
|
}
|
|
10586
10588
|
}
|
|
10587
10589
|
async function ghCreate(args, deps = {}) {
|
|
10588
|
-
const
|
|
10589
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((
|
|
10590
|
+
const exec3 = deps.exec ?? execFileP;
|
|
10591
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
10590
10592
|
const now = deps.now ?? Date.now;
|
|
10591
10593
|
const read = deps.readFile ?? import_promises.readFile;
|
|
10592
|
-
const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(
|
|
10593
|
-
const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(
|
|
10594
|
-
const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(
|
|
10595
|
-
const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(
|
|
10594
|
+
const restCreatePr = deps.restCreatePr ?? ((input) => defaultRestCreatePr(exec3, input));
|
|
10595
|
+
const restCreateIssue = deps.restCreateIssue ?? ((input) => defaultRestCreateIssue(exec3, input));
|
|
10596
|
+
const findOpenPr = deps.findOpenPr ?? ((input) => defaultFindOpenPr(exec3, input));
|
|
10597
|
+
const readRateLimit = deps.readRateLimit ?? (() => defaultReadRateLimit(exec3));
|
|
10596
10598
|
if (args[0] === "pr") {
|
|
10597
10599
|
const refusal = await preflightPrCreateRemoteHead(args, {
|
|
10598
|
-
exec:
|
|
10600
|
+
exec: exec3,
|
|
10599
10601
|
readRemoteHead: deps.readRemoteHead,
|
|
10600
10602
|
compareRefs: deps.compareRefs
|
|
10601
10603
|
});
|
|
@@ -10603,7 +10605,7 @@ async function ghCreate(args, deps = {}) {
|
|
|
10603
10605
|
}
|
|
10604
10606
|
const swapped = await bodyArgsViaFile(args);
|
|
10605
10607
|
const restDeps = {
|
|
10606
|
-
exec:
|
|
10608
|
+
exec: exec3,
|
|
10607
10609
|
sleep: sleep2,
|
|
10608
10610
|
now,
|
|
10609
10611
|
readFile: read,
|
|
@@ -10646,7 +10648,7 @@ async function ghCreate(args, deps = {}) {
|
|
|
10646
10648
|
}
|
|
10647
10649
|
for (let attempt = 1; attempt <= GH_CREATE_UPSTREAM_RETRIES; attempt++) {
|
|
10648
10650
|
try {
|
|
10649
|
-
const { stdout } = await
|
|
10651
|
+
const { stdout } = await exec3("gh", swapped.args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
10650
10652
|
return parseCreatedUrl(stdout);
|
|
10651
10653
|
} catch (e) {
|
|
10652
10654
|
const err = e;
|
|
@@ -10664,7 +10666,7 @@ async function ghCreate(args, deps = {}) {
|
|
|
10664
10666
|
if (args[0] === "issue" && isGhRateLimitError(errText)) {
|
|
10665
10667
|
try {
|
|
10666
10668
|
const result = await createIssueViaRestFallback(args, swapped.args, {
|
|
10667
|
-
exec:
|
|
10669
|
+
exec: exec3,
|
|
10668
10670
|
sleep: sleep2,
|
|
10669
10671
|
now,
|
|
10670
10672
|
readFile: read,
|
|
@@ -11414,8 +11416,8 @@ var PRE_SPAWN_DRAIN_TIMEOUT_MS = 2e3;
|
|
|
11414
11416
|
async function drainHttpPoolBeforeSpawn() {
|
|
11415
11417
|
const drained = await Promise.race([
|
|
11416
11418
|
closeHttpPool().then(() => true),
|
|
11417
|
-
new Promise((
|
|
11418
|
-
setTimeout(() =>
|
|
11419
|
+
new Promise((resolve8) => {
|
|
11420
|
+
setTimeout(() => resolve8(false), PRE_SPAWN_DRAIN_TIMEOUT_MS).unref?.();
|
|
11419
11421
|
})
|
|
11420
11422
|
]);
|
|
11421
11423
|
if (!drained) destroyHttpPool();
|
|
@@ -15002,7 +15004,7 @@ async function setBoardItemPriority(client, cfg, itemId, priority) {
|
|
|
15002
15004
|
await updateItemSingleSelect(client, cfg.projectId, itemId, cfg.priorityFieldId, optionId);
|
|
15003
15005
|
return cliPriorityToFieldName(priority);
|
|
15004
15006
|
}
|
|
15005
|
-
var defaultRetrySleep = (ms) => new Promise((
|
|
15007
|
+
var defaultRetrySleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
15006
15008
|
async function resolveProjectItemIdWithRetry(client, cfg, selector, opts = {}) {
|
|
15007
15009
|
const attempts = Math.max(1, opts.attempts ?? 5);
|
|
15008
15010
|
const delayMs = opts.delayMs ?? 300;
|
|
@@ -15785,6 +15787,135 @@ function checkCommands(docs, commandPaths) {
|
|
|
15785
15787
|
}
|
|
15786
15788
|
return { ok: findings.length === 0, findings, warnings: [] };
|
|
15787
15789
|
}
|
|
15790
|
+
function isRunbook(doc, body) {
|
|
15791
|
+
return doc === "docs/Guides/operator.md" || body.split(/\r?\n/).slice(0, 20).some((line) => line.includes("<!-- runbook -->"));
|
|
15792
|
+
}
|
|
15793
|
+
function shellClaims(body) {
|
|
15794
|
+
const claims = [];
|
|
15795
|
+
let fence = "";
|
|
15796
|
+
let enabled = false;
|
|
15797
|
+
body.split(/\r?\n/).forEach((text, index) => {
|
|
15798
|
+
const marker = /^\s*(`{3,}|~{3,})(.*)$/.exec(text);
|
|
15799
|
+
if (marker) {
|
|
15800
|
+
if (!fence) {
|
|
15801
|
+
fence = marker[1];
|
|
15802
|
+
enabled = /^(bash|sh|shell|console)?$/.test(marker[2].trim().toLowerCase());
|
|
15803
|
+
} else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) {
|
|
15804
|
+
fence = "";
|
|
15805
|
+
enabled = false;
|
|
15806
|
+
}
|
|
15807
|
+
return;
|
|
15808
|
+
}
|
|
15809
|
+
if (!enabled || /^\s*#/.test(text)) return;
|
|
15810
|
+
const parts = text.replace(/^\s*\$ /, "").match(/(?:"[^"\n]*"|'[^'\n]*'|<[^>\n]*>|[^\s;&|])+|&&|\|\||[;|]/g) ?? [];
|
|
15811
|
+
let tokens2 = [];
|
|
15812
|
+
const emit = () => {
|
|
15813
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(tokens2[0] ?? "")) tokens2.shift();
|
|
15814
|
+
if (tokens2.length) claims.push({ tokens: tokens2, line: index + 1 });
|
|
15815
|
+
tokens2 = [];
|
|
15816
|
+
};
|
|
15817
|
+
for (const part of parts) {
|
|
15818
|
+
if (part.startsWith("#")) break;
|
|
15819
|
+
if (["&&", "||", ";", "|"].includes(part)) emit();
|
|
15820
|
+
else tokens2.push(part.replace(/^(["'])(.*)\1$/, "$2"));
|
|
15821
|
+
}
|
|
15822
|
+
emit();
|
|
15823
|
+
});
|
|
15824
|
+
return claims;
|
|
15825
|
+
}
|
|
15826
|
+
function checkRunbookClaims(root, docs, deps) {
|
|
15827
|
+
const findings = [];
|
|
15828
|
+
const placeholder = (token) => token.includes("<");
|
|
15829
|
+
const paths = deps.commandPaths ?? /* @__PURE__ */ new Set();
|
|
15830
|
+
for (const [doc, body] of Object.entries(docs)) {
|
|
15831
|
+
for (const { tokens: tokens2, line } of shellClaims(body)) {
|
|
15832
|
+
const [cmd, ...args] = tokens2;
|
|
15833
|
+
const report = (kind, detail) => {
|
|
15834
|
+
findings.push({ kind, doc, line, detail });
|
|
15835
|
+
};
|
|
15836
|
+
if (cmd === "mmi-cli" && deps.commandPaths) {
|
|
15837
|
+
let resolved = "";
|
|
15838
|
+
let unknown = false;
|
|
15839
|
+
for (const token of args) {
|
|
15840
|
+
if (token.startsWith("-") || placeholder(token)) break;
|
|
15841
|
+
const next = resolved ? `${resolved} ${token}` : token;
|
|
15842
|
+
if ([...paths].some((path2) => path2 === next || path2.startsWith(`${next} `))) resolved = next;
|
|
15843
|
+
else if (paths.has(resolved)) break;
|
|
15844
|
+
else {
|
|
15845
|
+
report("unknown-command", `mmi-cli ${next}`);
|
|
15846
|
+
unknown = true;
|
|
15847
|
+
break;
|
|
15848
|
+
}
|
|
15849
|
+
}
|
|
15850
|
+
for (const token of unknown ? [] : args) {
|
|
15851
|
+
if (token === "--") break;
|
|
15852
|
+
const flag = token.split("=")[0];
|
|
15853
|
+
if (/^--[\w-]+$/.test(flag) && flag !== "--help" && !placeholder(flag) && !deps.commandOptions?.get(resolved)?.has(flag)) {
|
|
15854
|
+
report("unknown-flag", `mmi-cli ${resolved}: ${flag}`);
|
|
15855
|
+
}
|
|
15856
|
+
}
|
|
15857
|
+
} else if (cmd === "npm") {
|
|
15858
|
+
const run = args.indexOf("run");
|
|
15859
|
+
if (run < 0) continue;
|
|
15860
|
+
const prefix = args.indexOf("--prefix");
|
|
15861
|
+
const dir = prefix >= 0 ? args[prefix + 1] : ".";
|
|
15862
|
+
const script = args.slice(run + 1).find((token, i, tail) => !token.startsWith("-") && tail[i - 1] !== "--prefix");
|
|
15863
|
+
if (!dir || !script || placeholder(dir) || placeholder(script)) continue;
|
|
15864
|
+
const manifest = deps.readFile((0, import_node_path13.join)(root, dir, "package.json"));
|
|
15865
|
+
const scripts = manifest ? JSON.parse(manifest).scripts : void 0;
|
|
15866
|
+
if (!scripts || !Object.hasOwn(scripts, script)) report("missing-script", `npm run ${script} (${dir}/package.json)`);
|
|
15867
|
+
} else if ((cmd === "python" || cmd === "python3") && args[0] === "-m") {
|
|
15868
|
+
const module2 = args[1];
|
|
15869
|
+
if (!module2 || placeholder(module2)) continue;
|
|
15870
|
+
const rel = module2.replaceAll(".", "/");
|
|
15871
|
+
const first = rel.split("/")[0];
|
|
15872
|
+
const tracked = deps.trackedFirstSegments([first, `${first}.py`, `src/${first}`, `src/${first}.py`]);
|
|
15873
|
+
const roots = ["", "src/"].filter((prefix) => {
|
|
15874
|
+
if (!prefix) return tracked ? tracked.has(first) || tracked.has(`${first}.py`) : deps.exists((0, import_node_path13.join)(root, first)) || deps.exists((0, import_node_path13.join)(root, `${first}.py`));
|
|
15875
|
+
return tracked ? tracked.has(`src/${first}`) || tracked.has(`src/${first}.py`) : deps.exists((0, import_node_path13.join)(root, "src", first)) || deps.exists((0, import_node_path13.join)(root, "src", `${first}.py`));
|
|
15876
|
+
});
|
|
15877
|
+
if (roots.length && !roots.some((prefix) => [".py", "/__init__.py", "/__main__.py"].some((suffix) => deps.exists((0, import_node_path13.join)(root, `${prefix}${rel}${suffix}`))))) {
|
|
15878
|
+
report("missing-module", module2);
|
|
15879
|
+
}
|
|
15880
|
+
} else if (["node", "bash", "sh"].includes(cmd)) {
|
|
15881
|
+
const rel = args[0];
|
|
15882
|
+
if (!rel || rel.startsWith("-") || placeholder(rel) || /^(\/|[A-Za-z]:|\.\.\/)/.test(rel)) continue;
|
|
15883
|
+
const first = refFirstSegment(rel);
|
|
15884
|
+
const tracked = deps.trackedFirstSegments([first]);
|
|
15885
|
+
if ((tracked ? tracked.has(first) : deps.exists((0, import_node_path13.join)(root, first))) && !deps.exists((0, import_node_path13.join)(root, rel)) && !deps.isIgnored([rel]).has(rel)) report("missing-path", rel);
|
|
15886
|
+
}
|
|
15887
|
+
}
|
|
15888
|
+
}
|
|
15889
|
+
return findings;
|
|
15890
|
+
}
|
|
15891
|
+
function checkRunbookCoverage(docs, base, git3) {
|
|
15892
|
+
const changed = git3(["diff", "--name-only", `${base}...HEAD`]).split(/\r?\n/).filter(Boolean);
|
|
15893
|
+
const findings = [];
|
|
15894
|
+
const matches = (glob, path2) => {
|
|
15895
|
+
const pattern = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*\//g, "\0").replace(/\*\*/g, "").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]").replaceAll("\0", "(?:.*/)?").replaceAll("", ".*");
|
|
15896
|
+
return new RegExp(`^${pattern}$`).test(path2);
|
|
15897
|
+
};
|
|
15898
|
+
for (const [doc, body] of Object.entries(docs)) {
|
|
15899
|
+
const lines2 = stripFences(body);
|
|
15900
|
+
const headings = lines2.flatMap((text, i) => {
|
|
15901
|
+
const match = /^(#{1,6})\s+(.+)$/.exec(text);
|
|
15902
|
+
return match ? [{ level: match[1].length, title: match[2], start: i + 1 }] : [];
|
|
15903
|
+
});
|
|
15904
|
+
const diff = git3(["diff", "-U0", `${base}...HEAD`, "--", doc]);
|
|
15905
|
+
const ranges = [...diff.matchAll(/^@@ .*?\+(\d+)(?:,(\d+))? @@/gm)].map((m) => ({ start: Number(m[1]), count: m[2] === void 0 ? 1 : Number(m[2]) }));
|
|
15906
|
+
for (let i = 0; i < headings.length; i++) {
|
|
15907
|
+
const heading = headings[i];
|
|
15908
|
+
const end = headings.slice(i + 1).find((h) => h.level <= heading.level)?.start ?? lines2.length + 1;
|
|
15909
|
+
const markerEnd = headings[i + 1]?.start ?? end;
|
|
15910
|
+
const globs = lines2.slice(heading.start, markerEnd - 1).flatMap((text) => /<!--\s*runbook-covers:\s*(.*?)\s*-->/.exec(text)?.[1].split(",").map((s) => s.trim()) ?? []);
|
|
15911
|
+
const affected = changed.filter((path2) => globs.some((glob) => matches(glob, path2)));
|
|
15912
|
+
if (affected.length && !ranges.some((r) => r.count > 0 && r.start < end && r.start + r.count > heading.start)) {
|
|
15913
|
+
findings.push({ kind: "runbook-section-stale", doc, line: heading.start, detail: `${heading.title}: changed ${affected.join(", ")}; update this section or declare Runbook-Unaffected: <reason>` });
|
|
15914
|
+
}
|
|
15915
|
+
}
|
|
15916
|
+
}
|
|
15917
|
+
return findings;
|
|
15918
|
+
}
|
|
15788
15919
|
function readFileOrNull(path2) {
|
|
15789
15920
|
return (0, import_node_fs15.existsSync)(path2) ? (0, import_node_fs15.readFileSync)(path2, "utf8") : null;
|
|
15790
15921
|
}
|
|
@@ -15806,11 +15937,11 @@ function defaultListDocs(root) {
|
|
|
15806
15937
|
return [...ROOT_DOCS.filter((rel) => (0, import_node_fs15.existsSync)((0, import_node_path13.join)(root, rel))), ...docs];
|
|
15807
15938
|
}
|
|
15808
15939
|
var CHECK_IGNORE_MAX_BUFFER = 32 * 1024 * 1024;
|
|
15809
|
-
function defaultIsIgnored(root, relPaths,
|
|
15940
|
+
function defaultIsIgnored(root, relPaths, exec3 = import_node_child_process6.execFileSync) {
|
|
15810
15941
|
const inRepo = relPaths.filter((p) => !p.startsWith(".."));
|
|
15811
15942
|
if (inRepo.length === 0) return /* @__PURE__ */ new Set();
|
|
15812
15943
|
try {
|
|
15813
|
-
const out =
|
|
15944
|
+
const out = exec3("git", ["check-ignore", "--stdin"], {
|
|
15814
15945
|
cwd: root,
|
|
15815
15946
|
input: inRepo.join("\n"),
|
|
15816
15947
|
encoding: "utf8",
|
|
@@ -15829,17 +15960,19 @@ function defaultIsIgnored(root, relPaths, exec2 = import_node_child_process6.exe
|
|
|
15829
15960
|
throw error;
|
|
15830
15961
|
}
|
|
15831
15962
|
}
|
|
15832
|
-
function defaultTrackedFirstSegments(root, firstSegments,
|
|
15963
|
+
function defaultTrackedFirstSegments(root, firstSegments, exec3 = import_node_child_process6.execFileSync) {
|
|
15833
15964
|
if (firstSegments.length === 0) return /* @__PURE__ */ new Set();
|
|
15834
15965
|
try {
|
|
15835
|
-
const out =
|
|
15966
|
+
const out = exec3("git", ["ls-files", "-z", "--", ...firstSegments.map((s) => `:(literal)${s}`)], {
|
|
15836
15967
|
cwd: root,
|
|
15837
15968
|
encoding: "utf8",
|
|
15838
15969
|
maxBuffer: CHECK_IGNORE_MAX_BUFFER
|
|
15839
15970
|
});
|
|
15840
15971
|
const tracked = /* @__PURE__ */ new Set();
|
|
15841
15972
|
for (const path2 of out.split("\0")) {
|
|
15842
|
-
if (path2)
|
|
15973
|
+
if (path2) for (const segment of firstSegments) {
|
|
15974
|
+
if (path2 === segment || path2.startsWith(`${segment}/`)) tracked.add(segment);
|
|
15975
|
+
}
|
|
15843
15976
|
}
|
|
15844
15977
|
return tracked;
|
|
15845
15978
|
} catch (error) {
|
|
@@ -15863,7 +15996,7 @@ function runDocRefs(root, deps = {}) {
|
|
|
15863
15996
|
const walked = listDocs(root);
|
|
15864
15997
|
const ignoredDocs = walked.length ? isIgnored(walked) : /* @__PURE__ */ new Set();
|
|
15865
15998
|
const docs = Object.fromEntries(
|
|
15866
|
-
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile10((0, import_node_path13.join)(root, rel))]).filter(([, body]) => body != null)
|
|
15999
|
+
walked.filter((rel) => !ignoredDocs.has(rel)).map((rel) => [rel, readFile10((0, import_node_path13.join)(root, rel))]).filter(([rel, body]) => body != null && (!deps.runbooksOnly || isRunbook(rel, body)))
|
|
15867
16000
|
);
|
|
15868
16001
|
const refResult = checkRefs(root, { exists, isIgnored, trackedFirstSegments }, docs);
|
|
15869
16002
|
const findings = [
|
|
@@ -15880,10 +16013,22 @@ function runDocRefs(root, deps = {}) {
|
|
|
15880
16013
|
} else {
|
|
15881
16014
|
findings.push(...checkCommands(docs, commandPaths).findings);
|
|
15882
16015
|
}
|
|
16016
|
+
const runbooks = Object.fromEntries(Object.entries(docs).filter(([doc, body]) => isRunbook(doc, body)));
|
|
16017
|
+
findings.push(...checkRunbookClaims(root, runbooks, { readFile: readFile10, exists, isIgnored, trackedFirstSegments, commandPaths, commandOptions: deps.commandOptions }));
|
|
16018
|
+
const warnings = [...refResult.warnings];
|
|
16019
|
+
if (deps.runbooksOnly && !Object.keys(runbooks).length) {
|
|
16020
|
+
warnings.push({ kind: "runbook-missing", doc: "docs/Guides/operator.md", line: 0, detail: "no operator runbook or <!-- runbook --> document exists" });
|
|
16021
|
+
}
|
|
16022
|
+
if (deps.base) {
|
|
16023
|
+
const git3 = deps.git ?? ((args) => (0, import_node_child_process6.execFileSync)("git", args, { cwd: root, encoding: "utf8", maxBuffer: CHECK_IGNORE_MAX_BUFFER, windowsHide: true }));
|
|
16024
|
+
const stale = checkRunbookCoverage(runbooks, deps.base, git3);
|
|
16025
|
+
const override = /^Runbook-Unaffected:[ \t]*\S.*$/m.test(git3(["log", "--format=%B", `${deps.base}..HEAD`]));
|
|
16026
|
+
(override ? warnings : findings).push(...stale);
|
|
16027
|
+
}
|
|
15883
16028
|
return {
|
|
15884
16029
|
ok: findings.length === 0,
|
|
15885
16030
|
findings,
|
|
15886
|
-
warnings
|
|
16031
|
+
warnings,
|
|
15887
16032
|
docCount: Object.keys(docs).length
|
|
15888
16033
|
};
|
|
15889
16034
|
}
|
|
@@ -16001,6 +16146,7 @@ function createDocsIndexDeps(repoRoot2) {
|
|
|
16001
16146
|
// src/gate-budget.ts
|
|
16002
16147
|
var BLESSED_RUN_WITH_BUDGET_SHA = "5ce541add569e54e7ca96de67a768577d3d604e4";
|
|
16003
16148
|
var BLESSED_RUNNER_GATE_SHA = "a19e9392e5ad9b0675f02e4304d68dd933fad60f";
|
|
16149
|
+
var BLESSED_RUNBOOK_GUARD_SHA = "e661e0aedc0e58b73c738bf6cdfeac65e5914584";
|
|
16004
16150
|
var REMOTE_USE = /^mutmutco\/MMI-Hub\/\.github\/actions\/run-with-budget@(\S+)$/;
|
|
16005
16151
|
var LOCAL_USE = "./.github/actions/run-with-budget";
|
|
16006
16152
|
var FULL_SHA = /^[0-9a-f]{40}$/;
|
|
@@ -16375,6 +16521,8 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
16375
16521
|
// The pin is the CLI's blessed SHA, not an operator knob — one central place to bump (#3178).
|
|
16376
16522
|
GATE_BUDGET_SHA: BLESSED_RUN_WITH_BUDGET_SHA,
|
|
16377
16523
|
GATE_RUNNER_SHA: BLESSED_RUNNER_GATE_SHA,
|
|
16524
|
+
// #8129: the guard and its bundled CLI must come from the same immutable Hub revision.
|
|
16525
|
+
GATE_RUNBOOK_SHA: BLESSED_RUNBOOK_GUARD_SHA,
|
|
16378
16526
|
// #7407: repo-declared service containers and extra check steps; empty renders today's gate byte-for-byte.
|
|
16379
16527
|
GATE_SERVICES_YAML: "",
|
|
16380
16528
|
GATE_EXTRA_STEPS_YAML: ""
|
|
@@ -16412,6 +16560,8 @@ function gateSeedVars(cls, releaseTrack, runtime = "node", requiredCheckBranches
|
|
|
16412
16560
|
}
|
|
16413
16561
|
function withDerivedRepoVars(vars, parsed, cls, releaseTrack, requiredCheckBranches, requiredChecks) {
|
|
16414
16562
|
const out = { ...vars };
|
|
16563
|
+
out.OWNER ??= parsed.owner;
|
|
16564
|
+
out.REPO ??= parsed.name;
|
|
16415
16565
|
out.REPO_NAME ??= parsed.name;
|
|
16416
16566
|
out.REPO_SLUG ??= parsed.slug;
|
|
16417
16567
|
out.CLASS ??= cls;
|
|
@@ -18093,10 +18243,10 @@ var rollout_plan_default = {
|
|
|
18093
18243
|
note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
|
|
18094
18244
|
},
|
|
18095
18245
|
baseline: {
|
|
18096
|
-
version: "4.5.
|
|
18097
|
-
tag: "v4.5.
|
|
18098
|
-
commit: "
|
|
18099
|
-
npm: "@mutmutco/cli@4.5.
|
|
18246
|
+
version: "4.5.125",
|
|
18247
|
+
tag: "v4.5.125",
|
|
18248
|
+
commit: "00accba6d8e0",
|
|
18249
|
+
npm: "@mutmutco/cli@4.5.125"
|
|
18100
18250
|
},
|
|
18101
18251
|
exitCriterion: "fleet-n-of-n",
|
|
18102
18252
|
hubOnlyShortcut: "forbidden",
|
|
@@ -18113,14 +18263,14 @@ var rollout_plan_default = {
|
|
|
18113
18263
|
repo: "mutmutco/mmi-hub",
|
|
18114
18264
|
role: "canary",
|
|
18115
18265
|
schedule: "train",
|
|
18116
|
-
v3Target: "v4.5.
|
|
18266
|
+
v3Target: "v4.5.125"
|
|
18117
18267
|
}
|
|
18118
18268
|
],
|
|
18119
18269
|
rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
|
|
18120
18270
|
rollback: {
|
|
18121
18271
|
independent: true,
|
|
18122
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
18123
|
-
v3Target: "v4.5.
|
|
18272
|
+
mechanism: "npm dist-tag latest -> 4.5.125 and redeploy the Hub Lambda from tag v4.5.125 (00accba6d8e0); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
18273
|
+
v3Target: "v4.5.125 (@mutmutco/cli@4.5.125, tag commit 00accba6d8e0 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
18124
18274
|
}
|
|
18125
18275
|
},
|
|
18126
18276
|
{
|
|
@@ -20203,7 +20353,7 @@ async function runMergeTreePreflight(deps, ours, theirs) {
|
|
|
20203
20353
|
async function predictMergeConflicts(deps, ours, theirs) {
|
|
20204
20354
|
return runMergeTreePreflight(deps, ours, theirs);
|
|
20205
20355
|
}
|
|
20206
|
-
async function mergeWithToleratedResolution(deps, sourceRef, label,
|
|
20356
|
+
async function mergeWithToleratedResolution(deps, sourceRef, label, resolve8, extraTolerated = []) {
|
|
20207
20357
|
try {
|
|
20208
20358
|
await deps.run("git", ["merge", sourceRef, "--no-edit"]);
|
|
20209
20359
|
return;
|
|
@@ -20217,7 +20367,7 @@ async function mergeWithToleratedResolution(deps, sourceRef, label, resolve7, ex
|
|
|
20217
20367
|
unmerged.length === 0 ? `${label} merge failed without conflicted paths \u2014 merge aborted; inspect the repo state and rerun` : `${label} merge conflicts on untolerated path(s): ${blocking.join(", ")} \u2014 merge aborted (the train is misaligned; reconcile the branches via an approved alignment PR, then rerun)`
|
|
20218
20368
|
);
|
|
20219
20369
|
}
|
|
20220
|
-
await deps.run("git", ["checkout", `--${
|
|
20370
|
+
await deps.run("git", ["checkout", `--${resolve8}`, "--", ...unmerged]);
|
|
20221
20371
|
await deps.run("git", ["add", "--", ...unmerged]);
|
|
20222
20372
|
await deps.run("git", ["commit", "--no-edit"]);
|
|
20223
20373
|
}
|
|
@@ -20397,7 +20547,7 @@ var CORRELATE_SKEW_SLACK_MS = 1e4;
|
|
|
20397
20547
|
var CORRELATE_PAGE_LIMIT = 50;
|
|
20398
20548
|
var RUN_CONFIRM_ATTEMPTS = 3;
|
|
20399
20549
|
var RUN_CONFIRM_DELAY_MS = 1e3;
|
|
20400
|
-
var defaultSleep = (ms) => new Promise((
|
|
20550
|
+
var defaultSleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
20401
20551
|
function resolveSleep(deps) {
|
|
20402
20552
|
return deps.sleep ?? defaultSleep;
|
|
20403
20553
|
}
|
|
@@ -20977,6 +21127,14 @@ async function isStrayUnreleasedTag(deps, tag, remoteSha, repo) {
|
|
|
20977
21127
|
}
|
|
20978
21128
|
return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
|
|
20979
21129
|
}
|
|
21130
|
+
async function isPromotedUnreleasedTag(deps, tag, remoteSha, repo) {
|
|
21131
|
+
try {
|
|
21132
|
+
await deps.run("git", ["merge-base", "--is-ancestor", remoteSha, "origin/main"]);
|
|
21133
|
+
} catch {
|
|
21134
|
+
return false;
|
|
21135
|
+
}
|
|
21136
|
+
return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
|
|
21137
|
+
}
|
|
20980
21138
|
function cmpReleaseTag(a, b) {
|
|
20981
21139
|
const parse2 = (t) => {
|
|
20982
21140
|
const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(t.trim());
|
|
@@ -23568,7 +23726,7 @@ function lockTokenPid(token) {
|
|
|
23568
23726
|
const m = /^(\d+)-/.exec(token);
|
|
23569
23727
|
return m ? Number(m[1]) : void 0;
|
|
23570
23728
|
}
|
|
23571
|
-
var sleep = (ms) => new Promise((
|
|
23729
|
+
var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
23572
23730
|
var IMMEDIATE_RETRY_BUDGET = 3;
|
|
23573
23731
|
var FileLockBusyError = class extends Error {
|
|
23574
23732
|
lockPath;
|
|
@@ -24444,7 +24602,7 @@ async function defaultWatchRun(deps, runId, repo) {
|
|
|
24444
24602
|
}
|
|
24445
24603
|
let confirmError;
|
|
24446
24604
|
for (let attempt = 0; attempt < RUN_CONFIRM_ATTEMPTS2; attempt++) {
|
|
24447
|
-
if (attempt > 0) await new Promise((
|
|
24605
|
+
if (attempt > 0) await new Promise((resolve8) => setTimeout(resolve8, RUN_CONFIRM_DELAY_MS2));
|
|
24448
24606
|
try {
|
|
24449
24607
|
const parsed = JSON.parse(await deps.run("gh", ["run", "view", String(runId), "--repo", repo, "--json", "status,conclusion"]));
|
|
24450
24608
|
if (parsed.status === "completed") return parsed.conclusion === "success" ? "success" : "failure";
|
|
@@ -25486,11 +25644,11 @@ var defaultDeps = {
|
|
|
25486
25644
|
// #5813: the caller supplies that command line. `process.argv` is NOT it — the house shim splices
|
|
25487
25645
|
// the house root out in place, so re-reading it here re-ran the removed flat alias (`release …`
|
|
25488
25646
|
// instead of `devops release …`) and the converged child refused with exit 2.
|
|
25489
|
-
reexec: (argv) => new Promise((
|
|
25647
|
+
reexec: (argv) => new Promise((resolve8) => {
|
|
25490
25648
|
const args = [...argv];
|
|
25491
25649
|
const child2 = isWin2 ? (0, import_node_child_process8.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", windowsHide: true }) : (0, import_node_child_process8.spawn)("mmi-cli", args, { stdio: "inherit", windowsHide: true });
|
|
25492
|
-
child2.on("exit", (code) =>
|
|
25493
|
-
child2.on("error", () =>
|
|
25650
|
+
child2.on("exit", (code) => resolve8(code ?? 1));
|
|
25651
|
+
child2.on("error", () => resolve8(1));
|
|
25494
25652
|
}),
|
|
25495
25653
|
// stderr only — stdout may be carrying --json output.
|
|
25496
25654
|
warn: (line) => console.error(line)
|
|
@@ -25596,7 +25754,7 @@ var defaultDeps2 = {
|
|
|
25596
25754
|
}
|
|
25597
25755
|
},
|
|
25598
25756
|
registryWhoami: probeRegistryWhoami,
|
|
25599
|
-
sleep: (ms) => new Promise((
|
|
25757
|
+
sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
|
|
25600
25758
|
env: process.env
|
|
25601
25759
|
};
|
|
25602
25760
|
function message(e) {
|
|
@@ -27609,7 +27767,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
27609
27767
|
for (const note of reverifyNotes) deps.warn?.(`release ledger: ${note}`);
|
|
27610
27768
|
}
|
|
27611
27769
|
const ledgerMode = persisted ? "strict" : "migration";
|
|
27612
|
-
const
|
|
27770
|
+
const preMainPartial = await isStrayUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
27771
|
+
const promotedPartial = !preMainPartial && await isPromotedUnreleasedTag(deps, tag, tagSha, ctx.repo);
|
|
27772
|
+
const partialRelease = preMainPartial || promotedPartial;
|
|
27613
27773
|
if (partialRelease && meta.suite === "mm-work") await requireSuiteReleaseSummary(deps, options.announceSummaryFile);
|
|
27614
27774
|
if (!partialRelease) {
|
|
27615
27775
|
try {
|
|
@@ -27726,7 +27886,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
27726
27886
|
note: state2 === "failed" ? `release ${tag} was already published, but its deploy/publish on the release SHA FAILED \u2014 re-run the failed job (do NOT re-tag); the protected alignment PRs were reused` : state2 === "pending" ? `release ${tag} was already published; reused its protected alignment PRs, but a deploy/publish or alignment follow-up is unresolved \u2014 watch it to completion` : `release ${tag} was already published; alignment is complete and the deploy on the release SHA is proven green`
|
|
27727
27887
|
};
|
|
27728
27888
|
}
|
|
27729
|
-
if (!await probeAncestor(deps, "origin/main", tagSha, "release --resume: origin/main fast-forward ancestry")) {
|
|
27889
|
+
if (!promotedPartial && !await probeAncestor(deps, "origin/main", tagSha, "release --resume: origin/main fast-forward ancestry")) {
|
|
27730
27890
|
throw new Error(
|
|
27731
27891
|
`release --resume: origin/main is not an ancestor of ${tag} (${tagSha.slice(0, 12)}) \u2014 main has moved on since the partial release, so finishing it here would not be a fast-forward. Resolve by hand; nothing was written.`
|
|
27732
27892
|
);
|
|
@@ -27772,9 +27932,9 @@ ${recovery.note}`), recoveryInput);
|
|
|
27772
27932
|
}
|
|
27773
27933
|
await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict: ledgerMode === "strict", landed: "the immutable tag on origin at the resumed SHA" });
|
|
27774
27934
|
deps.warn?.("Release: Promoting the verified release");
|
|
27775
|
-
const versionEnv = await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", `${tagSha}:refs/heads/main`]);
|
|
27776
|
-
steps.push(`pushed ${tagSha.slice(0, 12)} to origin/main`);
|
|
27777
|
-
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
|
|
27935
|
+
const versionEnv = promotedPartial ? { status: "skipped", note: "" } : await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", `${tagSha}:refs/heads/main`]);
|
|
27936
|
+
steps.push(promotedPartial ? `origin/main already contains ${tagSha.slice(0, 12)} (the main push had landed): not pushed again` : `pushed ${tagSha.slice(0, 12)} to origin/main`);
|
|
27937
|
+
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: promotedPartial ? "re-verified live: origin/main contains the immutable tag SHA (#8128)" : `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
|
|
27778
27938
|
deps.warn?.("Release: Creating the GitHub Release");
|
|
27779
27939
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
27780
27940
|
const cutBy = (await ledger.load(anchors))?.cutBy ?? describeSessionIdentity();
|
|
@@ -37002,7 +37162,7 @@ function readSuiteCut(path2) {
|
|
|
37002
37162
|
} catch {
|
|
37003
37163
|
throw new Error(`suite cut record unreadable at ${path2}; repair it, never recut`);
|
|
37004
37164
|
}
|
|
37005
|
-
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinsUnchanged !== void 0 && (record.pinsUnchanged !== true || record.pinPr !== void 0) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr && !record.pinsUnchanged) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.heldMembers !== void 0 && (!Array.isArray(record.heldMembers) || record.heldMembers.some((h) => !h.id || !h.repo || typeof h.reason !== "string" || !h.reason.trim() || h.since !== void 0 && typeof h.since !== "string" || record.members.some((m) => m.id === h.id))) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0) || m.ruleset !== void 0 && (!["rc", "main"].includes(m.ruleset.after) || typeof m.ruleset.ok !== "boolean" || !Array.isArray(m.ruleset.lines))))
|
|
37165
|
+
if (record.schema !== SCHEMA || record.lead !== SUITE_REPO || !SHA2.test(record.leadDevelopment) || !SHA2.test(record.leadPreviousRc) || !Array.isArray(record.members) || record.members.length === 0 || record.pinPr !== void 0 && (typeof record.pinPr !== "string" || !record.pinPr.startsWith("https://github.com/mutmutco/MM-Work/pull/")) || record.pinsUnchanged !== void 0 && (record.pinsUnchanged !== true || record.pinPr !== void 0) || record.pinMerged !== void 0 && (record.pinMerged !== true && record.pinMerged !== false || !record.pinPr && !record.pinsUnchanged) || record.leadRcTag !== void 0 && (!RC_TAG.test(record.leadRcTag) || !SHA2.test(record.leadRcSha ?? "") || !record.pinMerged) || record.leadReleased !== void 0 && (record.leadReleased !== true || !record.leadRcTag) || record.announcement !== void 0 && (!["pending", "sent", "skipped"].includes(record.announcement) || !record.leadReleased) || record.heldMembers !== void 0 && (!Array.isArray(record.heldMembers) || record.heldMembers.some((h) => !h.id || !h.repo || typeof h.reason !== "string" || !h.reason.trim() || h.since !== void 0 && typeof h.since !== "string" || record.members.some((m) => m.id === h.id))) || record.members.some((m) => !m.id || !m.repo || !m.backend || !m.host || !SHA2.test(m.development) || !SHA2.test(m.previousRc) || m.rcWasAbsent !== void 0 && m.rcWasAbsent !== true || m.tag !== void 0 && (!RC_TAG.test(m.tag) || !SHA2.test(m.sha ?? "")) || m.carriedPublished !== void 0 && (m.carriedPublished !== true || !m.carried) || m.published !== void 0 && (m.published !== true || !m.tag) || m.released !== void 0 && (m.released !== true || !m.published) || m.mainPublished !== void 0 && (m.mainPublished !== true || !m.released || !!m.carried) || m.backendAfterRelease !== void 0 && (!m.released || typeof m.backendAfterRelease !== "string") || m.carried !== void 0 && (m.carried !== true || m.tag !== void 0 || m.published !== void 0 || !isPin(m.rcPin) || !isPin(m.mainPin)) || m.carried === void 0 && (m.rcPin !== void 0 || m.mainPin !== void 0) || m.ruleset !== void 0 && (!["rc", "main"].includes(m.ruleset.after) || typeof m.ruleset.ok !== "boolean" || !Array.isArray(m.ruleset.lines))))
|
|
37006
37166
|
throw new Error(`suite cut record has unknown or invalid state at ${path2}; repair it, never recut`);
|
|
37007
37167
|
return record;
|
|
37008
37168
|
}
|
|
@@ -37114,7 +37274,22 @@ async function pendingBackend(deps, m, dev) {
|
|
|
37114
37274
|
const files = (await deps.changedPaths(m.repo, live, dev)).filter((f) => !NOT_BACKEND.test(f));
|
|
37115
37275
|
return files.length ? `${files.length} backend file(s) changed since live ${version}, e.g. ${files[0]}` : "";
|
|
37116
37276
|
}
|
|
37277
|
+
function existingCutDescription(record) {
|
|
37278
|
+
const tags = [...record.members.flatMap((m) => m.tag ? [m.tag] : []), ...record.leadRcTag ? [record.leadRcTag] : []];
|
|
37279
|
+
return `from ${record.leadDevelopment.slice(0, 12)}; tags: ${tags.join(", ") || "none"}; pin PR: ${record.pinPr ?? "none"}`;
|
|
37280
|
+
}
|
|
37117
37281
|
async function suiteRcand(deps, opts) {
|
|
37282
|
+
const existing = readSuiteCut(deps.recordPath);
|
|
37283
|
+
try {
|
|
37284
|
+
return await suiteRcandRun(deps, opts);
|
|
37285
|
+
} catch (e) {
|
|
37286
|
+
if (existing && !opts.dryRun && opts.ownerGo && readSuiteCut(deps.recordPath))
|
|
37287
|
+
throw new Error(`${e.message}
|
|
37288
|
+
An existing cut record ${existingCutDescription(existing)} was resumed. mmi-cli devops suite abandon --owner-go retires it (subject to abandon safety checks).`);
|
|
37289
|
+
throw e;
|
|
37290
|
+
}
|
|
37291
|
+
}
|
|
37292
|
+
async function suiteRcandRun(deps, opts) {
|
|
37118
37293
|
if (!opts.ownerGo && !opts.dryRun) throw new Error("suite rcand requires --owner-go (rc uses live data); nothing was written");
|
|
37119
37294
|
const members = suiteMembers(await deps.registry());
|
|
37120
37295
|
const modules = trainModules(members);
|
|
@@ -37128,6 +37303,8 @@ async function suiteRcand(deps, opts) {
|
|
|
37128
37303
|
const lead = await deps.head(SUITE_REPO, "development");
|
|
37129
37304
|
plan.push(await kitCheck(deps, lead, active.map((m) => ({ id: m.id, url: m.bundles.dev })), "dev bundles"));
|
|
37130
37305
|
if (opts.dryRun) {
|
|
37306
|
+
const existing = readSuiteCut(deps.recordPath);
|
|
37307
|
+
if (existing) plan.push(`existing cut record: ${deps.recordPath} (${existingCutDescription(existing)})`);
|
|
37131
37308
|
plan.push(...await Promise.all(modules.map(async (m) => `${m.repo}: ${heldById.has(m.id) ? "HELD" : await dryRunStatus(deps, m)}`)));
|
|
37132
37309
|
return { plan };
|
|
37133
37310
|
}
|
|
@@ -37212,7 +37389,7 @@ async function suiteRcand(deps, opts) {
|
|
|
37212
37389
|
}
|
|
37213
37390
|
const refusals = [];
|
|
37214
37391
|
for (const member of record.members) {
|
|
37215
|
-
if (member.published) continue;
|
|
37392
|
+
if (member.published || member.carriedPublished) continue;
|
|
37216
37393
|
try {
|
|
37217
37394
|
await deps.preflight(member.repo);
|
|
37218
37395
|
} catch (e) {
|
|
@@ -37226,28 +37403,60 @@ async function suiteRcand(deps, opts) {
|
|
|
37226
37403
|
${refusals.join("\n")}
|
|
37227
37404
|
Each module must ship a live release that bind-mounts <deployPath>/data read-only and serves <data>/rc/module.js at /rc/module.js (docs/Guides/suite-trains.md).`);
|
|
37228
37405
|
}
|
|
37229
|
-
|
|
37230
|
-
|
|
37231
|
-
|
|
37232
|
-
|
|
37233
|
-
|
|
37234
|
-
|
|
37235
|
-
}
|
|
37236
|
-
|
|
37237
|
-
|
|
37238
|
-
|
|
37239
|
-
|
|
37240
|
-
|
|
37241
|
-
|
|
37242
|
-
|
|
37243
|
-
|
|
37244
|
-
|
|
37245
|
-
|
|
37246
|
-
await deps.
|
|
37247
|
-
|
|
37248
|
-
|
|
37406
|
+
const cutRecord = record;
|
|
37407
|
+
let writes = Promise.resolve();
|
|
37408
|
+
const persist = (mutate) => {
|
|
37409
|
+
const next = writes.then(() => {
|
|
37410
|
+
mutate();
|
|
37411
|
+
writeSuiteCut(deps.recordPath, cutRecord);
|
|
37412
|
+
});
|
|
37413
|
+
writes = next.catch(() => {
|
|
37414
|
+
});
|
|
37415
|
+
return next;
|
|
37416
|
+
};
|
|
37417
|
+
let running = 0;
|
|
37418
|
+
const waiting = [];
|
|
37419
|
+
const results = await Promise.allSettled(record.members.map(async (member) => {
|
|
37420
|
+
if (running >= 4) await new Promise((resolve8) => waiting.push(resolve8));
|
|
37421
|
+
else running++;
|
|
37422
|
+
try {
|
|
37423
|
+
if ((await deps.head(member.repo, "development"))?.toLowerCase() !== member.development.toLowerCase())
|
|
37424
|
+
throw new Error(`${member.repo}: development moved after the cut snapshot; refuse to add untested code`);
|
|
37425
|
+
if (member.carried) {
|
|
37426
|
+
if (!member.carriedPublished) {
|
|
37427
|
+
await deps.publish(member.repo, pinVersion(member.rcPin));
|
|
37428
|
+
await persist(() => {
|
|
37429
|
+
member.carriedPublished = true;
|
|
37430
|
+
});
|
|
37431
|
+
}
|
|
37432
|
+
return;
|
|
37433
|
+
}
|
|
37434
|
+
if (!member.tag) {
|
|
37435
|
+
const cut = await deps.rcand(member.repo, member.previousRc, member.rcWasAbsent);
|
|
37436
|
+
if (!RC_TAG.test(cut.tag) || !SHA2.test(cut.sha) || cut.sha.toLowerCase() === member.previousRc.toLowerCase()) throw new Error(`${member.repo}: rcand did not return a new immutable rc tag/SHA`);
|
|
37437
|
+
if (!await deps.contains(member.repo, member.development, cut.sha)) throw new Error(`${member.repo}: rc tag does not carry the snapshotted development SHA; refuse`);
|
|
37438
|
+
await persist(() => {
|
|
37439
|
+
member.tag = cut.tag;
|
|
37440
|
+
member.sha = cut.sha;
|
|
37441
|
+
});
|
|
37442
|
+
}
|
|
37443
|
+
const tag = member.tag;
|
|
37444
|
+
if ((await deps.tagSha(member.repo, tag))?.toLowerCase() !== member.sha.toLowerCase()) throw new Error(`${member.repo}: rc tag ${member.tag} moved or is unreadable; stop`);
|
|
37445
|
+
if (!member.published) {
|
|
37446
|
+
await deps.publish(member.repo, tag);
|
|
37447
|
+
await persist(() => {
|
|
37448
|
+
member.published = true;
|
|
37449
|
+
});
|
|
37450
|
+
}
|
|
37451
|
+
} finally {
|
|
37452
|
+
const next = waiting.shift();
|
|
37453
|
+
if (next) next();
|
|
37454
|
+
else running--;
|
|
37249
37455
|
}
|
|
37250
|
-
}
|
|
37456
|
+
}));
|
|
37457
|
+
const failures = results.flatMap((result, i) => result.status === "rejected" ? [`${record.members[i].repo}: ${result.reason instanceof Error ? result.reason.message : String(result.reason)}`] : []);
|
|
37458
|
+
if (failures.length) throw new Error(`suite member cuts failed:
|
|
37459
|
+
${failures.join("\n")}`);
|
|
37251
37460
|
for (const member of record.members) {
|
|
37252
37461
|
const [live, devHead] = await Promise.all([
|
|
37253
37462
|
deps.backend({ repo: member.repo, id: member.id, bundles: {} }),
|
|
@@ -37692,8 +37901,10 @@ async function runRcDataSwitch(deps, opts) {
|
|
|
37692
37901
|
}
|
|
37693
37902
|
|
|
37694
37903
|
// src/suite-train-io.ts
|
|
37695
|
-
var
|
|
37904
|
+
var import_node_child_process18 = require("node:child_process");
|
|
37696
37905
|
var import_node_path44 = require("node:path");
|
|
37906
|
+
var import_node_fs45 = require("node:fs");
|
|
37907
|
+
var import_node_path45 = require("node:path");
|
|
37697
37908
|
init_cli_shared();
|
|
37698
37909
|
|
|
37699
37910
|
// src/command-register-train-operations.ts
|
|
@@ -37875,7 +38086,7 @@ function clean3(out) {
|
|
|
37875
38086
|
return out.trim();
|
|
37876
38087
|
}
|
|
37877
38088
|
function sleeper(deps) {
|
|
37878
|
-
return deps.sleep ?? ((ms) => new Promise((
|
|
38089
|
+
return deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
37879
38090
|
}
|
|
37880
38091
|
function normalizeHotfixVersion(input) {
|
|
37881
38092
|
const m = /^v?(\d+\.\d+\.\d+)$/.exec(input.trim());
|
|
@@ -38048,8 +38259,8 @@ async function preflightHotfixBatchPicks(deps, sources, pickTolerated) {
|
|
|
38048
38259
|
if (sources.length === 0) return { failures: [], skipped: [] };
|
|
38049
38260
|
const { mkdtemp: mkdtemp2, rm: rm2 } = await import("node:fs/promises");
|
|
38050
38261
|
const { tmpdir: tmpdir12 } = await import("node:os");
|
|
38051
|
-
const { join:
|
|
38052
|
-
const dir = await mkdtemp2(
|
|
38262
|
+
const { join: join49 } = await import("node:path");
|
|
38263
|
+
const dir = await mkdtemp2(join49(tmpdir12(), "mmi-hotfix-preflight-"));
|
|
38053
38264
|
const wt = { run: (file, args) => deps.run(file, file === "git" ? ["-C", dir, ...args] : args) };
|
|
38054
38265
|
const failures = [];
|
|
38055
38266
|
const skipped = [];
|
|
@@ -39547,7 +39758,7 @@ async function updatePrHeadFromBase(input) {
|
|
|
39547
39758
|
} catch (e) {
|
|
39548
39759
|
throw new Error(`GitHub could not update ${input.head} from ${input.base} (${e.message.trim()}) \u2014 ${remedy}`);
|
|
39549
39760
|
}
|
|
39550
|
-
const sleep2 = input.sleep ?? ((ms) => new Promise((
|
|
39761
|
+
const sleep2 = input.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
39551
39762
|
for (let attempt = 0; attempt < PR_HEAD_UPDATE_API_READ_RETRIES; attempt++) {
|
|
39552
39763
|
const to = await readHead2();
|
|
39553
39764
|
if (to && to !== from) return { from, to, base: input.base, via: "update-branch-api" };
|
|
@@ -39565,7 +39776,7 @@ async function mergeAutoWithTransientRetry(prNumber, repo, deps) {
|
|
|
39565
39776
|
if (first.mergeStatus !== "failed") return first;
|
|
39566
39777
|
const ready = await deps.probeMergeReady(prNumber, repo).catch(() => ({ open: false, mergeable: false, checksPassing: false }));
|
|
39567
39778
|
if (!ready.open || !ready.mergeable || !ready.checksPassing) return first;
|
|
39568
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((
|
|
39779
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
39569
39780
|
await sleep2(PR_LAND_MERGE_RETRY_DELAY_MS);
|
|
39570
39781
|
const retried = await deps.mergeAuto(prNumber, repo);
|
|
39571
39782
|
if (retried.mergeStatus !== "failed") return retried;
|
|
@@ -39576,7 +39787,7 @@ var AUTO_MERGE_CONFIRM_DELAY_MS = 15e3;
|
|
|
39576
39787
|
async function confirmAutoMergeEnqueued(deps, options) {
|
|
39577
39788
|
const retries = options?.retries ?? AUTO_MERGE_CONFIRM_RETRIES;
|
|
39578
39789
|
const delayMs = options?.delayMs ?? AUTO_MERGE_CONFIRM_DELAY_MS;
|
|
39579
|
-
const sleep2 = deps.sleep ?? ((ms) => new Promise((
|
|
39790
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
39580
39791
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
39581
39792
|
if (await deps.readMerged().catch(() => false)) return "merged";
|
|
39582
39793
|
const stuck = await deps.readAutoMergeRequest().then((s) => s.trim()).catch(() => "");
|
|
@@ -39593,7 +39804,7 @@ async function confirmAutoMergeEnqueued(deps, options) {
|
|
|
39593
39804
|
async function readGhPrStateWithRetry(fetchState, options) {
|
|
39594
39805
|
const retries = options?.retries ?? PR_LAND_STATE_READ_RETRIES;
|
|
39595
39806
|
const delayMs = options?.delayMs ?? PR_LAND_STATE_READ_DELAY_MS;
|
|
39596
|
-
const sleep2 = options?.sleep ?? ((ms) => new Promise((
|
|
39807
|
+
const sleep2 = options?.sleep ?? ((ms) => new Promise((resolve8) => setTimeout(resolve8, ms)));
|
|
39597
39808
|
let lastError = "empty state";
|
|
39598
39809
|
for (let attempt = 0; attempt < retries; attempt++) {
|
|
39599
39810
|
try {
|
|
@@ -43221,7 +43432,7 @@ async function checkPrReview(number, repo, head, deps = {
|
|
|
43221
43432
|
}
|
|
43222
43433
|
}
|
|
43223
43434
|
function computePrPatchId(number, repo) {
|
|
43224
|
-
return new Promise((
|
|
43435
|
+
return new Promise((resolve8, reject) => {
|
|
43225
43436
|
const gh2 = (0, import_node_child_process14.spawn)("gh", ["pr", "diff", number, "--repo", repo], { windowsHide: true, stdio: ["ignore", "pipe", "pipe"] });
|
|
43226
43437
|
const git3 = (0, import_node_child_process14.spawn)("git", ["patch-id", "--stable"], { windowsHide: true, stdio: ["pipe", "pipe", "pipe"] });
|
|
43227
43438
|
let out = "";
|
|
@@ -43259,7 +43470,7 @@ function computePrPatchId(number, repo) {
|
|
|
43259
43470
|
if (gitCode !== 0) return reject(new Error(`git patch-id --stable exited ${gitCode}: ${gitErr.trim()}`));
|
|
43260
43471
|
const id = out.trim().split(/\s+/)[0];
|
|
43261
43472
|
if (!/^[0-9a-f]{40}$/.test(id ?? "")) return reject(new Error(`patch-id: PR #${number} diff is empty or unreadable (gh: ${ghErr.trim() || "no stderr"})`));
|
|
43262
|
-
|
|
43473
|
+
resolve8(id);
|
|
43263
43474
|
};
|
|
43264
43475
|
gh2.on("close", (code) => {
|
|
43265
43476
|
ghCode = code;
|
|
@@ -43310,7 +43521,7 @@ init_cli_shared();
|
|
|
43310
43521
|
var GIT_TIMEOUT_MS2 = 15e3;
|
|
43311
43522
|
function catFileBatch(root, ref, paths) {
|
|
43312
43523
|
if (paths.length === 0) return Promise.resolve([]);
|
|
43313
|
-
return new Promise((
|
|
43524
|
+
return new Promise((resolve8) => {
|
|
43314
43525
|
const chunks = [];
|
|
43315
43526
|
let settled = false;
|
|
43316
43527
|
const child2 = (0, import_node_child_process15.spawn)("git", ["-C", root, "cat-file", "--batch", "--buffer"], { windowsHide: true });
|
|
@@ -43318,7 +43529,7 @@ function catFileBatch(root, ref, paths) {
|
|
|
43318
43529
|
if (settled) return;
|
|
43319
43530
|
settled = true;
|
|
43320
43531
|
clearTimeout(timer);
|
|
43321
|
-
|
|
43532
|
+
resolve8(parseCatFileBatch(Buffer.concat(chunks), paths.length));
|
|
43322
43533
|
};
|
|
43323
43534
|
const timer = setTimeout(() => {
|
|
43324
43535
|
child2.kill("SIGKILL");
|
|
@@ -43743,7 +43954,7 @@ function healthPollIntervalMs() {
|
|
|
43743
43954
|
return HEALTH_POLL_INTERVAL_MS;
|
|
43744
43955
|
}
|
|
43745
43956
|
function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
|
|
43746
|
-
return new Promise((
|
|
43957
|
+
return new Promise((resolve8, reject) => {
|
|
43747
43958
|
let settled = false;
|
|
43748
43959
|
const finish = (fn) => {
|
|
43749
43960
|
if (settled) return;
|
|
@@ -43753,7 +43964,7 @@ function waitForProcessStability(child2, graceMs = earlyExitGraceMs()) {
|
|
|
43753
43964
|
child2.removeAllListeners("exit");
|
|
43754
43965
|
fn();
|
|
43755
43966
|
};
|
|
43756
|
-
const timer = setTimeout(() => finish(
|
|
43967
|
+
const timer = setTimeout(() => finish(resolve8), graceMs);
|
|
43757
43968
|
child2.on("error", (err) => finish(() => reject(new Error(`stage process failed to start: ${err.message}`))));
|
|
43758
43969
|
child2.on("exit", (code, signal) => {
|
|
43759
43970
|
const detail = code != null ? `code ${code}` : signal ? `signal ${signal}` : "unknown reason";
|
|
@@ -43946,10 +44157,10 @@ function pickStagePort(range, isFree) {
|
|
|
43946
44157
|
throw new Error(`no free stage port in range ${start}-${end} \u2014 every port is in use`);
|
|
43947
44158
|
}
|
|
43948
44159
|
function isPortFree(port) {
|
|
43949
|
-
return new Promise((
|
|
44160
|
+
return new Promise((resolve8) => {
|
|
43950
44161
|
const srv = (0, import_node_net.createServer)();
|
|
43951
|
-
srv.once("error", () =>
|
|
43952
|
-
srv.once("listening", () => srv.close(() =>
|
|
44162
|
+
srv.once("error", () => resolve8(false));
|
|
44163
|
+
srv.once("listening", () => srv.close(() => resolve8(true)));
|
|
43953
44164
|
srv.listen(port, "127.0.0.1");
|
|
43954
44165
|
});
|
|
43955
44166
|
}
|
|
@@ -44227,7 +44438,7 @@ async function killTree(pid) {
|
|
|
44227
44438
|
} catch {
|
|
44228
44439
|
}
|
|
44229
44440
|
}
|
|
44230
|
-
await new Promise((
|
|
44441
|
+
await new Promise((resolve8) => setTimeout(resolve8, 500));
|
|
44231
44442
|
try {
|
|
44232
44443
|
process.kill(-pid, "SIGKILL");
|
|
44233
44444
|
} catch {
|
|
@@ -44300,7 +44511,7 @@ async function waitForHealth(url, timeoutMs, anyStatus = false) {
|
|
|
44300
44511
|
} catch (e) {
|
|
44301
44512
|
last = e.message;
|
|
44302
44513
|
}
|
|
44303
|
-
await new Promise((
|
|
44514
|
+
await new Promise((resolve8) => setTimeout(resolve8, healthPollIntervalMs()));
|
|
44304
44515
|
}
|
|
44305
44516
|
throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
|
|
44306
44517
|
}
|
|
@@ -44660,7 +44871,7 @@ function unlinkNodeModulesJunction(wtPath) {
|
|
|
44660
44871
|
}
|
|
44661
44872
|
}
|
|
44662
44873
|
function defaultSleep2(ms) {
|
|
44663
|
-
return new Promise((
|
|
44874
|
+
return new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
44664
44875
|
}
|
|
44665
44876
|
async function removeResidueDirectory(wtPath, options = {}) {
|
|
44666
44877
|
const { sleep: sleep2 = defaultSleep2, removeEmptyDir = import_node_fs42.rmdirSync, exists = import_node_fs42.existsSync } = options;
|
|
@@ -46410,7 +46621,7 @@ ${list}`);
|
|
|
46410
46621
|
// #5400: after grace, name "GitHub delivered zero runs" instead of burning the full budget as pending.
|
|
46411
46622
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr checks-wait", number, repo),
|
|
46412
46623
|
baseBranch,
|
|
46413
|
-
sleep: (ms) => new Promise((
|
|
46624
|
+
sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
|
|
46414
46625
|
log: (message2) => console.warn(message2),
|
|
46415
46626
|
timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
|
|
46416
46627
|
// Liveness on stderr, one line per poll. A silent bounded wait is indistinguishable from a hang, and
|
|
@@ -46565,7 +46776,7 @@ ${list}`);
|
|
|
46565
46776
|
// #5400: same zero-runs delivery probe as checks-wait — do not burn the land budget on silence.
|
|
46566
46777
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr land", prNumber, repo),
|
|
46567
46778
|
baseBranch: "development",
|
|
46568
|
-
sleep: (ms) => new Promise((
|
|
46779
|
+
sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
|
|
46569
46780
|
log: (message2) => console.warn(message2),
|
|
46570
46781
|
// `pr land` inherits the same (raised) checks budget, so it needs the same liveness — otherwise the
|
|
46571
46782
|
// 30m wait is SILENT and reads exactly like the hang #2940 was filed about, only three times longer.
|
|
@@ -46616,7 +46827,7 @@ ${list}`);
|
|
|
46616
46827
|
} else {
|
|
46617
46828
|
lastFailure = void 0;
|
|
46618
46829
|
}
|
|
46619
|
-
await new Promise((
|
|
46830
|
+
await new Promise((resolve8) => setTimeout(resolve8, PR_LAND_POLL_MS));
|
|
46620
46831
|
}
|
|
46621
46832
|
if (lastFailure) {
|
|
46622
46833
|
throw new Error(
|
|
@@ -46774,7 +46985,7 @@ ${list}`);
|
|
|
46774
46985
|
checksDiagnosticCommand: `gh pr checks ${number} --repo ${repo}`,
|
|
46775
46986
|
pollHeadWorkflowRunCount: () => waitLoopHeadWorkflowRunCount("pr merge --wait", number, repo),
|
|
46776
46987
|
baseBranch,
|
|
46777
|
-
sleep: (ms) => new Promise((
|
|
46988
|
+
sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
|
|
46778
46989
|
log: (message2) => console.warn(message2),
|
|
46779
46990
|
timeoutMs: Math.max(1, budgetMs - (Date.now() - waitStarted)),
|
|
46780
46991
|
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`pr merge: --wait checks \u2014 ${state}, ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
@@ -46843,7 +47054,7 @@ ${list}`);
|
|
|
46843
47054
|
}
|
|
46844
47055
|
if (!o.auto && mergeRejectedBaseBranchModified(message2)) {
|
|
46845
47056
|
console.warn(`pr merge: the base branch was modified by a concurrent merge \u2014 waiting ${PR_MERGE_BASE_RACE_RETRY_DELAY_MS / 1e3}s and retrying PR #${number}'s merge once (#6052).`);
|
|
46846
|
-
await new Promise((
|
|
47057
|
+
await new Promise((resolve8) => setTimeout(resolve8, PR_MERGE_BASE_RACE_RETRY_DELAY_MS));
|
|
46847
47058
|
await execFileP("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: false, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
46848
47059
|
const m2 = String(e2.message || "");
|
|
46849
47060
|
if (/already been merged/i.test(m2)) {
|
|
@@ -47104,7 +47315,7 @@ ${list}`);
|
|
|
47104
47315
|
console.warn(`ci checks-wait: zero-runs probe FAILED (${read.error}) \u2014 not treating as delivery failure this cycle`);
|
|
47105
47316
|
return null;
|
|
47106
47317
|
},
|
|
47107
|
-
sleep: (ms) => new Promise((
|
|
47318
|
+
sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms)),
|
|
47108
47319
|
log: (message2) => console.warn(message2),
|
|
47109
47320
|
timeoutMs: timeoutMs ?? PR_CHECKS_TIMEOUT_MS,
|
|
47110
47321
|
progress: ({ state, elapsedMs, remainingMs }) => console.warn(`ci checks-wait: ${state} \u2014 ${Math.round(elapsedMs / 1e3)}s elapsed, ${Math.round(remainingMs / 6e4)}m left`)
|
|
@@ -47469,7 +47680,7 @@ function hotfixFollowUpLegs(runs, foldPort, alignment, foldNote, deployNote) {
|
|
|
47469
47680
|
}
|
|
47470
47681
|
return legs;
|
|
47471
47682
|
}
|
|
47472
|
-
async function runProjectInfoSyncLeg(cb, repo, sleep2 = (ms) => new Promise((
|
|
47683
|
+
async function runProjectInfoSyncLeg(cb, repo, sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))) {
|
|
47473
47684
|
for (let attempt = 0; ; attempt++) {
|
|
47474
47685
|
try {
|
|
47475
47686
|
return await cb(repo, true);
|
|
@@ -48198,18 +48409,63 @@ function rcRunCauseFromLog(log) {
|
|
|
48198
48409
|
function memberAnnounceSummaryFile(repo, summaryFile, memberSummaryFiles) {
|
|
48199
48410
|
return repo === SUITE_REPO ? summaryFile : memberSummaryFiles[repo];
|
|
48200
48411
|
}
|
|
48201
|
-
var suiteStateDir = (root) => (0,
|
|
48412
|
+
var suiteStateDir = (root) => (0, import_node_path45.join)(resolvePrimaryGitDir(root), "mmi-runtime", "suite");
|
|
48413
|
+
async function runSuiteMemberRcand(repo, previousRc, rcWasAbsent) {
|
|
48414
|
+
const deps = trainApplyDeps();
|
|
48415
|
+
deps.suiteCut = suiteCutContext(repo, SUITE_REPO);
|
|
48416
|
+
deps.announce = async () => ({ status: "skipped", note: "suite announcement belongs to MM-Work" });
|
|
48417
|
+
return rcandInCheckout(repo, deps, previousRc, rcWasAbsent);
|
|
48418
|
+
}
|
|
48419
|
+
async function rcandInCheckout(repo, deps, previousRc, rcWasAbsent) {
|
|
48420
|
+
if (repo !== SUITE_REPO) await prepareDevelopmentCheckout((args) => deps.run("git", args), repo);
|
|
48421
|
+
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
48422
|
+
let rcNow;
|
|
48423
|
+
try {
|
|
48424
|
+
rcNow = (await deps.run("git", ["rev-parse", "--verify", "origin/rc"])).trim();
|
|
48425
|
+
} catch (e) {
|
|
48426
|
+
if (!rcWasAbsent || !previousRc || repo === SUITE_REPO) throw e;
|
|
48427
|
+
const slug = repo.split("/")[1].toLowerCase();
|
|
48428
|
+
const meta = await fetchProjectBySlugChecked(slug, registryClientDeps(await loadConfig()));
|
|
48429
|
+
if (!meta.ok || meta.project?.releaseTrack !== "full" || meta.project.suite !== "mm-work")
|
|
48430
|
+
throw new Error(`${repo}: first rc branch requires releaseTrack full and suite=mm-work in META; no branch created`);
|
|
48431
|
+
const main = (await deps.run("git", ["rev-parse", "origin/main"])).trim();
|
|
48432
|
+
if (main.toLowerCase() !== previousRc.toLowerCase()) throw new Error(`${repo}: main moved since the cut snapshot; no rc branch created`);
|
|
48433
|
+
await deps.run("git", ["push", "origin", `${previousRc}:refs/heads/rc`]);
|
|
48434
|
+
await deps.run("git", ["fetch", "origin", "rc"]);
|
|
48435
|
+
rcNow = (await deps.run("git", ["rev-parse", "origin/rc"])).trim();
|
|
48436
|
+
}
|
|
48437
|
+
if (!(await deps.run("git", ["branch", "--list", "rc"])).trim()) {
|
|
48438
|
+
await deps.run("git", ["switch", "-c", "rc", "--track", "origin/rc"]);
|
|
48439
|
+
await deps.run("git", ["switch", "development"]);
|
|
48440
|
+
}
|
|
48441
|
+
if (previousRc && rcNow.toLowerCase() !== previousRc.toLowerCase()) {
|
|
48442
|
+
const tags = (await deps.run("git", ["tag", "--points-at", rcNow, "--list", "v*-rc.*"])).trim().split("\n").filter(Boolean);
|
|
48443
|
+
if (tags.length !== 1) throw new Error(`${repo}: rc moved since cut start without exactly one candidate tag; stop`);
|
|
48444
|
+
return { tag: tags[0], sha: rcNow };
|
|
48445
|
+
}
|
|
48446
|
+
try {
|
|
48447
|
+
const result = await runTrainApply("rcand", deps, { watch: true });
|
|
48448
|
+
if (result.deployStatus !== "success") throw new Error(`${repo}: rcand deploy not green (${result.deployStatus})`);
|
|
48449
|
+
return { tag: result.tag, sha: (await deps.run("git", ["rev-parse", result.tag])).trim() };
|
|
48450
|
+
} catch (e) {
|
|
48451
|
+
const existing = (await deps.run("git", ["tag", "--list", "v*-rc.*"])).trim();
|
|
48452
|
+
if (!existing || !/resume|partial|ledger|tag.*pushed/i.test(e.message)) throw e;
|
|
48453
|
+
const result = await runRcandResume(deps, { watch: true });
|
|
48454
|
+
if (result.deployStatus !== "success") throw new Error(`${repo}: resumed rcand deploy not green (${result.deployStatus})`);
|
|
48455
|
+
return { tag: result.tag, sha: result.tagSha };
|
|
48456
|
+
}
|
|
48457
|
+
}
|
|
48202
48458
|
function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr, memberSummaryFilesTr = {}) {
|
|
48203
|
-
const recordPath = (0,
|
|
48204
|
-
const base = (0,
|
|
48459
|
+
const recordPath = (0, import_node_path45.join)(suiteStateDir(root), "cut.json");
|
|
48460
|
+
const base = (0, import_node_path45.join)(suiteStateDir(root), "checkouts");
|
|
48205
48461
|
const checkout = async (repo) => {
|
|
48206
48462
|
if (repo === SUITE_REPO) return root;
|
|
48207
|
-
const target = (0,
|
|
48463
|
+
const target = (0, import_node_path45.join)(base, repo.split("/")[1]);
|
|
48208
48464
|
if (!(0, import_node_fs45.existsSync)(target)) {
|
|
48209
48465
|
(0, import_node_fs45.mkdirSync)(base, { recursive: true });
|
|
48210
48466
|
await exec("git", ["clone", "--quiet", "--branch", "development", `https://github.com/${repo}.git`, target], root, WATCH_TIMEOUT);
|
|
48211
48467
|
}
|
|
48212
|
-
if (!(0, import_node_fs45.existsSync)((0,
|
|
48468
|
+
if (!(0, import_node_fs45.existsSync)((0, import_node_path45.join)(target, ".git"))) throw new Error(`${repo}: suite checkout has no .git at ${target}; stop`);
|
|
48213
48469
|
const origin = await exec("git", ["remote", "get-url", "origin"], target);
|
|
48214
48470
|
if (!origin.toLowerCase().endsWith(`/${repo.toLowerCase()}.git`) && !origin.toLowerCase().endsWith(`/${repo.toLowerCase()}`))
|
|
48215
48471
|
throw new Error(`${repo}: suite checkout origin mismatch at ${target}; stop`);
|
|
@@ -48269,45 +48525,28 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
48269
48525
|
throw e;
|
|
48270
48526
|
}
|
|
48271
48527
|
};
|
|
48272
|
-
const runRcand = async (repo, previousRc, rcWasAbsent) =>
|
|
48273
|
-
if (repo
|
|
48274
|
-
|
|
48275
|
-
|
|
48276
|
-
|
|
48277
|
-
|
|
48278
|
-
|
|
48279
|
-
|
|
48280
|
-
|
|
48281
|
-
|
|
48282
|
-
|
|
48283
|
-
|
|
48284
|
-
|
|
48285
|
-
|
|
48286
|
-
|
|
48287
|
-
|
|
48288
|
-
|
|
48289
|
-
|
|
48290
|
-
|
|
48291
|
-
|
|
48292
|
-
|
|
48293
|
-
|
|
48294
|
-
if (previousRc && rcNow.toLowerCase() !== previousRc.toLowerCase()) {
|
|
48295
|
-
const tags = (await deps.run("git", ["tag", "--points-at", rcNow, "--list", "v*-rc.*"])).trim().split("\n").filter(Boolean);
|
|
48296
|
-
if (tags.length !== 1) throw new Error(`${repo}: rc moved since cut start without exactly one candidate tag; stop`);
|
|
48297
|
-
return { tag: tags[0], sha: rcNow };
|
|
48298
|
-
}
|
|
48299
|
-
try {
|
|
48300
|
-
const result = await runTrainApply("rcand", deps, { watch: true });
|
|
48301
|
-
if (result.deployStatus !== "success") throw new Error(`${repo}: rcand deploy not green (${result.deployStatus})`);
|
|
48302
|
-
return { tag: result.tag, sha: (await deps.run("git", ["rev-parse", result.tag])).trim() };
|
|
48303
|
-
} catch (e) {
|
|
48304
|
-
const existing = (await deps.run("git", ["tag", "--list", "v*-rc.*"])).trim();
|
|
48305
|
-
if (!existing || !/resume|partial|ledger|tag.*pushed/i.test(e.message)) throw e;
|
|
48306
|
-
const result = await runRcandResume(deps, { watch: true });
|
|
48307
|
-
if (result.deployStatus !== "success") throw new Error(`${repo}: resumed rcand deploy not green (${result.deployStatus})`);
|
|
48308
|
-
return { tag: result.tag, sha: result.tagSha };
|
|
48309
|
-
}
|
|
48310
|
-
});
|
|
48528
|
+
const runRcand = async (repo, previousRc, rcWasAbsent) => {
|
|
48529
|
+
if (repo === SUITE_REPO) return inTrain(repo, (deps) => rcandInCheckout(repo, deps, previousRc, rcWasAbsent));
|
|
48530
|
+
const target = await checkout(repo);
|
|
48531
|
+
return new Promise((resolveResult, reject) => {
|
|
48532
|
+
const child2 = (0, import_node_child_process18.fork)((0, import_node_path44.resolve)(process.argv[1]), ["devops", "suite", "member-rcand-internal", repo, previousRc ?? "", ...rcWasAbsent ? ["--rc-was-absent"] : []], {
|
|
48533
|
+
cwd: target,
|
|
48534
|
+
stdio: ["ignore", "inherit", "inherit", "ipc"],
|
|
48535
|
+
execArgv: []
|
|
48536
|
+
});
|
|
48537
|
+
let result;
|
|
48538
|
+
let failure;
|
|
48539
|
+
child2.on("message", (message2) => {
|
|
48540
|
+
result = message2.result;
|
|
48541
|
+
failure = message2.error;
|
|
48542
|
+
});
|
|
48543
|
+
child2.on("error", reject);
|
|
48544
|
+
child2.on("exit", (code) => {
|
|
48545
|
+
if (code === 0 && result) resolveResult(result);
|
|
48546
|
+
else reject(new Error(failure ?? `${repo}: member rcand process exited ${code} without a result`));
|
|
48547
|
+
});
|
|
48548
|
+
});
|
|
48549
|
+
};
|
|
48311
48550
|
const release = async (repo, expected, rcSha, opts) => inTrain(repo, async (deps) => {
|
|
48312
48551
|
await prepareRcCheckout((args) => deps.run("git", args), repo);
|
|
48313
48552
|
const plainWords = {
|
|
@@ -48362,7 +48601,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
48362
48601
|
if (!reuse) await gh(["workflow", "run", "module-rc-publish.yml", "--repo", "mutmutco/MMI-Hub", ...fields.flatMap((f) => ["--field", f])]);
|
|
48363
48602
|
let candidate = reuse;
|
|
48364
48603
|
for (let i = 0; !candidate && i < 25; i++) {
|
|
48365
|
-
await new Promise((
|
|
48604
|
+
await new Promise((resolve8) => setTimeout(resolve8, 1e3));
|
|
48366
48605
|
candidate = (await rcRuns()).find((r) => r.displayTitle === title && Date.parse(r.createdAt) >= since);
|
|
48367
48606
|
}
|
|
48368
48607
|
if (!candidate) throw new Error(`workflow dispatch outcome ambiguous for "${title}"; inspect Actions before retrying`);
|
|
@@ -48517,7 +48756,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
48517
48756
|
for (let i = 0; ; i++) {
|
|
48518
48757
|
const pin = await openSuitePinPr({ root, run: (file, args, cwd) => exec(file, args, cwd, GIT_TIMEOUT_MS) }, { repo, tag, stage: "main" });
|
|
48519
48758
|
if (pin.status !== "pending" || i >= 40) return pin;
|
|
48520
|
-
await new Promise((
|
|
48759
|
+
await new Promise((resolve8) => setTimeout(resolve8, 3e4));
|
|
48521
48760
|
}
|
|
48522
48761
|
},
|
|
48523
48762
|
suiteMapping: async (leadTag) => {
|
|
@@ -48533,7 +48772,7 @@ function suiteTrainIo(root, summaryFile, memberSummaryFiles = {}, summaryFileTr,
|
|
|
48533
48772
|
if (parseSuiteMapping(body)) return;
|
|
48534
48773
|
const dir = suiteStateDir(root);
|
|
48535
48774
|
(0, import_node_fs45.mkdirSync)(dir, { recursive: true });
|
|
48536
|
-
const file = (0,
|
|
48775
|
+
const file = (0, import_node_path45.join)(dir, `notes-${leadTag}.md`);
|
|
48537
48776
|
(0, import_node_fs45.writeFileSync)(file, `${body}
|
|
48538
48777
|
|
|
48539
48778
|
${suiteMappingBlock(leadTag, mapping)}`);
|
|
@@ -48599,11 +48838,23 @@ function suiteRcDataProdDeps() {
|
|
|
48599
48838
|
const rc = facts.stages.rc;
|
|
48600
48839
|
return rc?.present ? rc.substrate ?? "an unnamed substrate" : null;
|
|
48601
48840
|
},
|
|
48602
|
-
sleep: (ms) => new Promise((
|
|
48841
|
+
sleep: (ms) => new Promise((resolve8) => setTimeout(resolve8, ms))
|
|
48603
48842
|
};
|
|
48604
48843
|
}
|
|
48605
48844
|
function registerSuiteCommands(program3) {
|
|
48606
48845
|
const suite = program3.command("suite").description("MM-Work suite status, candidate cut and release (#7262/#7263)");
|
|
48846
|
+
suite.command("member-rcand-internal", { hidden: true }).argument("<repo>").argument("<previous-rc>").option("--rc-was-absent").action(async (repo, previousRc, o) => {
|
|
48847
|
+
if (!process.send) return fail("member rcand is an internal suite child operation");
|
|
48848
|
+
try {
|
|
48849
|
+
if (repo === SUITE_REPO || (await resolveRepo())?.toLowerCase() !== repo.toLowerCase())
|
|
48850
|
+
throw new Error(`${repo}: member rcand checkout mismatch; stop`);
|
|
48851
|
+
const result = await runSuiteMemberRcand(repo, previousRc || void 0, o.rcWasAbsent);
|
|
48852
|
+
process.send({ result }, () => process.disconnect());
|
|
48853
|
+
} catch (e) {
|
|
48854
|
+
process.exitCode = 1;
|
|
48855
|
+
process.send({ error: e.message }, () => process.disconnect());
|
|
48856
|
+
}
|
|
48857
|
+
});
|
|
48607
48858
|
const leadRoot = async () => {
|
|
48608
48859
|
const repo = await resolveRepo();
|
|
48609
48860
|
if (repo?.toLowerCase() !== SUITE_REPO.toLowerCase()) throw new Error(`suite commands run only from ${SUITE_REPO} (got ${repo ?? "unknown"})`);
|
|
@@ -48845,10 +49096,10 @@ function suiteStatusDeps(github, registry2) {
|
|
|
48845
49096
|
}
|
|
48846
49097
|
|
|
48847
49098
|
// src/packages-adopt-export.ts
|
|
48848
|
-
var
|
|
49099
|
+
var import_node_child_process19 = require("node:child_process");
|
|
48849
49100
|
var import_node_fs47 = require("node:fs");
|
|
48850
49101
|
var import_node_os22 = require("node:os");
|
|
48851
|
-
var
|
|
49102
|
+
var import_node_path46 = require("node:path");
|
|
48852
49103
|
var import_node_util6 = require("node:util");
|
|
48853
49104
|
init_cli_shared();
|
|
48854
49105
|
|
|
@@ -49468,25 +49719,25 @@ function expectedFromRun(run, opts) {
|
|
|
49468
49719
|
}
|
|
49469
49720
|
async function adoptExport(opts, deps) {
|
|
49470
49721
|
if (!/^sha256:[0-9a-f]{64}$/.test(opts.digest)) throw new Error("--digest must be sha256:<64 hex>, from the export run summary");
|
|
49471
|
-
const packagePath = (0,
|
|
49472
|
-
const lockPath = (0,
|
|
49722
|
+
const packagePath = (0, import_node_path46.join)(opts.dir, "package.json");
|
|
49723
|
+
const lockPath = (0, import_node_path46.join)(opts.dir, "package-lock.json");
|
|
49473
49724
|
const packageText = (0, import_node_fs47.readFileSync)(packagePath, "utf8");
|
|
49474
49725
|
const lockText = (0, import_node_fs47.readFileSync)(lockPath, "utf8");
|
|
49475
|
-
const work = (0, import_node_fs47.mkdtempSync)((0,
|
|
49726
|
+
const work = (0, import_node_fs47.mkdtempSync)((0, import_node_path46.join)((0, import_node_os22.tmpdir)(), "mmi-adopt-export-"));
|
|
49476
49727
|
try {
|
|
49477
49728
|
const run = JSON.parse((await deps.ghApi(`repos/${EXPORT_REPO}/actions/runs/${opts.run}`)).toString("utf8"));
|
|
49478
|
-
(0, import_node_fs47.writeFileSync)((0,
|
|
49479
|
-
(0, import_node_fs47.writeFileSync)((0,
|
|
49480
|
-
(0, import_node_fs47.writeFileSync)((0,
|
|
49481
|
-
const script = (0,
|
|
49729
|
+
(0, import_node_fs47.writeFileSync)((0, import_node_path46.join)(work, "expected.json"), JSON.stringify(expectedFromRun(run, opts)));
|
|
49730
|
+
(0, import_node_fs47.writeFileSync)((0, import_node_path46.join)(work, "provider.json"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}`));
|
|
49731
|
+
(0, import_node_fs47.writeFileSync)((0, import_node_path46.join)(work, "artifact.zip"), await deps.ghApi(`repos/${EXPORT_REPO}/actions/artifacts/${opts.artifact}/zip`));
|
|
49732
|
+
const script = (0, import_node_path46.join)(work, "package_export.py");
|
|
49482
49733
|
(0, import_node_fs47.writeFileSync)(script, package_export_default);
|
|
49483
49734
|
await deps.python(script, ["verify", work, opts.ds, opts.screens]);
|
|
49484
|
-
const verified = (0,
|
|
49485
|
-
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs47.readFileSync)((0,
|
|
49735
|
+
const verified = (0, import_node_path46.join)(work, "verified");
|
|
49736
|
+
const metadata = Object.fromEntries(PACKAGES.map(({ name, short: short3 }) => [name, JSON.parse((0, import_node_fs47.readFileSync)((0, import_node_path46.join)(verified, `${short3}.metadata.json`), "utf8"))]));
|
|
49486
49737
|
const plan = planAdoption(packageText, lockText, metadata);
|
|
49487
49738
|
for (const { name } of plan.result.adopted) {
|
|
49488
49739
|
const short3 = PACKAGES.find((p) => p.name === name).short;
|
|
49489
|
-
await deps.npm(["cache", "add", (0,
|
|
49740
|
+
await deps.npm(["cache", "add", (0, import_node_path46.join)(verified, `${short3}.tgz`)], opts.dir);
|
|
49490
49741
|
}
|
|
49491
49742
|
(0, import_node_fs47.writeFileSync)(packagePath, plan.packageText);
|
|
49492
49743
|
(0, import_node_fs47.writeFileSync)(lockPath, plan.lockText);
|
|
@@ -49502,7 +49753,7 @@ async function adoptExport(opts, deps) {
|
|
|
49502
49753
|
(0, import_node_fs47.rmSync)(work, { recursive: true, force: true });
|
|
49503
49754
|
}
|
|
49504
49755
|
}
|
|
49505
|
-
var execFileBuffer = (0, import_node_util6.promisify)(
|
|
49756
|
+
var execFileBuffer = (0, import_node_util6.promisify)(import_node_child_process19.execFile);
|
|
49506
49757
|
var MAX_OUTPUT = 200 * 1024 * 1024;
|
|
49507
49758
|
async function findPython() {
|
|
49508
49759
|
for (const bin of ["python3", "python"]) {
|
|
@@ -49628,7 +49879,7 @@ init_github_client();
|
|
|
49628
49879
|
init_cli_shared();
|
|
49629
49880
|
var import_node_fs49 = require("node:fs");
|
|
49630
49881
|
var import_node_os23 = require("node:os");
|
|
49631
|
-
var
|
|
49882
|
+
var import_node_path47 = require("node:path");
|
|
49632
49883
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
49633
49884
|
async function collectStatus() {
|
|
49634
49885
|
const repo = await resolveRepo();
|
|
@@ -49819,8 +50070,8 @@ async function collectOnboardStatus(opts = {}) {
|
|
|
49819
50070
|
}
|
|
49820
50071
|
const home = (0, import_node_os23.homedir)();
|
|
49821
50072
|
const plugin = onboardPluginGate({
|
|
49822
|
-
readKnown: () => readFileSyncSafe((0,
|
|
49823
|
-
readSettings: () => readFileSyncSafe((0,
|
|
50073
|
+
readKnown: () => readFileSyncSafe((0, import_node_path47.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs49.readFileSync),
|
|
50074
|
+
readSettings: () => readFileSyncSafe((0, import_node_path47.join)(home, ".claude", "settings.json"), import_node_fs49.readFileSync)
|
|
49824
50075
|
});
|
|
49825
50076
|
return {
|
|
49826
50077
|
guide: "Read the MMI org-guide skill before project work; load only the relevant topic. Use mmi-help for a short introduction.",
|
|
@@ -52015,6 +52266,65 @@ function registerOrgHealthQuery(program3, deps = defaultOrgHealthQueryDeps()) {
|
|
|
52015
52266
|
// src/api-read.ts
|
|
52016
52267
|
var import_node_fs50 = require("node:fs");
|
|
52017
52268
|
|
|
52269
|
+
// src/run-log-read.ts
|
|
52270
|
+
init_cli_shared();
|
|
52271
|
+
var FAILED2 = /* @__PURE__ */ new Set(["failure", "cancelled", "timed_out"]);
|
|
52272
|
+
function logRepo(raw) {
|
|
52273
|
+
const repo = parseBrokerRepo(raw, "--repo");
|
|
52274
|
+
if (repo.split("/").some((s) => s === "." || s === "..")) throw new Error("read run log: --repo refuses dot segments");
|
|
52275
|
+
return repo;
|
|
52276
|
+
}
|
|
52277
|
+
function logJob(raw) {
|
|
52278
|
+
if (!raw.trim() || /[\r\n\0]/.test(raw)) throw new Error("read run log: --job must be a non-empty job name");
|
|
52279
|
+
return raw;
|
|
52280
|
+
}
|
|
52281
|
+
function sliceRunLog(text, tail) {
|
|
52282
|
+
const lines2 = text.replace(/\r\n/g, "\n").split("\n");
|
|
52283
|
+
if (lines2.at(-1) === "") lines2.pop();
|
|
52284
|
+
const context = /* @__PURE__ */ new Set();
|
|
52285
|
+
lines2.forEach((line, i) => {
|
|
52286
|
+
if (line.includes("##[error]")) for (let j = Math.max(0, i - 3); j <= Math.min(lines2.length - 1, i + 3); j++) context.add(j);
|
|
52287
|
+
});
|
|
52288
|
+
return { errorLines: lines2.filter((_, i) => context.has(i)), tail: lines2.slice(-tail) };
|
|
52289
|
+
}
|
|
52290
|
+
async function readRunLog(id, o, runner) {
|
|
52291
|
+
const runId = parseBrokerId(id, "run id");
|
|
52292
|
+
const repo = logRepo(o.repo);
|
|
52293
|
+
const tail = parseBrokerInt(o.tail ?? "200", "--tail", 1, 2e3);
|
|
52294
|
+
const attempt = o.attempt === void 0 ? void 0 : parseBrokerInt(o.attempt, "--attempt", 1, 1e3);
|
|
52295
|
+
const jobName = o.job === void 0 ? void 0 : logJob(o.job);
|
|
52296
|
+
if (o.failed && jobName !== void 0) throw new Error("read run log: --failed and --job are mutually exclusive");
|
|
52297
|
+
const base = `repos/${repo}/actions/runs/${runId}`;
|
|
52298
|
+
const pages = JSON.parse(await readAppApi(`${base}${attempt === void 0 ? "" : `/attempts/${attempt}`}/jobs`, runner));
|
|
52299
|
+
const jobs = pages.flatMap((page) => page.jobs);
|
|
52300
|
+
const selected = jobs.filter((job) => jobName === void 0 ? FAILED2.has(job.conclusion ?? "") : job.name === jobName);
|
|
52301
|
+
const result = { runId, repo, jobs: [] };
|
|
52302
|
+
if (!selected.length) return { result, exitCode: 1, message: `read run log: no matching job for run ${runId} in ${repo}; conclusions seen: ${[...new Set(jobs.map((j) => j.conclusion ?? "pending"))].join(", ") || "none"}` };
|
|
52303
|
+
for (const job of selected) {
|
|
52304
|
+
const text = await readAppApi(`repos/${repo}/actions/jobs/${parseBrokerId(String(job.id), "job id")}/logs`, runner);
|
|
52305
|
+
result.jobs.push({ id: job.id, name: job.name, conclusion: job.conclusion, failedStep: job.steps?.find((s) => s.conclusion === "failure")?.name ?? null, ...sliceRunLog(text, tail) });
|
|
52306
|
+
}
|
|
52307
|
+
return { result, exitCode: 0 };
|
|
52308
|
+
}
|
|
52309
|
+
function registerRunLogRead(run, runner) {
|
|
52310
|
+
run.command("log").description("read workflow job logs on the App pool: error lines with \xB13 context and bounded tail (#8136); defaults to failed jobs, never personal").requiredOption("--repo <owner/repo>", "target repo on github.com", logRepo).addOption(new Option("--failed", "select failure/cancelled/timed_out jobs (default)").conflicts("job")).addOption(new Option("--job <name>", "select jobs with this exact name").argParser(logJob).conflicts("failed")).option("--tail <n>", "last lines per job (1-2000; default 200)", (v) => String(parseBrokerInt(v, "--tail", 1, 2e3)), "200").option("--attempt <n>", "run attempt (1-1000; default latest)", (v) => String(parseBrokerInt(v, "--attempt", 1, 1e3))).option("--json", "emit runId, repo and jobs with failedStep/errorLines/tail").argument("<runId>", "workflow run number", (v) => parseBrokerId(v, "run id")).action(async (id, o) => {
|
|
52311
|
+
requireBrokerApp("read run log");
|
|
52312
|
+
const env = brokerChildEnv();
|
|
52313
|
+
try {
|
|
52314
|
+
const { result, message: message2, exitCode } = await readRunLog(id, o, runner);
|
|
52315
|
+
if (message2) printLine(message2);
|
|
52316
|
+
else if (o.json) printLine(redactPrivateCredential(JSON.stringify(result), env));
|
|
52317
|
+
else for (const job of result.jobs) {
|
|
52318
|
+
printLine(redactPrivateCredential(`== ${job.name} (${job.id}) ${job.conclusion}: failing step "${job.failedStep ?? "none"}"`, env));
|
|
52319
|
+
for (const line of [...job.errorLines, ...job.tail]) printLine(redactPrivateCredential(line, env));
|
|
52320
|
+
}
|
|
52321
|
+
process.exitCode = exitCode;
|
|
52322
|
+
} catch (e) {
|
|
52323
|
+
return fail(redactPrivateCredential(`read run log: App-pool read failed (${e.message}) \u2014 re-run the same command; never falls back to personal`, env));
|
|
52324
|
+
}
|
|
52325
|
+
});
|
|
52326
|
+
}
|
|
52327
|
+
|
|
52018
52328
|
// src/app-read-broker.ts
|
|
52019
52329
|
init_cli_shared();
|
|
52020
52330
|
var OWNER_REPO_RE = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
|
|
@@ -52301,7 +52611,8 @@ function registerAppReadBrokerCommands(program3, resolveRepo2, overrides) {
|
|
|
52301
52611
|
const runStatusOpt = (v) => parseBrokerRunStatus(v);
|
|
52302
52612
|
const assigneeOpt = (v) => parseBrokerAssignee(v);
|
|
52303
52613
|
const read = program3.command("read").description(`closed typed GitHub reads on the App pool (${BROKER_JSON_FLAGS_NOTE})`);
|
|
52304
|
-
const run = read.command("run").description("workflow-run reads (view/list/watch
|
|
52614
|
+
const run = read.command("run").description("workflow-run reads (view/list/watch pass gh through; log is a bounded App REST read)");
|
|
52615
|
+
registerRunLogRead(run, overrides?.apiRunner);
|
|
52305
52616
|
run.command("view").description("read a workflow run (attempt/exit-status/json; formatters refused)").option("--repo <owner/repo>", "target repo (defaults to the current repo)", repoOpt).option("--json [fields]", "machine-readable output (comma-separated gh fields)", jsonOpt).option("--exit-status", "exit non-zero when the run failed").option("--attempt <n>", "the attempt number of the run (1-1000)", intOpt("--attempt", 1, 1e3)).argument("<id>", "workflow run number", idOpt("run id")).action(async (id, o) => {
|
|
52306
52617
|
let spec;
|
|
52307
52618
|
try {
|
|
@@ -52391,11 +52702,11 @@ var CONTENTS_BASE_RE = new RegExp(`^repos/${NAME}/${NAME}/contents/${FREE}$`);
|
|
|
52391
52702
|
var CONTENTS_QUERY_RE = /^ref=[^&\s]+$/;
|
|
52392
52703
|
var ACTIONS_RUNS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs$`);
|
|
52393
52704
|
var ACTIONS_RUN_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+$`);
|
|
52394
|
-
var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d
|
|
52705
|
+
var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+(?:/attempts/\\d+)?/jobs$`);
|
|
52395
52706
|
var ACTIONS_ARTIFACTS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+/artifacts$`);
|
|
52396
52707
|
var ACTIONS_ARTIFACT_ZIP_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/artifacts/\\d+/zip$`);
|
|
52397
52708
|
var ACTIONS_JOB_LOGS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/jobs/\\d+/logs$`);
|
|
52398
|
-
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs, repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>)";
|
|
52709
|
+
var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs, repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>)";
|
|
52399
52710
|
function parseApiGetPath(rawPath) {
|
|
52400
52711
|
const path2 = rawPath.trim();
|
|
52401
52712
|
const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
|
|
@@ -52429,8 +52740,16 @@ function buildApiGetArgs(path2, accept) {
|
|
|
52429
52740
|
async function runApiGet(run, path2, accept) {
|
|
52430
52741
|
return run(buildApiGetArgs(path2, accept), GIT_TIMEOUT_MS);
|
|
52431
52742
|
}
|
|
52743
|
+
var apiReadRunner = async (args, timeoutMs, env, binary) => (await execFileP("gh", args, { timeout: timeoutMs, ...binary ? { encoding: "latin1", maxBuffer: ZIP_MAX_BUFFER } : {}, ...env ? { env } : {} })).stdout;
|
|
52744
|
+
async function readAppApi(path2, runner = apiReadRunner) {
|
|
52745
|
+
const req = parseApiGetPath(path2);
|
|
52746
|
+
requireBrokerApp("read run log");
|
|
52747
|
+
const log = req.kind === "actions-job-logs";
|
|
52748
|
+
const body = await runApiGet((args, timeout) => runner(req.kind === "actions-jobs" ? [...args, "--slurp"] : args, log ? ZIP_TIMEOUT_MS : timeout, brokerChildEnv(), log || void 0), path2);
|
|
52749
|
+
return log ? Buffer.from(body, "latin1").toString("utf8") : body;
|
|
52750
|
+
}
|
|
52432
52751
|
function registerApiReadCommands(program3, overrides) {
|
|
52433
|
-
const spawn6 = overrides?.runner ??
|
|
52752
|
+
const spawn6 = overrides?.runner ?? apiReadRunner;
|
|
52434
52753
|
const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts)");
|
|
52435
52754
|
api.command("get").description("GET one allowlisted REST path and print the response body to stdout (JSON, or raw file bytes with --accept raw); refuses everything outside the allowlist").option("--accept <type>", "response media type: json (default) or raw (file bytes)", (v) => {
|
|
52436
52755
|
acceptHeaderFor(v);
|
|
@@ -53309,7 +53628,7 @@ function registerSessionReport(program3) {
|
|
|
53309
53628
|
|
|
53310
53629
|
// src/stage-commands.ts
|
|
53311
53630
|
var import_node_fs51 = require("node:fs");
|
|
53312
|
-
var
|
|
53631
|
+
var import_node_path48 = require("node:path");
|
|
53313
53632
|
init_cli_shared();
|
|
53314
53633
|
init_clean_exit();
|
|
53315
53634
|
|
|
@@ -53557,8 +53876,8 @@ function registerStageCommands(program3) {
|
|
|
53557
53876
|
const repo = Array.isArray(project2?.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : slug;
|
|
53558
53877
|
const resolution = decideStage({
|
|
53559
53878
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
53560
|
-
hasCompose: (0, import_node_fs51.existsSync)((0,
|
|
53561
|
-
hasEnvExample: (0, import_node_fs51.existsSync)((0,
|
|
53879
|
+
hasCompose: (0, import_node_fs51.existsSync)((0, import_node_path48.join)(process.cwd(), "docker-compose.yml")),
|
|
53880
|
+
hasEnvExample: (0, import_node_fs51.existsSync)((0, import_node_path48.join)(process.cwd(), ".env.example")),
|
|
53562
53881
|
repo
|
|
53563
53882
|
});
|
|
53564
53883
|
if (resolution.source === "derived" && resolution.derived) {
|
|
@@ -53853,7 +54172,7 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
53853
54172
|
var import_node_crypto17 = require("node:crypto");
|
|
53854
54173
|
var import_node_fs52 = require("node:fs");
|
|
53855
54174
|
var import_promises11 = require("node:fs/promises");
|
|
53856
|
-
var
|
|
54175
|
+
var import_node_path49 = require("node:path");
|
|
53857
54176
|
var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
|
53858
54177
|
var MAX_BYTES2 = 5 * 1024 * 1024 * 1024;
|
|
53859
54178
|
async function sha256File(path2) {
|
|
@@ -53863,7 +54182,7 @@ async function sha256File(path2) {
|
|
|
53863
54182
|
}
|
|
53864
54183
|
async function putTenantArtifact(repo, stage, inputPath, deps) {
|
|
53865
54184
|
if (!["dev", "rc", "main"].includes(stage)) throw new Error("tenant artifact put: <stage> must be dev, rc, or main");
|
|
53866
|
-
const path2 = (0,
|
|
54185
|
+
const path2 = (0, import_node_path49.resolve)(inputPath);
|
|
53867
54186
|
const info = await (0, import_promises11.stat)(path2);
|
|
53868
54187
|
if (!info.isFile()) throw new Error("tenant artifact put: input path must be a file");
|
|
53869
54188
|
if (!Number.isSafeInteger(info.size) || info.size < 1 || info.size > MAX_BYTES2) throw new Error(`tenant artifact put: file must be 1..${MAX_BYTES2} bytes`);
|
|
@@ -54053,7 +54372,7 @@ async function resolveWhoami(deps) {
|
|
|
54053
54372
|
}
|
|
54054
54373
|
|
|
54055
54374
|
// src/command-register-developer.ts
|
|
54056
|
-
var
|
|
54375
|
+
var import_node_path55 = require("node:path");
|
|
54057
54376
|
init_house_map();
|
|
54058
54377
|
init_hub_url();
|
|
54059
54378
|
|
|
@@ -54086,21 +54405,21 @@ async function executeWaveLand(plan, deps) {
|
|
|
54086
54405
|
}
|
|
54087
54406
|
|
|
54088
54407
|
// src/dist-drift.ts
|
|
54089
|
-
var
|
|
54408
|
+
var import_node_child_process20 = require("node:child_process");
|
|
54090
54409
|
var import_node_crypto19 = require("node:crypto");
|
|
54091
54410
|
var import_node_fs54 = require("node:fs");
|
|
54092
54411
|
var import_node_os24 = require("node:os");
|
|
54093
|
-
var
|
|
54412
|
+
var import_node_path51 = require("node:path");
|
|
54094
54413
|
|
|
54095
54414
|
// ../scripts/distribution-digest.mjs
|
|
54096
54415
|
var import_node_crypto18 = require("node:crypto");
|
|
54097
54416
|
var import_node_fs53 = require("node:fs");
|
|
54098
|
-
var
|
|
54417
|
+
var import_node_path50 = require("node:path");
|
|
54099
54418
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
54100
54419
|
function repoPath(root, declaredPath, label) {
|
|
54101
|
-
const absoluteRoot = (0,
|
|
54102
|
-
const target = (0,
|
|
54103
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
54420
|
+
const absoluteRoot = (0, import_node_path50.resolve)(root);
|
|
54421
|
+
const target = (0, import_node_path50.resolve)(root, declaredPath);
|
|
54422
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path50.sep}`)) {
|
|
54104
54423
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
54105
54424
|
}
|
|
54106
54425
|
return target;
|
|
@@ -54132,6 +54451,7 @@ function digestPackedFiles(packageRoot, packedFiles) {
|
|
|
54132
54451
|
var DIST_ARTIFACTS = [
|
|
54133
54452
|
{ path: "cli/dist/index.cjs", packageDir: "cli", output: "index.cjs" },
|
|
54134
54453
|
{ path: "cli/dist/main.cjs", packageDir: "cli", output: "main.cjs" },
|
|
54454
|
+
{ path: "cli/dist/runbook-sweep.cjs", packageDir: "cli", output: "runbook-sweep.cjs" },
|
|
54135
54455
|
{ path: "updater/dist/index.cjs", packageDir: "updater", output: "index.cjs" }
|
|
54136
54456
|
];
|
|
54137
54457
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
@@ -54214,7 +54534,7 @@ function walkFiles(root) {
|
|
|
54214
54534
|
const files = [];
|
|
54215
54535
|
const walk2 = (directory) => {
|
|
54216
54536
|
for (const entry of (0, import_node_fs54.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
54217
|
-
const child2 = (0,
|
|
54537
|
+
const child2 = (0, import_node_path51.join)(directory, entry.name);
|
|
54218
54538
|
if (entry.isDirectory()) walk2(child2);
|
|
54219
54539
|
else files.push(child2);
|
|
54220
54540
|
}
|
|
@@ -54224,14 +54544,14 @@ function walkFiles(root) {
|
|
|
54224
54544
|
}
|
|
54225
54545
|
function bomPathFor(root) {
|
|
54226
54546
|
try {
|
|
54227
|
-
const registry2 = JSON.parse((0, import_node_fs54.readFileSync)((0,
|
|
54228
|
-
return (0,
|
|
54547
|
+
const registry2 = JSON.parse((0, import_node_fs54.readFileSync)((0, import_node_path51.join)(root, "surfaces.json"), "utf8"));
|
|
54548
|
+
return (0, import_node_path51.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
54229
54549
|
} catch {
|
|
54230
|
-
return (0,
|
|
54550
|
+
return (0, import_node_path51.join)(root, "distribution-bom.json");
|
|
54231
54551
|
}
|
|
54232
54552
|
}
|
|
54233
54553
|
function rebuildTo(packageRoot, outDir) {
|
|
54234
|
-
(0,
|
|
54554
|
+
(0, import_node_child_process20.execFileSync)(process.execPath, ["build.mjs"], {
|
|
54235
54555
|
cwd: packageRoot,
|
|
54236
54556
|
env: { ...process.env, MMI_DIST_OUTDIR: outDir },
|
|
54237
54557
|
windowsHide: true,
|
|
@@ -54240,28 +54560,28 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
54240
54560
|
});
|
|
54241
54561
|
}
|
|
54242
54562
|
function runDistStatus(root) {
|
|
54243
|
-
const stage = (0, import_node_fs54.mkdtempSync)((0,
|
|
54563
|
+
const stage = (0, import_node_fs54.mkdtempSync)((0, import_node_path51.join)((0, import_node_os24.tmpdir)(), "mmi-dist-drift-"));
|
|
54244
54564
|
let overlayCount = 0;
|
|
54245
54565
|
try {
|
|
54246
|
-
const cliOut = (0,
|
|
54247
|
-
const hubOut = (0,
|
|
54248
|
-
rebuildTo((0,
|
|
54249
|
-
rebuildTo((0,
|
|
54566
|
+
const cliOut = (0, import_node_path51.join)(stage, "cli-dist");
|
|
54567
|
+
const hubOut = (0, import_node_path51.join)(stage, "hub-dist");
|
|
54568
|
+
rebuildTo((0, import_node_path51.join)(root, "cli"), cliOut);
|
|
54569
|
+
rebuildTo((0, import_node_path51.join)(root, "updater"), hubOut);
|
|
54250
54570
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
54251
54571
|
const rebuilt = (path2) => {
|
|
54252
54572
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
54253
|
-
return spec ? readOrNull((0,
|
|
54573
|
+
return spec ? readOrNull((0, import_node_path51.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
54254
54574
|
};
|
|
54255
|
-
const committed = (path2) => readOrNull((0,
|
|
54256
|
-
const tree = (path2) => readOrNull((0,
|
|
54257
|
-
const distRoot = (0,
|
|
54258
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
54575
|
+
const committed = (path2) => readOrNull((0, import_node_path51.join)(root, path2));
|
|
54576
|
+
const tree = (path2) => readOrNull((0, import_node_path51.join)(root, path2));
|
|
54577
|
+
const distRoot = (0, import_node_path51.join)(root, "cli", "dist");
|
|
54578
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path51.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
54259
54579
|
const bom = JSON.parse((0, import_node_fs54.readFileSync)(bomPathFor(root), "utf8"));
|
|
54260
54580
|
const digest = (entries) => {
|
|
54261
|
-
const overlay = (0,
|
|
54581
|
+
const overlay = (0, import_node_path51.join)(stage, `overlay-${overlayCount++}`);
|
|
54262
54582
|
for (const entry of entries) {
|
|
54263
|
-
const target = (0,
|
|
54264
|
-
(0, import_node_fs54.mkdirSync)((0,
|
|
54583
|
+
const target = (0, import_node_path51.join)(overlay, entry.path);
|
|
54584
|
+
(0, import_node_fs54.mkdirSync)((0, import_node_path51.dirname)(target), { recursive: true });
|
|
54265
54585
|
(0, import_node_fs54.writeFileSync)(target, entry.bytes);
|
|
54266
54586
|
}
|
|
54267
54587
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
@@ -54272,6 +54592,123 @@ function runDistStatus(root) {
|
|
|
54272
54592
|
}
|
|
54273
54593
|
}
|
|
54274
54594
|
|
|
54595
|
+
// src/doc-refs-live.ts
|
|
54596
|
+
var import_node_path52 = require("node:path");
|
|
54597
|
+
var concrete = (value) => !!value && !/[<>{}$]/.test(value);
|
|
54598
|
+
var liveOption = (tokens2, flag) => {
|
|
54599
|
+
const index = tokens2.indexOf(flag);
|
|
54600
|
+
return index >= 0 ? tokens2[index + 1] : tokens2.find((token) => token.startsWith(`${flag}=`))?.slice(flag.length + 1);
|
|
54601
|
+
};
|
|
54602
|
+
async function checkRunbookLive(docs, deps) {
|
|
54603
|
+
const findings = [];
|
|
54604
|
+
const warnings = [];
|
|
54605
|
+
const cache = /* @__PURE__ */ new Map();
|
|
54606
|
+
const names = (type, scope) => {
|
|
54607
|
+
const key = `${type}:${scope}`;
|
|
54608
|
+
if (!cache.has(key)) cache.set(key, Promise.resolve().then(() => deps[type](scope)));
|
|
54609
|
+
return cache.get(key);
|
|
54610
|
+
};
|
|
54611
|
+
for (const [doc, body] of Object.entries(docs)) {
|
|
54612
|
+
if (!isRunbook(doc, body)) continue;
|
|
54613
|
+
for (const { tokens: raw, line } of shellClaims(body)) {
|
|
54614
|
+
if (raw[0] !== "mmi-cli") continue;
|
|
54615
|
+
const end = raw.indexOf("--");
|
|
54616
|
+
const tokens2 = end < 0 ? raw : raw.slice(0, end);
|
|
54617
|
+
const task = liveOption(tokens2, "--task");
|
|
54618
|
+
const repo = liveOption(tokens2, "--repo") ?? tokens2.find((token) => /^[\w.-]+\/[\w.-]+$/.test(token));
|
|
54619
|
+
const key = liveOption(tokens2, "--key");
|
|
54620
|
+
const scope = liveOption(tokens2, "--repo") ?? (liveOption(tokens2, "--slug") === "_org" ? "_org" : void 0);
|
|
54621
|
+
const checks = [];
|
|
54622
|
+
if (tokens2.includes("run-task") && concrete(repo) && concrete(task)) checks.push({ type: "tasks", scope: repo, name: task, kind: "unknown-task" });
|
|
54623
|
+
if (tokens2[1] === "vault" && tokens2[2] === "secrets" && ["use", "set", "rm"].includes(tokens2[3]) && concrete(scope) && concrete(key)) checks.push({ type: "vaultKeys", scope, name: key, kind: "unknown-vault-key" });
|
|
54624
|
+
for (const check of checks) {
|
|
54625
|
+
const detail = `${check.scope}: ${check.name}`;
|
|
54626
|
+
try {
|
|
54627
|
+
if (!(await names(check.type, check.scope)).has(check.name)) findings.push({ kind: check.kind, doc, line, detail });
|
|
54628
|
+
} catch {
|
|
54629
|
+
warnings.push({ kind: "live-unverified", doc, line, detail: `${check.type} ${check.scope}: remote names unreadable (auth/transport or unsupported scope)` });
|
|
54630
|
+
}
|
|
54631
|
+
}
|
|
54632
|
+
}
|
|
54633
|
+
let fence = "";
|
|
54634
|
+
const seen = /* @__PURE__ */ new Set();
|
|
54635
|
+
for (const [index, text] of body.split(/\r?\n/).entries()) {
|
|
54636
|
+
const marker = /^\s*(`{3,}|~{3,})(.*)$/.exec(text);
|
|
54637
|
+
if (marker) {
|
|
54638
|
+
if (!fence) fence = marker[1];
|
|
54639
|
+
else if (marker[1][0] === fence[0] && marker[1].length >= fence.length && !marker[2].trim()) fence = "";
|
|
54640
|
+
continue;
|
|
54641
|
+
}
|
|
54642
|
+
const spans = fence ? [text] : [...text.matchAll(/`([^`]+)`/g)].map((match) => match[1]);
|
|
54643
|
+
for (const span of spans) for (const match of span.matchAll(/https:\/\/[^\s`"']+/g)) {
|
|
54644
|
+
const url = match[0].replace(/[),.;]+$/, "");
|
|
54645
|
+
if (!concrete(url) || seen.has(url)) continue;
|
|
54646
|
+
let host;
|
|
54647
|
+
try {
|
|
54648
|
+
host = new URL(url).hostname;
|
|
54649
|
+
} catch {
|
|
54650
|
+
continue;
|
|
54651
|
+
}
|
|
54652
|
+
if (/(^|\.)example\./i.test(host)) continue;
|
|
54653
|
+
seen.add(url);
|
|
54654
|
+
try {
|
|
54655
|
+
const status = await deps.endpoint(url);
|
|
54656
|
+
if (status >= 500) findings.push({ kind: "endpoint-down", doc, line: index + 1, detail: `${url}: HTTP ${status}` });
|
|
54657
|
+
} catch {
|
|
54658
|
+
findings.push({ kind: "endpoint-down", doc, line: index + 1, detail: `${url}: network error or 10s timeout` });
|
|
54659
|
+
}
|
|
54660
|
+
}
|
|
54661
|
+
}
|
|
54662
|
+
}
|
|
54663
|
+
return { findings, warnings };
|
|
54664
|
+
}
|
|
54665
|
+
async function runLiveDocRefs(root, deps, live) {
|
|
54666
|
+
const offline = runDocRefs(root, { ...deps, runbooksOnly: true });
|
|
54667
|
+
const read = deps.readFile ?? readFileOrNull;
|
|
54668
|
+
const docs = Object.fromEntries((deps.listDocs ?? defaultListDocs)(root).flatMap((doc) => {
|
|
54669
|
+
const body = read((0, import_node_path52.join)(root, doc));
|
|
54670
|
+
return body !== null && isRunbook(doc, body) && !deps.isIgnored?.([doc]).has(doc) ? [[doc, body]] : [];
|
|
54671
|
+
}));
|
|
54672
|
+
const remote = await checkRunbookLive(docs, live);
|
|
54673
|
+
const findings = [...offline.findings, ...remote.findings];
|
|
54674
|
+
return { ...offline, findings, warnings: [...offline.warnings, ...remote.warnings], ok: findings.length === 0 };
|
|
54675
|
+
}
|
|
54676
|
+
|
|
54677
|
+
// src/doc-refs-live-io.ts
|
|
54678
|
+
var import_node_child_process21 = require("node:child_process");
|
|
54679
|
+
var import_node_util7 = require("node:util");
|
|
54680
|
+
var exec2 = (0, import_node_util7.promisify)(import_node_child_process21.execFile);
|
|
54681
|
+
function createRunbookLiveDeps(cliBin) {
|
|
54682
|
+
const json = async (args) => {
|
|
54683
|
+
const { stdout } = await exec2(process.execPath, [cliBin, ...args, "--json"], { windowsHide: true, timeout: 3e4, maxBuffer: 4 * 1024 * 1024 });
|
|
54684
|
+
return JSON.parse(stdout);
|
|
54685
|
+
};
|
|
54686
|
+
return {
|
|
54687
|
+
tasks: async (repo) => {
|
|
54688
|
+
const project2 = await json(["oracle", "org", "project", "get", repo]);
|
|
54689
|
+
if (!project2 || typeof project2 !== "object" || Array.isArray(project2)) throw new Error("unreadable META");
|
|
54690
|
+
return new Set(Object.keys(project2.tenantTasks ?? {}));
|
|
54691
|
+
},
|
|
54692
|
+
vaultKeys: async (scope) => {
|
|
54693
|
+
if (scope === "_org") throw new Error("org names listing unsupported");
|
|
54694
|
+
const result = await json(["vault", "secrets", "list", "--repo", scope]);
|
|
54695
|
+
if (!Array.isArray(result?.secrets) || result.secrets.some((item) => typeof item.key !== "string")) throw new Error("unreadable names");
|
|
54696
|
+
return new Set(result.secrets.map((item) => item.key));
|
|
54697
|
+
},
|
|
54698
|
+
endpoint: async (url) => {
|
|
54699
|
+
const probe = async () => {
|
|
54700
|
+
const response = await fetch(url, { method: "GET", signal: AbortSignal.timeout(1e4) });
|
|
54701
|
+
await response.body?.cancel();
|
|
54702
|
+
return response.status;
|
|
54703
|
+
};
|
|
54704
|
+
const first = await probe().catch(() => 599);
|
|
54705
|
+
if (first < 500) return first;
|
|
54706
|
+
await new Promise((resolve8) => setTimeout(resolve8, 2e4));
|
|
54707
|
+
return probe();
|
|
54708
|
+
}
|
|
54709
|
+
};
|
|
54710
|
+
}
|
|
54711
|
+
|
|
54275
54712
|
// src/edge-commands.ts
|
|
54276
54713
|
init_cli_shared();
|
|
54277
54714
|
|
|
@@ -54369,7 +54806,7 @@ init_hub_auth();
|
|
|
54369
54806
|
|
|
54370
54807
|
// src/schedules-lift-command.ts
|
|
54371
54808
|
var import_promises12 = require("node:fs/promises");
|
|
54372
|
-
var
|
|
54809
|
+
var import_node_path53 = require("node:path");
|
|
54373
54810
|
init_clean_exit();
|
|
54374
54811
|
init_cli_shared();
|
|
54375
54812
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -54398,7 +54835,7 @@ async function readWorkflowFiles(dir) {
|
|
|
54398
54835
|
const files = [];
|
|
54399
54836
|
for (const name of names.sort()) {
|
|
54400
54837
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
54401
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0,
|
|
54838
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises12.readFile)((0, import_node_path53.join)(dir, name), "utf8") });
|
|
54402
54839
|
}
|
|
54403
54840
|
return files;
|
|
54404
54841
|
}
|
|
@@ -54480,9 +54917,9 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
54480
54917
|
}
|
|
54481
54918
|
|
|
54482
54919
|
// src/spawn-policy-core.ts
|
|
54483
|
-
var
|
|
54920
|
+
var import_node_child_process22 = require("node:child_process");
|
|
54484
54921
|
var import_node_fs55 = require("node:fs");
|
|
54485
|
-
var
|
|
54922
|
+
var import_node_path54 = require("node:path");
|
|
54486
54923
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
54487
54924
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
54488
54925
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -54551,7 +54988,7 @@ function findViolationsInSource(raw) {
|
|
|
54551
54988
|
return found;
|
|
54552
54989
|
}
|
|
54553
54990
|
function policedFiles(root) {
|
|
54554
|
-
const r = (0,
|
|
54991
|
+
const r = (0, import_node_child_process22.spawnSync)("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
54555
54992
|
cwd: root,
|
|
54556
54993
|
encoding: "utf8",
|
|
54557
54994
|
windowsHide: true,
|
|
@@ -54568,7 +55005,7 @@ function runSpawnPolicy(root) {
|
|
|
54568
55005
|
for (const file of files) {
|
|
54569
55006
|
let raw;
|
|
54570
55007
|
try {
|
|
54571
|
-
raw = (0, import_node_fs55.readFileSync)((0,
|
|
55008
|
+
raw = (0, import_node_fs55.readFileSync)((0, import_node_path54.join)(root, file), "utf8");
|
|
54572
55009
|
} catch {
|
|
54573
55010
|
continue;
|
|
54574
55011
|
}
|
|
@@ -54588,7 +55025,7 @@ function runSpawnPolicy(root) {
|
|
|
54588
55025
|
function registerDeveloperCommands(program3) {
|
|
54589
55026
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
54590
55027
|
rules.command("gitignore").option("--write", "upsert the managed block into .gitignore (default: check only, non-zero exit on drift)").option("--json", "machine-readable output").description("verify (or --write) this repo's org-managed .gitignore block matches the SSOT").action((opts) => {
|
|
54591
|
-
const path2 = (0,
|
|
55028
|
+
const path2 = (0, import_node_path55.join)(process.cwd(), ".gitignore");
|
|
54592
55029
|
const current = (0, import_node_fs56.existsSync)(path2) ? (0, import_node_fs56.readFileSync)(path2, "utf8") : null;
|
|
54593
55030
|
const plan = planManagedGitignore(current);
|
|
54594
55031
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
@@ -54734,17 +55171,23 @@ function registerDeveloperCommands(program3) {
|
|
|
54734
55171
|
await failGraceful(e.message);
|
|
54735
55172
|
}
|
|
54736
55173
|
});
|
|
54737
|
-
docs.command("refs").description("deterministic doc reference gate: repo paths, markdown links, mmi-cli command refs, and pins across docs, org-guide, mmi-help, README and architecture must resolve, or exit 1").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 docs refs reads the current checkout").hideHelp()).action(async (o) => {
|
|
55174
|
+
docs.command("refs").description("deterministic doc reference gate: repo paths, markdown links, mmi-cli command refs, and pins across docs, org-guide, mmi-help, README and architecture must resolve, or exit 1").option("--json", "machine-readable findings list: { ok, docCount, findings[], warnings[] }").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 docs refs reads the current checkout").hideHelp()).option("--base <ref>", "require covered runbook sections to be touched against this merge base (#8126)").option("--runbooks-only", "check only operator runbooks (#8126)").option("--live", "check operator runbooks against registry tasks, vault names and HTTPS endpoints (implies --runbooks-only)").action(async (o) => {
|
|
54738
55175
|
try {
|
|
54739
55176
|
if (o.repo) {
|
|
54740
55177
|
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli oracle docs refs${o.json ? " --json" : ""}`, "docs refs");
|
|
54741
55178
|
if (!guard.ok) return failGraceful(guard.message);
|
|
54742
55179
|
}
|
|
54743
55180
|
const root = await repoRoot();
|
|
54744
|
-
const
|
|
54745
|
-
|
|
54746
|
-
);
|
|
54747
|
-
const
|
|
55181
|
+
const manifest = buildCommandManifest(program3);
|
|
55182
|
+
const commandPaths = new Set(manifest.index.map((entry) => entry.path));
|
|
55183
|
+
const commandOptions = /* @__PURE__ */ new Map();
|
|
55184
|
+
const collect = (entry) => {
|
|
55185
|
+
commandOptions.set(entry.path, new Set(entry.options.flatMap((option) => option.flags.match(/--[\w-]+/g) ?? [])));
|
|
55186
|
+
entry.subcommands.forEach(collect);
|
|
55187
|
+
};
|
|
55188
|
+
collect(manifest.tree);
|
|
55189
|
+
const deps = { commandPaths, commandOptions, base: o.base, runbooksOnly: o.runbooksOnly };
|
|
55190
|
+
const result = o.live ? await runLiveDocRefs(root, deps, createRunbookLiveDeps(process.argv[1])) : runDocRefs(root, deps);
|
|
54748
55191
|
if (o.json) {
|
|
54749
55192
|
consoleIo.log(JSON.stringify({ ok: result.ok, docCount: result.docCount, findings: result.findings, warnings: result.warnings }, null, 2));
|
|
54750
55193
|
if (!result.ok) process.exitCode = 1;
|
|
@@ -54863,7 +55306,7 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
54863
55306
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
54864
55307
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
54865
55308
|
function envHealLockPath(home) {
|
|
54866
|
-
return (0,
|
|
55309
|
+
return (0, import_node_path56.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
54867
55310
|
}
|
|
54868
55311
|
async function withEnvHealLock(what, run) {
|
|
54869
55312
|
try {
|
|
@@ -55035,7 +55478,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
55035
55478
|
// has no generated routing index would
|
|
55036
55479
|
// get a permanent — demanding an artifact it never asked for.
|
|
55037
55480
|
docsIndexState: (root) => {
|
|
55038
|
-
if (!(0, import_node_fs57.existsSync)((0,
|
|
55481
|
+
if (!(0, import_node_fs57.existsSync)((0, import_node_path56.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
55039
55482
|
const real = createDocsIndexDeps(root);
|
|
55040
55483
|
let docs;
|
|
55041
55484
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -55044,7 +55487,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
55044
55487
|
},
|
|
55045
55488
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
55046
55489
|
healDocsIndex: (root) => {
|
|
55047
|
-
if (!(0, import_node_fs57.existsSync)((0,
|
|
55490
|
+
if (!(0, import_node_fs57.existsSync)((0, import_node_path56.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
55048
55491
|
const real = createDocsIndexDeps(root);
|
|
55049
55492
|
let docs;
|
|
55050
55493
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -55065,8 +55508,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
55065
55508
|
});
|
|
55066
55509
|
const raced = await Promise.race([
|
|
55067
55510
|
work.then((r) => ({ ...r, timedOut: false })),
|
|
55068
|
-
new Promise((
|
|
55069
|
-
ceiling = setTimeout(() =>
|
|
55511
|
+
new Promise((resolve8) => {
|
|
55512
|
+
ceiling = setTimeout(() => resolve8({ timedOut: true, scanned: 0, findings: 0, fixed: 0, failed: 0 }), BOARD_DOCTOR_TIMEOUT_MS);
|
|
55070
55513
|
})
|
|
55071
55514
|
]);
|
|
55072
55515
|
if (raced.timedOut) return { scanned: 0, findings: 0, fixed: 0, failed: 0, timedOut: true };
|
|
@@ -55099,8 +55542,8 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
55099
55542
|
incomplete: nb.incomplete,
|
|
55100
55543
|
timedOut: false
|
|
55101
55544
|
})),
|
|
55102
|
-
new Promise((
|
|
55103
|
-
ceiling = setTimeout(() =>
|
|
55545
|
+
new Promise((resolve8) => {
|
|
55546
|
+
ceiling = setTimeout(() => resolve8({ driftLines: [], incomplete: [], timedOut: true }), SCHEDULES_DRIFT_TIMEOUT_MS);
|
|
55104
55547
|
})
|
|
55105
55548
|
]);
|
|
55106
55549
|
if (!raced.timedOut && raced.incomplete.length === 0) writeSchedulesDriftCache(cachePath, raced.driftLines);
|
|
@@ -56152,16 +56595,16 @@ function ciAuditDeps2() {
|
|
|
56152
56595
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
56153
56596
|
readSeedFile: (path2) => {
|
|
56154
56597
|
if (!root) return null;
|
|
56155
|
-
const fullPath = (0,
|
|
56598
|
+
const fullPath = (0, import_node_path56.join)(root, path2);
|
|
56156
56599
|
return (0, import_node_fs57.existsSync)(fullPath) ? (0, import_node_fs57.readFileSync)(fullPath, "utf8") : null;
|
|
56157
56600
|
}
|
|
56158
56601
|
};
|
|
56159
56602
|
}
|
|
56160
56603
|
function hubRoot2() {
|
|
56161
|
-
const fromPkg = (0,
|
|
56604
|
+
const fromPkg = (0, import_node_path56.join)(__dirname, "..", "..");
|
|
56162
56605
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
56163
|
-
if ((0, import_node_fs57.existsSync)((0,
|
|
56164
|
-
if ((0, import_node_fs57.existsSync)((0,
|
|
56606
|
+
if ((0, import_node_fs57.existsSync)((0, import_node_path56.join)(fromPkg, marker))) return fromPkg;
|
|
56607
|
+
if ((0, import_node_fs57.existsSync)((0, import_node_path56.join)(process.cwd(), marker))) return process.cwd();
|
|
56165
56608
|
return null;
|
|
56166
56609
|
}
|
|
56167
56610
|
registerQueryCommands(program2);
|
|
@@ -56330,7 +56773,7 @@ function directoryBytes(path2) {
|
|
|
56330
56773
|
return 0;
|
|
56331
56774
|
}
|
|
56332
56775
|
for (const entry of entries) {
|
|
56333
|
-
const child2 = (0,
|
|
56776
|
+
const child2 = (0, import_node_path56.join)(path2, entry.name);
|
|
56334
56777
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
56335
56778
|
else {
|
|
56336
56779
|
try {
|
|
@@ -56360,7 +56803,7 @@ function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
|
56360
56803
|
dirBytes,
|
|
56361
56804
|
listStagingDirs: (root) => (0, import_node_fs57.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
56362
56805
|
try {
|
|
56363
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
56806
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path56.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs57.statSync)(p).mtimeMs) };
|
|
56364
56807
|
} catch {
|
|
56365
56808
|
return { name: d.name, mtimeMs: Date.now() };
|
|
56366
56809
|
}
|
|
@@ -56374,7 +56817,7 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
56374
56817
|
return {
|
|
56375
56818
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
56376
56819
|
mtimeMs: (name) => {
|
|
56377
|
-
const p = (0,
|
|
56820
|
+
const p = (0, import_node_path56.join)(stagingRoot, name);
|
|
56378
56821
|
if (!(0, import_node_fs57.existsSync)(p)) return null;
|
|
56379
56822
|
try {
|
|
56380
56823
|
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs57.statSync)(q).mtimeMs);
|