@mutmutco/cli 4.5.3 → 4.5.5
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 +537 -226
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -4534,7 +4534,7 @@ function useColor() {
|
|
|
4534
4534
|
var program = new Command();
|
|
4535
4535
|
|
|
4536
4536
|
// src/command-composition.ts
|
|
4537
|
-
var
|
|
4537
|
+
var import_node_fs50 = require("node:fs");
|
|
4538
4538
|
init_clean_exit();
|
|
4539
4539
|
init_cli_shared();
|
|
4540
4540
|
|
|
@@ -5437,7 +5437,7 @@ function resolveVerbAliasShim(argv) {
|
|
|
5437
5437
|
|
|
5438
5438
|
// src/command-composition.ts
|
|
5439
5439
|
var import_node_os22 = require("node:os");
|
|
5440
|
-
var
|
|
5440
|
+
var import_node_path48 = require("node:path");
|
|
5441
5441
|
|
|
5442
5442
|
// src/board-read.ts
|
|
5443
5443
|
var import_node_fs13 = require("node:fs");
|
|
@@ -15998,10 +15998,10 @@ var rollout_plan_default = {
|
|
|
15998
15998
|
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)."
|
|
15999
15999
|
},
|
|
16000
16000
|
baseline: {
|
|
16001
|
-
version: "4.5.
|
|
16002
|
-
tag: "v4.5.
|
|
16003
|
-
commit: "
|
|
16004
|
-
npm: "@mutmutco/cli@4.5.
|
|
16001
|
+
version: "4.5.5",
|
|
16002
|
+
tag: "v4.5.5",
|
|
16003
|
+
commit: "696fa43d4d49",
|
|
16004
|
+
npm: "@mutmutco/cli@4.5.5"
|
|
16005
16005
|
},
|
|
16006
16006
|
exitCriterion: "fleet-n-of-n",
|
|
16007
16007
|
hubOnlyShortcut: "forbidden",
|
|
@@ -16018,14 +16018,14 @@ var rollout_plan_default = {
|
|
|
16018
16018
|
repo: "mutmutco/mmi-hub",
|
|
16019
16019
|
role: "canary",
|
|
16020
16020
|
schedule: "train",
|
|
16021
|
-
v3Target: "v4.5.
|
|
16021
|
+
v3Target: "v4.5.5"
|
|
16022
16022
|
}
|
|
16023
16023
|
],
|
|
16024
16024
|
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.",
|
|
16025
16025
|
rollback: {
|
|
16026
16026
|
independent: true,
|
|
16027
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
16028
|
-
v3Target: "v4.5.
|
|
16027
|
+
mechanism: "npm dist-tag latest -> 4.5.5 and redeploy the Hub Lambda from tag v4.5.5 (696fa43d4d49); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16028
|
+
v3Target: "v4.5.5 (@mutmutco/cli@4.5.5, tag commit 696fa43d4d49 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16029
16029
|
}
|
|
16030
16030
|
},
|
|
16031
16031
|
{
|
|
@@ -18939,37 +18939,65 @@ function isTerminalVercelState(state) {
|
|
|
18939
18939
|
function str(v) {
|
|
18940
18940
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
18941
18941
|
}
|
|
18942
|
-
|
|
18943
|
-
const
|
|
18944
|
-
|
|
18945
|
-
|
|
18946
|
-
|
|
18947
|
-
const
|
|
18948
|
-
|
|
18949
|
-
const
|
|
18950
|
-
const
|
|
18951
|
-
const state =
|
|
18942
|
+
function matchesTarget(environment, target) {
|
|
18943
|
+
const production = environment.toLowerCase().startsWith("production");
|
|
18944
|
+
return target === "production" ? production : !production;
|
|
18945
|
+
}
|
|
18946
|
+
async function readOne(api, repo, id) {
|
|
18947
|
+
const statuses = await api(`repos/${repo}/deployments/${id}/statuses?per_page=10`);
|
|
18948
|
+
const rows = Array.isArray(statuses) ? statuses : [];
|
|
18949
|
+
const latest = rows.find((s) => str(s.state) !== "inactive") ?? rows[0];
|
|
18950
|
+
const raw = str(latest?.state);
|
|
18951
|
+
const state = raw === void 0 ? "none" : raw === "inactive" ? "success" : raw;
|
|
18952
18952
|
const url = str(latest?.environment_url) ?? str(latest?.target_url);
|
|
18953
|
+
const logUrl = str(latest?.log_url);
|
|
18954
|
+
return { state, ...url ? { url } : {}, ...logUrl ? { logUrl } : {} };
|
|
18955
|
+
}
|
|
18956
|
+
var FAILED = /* @__PURE__ */ new Set(["failure", "error"]);
|
|
18957
|
+
async function readVercelDeployment(api, repo, sha, target, since) {
|
|
18958
|
+
const listed = await api(`repos/${repo}/deployments?sha=${encodeURIComponent(sha)}&per_page=30`);
|
|
18959
|
+
const rows = Array.isArray(listed) ? listed : [];
|
|
18960
|
+
const newest = /* @__PURE__ */ new Map();
|
|
18961
|
+
for (const d of rows) {
|
|
18962
|
+
const env = str(d.environment) ?? "";
|
|
18963
|
+
if (typeof d.id !== "number" || !matchesTarget(env, target) || newest.has(env)) continue;
|
|
18964
|
+
const created = Date.parse(str(d.created_at) ?? "");
|
|
18965
|
+
if (since !== void 0 && Number.isFinite(created) && created < since) continue;
|
|
18966
|
+
newest.set(env, d.id);
|
|
18967
|
+
}
|
|
18968
|
+
if (newest.size === 0) return void 0;
|
|
18969
|
+
const reads = await Promise.all([...newest].map(async ([env, id]) => ({ env, id, ...await readOne(api, repo, id) })));
|
|
18970
|
+
const failed = reads.find((r) => FAILED.has(r.state));
|
|
18971
|
+
const unfinished = reads.find((r) => !isTerminalVercelState(r.state));
|
|
18972
|
+
const pick = failed ?? unfinished ?? reads[0];
|
|
18953
18973
|
return {
|
|
18954
|
-
id:
|
|
18955
|
-
environment:
|
|
18956
|
-
sha
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
...
|
|
18960
|
-
...str(latest?.log_url) ? { logUrl: str(latest?.log_url) } : {}
|
|
18974
|
+
id: pick.id,
|
|
18975
|
+
environment: reads.map((r) => r.env).join(", "),
|
|
18976
|
+
sha,
|
|
18977
|
+
state: pick.state,
|
|
18978
|
+
...reads[0].url ? { url: reads[0].url } : {},
|
|
18979
|
+
...pick.logUrl ? { logUrl: pick.logUrl } : {}
|
|
18961
18980
|
};
|
|
18962
18981
|
}
|
|
18963
18982
|
var VERCEL_WATCH_ATTEMPTS = 120;
|
|
18964
18983
|
var VERCEL_WATCH_DELAY_MS = 1e4;
|
|
18965
|
-
async function
|
|
18984
|
+
async function watchVercelDeployment(api, repo, sha, target, sleep2, onTick, since) {
|
|
18966
18985
|
let last;
|
|
18986
|
+
let readOnce = false;
|
|
18987
|
+
let lastError;
|
|
18967
18988
|
for (let attempt = 0; attempt < VERCEL_WATCH_ATTEMPTS; attempt++) {
|
|
18968
18989
|
if (attempt > 0) await sleep2(VERCEL_WATCH_DELAY_MS);
|
|
18969
|
-
|
|
18990
|
+
try {
|
|
18991
|
+
last = await readVercelDeployment(api, repo, sha, target, since);
|
|
18992
|
+
readOnce = true;
|
|
18993
|
+
} catch (e) {
|
|
18994
|
+
lastError = e;
|
|
18995
|
+
continue;
|
|
18996
|
+
}
|
|
18970
18997
|
onTick?.(attempt, last);
|
|
18971
18998
|
if (last && isTerminalVercelState(last.state)) return last;
|
|
18972
18999
|
}
|
|
19000
|
+
if (!readOnce) throw lastError;
|
|
18973
19001
|
return last;
|
|
18974
19002
|
}
|
|
18975
19003
|
async function probeVercelUrl(url, fetchImpl = fetch) {
|
|
@@ -19275,38 +19303,14 @@ function staleServedProcess(probe) {
|
|
|
19275
19303
|
function servedChannelRow(product, conclusion, evidence) {
|
|
19276
19304
|
return { workflow: `served-channel(${product})`, runUrlNote: evidence, conclusion };
|
|
19277
19305
|
}
|
|
19278
|
-
var JERV_GATEWAY_REMEDY = "from the Jerv-Hub checkout, re-run the Gateway deploy on its current release tag: `bash scripts/jerv-gateway-release-deploy.sh --tag <jerv-hub tag>`";
|
|
19279
|
-
var SERVED_CHANNELS = {
|
|
19280
|
-
"mutmutco/mm-strategy": {
|
|
19281
|
-
product: "mm-strategy",
|
|
19282
|
-
url: "https://strategy.mutmut.work/release/version",
|
|
19283
|
-
mover: "the MM-Strategy container deploy (the running server's own version is the pointer)",
|
|
19284
|
-
remedy: "`mmi-cli devops runtime tenant redeploy mutmutco/MM-Strategy main`"
|
|
19285
|
-
},
|
|
19286
|
-
"mutmutco/jerv-hub": {
|
|
19287
|
-
product: "jerv",
|
|
19288
|
-
url: "https://get.jerv.ai/jerv/release/version",
|
|
19289
|
-
mover: "the Jerv Gateway deploy (`jerv-gate fetch-release --product jerv`)",
|
|
19290
|
-
remedy: JERV_GATEWAY_REMEDY
|
|
19291
|
-
},
|
|
19292
|
-
"mutmutco/jerv-jervcode": {
|
|
19293
|
-
product: "jervcode",
|
|
19294
|
-
url: "https://get.jerv.ai/jervcode/release/version",
|
|
19295
|
-
// Jerv-JervCode#6456: JervCode's own publish.yml `serve` job moves and proves this pointer.
|
|
19296
|
-
mover: "JervCode's own release (publish.yml `serve` job: `jerv-gate fetch-release --product jervcode` on jerv-central)",
|
|
19297
|
-
remedy: "re-run the `serve` job of the JervCode release's publish.yml run (`gh run rerun <run-id> --job serve --repo mutmutco/Jerv-JervCode`), or `gh workflow run publish.yml --repo mutmutco/Jerv-JervCode -f tag=<tag>`"
|
|
19298
|
-
}
|
|
19299
|
-
};
|
|
19300
19306
|
function servedChannelFor(repo, installer) {
|
|
19301
|
-
|
|
19302
|
-
if (!installer) return known;
|
|
19307
|
+
if (!installer) return void 0;
|
|
19303
19308
|
if (isHubControlRepo(repo)) return void 0;
|
|
19304
|
-
const url = installerVersionUrl(installer);
|
|
19305
19309
|
return {
|
|
19306
19310
|
product: installer.product,
|
|
19307
|
-
url,
|
|
19308
|
-
mover:
|
|
19309
|
-
remedy:
|
|
19311
|
+
url: installerVersionUrl(installer),
|
|
19312
|
+
mover: `this repo's own release (it serves /${installer.product} on its installer host)`,
|
|
19313
|
+
remedy: `re-run this repo's own served-channel publish for ${installer.product}, then \`mmi-cli devops release --resume\``
|
|
19310
19314
|
};
|
|
19311
19315
|
}
|
|
19312
19316
|
var SERVED_CHANNEL_PROBE_ATTEMPTS = 3;
|
|
@@ -19444,34 +19448,28 @@ function prodReleaseTargets(model) {
|
|
|
19444
19448
|
function trainObservesShipment(model) {
|
|
19445
19449
|
return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0 || model === "vercel";
|
|
19446
19450
|
}
|
|
19447
|
-
async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
|
|
19448
|
-
const
|
|
19449
|
-
|
|
19451
|
+
async function vercelDeployDispatch(deps, ctx, ref, watch, headSha, since) {
|
|
19452
|
+
const target = ref === "main" ? "production" : "preview";
|
|
19453
|
+
const plane = target === "production" ? "Production" : `${ref} (non-Production)`;
|
|
19454
|
+
const note = `no dispatch: Vercel deploys ${ref} on push (${plane}) \u2014 observed via the Vercel GitHub app Deployment`;
|
|
19450
19455
|
if (!headSha) return { note, deployStatus: "pending" };
|
|
19451
|
-
const
|
|
19452
|
-
const row = (d, conclusion) => ({
|
|
19453
|
-
workflow: "vercel",
|
|
19454
|
-
conclusion,
|
|
19455
|
-
...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" },
|
|
19456
|
-
event: "push"
|
|
19457
|
-
});
|
|
19458
|
-
const verdict = (d) => d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
|
|
19456
|
+
const after = target === "preview" ? rcWindow(since) : void 0;
|
|
19459
19457
|
let read;
|
|
19460
19458
|
try {
|
|
19461
19459
|
if (!watch) {
|
|
19462
19460
|
deps.warn?.("Release: Checking Vercel deployment status");
|
|
19463
|
-
read = await readVercelDeployment(
|
|
19461
|
+
read = await readVercelDeployment(vercelApi(deps), ctx.repo, headSha, target, after);
|
|
19464
19462
|
} else {
|
|
19465
|
-
deps.warn?.(
|
|
19466
|
-
read = await
|
|
19463
|
+
deps.warn?.(`Release: Waiting for Vercel to deploy ${ref}`);
|
|
19464
|
+
read = await watchVercelDeployment(vercelApi(deps), ctx.repo, headSha, target, resolveSleep(deps), (attempt, d) => {
|
|
19467
19465
|
if (attempt > 0 && attempt % 6 === 0) deps.warn?.(`Vercel deployment ${d ? d.state : "not posted yet"} \u2014 ${attempt / 6}m elapsed`);
|
|
19468
|
-
});
|
|
19466
|
+
}, after);
|
|
19469
19467
|
}
|
|
19470
19468
|
} catch (e) {
|
|
19471
|
-
return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [
|
|
19469
|
+
return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [vercelRow(void 0, "failure")], deployStatus: "failure" };
|
|
19472
19470
|
}
|
|
19473
|
-
let deployStatus =
|
|
19474
|
-
let detail = read ? `Vercel
|
|
19471
|
+
let deployStatus = vercelVerdict(read);
|
|
19472
|
+
let detail = read ? `Vercel ${plane} deployment ${read.state}${read.url ? ` at ${read.url}` : ""}` : "no Vercel Deployment posted for this SHA (is the Vercel GitHub app connected?)";
|
|
19475
19473
|
if (watch && deployStatus === "success" && read?.url) {
|
|
19476
19474
|
const probe = await (deps.probeUrl ?? probeVercelUrl)(read.url);
|
|
19477
19475
|
if (probe.ok) detail = `${detail}; URL answered HTTP ${probe.status}`;
|
|
@@ -19480,7 +19478,27 @@ async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
|
|
|
19480
19478
|
detail = `${detail}; URL probe FAILED (${probe.error ?? `HTTP ${probe.status}`})`;
|
|
19481
19479
|
}
|
|
19482
19480
|
}
|
|
19483
|
-
return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [
|
|
19481
|
+
return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [vercelRow(read, deployStatus)], deployStatus };
|
|
19482
|
+
}
|
|
19483
|
+
function vercelApi(deps) {
|
|
19484
|
+
return async (path2) => JSON.parse(await deps.run("gh", ["api", path2]));
|
|
19485
|
+
}
|
|
19486
|
+
function vercelVerdict(d) {
|
|
19487
|
+
return d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
|
|
19488
|
+
}
|
|
19489
|
+
function vercelRow(d, conclusion) {
|
|
19490
|
+
return { workflow: "vercel", conclusion, ...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" }, event: "push" };
|
|
19491
|
+
}
|
|
19492
|
+
async function listVercelDeployRows(deps, repo, sha, ref = "main", since) {
|
|
19493
|
+
const target = ref === "main" ? "production" : "preview";
|
|
19494
|
+
const read = await readVercelDeployment(vercelApi(deps), repo, sha, target, target === "preview" ? rcWindow(since) : void 0);
|
|
19495
|
+
if (!read) return [];
|
|
19496
|
+
const verdict = vercelVerdict(read);
|
|
19497
|
+
const probe = verdict === "success" && read.url ? await (deps.probeUrl ?? probeVercelUrl)(read.url) : void 0;
|
|
19498
|
+
return [vercelRow(read, probe && !probe.ok ? "failure" : verdict)];
|
|
19499
|
+
}
|
|
19500
|
+
function rcWindow(since) {
|
|
19501
|
+
return since === void 0 ? void 0 : since - 6e4;
|
|
19484
19502
|
}
|
|
19485
19503
|
function shipLegsGreen(deploy, publish) {
|
|
19486
19504
|
const green = (leg) => leg?.superseded !== true && (leg?.state === "complete" || leg?.state === "skipped");
|
|
@@ -19662,7 +19680,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
19662
19680
|
deployStatus: aggregateWorkflowRuns(workflowRuns)
|
|
19663
19681
|
};
|
|
19664
19682
|
}
|
|
19665
|
-
if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha);
|
|
19683
|
+
if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha, autoRunSince);
|
|
19666
19684
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
19667
19685
|
}
|
|
19668
19686
|
async function readEdgeDomains(deps, slug) {
|
|
@@ -21931,6 +21949,16 @@ function deployPhaseInput(model, dispatch, opts = {}) {
|
|
|
21931
21949
|
...runMetadata({ runId: dispatch.runId, runUrl: dispatch.runUrl, workflow: "tenant-deploy.yml", event: "workflow_dispatch" }, runRepo, "dispatch"),
|
|
21932
21950
|
note: dispatch.note
|
|
21933
21951
|
};
|
|
21952
|
+
case "vercel": {
|
|
21953
|
+
const row = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "vercel");
|
|
21954
|
+
return {
|
|
21955
|
+
state: runRowState(row, dispatch.deployStatus),
|
|
21956
|
+
...sha ? { sha } : {},
|
|
21957
|
+
workflow: "vercel",
|
|
21958
|
+
...row?.runUrl ? { runUrl: row.runUrl } : {},
|
|
21959
|
+
note: dispatch.note
|
|
21960
|
+
};
|
|
21961
|
+
}
|
|
21934
21962
|
default:
|
|
21935
21963
|
return {
|
|
21936
21964
|
state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
|
|
@@ -22012,6 +22040,13 @@ function phaseInputsFromRunRows(model, rows, tagSha, opts = {}) {
|
|
|
22012
22040
|
note: `${publishRun.workflow} run on the release SHA`
|
|
22013
22041
|
} : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
|
|
22014
22042
|
};
|
|
22043
|
+
case "vercel": {
|
|
22044
|
+
const vercelRow2 = rows.find((r) => r.workflow === "vercel");
|
|
22045
|
+
return {
|
|
22046
|
+
deploy: vercelRow2 ? { state: runRowState(vercelRow2, "pending"), sha: tagSha, workflow: "vercel", ...vercelRow2.runUrl ? { runUrl: vercelRow2.runUrl } : {}, note: "Vercel GitHub app Deployment on the release SHA" } : { state: "pending", sha: tagSha, note: "no Vercel Deployment posted for the release SHA yet (is the Vercel GitHub app connected?)" },
|
|
22047
|
+
publish: { state: "skipped", sha: tagSha, note: "vercel has no publish leg" }
|
|
22048
|
+
};
|
|
22049
|
+
}
|
|
22015
22050
|
default:
|
|
22016
22051
|
return {
|
|
22017
22052
|
deploy: { state: "pending", sha: tagSha, note: `${model} deploys outside the release SHA's run set \u2014 historical outcome uncorrelatable` },
|
|
@@ -22218,6 +22253,17 @@ function hotfixRetryDeployShape(ledger) {
|
|
|
22218
22253
|
if (!open2.every((phase) => RETRY_DEPLOY_LEGS.has(phase))) return false;
|
|
22219
22254
|
return open2.some((phase) => ledger.phases[phase].state === "failed");
|
|
22220
22255
|
}
|
|
22256
|
+
function rcandRetryDeployShape(ledger) {
|
|
22257
|
+
if (ledger.lane !== "rcand") return false;
|
|
22258
|
+
if (ledger.phases.promotion.state !== "complete") return false;
|
|
22259
|
+
const deploy = ledger.phases.deploy;
|
|
22260
|
+
if (deploy.state !== "failed" || deploy.superseded === true || deploy.workflow !== "tenant-deploy.yml") return false;
|
|
22261
|
+
return RELEASE_LEDGER_PHASES.every((phase) => {
|
|
22262
|
+
if (phase === "deploy") return true;
|
|
22263
|
+
const state = ledger.phases[phase].state;
|
|
22264
|
+
return state === "complete" || state === "skipped";
|
|
22265
|
+
});
|
|
22266
|
+
}
|
|
22221
22267
|
function ledgerFailedRemedy(ledger) {
|
|
22222
22268
|
const derived = deriveReleasePhaseStatus(ledger);
|
|
22223
22269
|
const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
|
|
@@ -22226,6 +22272,9 @@ function ledgerFailedRemedy(ledger) {
|
|
|
22226
22272
|
const failed = openLegs.filter((l) => l.state === "failed").map((l) => l.phase);
|
|
22227
22273
|
return `the published ${ledger.tag} hotfix can retry the failed ${joinLegNames(failed)} of the same tag: \`mmi-cli devops hotfix release ${ledger.tag} --retry-deploy\` (the deploy helper is idempotent; alignment re-attempts the fold-port PR) (#6985)${tails}`;
|
|
22228
22274
|
}
|
|
22275
|
+
if (rcandRetryDeployShape(ledger)) {
|
|
22276
|
+
return `fix the rc deploy failure, then retry the failed deploy of the promoted ${ledger.tag} candidate (same tag and SHA, never re-cut): \`mmi-cli devops rcand --retry-deploy --watch\` (#7053)${tails}`;
|
|
22277
|
+
}
|
|
22229
22278
|
const deployOnlyDeadlock = openLegs.length > 0 && openLegs.every((l) => l.phase === "deploy" && supersedeableDeployLegShape(l));
|
|
22230
22279
|
const supersession = deployOnlyDeadlock ? `; when the deploy failure is content-caused on the already-published ${ledger.tag} (the immutable tag can never deploy green), the sanctioned exit is \`mmi-cli devops release --supersede-deploy --apply\` after explicit master approval \u2014 the next patch train carries the cause fix (#6553)` : "";
|
|
22231
22280
|
return `diagnose the failed leg, then ${ledgerLaneResumeCommand(ledger)} once its cause is fixed (an abort only with fresh approval)${supersession}${tails}`;
|
|
@@ -22275,6 +22324,10 @@ async function healStaleAlignmentLeg(deps, cwd, expected) {
|
|
|
22275
22324
|
return prior.tag;
|
|
22276
22325
|
});
|
|
22277
22326
|
}
|
|
22327
|
+
function rcPromotedAt(ledger) {
|
|
22328
|
+
const at = Date.parse(ledger.phases.promotion.updatedAt);
|
|
22329
|
+
return Number.isFinite(at) ? at : void 0;
|
|
22330
|
+
}
|
|
22278
22331
|
async function healUnfoldedRunLegs(deps, cwd, model) {
|
|
22279
22332
|
const path2 = releaseLedgerPath(cwd);
|
|
22280
22333
|
let prior;
|
|
@@ -22306,15 +22359,16 @@ async function healUnfoldedRunLegs(deps, cwd, model) {
|
|
|
22306
22359
|
}
|
|
22307
22360
|
const targets = prodReleaseTargets(model);
|
|
22308
22361
|
const runless = legs.filter((phase) => openLeg(current.phases[phase]) && current.phases[phase].runId == null);
|
|
22309
|
-
if (runless.length > 0 && targets.length > 0) {
|
|
22362
|
+
if (runless.length > 0 && (targets.length > 0 || model === "vercel")) {
|
|
22310
22363
|
let rows;
|
|
22311
22364
|
try {
|
|
22312
|
-
rows = await listOwnWorkflowRuns(deps, releaseRunRepoFor(model, prior.repo), targets, prior.tagSha);
|
|
22365
|
+
rows = model === "vercel" ? await listVercelDeployRows(deps, prior.repo, prior.tagSha, prior.lane === "rcand" ? "rc" : "main", rcPromotedAt(prior)) : await listOwnWorkflowRuns(deps, releaseRunRepoFor(model, prior.repo), targets, prior.tagSha);
|
|
22313
22366
|
} catch {
|
|
22314
22367
|
rows = [];
|
|
22315
22368
|
}
|
|
22316
22369
|
const inputs = phaseInputsFromRunRows(model, rows, prior.tagSha, { repo: prior.repo });
|
|
22317
|
-
const
|
|
22370
|
+
const correlated = (entry) => entry.runId != null || entry.workflow === "vercel";
|
|
22371
|
+
const foldable = runless.filter((phase) => correlated(inputs[phase]) && inputs[phase].state !== "pending");
|
|
22318
22372
|
if (foldable.length > 0) {
|
|
22319
22373
|
const written = await withReleaseLedger(path2, anchors, (ledger) => {
|
|
22320
22374
|
if (!ledger) return { value: [] };
|
|
@@ -23212,6 +23266,13 @@ async function runTrainDoctor(input) {
|
|
|
23212
23266
|
unverified(`required status checks on ${stage}`, e);
|
|
23213
23267
|
}
|
|
23214
23268
|
}
|
|
23269
|
+
if (meta?.deployModel === "vercel") {
|
|
23270
|
+
const tip = await git3(["rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"]).catch(() => "");
|
|
23271
|
+
const rows = tip ? await listVercelDeployRows(train, repo, tip).catch(() => void 0) : void 0;
|
|
23272
|
+
if (rows && !rows.length) {
|
|
23273
|
+
add({ code: "vercel-unobserved", severity: "warning", source: "origin", title: `no Vercel Production Deployment on origin/main@${tip.slice(0, 12)} \u2014 the Vercel GitHub app or the git-connected Vercel project may be missing`, remedy: `connect ${repo} to its Vercel project (production branch main) and enable the Vercel GitHub app for the repo; the train reads the app's Deployments, never a Vercel token` });
|
|
23274
|
+
}
|
|
23275
|
+
}
|
|
23215
23276
|
let ledgerTag;
|
|
23216
23277
|
let ledgerPath;
|
|
23217
23278
|
try {
|
|
@@ -24176,10 +24237,15 @@ async function recoverFailedDispatchPhase(deps, ledger, anchors, phase, dispatch
|
|
|
24176
24237
|
note: notes.length > 0 ? `${dispatch.note}; ${notes.join("; ")}` : dispatch.note
|
|
24177
24238
|
};
|
|
24178
24239
|
}
|
|
24240
|
+
var RCAND_RETRY_DEPLOY_COMMAND = "mmi-cli devops rcand --retry-deploy --watch";
|
|
24241
|
+
function rcPushedAt(ledger) {
|
|
24242
|
+
const at = Date.parse(ledger?.phases.promotion.updatedAt ?? "");
|
|
24243
|
+
return Number.isFinite(at) ? at : void 0;
|
|
24244
|
+
}
|
|
24179
24245
|
async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts = {}) {
|
|
24180
24246
|
const sha = anchors.tagSha;
|
|
24181
24247
|
const landed = "the immutable rc tag, the green wall, origin/rc, and the rc deploy dispatch";
|
|
24182
|
-
const
|
|
24248
|
+
const recovered = await recoverFailedDispatchPhase(
|
|
24183
24249
|
deps,
|
|
24184
24250
|
ledger,
|
|
24185
24251
|
anchors,
|
|
@@ -24192,6 +24258,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
24192
24258
|
workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "rc deploy workflow"
|
|
24193
24259
|
}
|
|
24194
24260
|
);
|
|
24261
|
+
const dispatch = recovered.deployStatus === "failure" && !recovered.note.includes(RCAND_RETRY_DEPLOY_COMMAND) ? { ...recovered, note: `once the cause is fixed, retry this rc deploy with \`${RCAND_RETRY_DEPLOY_COMMAND}\` (same tag and SHA, never re-cut): ${recovered.note}` } : recovered;
|
|
24195
24262
|
const unverifiable = !isCentralDispatchModel(deployModel) && dispatch.deployStatus === "pending" && dispatch.runId == null && !dispatch.workflowRuns?.length;
|
|
24196
24263
|
const deployEntry = unverifiable ? { state: "skipped", sha, note: `${dispatch.note} \u2014 no run to verify on rc; recorded skipped, never pending (#6068)` } : deployPhaseInput(deployModel, dispatch, { releaseSha: sha });
|
|
24197
24264
|
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployEntry), { ...opts, landed: "the immutable rc tag, the green wall, and origin/rc" });
|
|
@@ -24200,7 +24267,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
24200
24267
|
return dispatch;
|
|
24201
24268
|
}
|
|
24202
24269
|
function releaseAnnounceDeferral(model, summaryFile) {
|
|
24203
|
-
return prodReleaseTargets(model).length > 0 ? `\`mmi-cli devops release --resume --watch${summaryFile ? ` --announce-summary-file ${summaryFile}` : ""}\` announces it once they are green` : "a central deploy is proven only under --watch, so this release stays unannounced";
|
|
24270
|
+
return prodReleaseTargets(model).length > 0 || model === "vercel" ? `\`mmi-cli devops release --resume --watch${summaryFile ? ` --announce-summary-file ${summaryFile}` : ""}\` announces it once they are green` : "a central deploy is proven only under --watch, so this release stays unannounced";
|
|
24204
24271
|
}
|
|
24205
24272
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
|
|
24206
24273
|
const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
|
|
@@ -24487,7 +24554,8 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
|
24487
24554
|
const terminal = deployLeg.state === "complete" || deployLeg.state === "skipped";
|
|
24488
24555
|
const legStatus = deployLeg.state === "failed" ? "failure" : terminal ? "success" : "pending";
|
|
24489
24556
|
const hasDispatchEvidence = deployLeg.runId != null || deployLeg.workflow != null || deployLeg.updatedAt !== "never";
|
|
24490
|
-
const
|
|
24557
|
+
const vercelReread = deployModel === "vercel" && !terminal;
|
|
24558
|
+
const dispatch02 = (terminal || hasDispatchEvidence) && !vercelReread ? {
|
|
24491
24559
|
note: deployLeg.note ?? deployLeg.error ?? `persisted rc deploy leg is ${deployLeg.state}`,
|
|
24492
24560
|
deployStatus: legStatus,
|
|
24493
24561
|
...deployLeg.runId != null ? { runId: deployLeg.runId } : {},
|
|
@@ -24499,7 +24567,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
|
24499
24567
|
...deployLeg.event ? { event: deployLeg.event } : {},
|
|
24500
24568
|
conclusion: legStatus
|
|
24501
24569
|
}] } : {}
|
|
24502
|
-
} : await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, (deps.now ?? Date.now)(), tagSha);
|
|
24570
|
+
} : await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, vercelReread ? rcPushedAt(persisted) : (deps.now ?? Date.now)(), tagSha, "report");
|
|
24503
24571
|
const dispatch2 = terminal ? dispatch02 : await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch02, { strict: true });
|
|
24504
24572
|
const ledgerReport2 = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24505
24573
|
return {
|
|
@@ -24565,10 +24633,11 @@ ${spent.note}`);
|
|
|
24565
24633
|
}
|
|
24566
24634
|
await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict, landed: "the immutable rc tag on origin at the resumed SHA" });
|
|
24567
24635
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
24636
|
+
deps.warn?.("Release: Promoting the verified release candidate");
|
|
24568
24637
|
await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/rc`]);
|
|
24569
24638
|
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/rc (tag preserved, never re-cut)` }), { strict, landed: "the immutable rc tag and the re-proven required-check wall" });
|
|
24570
24639
|
await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "skipped", sha: tagSha, note: "rcand mints no GitHub Release \u2014 the rc tag is the candidate" }), { strict, landed: "the immutable rc tag, the green wall, and origin/rc" });
|
|
24571
|
-
const dispatch0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha);
|
|
24640
|
+
const dispatch0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha, "report");
|
|
24572
24641
|
const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch0, { strict });
|
|
24573
24642
|
const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24574
24643
|
return {
|
|
@@ -24590,6 +24659,64 @@ ${spent.note}`);
|
|
|
24590
24659
|
note: `reused immutable ${tag}; pushed its exact SHA to rc and dispatched the rc deploy`
|
|
24591
24660
|
};
|
|
24592
24661
|
}
|
|
24662
|
+
async function runRcandRetryDeploy(deps, options = {}) {
|
|
24663
|
+
return withTrainWriter(deps, "rcand --retry-deploy", () => runRcandRetryDeployLocked(deps, options));
|
|
24664
|
+
}
|
|
24665
|
+
async function runRcandRetryDeployLocked(deps, options) {
|
|
24666
|
+
const label = "rcand --retry-deploy";
|
|
24667
|
+
const ctx = await buildTrainApplyContext(deps);
|
|
24668
|
+
await requireCleanTree(deps);
|
|
24669
|
+
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
24670
|
+
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
24671
|
+
const deployModel = await preflight(deps, ctx, "rc", meta);
|
|
24672
|
+
if (!isCentralDispatchModel(deployModel)) {
|
|
24673
|
+
throw new Error(`${label}: the ${deployModel} rc deploy is the repo's own push-triggered workflow, not a dispatch \u2014 rerun that failed run (gh run rerun <run-id> --failed), then mmi-cli devops rcand --resume re-verifies it; nothing was written`);
|
|
24674
|
+
}
|
|
24675
|
+
const candidate = await activeRcandLedgerCandidate(deps, process.cwd());
|
|
24676
|
+
if (!candidate) {
|
|
24677
|
+
throw new Error(`${label}: no active rcand ledger whose rc tag is live on origin at its recorded SHA \u2014 there is no promoted candidate deploy to retry; nothing was written`);
|
|
24678
|
+
}
|
|
24679
|
+
const { tag, sha: tagSha } = candidate;
|
|
24680
|
+
const ledger = openReleaseLedgerWriter(process.cwd());
|
|
24681
|
+
const anchors = { repo: ctx.repo, tag, tagSha };
|
|
24682
|
+
let persisted;
|
|
24683
|
+
try {
|
|
24684
|
+
persisted = (await reverifyPersistedRunPhases(deps, ledger.path, anchors, { sha: tagSha, repo: ctx.repo })).ledger;
|
|
24685
|
+
} catch (e) {
|
|
24686
|
+
throw new Error(`${label}: ${e instanceof Error ? e.message : String(e)}
|
|
24687
|
+
Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
24688
|
+
}
|
|
24689
|
+
if (!persisted || !rcandRetryDeployShape(persisted)) {
|
|
24690
|
+
const open2 = persisted ? deriveReleasePhaseStatus(persisted).legs.filter((l) => l.state === "pending" || l.state === "failed") : [];
|
|
24691
|
+
throw new Error(
|
|
24692
|
+
`${label}: the ${tag} ledger has no failed rc deploy to retry on a promoted candidate (${open2.map((l) => `${l.phase}=${l.state}`).join(", ") || "phases-green"}) \u2014 finish an incomplete candidate with mmi-cli devops rcand --resume; nothing was written`
|
|
24693
|
+
);
|
|
24694
|
+
}
|
|
24695
|
+
if (!await probeAncestor(deps, tagSha, "origin/rc", `${label}: origin/rc ancestry`)) {
|
|
24696
|
+
throw new Error(`${label}: origin/rc does not contain ${tag} (${tagSha.slice(0, 12)}) \u2014 the candidate is not promoted; nothing was written`);
|
|
24697
|
+
}
|
|
24698
|
+
const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, options.watch ?? false, (deps.now ?? Date.now)(), tagSha, "report");
|
|
24699
|
+
const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, d0, { strict: true });
|
|
24700
|
+
const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24701
|
+
return {
|
|
24702
|
+
...ctx,
|
|
24703
|
+
command: "rcand-retry-deploy",
|
|
24704
|
+
stage: "rc",
|
|
24705
|
+
ref: "rc",
|
|
24706
|
+
tag,
|
|
24707
|
+
tagSha,
|
|
24708
|
+
deployModel,
|
|
24709
|
+
promoted: true,
|
|
24710
|
+
checks: persisted.phases.ordinaryCi.note ?? "required-check wall was completed before origin/rc promotion",
|
|
24711
|
+
dispatch: dispatch.note,
|
|
24712
|
+
runId: dispatch.runId,
|
|
24713
|
+
runUrl: dispatch.runUrl,
|
|
24714
|
+
workflowRuns: dispatch.workflowRuns,
|
|
24715
|
+
deployStatus: dispatch.deployStatus,
|
|
24716
|
+
...ledgerReport ? { ledger: ledgerReport } : {},
|
|
24717
|
+
note: `re-dispatched the failed rc deploy of immutable ${tag} at ${tagSha.slice(0, 12)}; nothing re-cut, re-tagged or re-pushed`
|
|
24718
|
+
};
|
|
24719
|
+
}
|
|
24593
24720
|
async function runReleaseResumePipeline(deps, options, prepared) {
|
|
24594
24721
|
const watch = options.watch ?? false;
|
|
24595
24722
|
const { ctx, meta, branchHints, directTrack, deployModel } = prepared;
|
|
@@ -24653,8 +24780,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24653
24780
|
deps.warn?.("Release: Checking deployment and publishing progress");
|
|
24654
24781
|
const historicalTargets = prodReleaseTargets(deployModel);
|
|
24655
24782
|
const historicalRepo = releaseRunRepoFor(deployModel, ctx.repo);
|
|
24656
|
-
|
|
24657
|
-
let
|
|
24783
|
+
const vercel = deployModel === "vercel";
|
|
24784
|
+
let historicalRows = vercel ? await listVercelDeployRows(deps, ctx.repo, tagSha).catch(() => []) : historicalTargets.length ? await listOwnWorkflowRuns(deps, historicalRepo, historicalTargets, tagSha) : [];
|
|
24785
|
+
let historicalDeploy = historicalTargets.length || vercel && historicalRows.length ? aggregateWorkflowRuns(historicalRows) : "pending";
|
|
24658
24786
|
let historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}`;
|
|
24659
24787
|
if (historicalDeploy === "failure") {
|
|
24660
24788
|
const recovered = await recoverFailedDispatchPhase(
|
|
@@ -24666,7 +24794,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24666
24794
|
{ repo: historicalRepo, sha: tagSha, watched: false, workflow: "historical release workflow" }
|
|
24667
24795
|
);
|
|
24668
24796
|
historicalRows = recovered.workflowRuns ?? historicalRows;
|
|
24669
|
-
historicalDeploy = historicalTargets.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
|
|
24797
|
+
historicalDeploy = historicalTargets.length || vercel && historicalRows.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
|
|
24670
24798
|
historicalNote = recovered.note;
|
|
24671
24799
|
}
|
|
24672
24800
|
const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
|
|
@@ -24704,7 +24832,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24704
24832
|
Object.assign(dispatch2, shippedDispatch);
|
|
24705
24833
|
if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
|
|
24706
24834
|
const settledBySupersede = persisted?.phases.deploy.superseded === true || persisted?.phases.publish.superseded === true;
|
|
24707
|
-
const announceNote2 = persisted && !shippedBefore && !settledBySupersede && (prodReleaseTargets(deployModel).length > 0 || companion !== null) ? await announceShippedRelease(
|
|
24835
|
+
const announceNote2 = persisted && !shippedBefore && !settledBySupersede && (prodReleaseTargets(deployModel).length > 0 || deployModel === "vercel" || companion !== null) ? await announceShippedRelease(
|
|
24708
24836
|
deps,
|
|
24709
24837
|
{ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
|
|
24710
24838
|
deployModel,
|
|
@@ -25152,6 +25280,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
25152
25280
|
if (!start.ok) throw new Error(`rcand refused before the rc tag was pushed: the durable phase ledger could not be started at ${ledger.path} (${start.error}) - nothing was tagged or pushed (#5987/#6068)`);
|
|
25153
25281
|
ledgerAnchors2 = anchors2;
|
|
25154
25282
|
}
|
|
25283
|
+
deps.warn?.("Release: Publishing the release candidate tag");
|
|
25155
25284
|
tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
25156
25285
|
} catch (e) {
|
|
25157
25286
|
if (ledger && ledgerAnchors2) {
|
|
@@ -25188,6 +25317,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
25188
25317
|
}
|
|
25189
25318
|
await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: rcSha, note: checks2 }), { landed: "the immutable rc tag pushed at " + rcSha.slice(0, 12) });
|
|
25190
25319
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
25320
|
+
deps.warn?.("Release: Promoting the verified release candidate");
|
|
25191
25321
|
try {
|
|
25192
25322
|
await runGitPush(deps, ["push", "origin", "rc"]);
|
|
25193
25323
|
} catch (e) {
|
|
@@ -25196,7 +25326,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
25196
25326
|
}
|
|
25197
25327
|
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: rcSha, note: `immutable ${tag2} pushed at ${rcSha.slice(0, 12)}; origin/rc fast-forwarded to it` }), { landed: "the immutable rc tag and the green required-check wall" });
|
|
25198
25328
|
await recordPhase(ledger, deps, anchors, "githubRelease", phaseEntry({ state: "skipped", sha: rcSha, note: "rcand mints no GitHub Release \u2014 the rc tag is the candidate" }), { landed: "the immutable rc tag, the green wall, and origin/rc" });
|
|
25199
|
-
const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha);
|
|
25329
|
+
const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report");
|
|
25200
25330
|
const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
|
|
25201
25331
|
const rcLedgerReport = releaseLedgerReport(await ledger?.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
25202
25332
|
return { ...ctx, command, stage: "rc", ref: "rc", tag: tag2, bumpIntent, deployModel: deployModel2, promoted: true, checks: checks2, resumeNote, dispatch: d2.note, runId: d2.runId, runUrl: d2.runUrl, workflowRuns: d2.workflowRuns, deployStatus: d2.deployStatus, ...rcLedgerReport ? { ledger: rcLedgerReport } : {} };
|
|
@@ -25486,7 +25616,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
25486
25616
|
if (directTrack) {
|
|
25487
25617
|
throw new Error("direct-track repos release straight to main (no rc); run `mmi-cli devops release --apply` from development instead of rcand");
|
|
25488
25618
|
}
|
|
25489
|
-
return runTrainApplyPipeline("rcand", pipelineInput);
|
|
25619
|
+
return withCheckoutRestoredAfterRelease(deps, await runTrainApplyPipeline("rcand", pipelineInput), "development");
|
|
25490
25620
|
}
|
|
25491
25621
|
if (directTrack) {
|
|
25492
25622
|
return withCheckoutRestoredAfterRelease(
|
|
@@ -38218,8 +38348,9 @@ function registerStageCommands(program3) {
|
|
|
38218
38348
|
const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
38219
38349
|
const repo = Array.isArray(project2.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : await repoSlug();
|
|
38220
38350
|
try {
|
|
38351
|
+
const sha = await gitOut(["rev-parse", `origin/${branch}`]).catch(() => gitOut(["rev-parse", "HEAD"]));
|
|
38221
38352
|
const gh = defaultGitHubClient();
|
|
38222
|
-
const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo,
|
|
38353
|
+
const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo, sha, "preview");
|
|
38223
38354
|
return { branch, ...deployment ? { deployment } : {} };
|
|
38224
38355
|
} catch (e) {
|
|
38225
38356
|
return { branch, error: e.message };
|
|
@@ -38660,7 +38791,7 @@ function renderVerifySecrets(body) {
|
|
|
38660
38791
|
|
|
38661
38792
|
// src/command-register-collaboration.ts
|
|
38662
38793
|
var import_node_child_process15 = require("node:child_process");
|
|
38663
|
-
var
|
|
38794
|
+
var import_node_fs43 = require("node:fs");
|
|
38664
38795
|
var import_promises7 = require("node:fs/promises");
|
|
38665
38796
|
init_clean_exit();
|
|
38666
38797
|
init_cli_shared();
|
|
@@ -38684,7 +38815,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
38684
38815
|
}
|
|
38685
38816
|
|
|
38686
38817
|
// src/command-register-collaboration.ts
|
|
38687
|
-
var
|
|
38818
|
+
var import_node_path41 = require("node:path");
|
|
38688
38819
|
|
|
38689
38820
|
// src/attach-to-project.ts
|
|
38690
38821
|
async function attachViaHubApp(cfg, contentNodeId) {
|
|
@@ -39768,9 +39899,9 @@ function changedFilesSince(base, cwd) {
|
|
|
39768
39899
|
const raw = git2(["diff", "--name-status", "-M", base, "--"], cwd).trim();
|
|
39769
39900
|
const changed = raw ? raw.split("\n").map((line) => {
|
|
39770
39901
|
const parts = line.split(" ");
|
|
39771
|
-
const
|
|
39772
|
-
const row = { status: parts[0][0], path:
|
|
39773
|
-
if (parts.length > 2) row.from =
|
|
39902
|
+
const norm2 = (p) => p.replace(/\\/g, "/");
|
|
39903
|
+
const row = { status: parts[0][0], path: norm2(parts[parts.length - 1]) };
|
|
39904
|
+
if (parts.length > 2) row.from = norm2(parts[1]);
|
|
39774
39905
|
return row;
|
|
39775
39906
|
}) : [];
|
|
39776
39907
|
const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
|
|
@@ -40833,6 +40964,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
40833
40964
|
return void 0;
|
|
40834
40965
|
}
|
|
40835
40966
|
|
|
40967
|
+
// src/worktree-residue-sweep.ts
|
|
40968
|
+
var import_node_fs42 = require("node:fs");
|
|
40969
|
+
var import_node_path40 = require("node:path");
|
|
40970
|
+
|
|
40836
40971
|
// src/worktree-merge-cleanup.ts
|
|
40837
40972
|
var import_node_fs41 = require("node:fs");
|
|
40838
40973
|
var import_node_path39 = require("node:path");
|
|
@@ -41678,6 +41813,122 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
41678
41813
|
return lines2;
|
|
41679
41814
|
}
|
|
41680
41815
|
|
|
41816
|
+
// src/worktree-residue-sweep.ts
|
|
41817
|
+
var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
|
|
41818
|
+
function norm(p) {
|
|
41819
|
+
const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41820
|
+
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
41821
|
+
}
|
|
41822
|
+
function residueLedgerPath(gitCommonDir) {
|
|
41823
|
+
return (0, import_node_path40.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
41824
|
+
}
|
|
41825
|
+
function readPaths(file, deps) {
|
|
41826
|
+
try {
|
|
41827
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs42.readFileSync)(f, "utf8")))(file));
|
|
41828
|
+
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
41829
|
+
} catch {
|
|
41830
|
+
return [];
|
|
41831
|
+
}
|
|
41832
|
+
}
|
|
41833
|
+
function writePaths(file, paths, deps) {
|
|
41834
|
+
try {
|
|
41835
|
+
const write = deps.writeLedger ?? ((f, t) => {
|
|
41836
|
+
(0, import_node_fs42.mkdirSync)((0, import_node_path40.dirname)(f), { recursive: true });
|
|
41837
|
+
(0, import_node_fs42.writeFileSync)(f, t, "utf8");
|
|
41838
|
+
});
|
|
41839
|
+
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
41840
|
+
`);
|
|
41841
|
+
} catch {
|
|
41842
|
+
}
|
|
41843
|
+
}
|
|
41844
|
+
function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
41845
|
+
const file = residueLedgerPath(gitCommonDir);
|
|
41846
|
+
const paths = readPaths(file, deps);
|
|
41847
|
+
if (!paths.some((p) => norm(p) === norm(path2))) paths.push(path2);
|
|
41848
|
+
writePaths(file, paths, deps);
|
|
41849
|
+
}
|
|
41850
|
+
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
41851
|
+
const exists = deps.exists ?? import_node_fs42.existsSync;
|
|
41852
|
+
const readDir = deps.readDir ?? ((p) => (0, import_node_fs42.readdirSync)(p));
|
|
41853
|
+
const isDir = deps.isDir ?? ((p) => {
|
|
41854
|
+
try {
|
|
41855
|
+
return (0, import_node_fs42.statSync)(p).isDirectory();
|
|
41856
|
+
} catch {
|
|
41857
|
+
return false;
|
|
41858
|
+
}
|
|
41859
|
+
});
|
|
41860
|
+
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
41861
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs42.rmdirSync)(p));
|
|
41862
|
+
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
41863
|
+
const live = new Set(registered.map(norm));
|
|
41864
|
+
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
41865
|
+
const file = residueLedgerPath(gitCommonDir);
|
|
41866
|
+
const recorded = readPaths(file, deps);
|
|
41867
|
+
const remaining = [];
|
|
41868
|
+
for (const path2 of recorded) {
|
|
41869
|
+
const key = norm(path2);
|
|
41870
|
+
if (live.has(key) || key === primary) {
|
|
41871
|
+
report.dropped.push(path2);
|
|
41872
|
+
continue;
|
|
41873
|
+
}
|
|
41874
|
+
if (!exists(path2)) {
|
|
41875
|
+
report.dropped.push(path2);
|
|
41876
|
+
continue;
|
|
41877
|
+
}
|
|
41878
|
+
const removed = await removeRecorded(path2);
|
|
41879
|
+
if (removed.ok && !exists(path2)) report.swept.push(path2);
|
|
41880
|
+
else {
|
|
41881
|
+
report.kept.push({ path: path2, error: removed.ok ? "directory remains" : removed.error });
|
|
41882
|
+
remaining.push(path2);
|
|
41883
|
+
}
|
|
41884
|
+
}
|
|
41885
|
+
if (recorded.length) writePaths(file, remaining, deps);
|
|
41886
|
+
const roots = /* @__PURE__ */ new Set();
|
|
41887
|
+
for (const p of [...registered.slice(1), ...recorded]) roots.add((0, import_node_path40.dirname)(p));
|
|
41888
|
+
const keptKeys = new Set(remaining.map(norm));
|
|
41889
|
+
for (const root of roots) {
|
|
41890
|
+
if (!isDir(root) || live.has(norm(root)) || norm(root) === primary) continue;
|
|
41891
|
+
let children;
|
|
41892
|
+
try {
|
|
41893
|
+
children = readDir(root);
|
|
41894
|
+
} catch {
|
|
41895
|
+
continue;
|
|
41896
|
+
}
|
|
41897
|
+
for (const name of children) {
|
|
41898
|
+
const child2 = (0, import_node_path40.join)(root, name);
|
|
41899
|
+
const key = norm(child2);
|
|
41900
|
+
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
41901
|
+
let entries;
|
|
41902
|
+
try {
|
|
41903
|
+
entries = readDir(child2);
|
|
41904
|
+
} catch {
|
|
41905
|
+
continue;
|
|
41906
|
+
}
|
|
41907
|
+
if (entries.length === 0) {
|
|
41908
|
+
try {
|
|
41909
|
+
removeEmpty(child2);
|
|
41910
|
+
report.emptyRemoved.push(child2);
|
|
41911
|
+
} catch {
|
|
41912
|
+
}
|
|
41913
|
+
} else {
|
|
41914
|
+
report.unrecorded.push(child2);
|
|
41915
|
+
}
|
|
41916
|
+
}
|
|
41917
|
+
}
|
|
41918
|
+
return report;
|
|
41919
|
+
}
|
|
41920
|
+
function renderResidueSweepLines(report) {
|
|
41921
|
+
if (!report) return [];
|
|
41922
|
+
const lines2 = [];
|
|
41923
|
+
const cleaned = report.swept.length + report.emptyRemoved.length;
|
|
41924
|
+
if (cleaned) lines2.push(`pr merge: removed ${cleaned} leftover worktree folder(s) from earlier merges`);
|
|
41925
|
+
for (const k of report.kept) lines2.push(`pr merge: leftover folder still locked, retried on the next merge \u2014 ${k.path}`);
|
|
41926
|
+
if (report.unrecorded.length) {
|
|
41927
|
+
lines2.push(`pr merge: ${report.unrecorded.length} unregistered folder(s) with content under the worktree root were left untouched (not recorded by a merge): ${report.unrecorded.join(", ")}`);
|
|
41928
|
+
}
|
|
41929
|
+
return lines2;
|
|
41930
|
+
}
|
|
41931
|
+
|
|
41681
41932
|
// src/pr-deploy-dev.ts
|
|
41682
41933
|
function slugOfRepo(repo) {
|
|
41683
41934
|
return repo.split("/")[1]?.toLowerCase() ?? "";
|
|
@@ -41712,10 +41963,10 @@ function argvWantsJson2() {
|
|
|
41712
41963
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
41713
41964
|
}
|
|
41714
41965
|
function hubRoot() {
|
|
41715
|
-
const fromPkg = (0,
|
|
41966
|
+
const fromPkg = (0, import_node_path41.join)(__dirname, "..", "..");
|
|
41716
41967
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
41717
|
-
if ((0,
|
|
41718
|
-
if ((0,
|
|
41968
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(fromPkg, marker))) return fromPkg;
|
|
41969
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(process.cwd(), marker))) return process.cwd();
|
|
41719
41970
|
return null;
|
|
41720
41971
|
}
|
|
41721
41972
|
function ciAuditDeps() {
|
|
@@ -41727,8 +41978,8 @@ function ciAuditDeps() {
|
|
|
41727
41978
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
41728
41979
|
readSeedFile: (path2) => {
|
|
41729
41980
|
if (!root) return null;
|
|
41730
|
-
const fullPath = (0,
|
|
41731
|
-
return (0,
|
|
41981
|
+
const fullPath = (0, import_node_path41.join)(root, path2);
|
|
41982
|
+
return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
|
|
41732
41983
|
}
|
|
41733
41984
|
};
|
|
41734
41985
|
}
|
|
@@ -42453,11 +42704,11 @@ ${list}`);
|
|
|
42453
42704
|
}
|
|
42454
42705
|
});
|
|
42455
42706
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
42456
|
-
const wfDir = (0,
|
|
42457
|
-
if (!(0,
|
|
42458
|
-
return (0,
|
|
42707
|
+
const wfDir = (0, import_node_path41.join)(cwd, ".github", "workflows");
|
|
42708
|
+
if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
|
|
42709
|
+
return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
42459
42710
|
try {
|
|
42460
|
-
return workflowReportsPrChecks((0,
|
|
42711
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path41.join)(wfDir, name), "utf8"));
|
|
42461
42712
|
} catch {
|
|
42462
42713
|
return true;
|
|
42463
42714
|
}
|
|
@@ -42653,7 +42904,7 @@ ${list}`);
|
|
|
42653
42904
|
jsonParity(pr.command("review-verdict <number>").description("post an explicit review verdict comment: computes the PR diff patch-id and head sha, renders the v1 contract body, and posts it as a PR comment").requiredOption("--repo <owner/repo>", "target repo").requiredOption("--verdict <verdict>", `one of ${REVIEW_VERDICTS.join("|")}`).requiredOption("--scope <text>", "one line: what was reviewed").requiredOption("--risk <text>", "one line: the residual risk").option("--unverified <text>", "a claim the reviewer could not verify (repeatable)", (v, acc) => [...acc, v], []).requiredOption("--reviewer <id>", "reviewer seat or model id").option("--findings-file <path>", "Markdown findings appended after the JSON block")).action(async (number, o) => {
|
|
42654
42905
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
42655
42906
|
const verdict = o.verdict;
|
|
42656
|
-
const findings = o.findingsFile ? (0,
|
|
42907
|
+
const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
42657
42908
|
let patch;
|
|
42658
42909
|
let head;
|
|
42659
42910
|
try {
|
|
@@ -42927,7 +43178,7 @@ ${list}`);
|
|
|
42927
43178
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
42928
43179
|
closingGuardInput,
|
|
42929
43180
|
method === "--squash",
|
|
42930
|
-
o.squashBodyFile ? (0,
|
|
43181
|
+
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
42931
43182
|
);
|
|
42932
43183
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
42933
43184
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -42959,7 +43210,7 @@ ${list}`);
|
|
|
42959
43210
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
42960
43211
|
let foreignCheckout;
|
|
42961
43212
|
if (foreignCwd) {
|
|
42962
|
-
const sibling = (0,
|
|
43213
|
+
const sibling = (0, import_node_path41.join)((0, import_node_path41.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
42963
43214
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
42964
43215
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
42965
43216
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -43057,7 +43308,7 @@ ${list}`);
|
|
|
43057
43308
|
}
|
|
43058
43309
|
if (!repoForPostCleanup) throw e;
|
|
43059
43310
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
43060
|
-
const commitMessage = bodyFile ? (0,
|
|
43311
|
+
const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
|
|
43061
43312
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
43062
43313
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
43063
43314
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -43164,6 +43415,15 @@ ${list}`);
|
|
|
43164
43415
|
return;
|
|
43165
43416
|
}
|
|
43166
43417
|
const primaryRoot = beforeWorktrees[0]?.path ?? (startingPath || process.cwd());
|
|
43418
|
+
let gitCommonDir;
|
|
43419
|
+
let residueSweep;
|
|
43420
|
+
if (!(foreignCwd && !foreignCheckout)) {
|
|
43421
|
+
try {
|
|
43422
|
+
gitCommonDir = (0, import_node_path41.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
|
|
43423
|
+
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
43424
|
+
} catch {
|
|
43425
|
+
}
|
|
43426
|
+
}
|
|
43167
43427
|
let localCleanup;
|
|
43168
43428
|
if (foreignCwd && !foreignCheckout) {
|
|
43169
43429
|
localCleanup = { branch: headRef, localBranch: { name: headRef, status: "not-attempted", reason: "skipped-foreign-cwd" } };
|
|
@@ -43177,7 +43437,7 @@ ${list}`);
|
|
|
43177
43437
|
preserveWorktree: o.preserveWorktree,
|
|
43178
43438
|
gcAcknowledged: o.gc,
|
|
43179
43439
|
expectedHeadOid: headRefOid,
|
|
43180
|
-
pathExists: (p) => (0,
|
|
43440
|
+
pathExists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
43181
43441
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
43182
43442
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
43183
43443
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -43208,6 +43468,7 @@ ${list}`);
|
|
|
43208
43468
|
remote
|
|
43209
43469
|
});
|
|
43210
43470
|
const worktree = localCleanup?.worktree;
|
|
43471
|
+
if (gitCommonDir && worktree?.path && worktree.residue === "left") recordResidue(gitCommonDir, worktree.path);
|
|
43211
43472
|
const worktreePartial = isPrMergeWorktreePartial(localCleanup) && worktree?.path ? {
|
|
43212
43473
|
kind: "worktree-directory",
|
|
43213
43474
|
path: worktree.path,
|
|
@@ -43272,6 +43533,7 @@ ${list}`);
|
|
|
43272
43533
|
...partialCleanup.length ? { cleanupStatus: "partial", partialCleanup } : {},
|
|
43273
43534
|
...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
|
|
43274
43535
|
...localCleanup?.localBranch ? { localBranch: localCleanup.localBranch } : {},
|
|
43536
|
+
...residueSweep && (residueSweep.swept.length || residueSweep.emptyRemoved.length || residueSweep.kept.length || residueSweep.unrecorded.length) ? { residueSweep } : {},
|
|
43275
43537
|
// `boardAdvance` keeps its published array shape; `boardAdvanceStatus` is the field a caller reads to
|
|
43276
43538
|
// tell "merged clean" from "merged, but the board did not follow" without guessing from the exit code.
|
|
43277
43539
|
...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
|
|
@@ -43281,6 +43543,7 @@ ${list}`);
|
|
|
43281
43543
|
...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
|
|
43282
43544
|
}));
|
|
43283
43545
|
for (const line of renderPrMergeCleanupLines(localCleanup)) console.warn(line);
|
|
43546
|
+
for (const line of renderResidueSweepLines(residueSweep)) console.warn(line);
|
|
43284
43547
|
if (devDeploy && !devDeploy.ok) {
|
|
43285
43548
|
console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
|
|
43286
43549
|
}
|
|
@@ -43298,7 +43561,7 @@ ${list}`);
|
|
|
43298
43561
|
}
|
|
43299
43562
|
|
|
43300
43563
|
// src/command-register-developer.ts
|
|
43301
|
-
var
|
|
43564
|
+
var import_node_fs48 = require("node:fs");
|
|
43302
43565
|
init_clean_exit();
|
|
43303
43566
|
init_cli_shared();
|
|
43304
43567
|
init_error_codes();
|
|
@@ -43331,7 +43594,7 @@ async function resolveWhoami(deps) {
|
|
|
43331
43594
|
}
|
|
43332
43595
|
|
|
43333
43596
|
// src/command-register-developer.ts
|
|
43334
|
-
var
|
|
43597
|
+
var import_node_path46 = require("node:path");
|
|
43335
43598
|
init_house_map();
|
|
43336
43599
|
init_hub_url();
|
|
43337
43600
|
|
|
@@ -43364,7 +43627,7 @@ async function executeWaveLand(plan, deps) {
|
|
|
43364
43627
|
}
|
|
43365
43628
|
|
|
43366
43629
|
// src/box-commands.ts
|
|
43367
|
-
var
|
|
43630
|
+
var import_node_fs44 = require("node:fs");
|
|
43368
43631
|
init_clean_exit();
|
|
43369
43632
|
|
|
43370
43633
|
// src/box.ts
|
|
@@ -43576,7 +43839,7 @@ function registerBoxCommands(program3) {
|
|
|
43576
43839
|
}
|
|
43577
43840
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
43578
43841
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
43579
|
-
if (wroteScript) (0,
|
|
43842
|
+
if (wroteScript) (0, import_node_fs44.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
43580
43843
|
if (o.json) {
|
|
43581
43844
|
console.log(JSON.stringify({
|
|
43582
43845
|
box: found,
|
|
@@ -43598,19 +43861,19 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
43598
43861
|
// src/dist-drift.ts
|
|
43599
43862
|
var import_node_child_process16 = require("node:child_process");
|
|
43600
43863
|
var import_node_crypto13 = require("node:crypto");
|
|
43601
|
-
var
|
|
43864
|
+
var import_node_fs46 = require("node:fs");
|
|
43602
43865
|
var import_node_os21 = require("node:os");
|
|
43603
|
-
var
|
|
43866
|
+
var import_node_path43 = require("node:path");
|
|
43604
43867
|
|
|
43605
43868
|
// ../scripts/distribution-digest.mjs
|
|
43606
43869
|
var import_node_crypto12 = require("node:crypto");
|
|
43607
|
-
var
|
|
43608
|
-
var
|
|
43870
|
+
var import_node_fs45 = require("node:fs");
|
|
43871
|
+
var import_node_path42 = require("node:path");
|
|
43609
43872
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
43610
43873
|
function repoPath(root, declaredPath, label) {
|
|
43611
|
-
const absoluteRoot = (0,
|
|
43612
|
-
const target = (0,
|
|
43613
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
43874
|
+
const absoluteRoot = (0, import_node_path42.resolve)(root);
|
|
43875
|
+
const target = (0, import_node_path42.resolve)(root, declaredPath);
|
|
43876
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path42.sep}`)) {
|
|
43614
43877
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
43615
43878
|
}
|
|
43616
43879
|
return target;
|
|
@@ -43618,7 +43881,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
43618
43881
|
function digestFiles(files) {
|
|
43619
43882
|
const hash = (0, import_node_crypto12.createHash)("sha256");
|
|
43620
43883
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
43621
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
43884
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
|
|
43622
43885
|
hash.update(file.relative, "utf8");
|
|
43623
43886
|
hash.update("\0");
|
|
43624
43887
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -43633,8 +43896,8 @@ function digestFiles(files) {
|
|
|
43633
43896
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
43634
43897
|
return digestFiles(packedFiles.map((path2) => {
|
|
43635
43898
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
43636
|
-
if (!(0,
|
|
43637
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
43899
|
+
if (!(0, import_node_fs45.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
43900
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs45.lstatSync)(absolute) };
|
|
43638
43901
|
}));
|
|
43639
43902
|
}
|
|
43640
43903
|
|
|
@@ -43718,13 +43981,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
43718
43981
|
return lines2;
|
|
43719
43982
|
}
|
|
43720
43983
|
function readOrNull(path2) {
|
|
43721
|
-
return (0,
|
|
43984
|
+
return (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2) : null;
|
|
43722
43985
|
}
|
|
43723
43986
|
function walkFiles(root) {
|
|
43724
43987
|
const files = [];
|
|
43725
43988
|
const walk2 = (directory) => {
|
|
43726
|
-
for (const entry of (0,
|
|
43727
|
-
const child2 = (0,
|
|
43989
|
+
for (const entry of (0, import_node_fs46.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
43990
|
+
const child2 = (0, import_node_path43.join)(directory, entry.name);
|
|
43728
43991
|
if (entry.isDirectory()) walk2(child2);
|
|
43729
43992
|
else files.push(child2);
|
|
43730
43993
|
}
|
|
@@ -43734,10 +43997,10 @@ function walkFiles(root) {
|
|
|
43734
43997
|
}
|
|
43735
43998
|
function bomPathFor(root) {
|
|
43736
43999
|
try {
|
|
43737
|
-
const registry2 = JSON.parse((0,
|
|
43738
|
-
return (0,
|
|
44000
|
+
const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path43.join)(root, "surfaces.json"), "utf8"));
|
|
44001
|
+
return (0, import_node_path43.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
43739
44002
|
} catch {
|
|
43740
|
-
return (0,
|
|
44003
|
+
return (0, import_node_path43.join)(root, "distribution-bom.json");
|
|
43741
44004
|
}
|
|
43742
44005
|
}
|
|
43743
44006
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -43750,35 +44013,35 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
43750
44013
|
});
|
|
43751
44014
|
}
|
|
43752
44015
|
function runDistStatus(root) {
|
|
43753
|
-
const stage = (0,
|
|
44016
|
+
const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
|
|
43754
44017
|
let overlayCount = 0;
|
|
43755
44018
|
try {
|
|
43756
|
-
const cliOut = (0,
|
|
43757
|
-
const hubOut = (0,
|
|
43758
|
-
rebuildTo((0,
|
|
43759
|
-
rebuildTo((0,
|
|
44019
|
+
const cliOut = (0, import_node_path43.join)(stage, "cli-dist");
|
|
44020
|
+
const hubOut = (0, import_node_path43.join)(stage, "hub-dist");
|
|
44021
|
+
rebuildTo((0, import_node_path43.join)(root, "cli"), cliOut);
|
|
44022
|
+
rebuildTo((0, import_node_path43.join)(root, "updater"), hubOut);
|
|
43760
44023
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
43761
44024
|
const rebuilt = (path2) => {
|
|
43762
44025
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
43763
|
-
return spec ? readOrNull((0,
|
|
44026
|
+
return spec ? readOrNull((0, import_node_path43.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
43764
44027
|
};
|
|
43765
|
-
const committed = (path2) => readOrNull((0,
|
|
43766
|
-
const tree = (path2) => readOrNull((0,
|
|
43767
|
-
const distRoot = (0,
|
|
43768
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
43769
|
-
const bom = JSON.parse((0,
|
|
44028
|
+
const committed = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
|
|
44029
|
+
const tree = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
|
|
44030
|
+
const distRoot = (0, import_node_path43.join)(root, "cli", "dist");
|
|
44031
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path43.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
44032
|
+
const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
|
|
43770
44033
|
const digest = (entries) => {
|
|
43771
|
-
const overlay = (0,
|
|
44034
|
+
const overlay = (0, import_node_path43.join)(stage, `overlay-${overlayCount++}`);
|
|
43772
44035
|
for (const entry of entries) {
|
|
43773
|
-
const target = (0,
|
|
43774
|
-
(0,
|
|
43775
|
-
(0,
|
|
44036
|
+
const target = (0, import_node_path43.join)(overlay, entry.path);
|
|
44037
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(target), { recursive: true });
|
|
44038
|
+
(0, import_node_fs46.writeFileSync)(target, entry.bytes);
|
|
43776
44039
|
}
|
|
43777
44040
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
43778
44041
|
};
|
|
43779
44042
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
43780
44043
|
} finally {
|
|
43781
|
-
(0,
|
|
44044
|
+
(0, import_node_fs46.rmSync)(stage, { recursive: true, force: true });
|
|
43782
44045
|
}
|
|
43783
44046
|
}
|
|
43784
44047
|
|
|
@@ -43857,7 +44120,7 @@ init_hub_auth();
|
|
|
43857
44120
|
|
|
43858
44121
|
// src/schedules-lift-command.ts
|
|
43859
44122
|
var import_promises8 = require("node:fs/promises");
|
|
43860
|
-
var
|
|
44123
|
+
var import_node_path44 = require("node:path");
|
|
43861
44124
|
init_clean_exit();
|
|
43862
44125
|
init_cli_shared();
|
|
43863
44126
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -43886,7 +44149,7 @@ async function readWorkflowFiles(dir) {
|
|
|
43886
44149
|
const files = [];
|
|
43887
44150
|
for (const name of names.sort()) {
|
|
43888
44151
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
43889
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0,
|
|
44152
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path44.join)(dir, name), "utf8") });
|
|
43890
44153
|
}
|
|
43891
44154
|
return files;
|
|
43892
44155
|
}
|
|
@@ -43969,8 +44232,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
43969
44232
|
|
|
43970
44233
|
// src/spawn-policy-core.ts
|
|
43971
44234
|
var import_node_child_process17 = require("node:child_process");
|
|
43972
|
-
var
|
|
43973
|
-
var
|
|
44235
|
+
var import_node_fs47 = require("node:fs");
|
|
44236
|
+
var import_node_path45 = require("node:path");
|
|
43974
44237
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
43975
44238
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
43976
44239
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -44056,7 +44319,7 @@ function runSpawnPolicy(root) {
|
|
|
44056
44319
|
for (const file of files) {
|
|
44057
44320
|
let raw;
|
|
44058
44321
|
try {
|
|
44059
|
-
raw = (0,
|
|
44322
|
+
raw = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)(root, file), "utf8");
|
|
44060
44323
|
} catch {
|
|
44061
44324
|
continue;
|
|
44062
44325
|
}
|
|
@@ -44076,19 +44339,19 @@ function runSpawnPolicy(root) {
|
|
|
44076
44339
|
function registerDeveloperCommands(program3) {
|
|
44077
44340
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
44078
44341
|
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) => {
|
|
44079
|
-
const path2 = (0,
|
|
44080
|
-
const current = (0,
|
|
44342
|
+
const path2 = (0, import_node_path46.join)(process.cwd(), ".gitignore");
|
|
44343
|
+
const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
|
|
44081
44344
|
const plan = planManagedGitignore(current);
|
|
44082
44345
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
44083
44346
|
if (opts.json) {
|
|
44084
|
-
if (opts.write && plan.changed) (0,
|
|
44347
|
+
if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
44085
44348
|
console.log(JSON.stringify(plan, null, 2));
|
|
44086
44349
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
44087
44350
|
return;
|
|
44088
44351
|
}
|
|
44089
44352
|
if (opts.write) {
|
|
44090
44353
|
if (plan.changed) {
|
|
44091
|
-
(0,
|
|
44354
|
+
(0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
44092
44355
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
44093
44356
|
} else {
|
|
44094
44357
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -44354,6 +44617,15 @@ function neutralizeHubProductBrands(text) {
|
|
|
44354
44617
|
s = s.replace(/\bdesign-system\s+design-system\b/gi, "design-system");
|
|
44355
44618
|
return s.replace(/\s{2,}/g, " ").trim();
|
|
44356
44619
|
}
|
|
44620
|
+
var JERV_SIDE = /\bjerv/i;
|
|
44621
|
+
function namesJervSide(text) {
|
|
44622
|
+
return JERV_SIDE.test(text);
|
|
44623
|
+
}
|
|
44624
|
+
function hubSummaryJervRefusal(lines2) {
|
|
44625
|
+
const bad = lines2.filter(namesJervSide);
|
|
44626
|
+
if (bad.length === 0) return null;
|
|
44627
|
+
return `--announce-summary-file names a Jerv-side project in ${bad.length} line(s) \u2014 an MMI-Hub release note describes MMI-Hub only and never mentions Jerv-side projects; drop or rewrite those lines, then rerun the same command`;
|
|
44628
|
+
}
|
|
44357
44629
|
function escapeMrkdwn(text) {
|
|
44358
44630
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
44359
44631
|
}
|
|
@@ -44465,6 +44737,7 @@ async function announceRelease(deps, args) {
|
|
|
44465
44737
|
}
|
|
44466
44738
|
lines2 = curateReleaseNotes(view.body ?? "", MAX_BULLETS, neutralize);
|
|
44467
44739
|
}
|
|
44740
|
+
if (neutralize) lines2 = lines2.filter((l) => !namesJervSide(l));
|
|
44468
44741
|
if (lines2.length === 0) lines2 = ["\u2022 maintenance release \u2014 see the notes for details"];
|
|
44469
44742
|
const text = formatAnnouncement({
|
|
44470
44743
|
repo: args.repo,
|
|
@@ -45224,6 +45497,8 @@ async function runHotfixReleaseLocked(deps, versionInput, options, doctor) {
|
|
|
45224
45497
|
if (lines2.length < 3 || lines2.length > 6) {
|
|
45225
45498
|
throw new Error(`--announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`);
|
|
45226
45499
|
}
|
|
45500
|
+
const jerv = hubSummaryJervRefusal(lines2);
|
|
45501
|
+
if (jerv) throw new Error(jerv);
|
|
45227
45502
|
let rangeLog;
|
|
45228
45503
|
try {
|
|
45229
45504
|
rangeLog = await deps.run("git", ["log", "--format=%B", "origin/main..HEAD"]);
|
|
@@ -45307,6 +45582,10 @@ ${decision.note}`);
|
|
|
45307
45582
|
deployNote = dispatch.note;
|
|
45308
45583
|
runs.push(hotfixWorkflowRunFromDispatch("tenant-deploy.yml", deploy));
|
|
45309
45584
|
if (publish) runs.push(hotfixWorkflowRunFromDispatch("publish.yml", publish));
|
|
45585
|
+
} else if (deployModel === "vercel") {
|
|
45586
|
+
const vercel = await dispatchDeploy(deps, ctx, "main", "main", deployModel, true, (deps.now ?? Date.now)(), mergedSha, "report");
|
|
45587
|
+
runs.push(hotfixWorkflowRunFromDispatch("vercel", vercel));
|
|
45588
|
+
deployNote = vercel.note;
|
|
45310
45589
|
} else {
|
|
45311
45590
|
deployNote = `no hotfix deploy dispatch for deployModel=${deployModel} \u2014 prod deploy is repo-specific`;
|
|
45312
45591
|
}
|
|
@@ -45816,8 +46095,8 @@ function checkHotfixCarries(options) {
|
|
|
45816
46095
|
}
|
|
45817
46096
|
|
|
45818
46097
|
// src/train-commands.ts
|
|
45819
|
-
var
|
|
45820
|
-
var
|
|
46098
|
+
var import_node_fs49 = require("node:fs");
|
|
46099
|
+
var import_node_path47 = require("node:path");
|
|
45821
46100
|
init_cli_shared();
|
|
45822
46101
|
init_clean_exit();
|
|
45823
46102
|
init_client_version();
|
|
@@ -45832,7 +46111,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
45832
46111
|
}
|
|
45833
46112
|
function readRepoVersion() {
|
|
45834
46113
|
try {
|
|
45835
|
-
return JSON.parse((0,
|
|
46114
|
+
return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path47.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
45836
46115
|
} catch {
|
|
45837
46116
|
return void 0;
|
|
45838
46117
|
}
|
|
@@ -45934,8 +46213,8 @@ async function withReleaseProgress(deps, initial, action) {
|
|
|
45934
46213
|
clearInterval(heartbeat);
|
|
45935
46214
|
}
|
|
45936
46215
|
}
|
|
45937
|
-
function releaseProgressVerdict(status) {
|
|
45938
|
-
return status === "complete" ?
|
|
46216
|
+
function releaseProgressVerdict(status, subject = "Release") {
|
|
46217
|
+
return status === "complete" ? `${subject} complete` : status === "failed" ? `${subject} failed \u2014 a follow-up needs attention` : `${subject} pending \u2014 waiting for unfinished follow-ups`;
|
|
45939
46218
|
}
|
|
45940
46219
|
|
|
45941
46220
|
// src/command-register-train-operations.ts
|
|
@@ -45976,7 +46255,7 @@ var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
|
|
|
45976
46255
|
var NPM_TRAIN_TIMEOUT_MS = 6e4;
|
|
45977
46256
|
var NPM_FOLD_TIMEOUT_MS = NODE_PREPARE_TIMEOUT_MS;
|
|
45978
46257
|
var JERV_GATEWAY_DEPLOY_TIMEOUT_MS = 20 * 6e4;
|
|
45979
|
-
async function announceSummaryRefusal(path2) {
|
|
46258
|
+
async function announceSummaryRefusal(path2, isHub = false) {
|
|
45980
46259
|
let lines2;
|
|
45981
46260
|
try {
|
|
45982
46261
|
lines2 = summaryFileLines(await (0, import_promises10.readFile)(path2, "utf8"));
|
|
@@ -45986,6 +46265,10 @@ async function announceSummaryRefusal(path2) {
|
|
|
45986
46265
|
if (lines2.length < 3 || lines2.length > 6) {
|
|
45987
46266
|
return `release: --announce-summary-file must contain 3-6 non-empty LLM-curated lines (found ${lines2.length}) \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`;
|
|
45988
46267
|
}
|
|
46268
|
+
if (isHub) {
|
|
46269
|
+
const jerv = hubSummaryJervRefusal(lines2);
|
|
46270
|
+
if (jerv) return `release: ${jerv}`;
|
|
46271
|
+
}
|
|
45989
46272
|
try {
|
|
45990
46273
|
await execFileP("git", ["fetch", "origin"], { timeout: GIT_TIMEOUT_MS });
|
|
45991
46274
|
} catch (e) {
|
|
@@ -46168,8 +46451,31 @@ function renderReleasePublishRetry(r) {
|
|
|
46168
46451
|
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
46169
46452
|
}
|
|
46170
46453
|
function renderRcandResume(r) {
|
|
46171
|
-
const
|
|
46172
|
-
|
|
46454
|
+
const verb = r.command === "rcand-retry-deploy" ? "retry-deploy" : "resume";
|
|
46455
|
+
const base = `mmi-cli devops rcand --${verb}: promoted ${r.repo} \u2014 rc at ${r.tag} (${r.tagSha.slice(0, 12)}) [${r.deployModel}]; ${renderDeployLine(r)}; ${r.note}`;
|
|
46456
|
+
return appendVerdictAndLedger(base, r.releaseVerdict, r.ledger);
|
|
46457
|
+
}
|
|
46458
|
+
function progressSubject(commandName) {
|
|
46459
|
+
return commandName === "rcand" ? "Release candidate" : "Release";
|
|
46460
|
+
}
|
|
46461
|
+
function emitRcandContinuation(label, raw, o) {
|
|
46462
|
+
const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
|
|
46463
|
+
const { followUpStatus, releaseVerdict } = buildReleaseVerdict("rcand", result, void 0);
|
|
46464
|
+
const reported = { ...result, releaseVerdict };
|
|
46465
|
+
emitTrainResult(label, o.json ? JSON.stringify(reported, null, 2) : renderRcandResume(reported), o.out);
|
|
46466
|
+
consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus, progressSubject("rcand"))}`);
|
|
46467
|
+
applyTrainFollowUpExit(followUpStatus);
|
|
46468
|
+
}
|
|
46469
|
+
function appendVerdictAndLedger(base, v, ledger) {
|
|
46470
|
+
const lines2 = [base];
|
|
46471
|
+
if (v) {
|
|
46472
|
+
const verdict = v.followUpStatus === "failed" ? "PROMOTED, FOLLOW-UP FAILED" : v.followUpStatus === "pending" ? "PROMOTED, FOLLOW-UP PENDING" : "SUCCEEDED";
|
|
46473
|
+
const followUpNote = trainFollowUpNote(v.followUpStatus);
|
|
46474
|
+
lines2[0] = `${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}${followUpNote ? ` (${followUpNote})` : ""}`;
|
|
46475
|
+
lines2.push(...v.legs.filter((leg) => leg.status === "failed").map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`));
|
|
46476
|
+
}
|
|
46477
|
+
if (ledger) lines2.push(...formatReleaseLedgerReport(ledger).map((line) => ` ${line}`));
|
|
46478
|
+
return lines2.join("\n");
|
|
46173
46479
|
}
|
|
46174
46480
|
function renderAlignment(label, alignment) {
|
|
46175
46481
|
if (alignment.status !== "pr-pending") return `${label}: ${alignment.note}`;
|
|
@@ -46277,16 +46583,17 @@ function buildReleaseVerdict(commandName, result, projectInfoSync) {
|
|
|
46277
46583
|
alignment: alignmentPending ? "unresolved" : "ok",
|
|
46278
46584
|
foldPort: "ok"
|
|
46279
46585
|
});
|
|
46586
|
+
const legs = releaseFollowUpLegs(result, projectInfoSync);
|
|
46280
46587
|
return {
|
|
46281
46588
|
followUpStatus,
|
|
46282
|
-
releaseVerdict:
|
|
46589
|
+
releaseVerdict: {
|
|
46283
46590
|
releaseStatus: "succeeded",
|
|
46284
46591
|
followUpStatus,
|
|
46285
46592
|
promoted: result.promoted,
|
|
46286
46593
|
tag: result.tag,
|
|
46287
46594
|
releaseUrl: result.release?.url ?? null,
|
|
46288
|
-
legs:
|
|
46289
|
-
}
|
|
46595
|
+
legs: commandName === "rcand" ? legs.filter((leg) => leg.leg !== "project-info") : legs
|
|
46596
|
+
}
|
|
46290
46597
|
};
|
|
46291
46598
|
}
|
|
46292
46599
|
function buildResumeReleaseVerdict(result, projectInfoSync) {
|
|
@@ -46339,22 +46646,7 @@ function renderTrainApply(commandName, r) {
|
|
|
46339
46646
|
base = `${base}; project info: ${r.projectInfoSync.note}`;
|
|
46340
46647
|
}
|
|
46341
46648
|
if (r.announceNote) base = `${base}; announce: ${r.announceNote}`;
|
|
46342
|
-
|
|
46343
|
-
const v = r.releaseVerdict;
|
|
46344
|
-
const verdict = v.followUpStatus === "failed" ? "PROMOTED, FOLLOW-UP FAILED" : v.followUpStatus === "pending" ? "PROMOTED, FOLLOW-UP PENDING" : "SUCCEEDED";
|
|
46345
|
-
const failedLegs = v.legs.filter((leg) => leg.status === "failed");
|
|
46346
|
-
const followUpNote = trainFollowUpNote(v.followUpStatus);
|
|
46347
|
-
return [
|
|
46348
|
-
`${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}${followUpNote ? ` (${followUpNote})` : ""}`,
|
|
46349
|
-
...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`),
|
|
46350
|
-
// #5987: the durable ledger's phase legs + derived typed verdict ride the same receipt.
|
|
46351
|
-
...r.ledger ? formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`) : []
|
|
46352
|
-
].join("\n");
|
|
46353
|
-
}
|
|
46354
|
-
if (r.ledger) {
|
|
46355
|
-
return [base, ...formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`)].join("\n");
|
|
46356
|
-
}
|
|
46357
|
-
return base;
|
|
46649
|
+
return appendVerdictAndLedger(base, r.releaseVerdict, r.ledger);
|
|
46358
46650
|
}
|
|
46359
46651
|
function applyTrainFollowUpExit(status) {
|
|
46360
46652
|
const code = trainFollowUpExitCode(status);
|
|
@@ -46404,6 +46696,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46404
46696
|
trainCmd.addOption(new Option(f.flags, f.description).hideHelp());
|
|
46405
46697
|
}
|
|
46406
46698
|
}
|
|
46699
|
+
const retryDeploy = new Option("--retry-deploy", "re-dispatch the FAILED rc deploy of the promoted candidate in the active rcand ledger \u2014 same tag and SHA, never re-cut or re-pushed (#7053)");
|
|
46700
|
+
trainCmd.addOption(commandName === "rcand" ? retryDeploy : retryDeploy.hideHelp());
|
|
46407
46701
|
trainCmd.option("--repo <owner/repo>", "dry-run plan for a target repo without relying on the current checkout; --apply still uses the current checkout").action(async (o) => {
|
|
46408
46702
|
try {
|
|
46409
46703
|
await requireFreshTrainCli(commandName);
|
|
@@ -46431,8 +46725,30 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46431
46725
|
if (o.retryPublish && commandName !== "release") {
|
|
46432
46726
|
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
|
|
46433
46727
|
}
|
|
46434
|
-
if (o.
|
|
46435
|
-
return fail(`${commandName}: --
|
|
46728
|
+
if (o.retryDeploy && commandName !== "rcand") {
|
|
46729
|
+
return fail(`${commandName}: --retry-deploy applies only to rcand \u2014 it re-dispatches a failed rc deploy of the promoted candidate. Run: mmi-cli devops rcand --retry-deploy --watch (a failed hotfix deploy: mmi-cli devops hotfix release <tag> --retry-deploy)`);
|
|
46730
|
+
}
|
|
46731
|
+
if (o.out && !o.apply && !o.resume && !o.retryDeploy) {
|
|
46732
|
+
return fail(`${commandName}: --out writes a terminal result receipt (--apply, --resume, --retry-deploy, --abort, --retry-publish) \u2014 the dry-run plan keeps its stdout/JSON output. Drop --out or rerun with --apply`);
|
|
46733
|
+
}
|
|
46734
|
+
if (o.retryDeploy) {
|
|
46735
|
+
if (o.apply || o.resume) return fail("rcand: --retry-deploy cannot be combined with --apply or --resume \u2014 it re-dispatches only the failed rc deploy of the already-promoted candidate");
|
|
46736
|
+
if (o.repo) {
|
|
46737
|
+
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops rcand --retry-deploy${o.watch ? " --watch" : ""}`);
|
|
46738
|
+
if (!guard.ok) return fail(`rcand: ${guard.message}`);
|
|
46739
|
+
}
|
|
46740
|
+
try {
|
|
46741
|
+
const raw = await withReleaseProgress(
|
|
46742
|
+
trainApplyDeps(),
|
|
46743
|
+
"Checking the failed release candidate deploy",
|
|
46744
|
+
(deps) => runRcandRetryDeploy(deps, { watch: o.watch })
|
|
46745
|
+
);
|
|
46746
|
+
return emitRcandContinuation("rcand --retry-deploy", raw, o);
|
|
46747
|
+
} catch (e) {
|
|
46748
|
+
applyTrainFollowUpExit("failed");
|
|
46749
|
+
consoleIo.err("mmi-cli: Release: Release candidate blocked \u2014 see the error below");
|
|
46750
|
+
return failGraceful(`rcand --retry-deploy: ${e.message}`);
|
|
46751
|
+
}
|
|
46436
46752
|
}
|
|
46437
46753
|
if (o.supersedeDeploy && o.supersedePublish) {
|
|
46438
46754
|
if (o.resume) return fail("release: --supersede-deploy --supersede-publish and --resume are mutually exclusive \u2014 the closure settles the ledger so a NEW train can run, resume re-derives the very legs it closes");
|
|
@@ -46536,17 +46852,12 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46536
46852
|
if (o.apply) return fail(`${commandName}: --resume and --apply are mutually exclusive \u2014 --apply cuts the NEXT version, --resume finishes the immutable tag already on origin`);
|
|
46537
46853
|
try {
|
|
46538
46854
|
if (commandName === "rcand") {
|
|
46539
|
-
const raw2 = await
|
|
46540
|
-
|
|
46541
|
-
|
|
46542
|
-
|
|
46543
|
-
|
|
46544
|
-
|
|
46545
|
-
rcRetirement: "ok",
|
|
46546
|
-
alignment: "ok",
|
|
46547
|
-
foldPort: "ok"
|
|
46548
|
-
}));
|
|
46549
|
-
return;
|
|
46855
|
+
const raw2 = await withReleaseProgress(
|
|
46856
|
+
trainApplyDeps(),
|
|
46857
|
+
"Checking the existing release candidate",
|
|
46858
|
+
(deps) => runRcandResume(deps, { watch: o.watch })
|
|
46859
|
+
);
|
|
46860
|
+
return emitRcandContinuation("rcand --resume", raw2, o);
|
|
46550
46861
|
}
|
|
46551
46862
|
const raw = await withReleaseProgress(
|
|
46552
46863
|
trainApplyDeps(),
|
|
@@ -46567,7 +46878,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46567
46878
|
return;
|
|
46568
46879
|
} catch (e) {
|
|
46569
46880
|
applyTrainFollowUpExit("failed");
|
|
46570
|
-
|
|
46881
|
+
consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
|
|
46571
46882
|
return failGraceful(`${commandName} --resume: ${e.message}`);
|
|
46572
46883
|
}
|
|
46573
46884
|
}
|
|
@@ -46603,7 +46914,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46603
46914
|
isHub ? "release: a new MMI-Hub release requires --announce-summary-file <path> with 3-6 short LLM-curated lines; create the summary file, then rerun the same release command \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing" : `release: ${slug} announces to its project release channel, so a new release requires --announce-summary-file <path> with 3-6 short plain-language lines in the project's releaseLanguage (${releaseLanguage2}); create the summary file, then rerun the same release command \u2014 see docs/Guides/train-troubleshooting.md#announce-summary-missing`
|
|
46604
46915
|
);
|
|
46605
46916
|
}
|
|
46606
|
-
const refusal = await announceSummaryRefusal(o.announceSummaryFile);
|
|
46917
|
+
const refusal = await announceSummaryRefusal(o.announceSummaryFile, isHub);
|
|
46607
46918
|
if (refusal) return fail(refusal);
|
|
46608
46919
|
}
|
|
46609
46920
|
}
|
|
@@ -46611,7 +46922,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46611
46922
|
try {
|
|
46612
46923
|
const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
46613
46924
|
const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev });
|
|
46614
|
-
const raw = commandName === "release" ?
|
|
46925
|
+
const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
|
|
46615
46926
|
const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
|
|
46616
46927
|
let projectInfoSync;
|
|
46617
46928
|
if (commandName === "release") {
|
|
@@ -46629,14 +46940,14 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46629
46940
|
};
|
|
46630
46941
|
const output = o.json ? JSON.stringify(reported, null, 2) : renderTrainApply(commandName, reported);
|
|
46631
46942
|
emitTrainResult(`${commandName} apply`, output, o.out);
|
|
46632
|
-
|
|
46943
|
+
consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus, progressSubject(commandName))}`);
|
|
46633
46944
|
applyTrainFollowUpExit(followUpStatus);
|
|
46634
46945
|
return;
|
|
46635
46946
|
} catch (e) {
|
|
46636
46947
|
const error = e;
|
|
46637
46948
|
writeTrainFailureReceipt(commandName, o.json === true, o.out, error);
|
|
46638
46949
|
process.exitCode = 1;
|
|
46639
|
-
|
|
46950
|
+
consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
|
|
46640
46951
|
return await failGraceful(`${commandName}: ${error.message}`);
|
|
46641
46952
|
}
|
|
46642
46953
|
}
|
|
@@ -46816,7 +47127,7 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
46816
47127
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
46817
47128
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
46818
47129
|
function envHealLockPath(home) {
|
|
46819
|
-
return (0,
|
|
47130
|
+
return (0, import_node_path48.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
46820
47131
|
}
|
|
46821
47132
|
async function withEnvHealLock(what, run) {
|
|
46822
47133
|
try {
|
|
@@ -46953,7 +47264,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46953
47264
|
);
|
|
46954
47265
|
const result = applyPluginCachePlan(
|
|
46955
47266
|
plan,
|
|
46956
|
-
(p) => (0,
|
|
47267
|
+
(p) => (0, import_node_fs50.rmSync)(p, { recursive: true }),
|
|
46957
47268
|
stagingApplyFsGuard(configRoot)
|
|
46958
47269
|
);
|
|
46959
47270
|
return {
|
|
@@ -46988,7 +47299,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46988
47299
|
// has no generated routing index would
|
|
46989
47300
|
// get a permanent — demanding an artifact it never asked for.
|
|
46990
47301
|
docsIndexState: (root) => {
|
|
46991
|
-
if (!(0,
|
|
47302
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
46992
47303
|
const real = createDocsIndexDeps(root);
|
|
46993
47304
|
let docs;
|
|
46994
47305
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -46997,7 +47308,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46997
47308
|
},
|
|
46998
47309
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
46999
47310
|
healDocsIndex: (root) => {
|
|
47000
|
-
if (!(0,
|
|
47311
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
47001
47312
|
const real = createDocsIndexDeps(root);
|
|
47002
47313
|
let docs;
|
|
47003
47314
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -47623,7 +47934,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
47623
47934
|
if (dupe) return fail(`org project set: KEY "${dupe}" was passed to both --var and --set; --set is an alias of --var, so pass each KEY once`);
|
|
47624
47935
|
if (o.secretsFile) {
|
|
47625
47936
|
try {
|
|
47626
|
-
vars.push(`secrets=${(0,
|
|
47937
|
+
vars.push(`secrets=${(0, import_node_fs50.readFileSync)(o.secretsFile, "utf8")}`);
|
|
47627
47938
|
} catch (e) {
|
|
47628
47939
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
47629
47940
|
}
|
|
@@ -47956,16 +48267,16 @@ function ciAuditDeps2() {
|
|
|
47956
48267
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
47957
48268
|
readSeedFile: (path2) => {
|
|
47958
48269
|
if (!root) return null;
|
|
47959
|
-
const fullPath = (0,
|
|
47960
|
-
return (0,
|
|
48270
|
+
const fullPath = (0, import_node_path48.join)(root, path2);
|
|
48271
|
+
return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
|
|
47961
48272
|
}
|
|
47962
48273
|
};
|
|
47963
48274
|
}
|
|
47964
48275
|
function hubRoot2() {
|
|
47965
|
-
const fromPkg = (0,
|
|
48276
|
+
const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
|
|
47966
48277
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
47967
|
-
if ((0,
|
|
47968
|
-
if ((0,
|
|
48278
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(fromPkg, marker))) return fromPkg;
|
|
48279
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(process.cwd(), marker))) return process.cwd();
|
|
47969
48280
|
return null;
|
|
47970
48281
|
}
|
|
47971
48282
|
registerQueryCommands(program2);
|
|
@@ -48065,10 +48376,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
48065
48376
|
targets = resolution.targets;
|
|
48066
48377
|
}
|
|
48067
48378
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
48068
|
-
const fileMatrix = (0,
|
|
48379
|
+
const fileMatrix = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
48069
48380
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
48070
48381
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
48071
|
-
const sanctioned = (0,
|
|
48382
|
+
const sanctioned = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
48072
48383
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
48073
48384
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
48074
48385
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -48099,16 +48410,16 @@ function directoryBytes(path2) {
|
|
|
48099
48410
|
let total = 0;
|
|
48100
48411
|
let entries;
|
|
48101
48412
|
try {
|
|
48102
|
-
entries = (0,
|
|
48413
|
+
entries = (0, import_node_fs50.readdirSync)(path2, { withFileTypes: true });
|
|
48103
48414
|
} catch {
|
|
48104
48415
|
return 0;
|
|
48105
48416
|
}
|
|
48106
48417
|
for (const entry of entries) {
|
|
48107
|
-
const child2 = (0,
|
|
48418
|
+
const child2 = (0, import_node_path48.join)(path2, entry.name);
|
|
48108
48419
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
48109
48420
|
else {
|
|
48110
48421
|
try {
|
|
48111
|
-
total += (0,
|
|
48422
|
+
total += (0, import_node_fs50.statSync)(child2).size;
|
|
48112
48423
|
} catch {
|
|
48113
48424
|
}
|
|
48114
48425
|
}
|
|
@@ -48116,25 +48427,25 @@ function directoryBytes(path2) {
|
|
|
48116
48427
|
return total;
|
|
48117
48428
|
}
|
|
48118
48429
|
function listDirEntries(dir) {
|
|
48119
|
-
return (0,
|
|
48430
|
+
return (0, import_node_fs50.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
48120
48431
|
}
|
|
48121
48432
|
function readInstalledPluginRefs(configRoot) {
|
|
48122
48433
|
const p = installedPluginsPathForConfig(configRoot);
|
|
48123
|
-
if (!(0,
|
|
48434
|
+
if (!(0, import_node_fs50.existsSync)(p)) return [];
|
|
48124
48435
|
try {
|
|
48125
|
-
return installedPluginPaths((0,
|
|
48436
|
+
return installedPluginPaths((0, import_node_fs50.readFileSync)(p, "utf8"));
|
|
48126
48437
|
} catch {
|
|
48127
48438
|
return null;
|
|
48128
48439
|
}
|
|
48129
48440
|
}
|
|
48130
48441
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
48131
48442
|
return {
|
|
48132
|
-
exists: (p) => (0,
|
|
48133
|
-
listVersionDirs: (root) => (0,
|
|
48443
|
+
exists: (p) => (0, import_node_fs50.existsSync)(p),
|
|
48444
|
+
listVersionDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
48134
48445
|
dirBytes,
|
|
48135
|
-
listStagingDirs: (root) => (0,
|
|
48446
|
+
listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
48136
48447
|
try {
|
|
48137
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
48448
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path48.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
|
|
48138
48449
|
} catch {
|
|
48139
48450
|
return { name: d.name, mtimeMs: Date.now() };
|
|
48140
48451
|
}
|
|
@@ -48148,10 +48459,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
48148
48459
|
return {
|
|
48149
48460
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
48150
48461
|
mtimeMs: (name) => {
|
|
48151
|
-
const p = (0,
|
|
48152
|
-
if (!(0,
|
|
48462
|
+
const p = (0, import_node_path48.join)(stagingRoot, name);
|
|
48463
|
+
if (!(0, import_node_fs50.existsSync)(p)) return null;
|
|
48153
48464
|
try {
|
|
48154
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
48465
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);
|
|
48155
48466
|
} catch {
|
|
48156
48467
|
return null;
|
|
48157
48468
|
}
|
|
@@ -48177,7 +48488,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
48177
48488
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
48178
48489
|
);
|
|
48179
48490
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
48180
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
48491
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs50.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
48181
48492
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
48182
48493
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
48183
48494
|
else console.log(renderPluginCachePlan(plan, result));
|