@mutmutco/cli 4.5.4 → 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 +515 -196
- 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) {
|
|
@@ -19420,34 +19448,28 @@ function prodReleaseTargets(model) {
|
|
|
19420
19448
|
function trainObservesShipment(model) {
|
|
19421
19449
|
return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0 || model === "vercel";
|
|
19422
19450
|
}
|
|
19423
|
-
async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
|
|
19424
|
-
const
|
|
19425
|
-
|
|
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`;
|
|
19426
19455
|
if (!headSha) return { note, deployStatus: "pending" };
|
|
19427
|
-
const
|
|
19428
|
-
const row = (d, conclusion) => ({
|
|
19429
|
-
workflow: "vercel",
|
|
19430
|
-
conclusion,
|
|
19431
|
-
...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" },
|
|
19432
|
-
event: "push"
|
|
19433
|
-
});
|
|
19434
|
-
const verdict = (d) => d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
|
|
19456
|
+
const after = target === "preview" ? rcWindow(since) : void 0;
|
|
19435
19457
|
let read;
|
|
19436
19458
|
try {
|
|
19437
19459
|
if (!watch) {
|
|
19438
19460
|
deps.warn?.("Release: Checking Vercel deployment status");
|
|
19439
|
-
read = await readVercelDeployment(
|
|
19461
|
+
read = await readVercelDeployment(vercelApi(deps), ctx.repo, headSha, target, after);
|
|
19440
19462
|
} else {
|
|
19441
|
-
deps.warn?.(
|
|
19442
|
-
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) => {
|
|
19443
19465
|
if (attempt > 0 && attempt % 6 === 0) deps.warn?.(`Vercel deployment ${d ? d.state : "not posted yet"} \u2014 ${attempt / 6}m elapsed`);
|
|
19444
|
-
});
|
|
19466
|
+
}, after);
|
|
19445
19467
|
}
|
|
19446
19468
|
} catch (e) {
|
|
19447
|
-
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" };
|
|
19448
19470
|
}
|
|
19449
|
-
let deployStatus =
|
|
19450
|
-
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?)";
|
|
19451
19473
|
if (watch && deployStatus === "success" && read?.url) {
|
|
19452
19474
|
const probe = await (deps.probeUrl ?? probeVercelUrl)(read.url);
|
|
19453
19475
|
if (probe.ok) detail = `${detail}; URL answered HTTP ${probe.status}`;
|
|
@@ -19456,7 +19478,27 @@ async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
|
|
|
19456
19478
|
detail = `${detail}; URL probe FAILED (${probe.error ?? `HTTP ${probe.status}`})`;
|
|
19457
19479
|
}
|
|
19458
19480
|
}
|
|
19459
|
-
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;
|
|
19460
19502
|
}
|
|
19461
19503
|
function shipLegsGreen(deploy, publish) {
|
|
19462
19504
|
const green = (leg) => leg?.superseded !== true && (leg?.state === "complete" || leg?.state === "skipped");
|
|
@@ -19638,7 +19680,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
19638
19680
|
deployStatus: aggregateWorkflowRuns(workflowRuns)
|
|
19639
19681
|
};
|
|
19640
19682
|
}
|
|
19641
|
-
if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha);
|
|
19683
|
+
if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha, autoRunSince);
|
|
19642
19684
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
19643
19685
|
}
|
|
19644
19686
|
async function readEdgeDomains(deps, slug) {
|
|
@@ -21907,6 +21949,16 @@ function deployPhaseInput(model, dispatch, opts = {}) {
|
|
|
21907
21949
|
...runMetadata({ runId: dispatch.runId, runUrl: dispatch.runUrl, workflow: "tenant-deploy.yml", event: "workflow_dispatch" }, runRepo, "dispatch"),
|
|
21908
21950
|
note: dispatch.note
|
|
21909
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
|
+
}
|
|
21910
21962
|
default:
|
|
21911
21963
|
return {
|
|
21912
21964
|
state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
|
|
@@ -21988,6 +22040,13 @@ function phaseInputsFromRunRows(model, rows, tagSha, opts = {}) {
|
|
|
21988
22040
|
note: `${publishRun.workflow} run on the release SHA`
|
|
21989
22041
|
} : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
|
|
21990
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
|
+
}
|
|
21991
22050
|
default:
|
|
21992
22051
|
return {
|
|
21993
22052
|
deploy: { state: "pending", sha: tagSha, note: `${model} deploys outside the release SHA's run set \u2014 historical outcome uncorrelatable` },
|
|
@@ -22194,6 +22253,17 @@ function hotfixRetryDeployShape(ledger) {
|
|
|
22194
22253
|
if (!open2.every((phase) => RETRY_DEPLOY_LEGS.has(phase))) return false;
|
|
22195
22254
|
return open2.some((phase) => ledger.phases[phase].state === "failed");
|
|
22196
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
|
+
}
|
|
22197
22267
|
function ledgerFailedRemedy(ledger) {
|
|
22198
22268
|
const derived = deriveReleasePhaseStatus(ledger);
|
|
22199
22269
|
const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
|
|
@@ -22202,6 +22272,9 @@ function ledgerFailedRemedy(ledger) {
|
|
|
22202
22272
|
const failed = openLegs.filter((l) => l.state === "failed").map((l) => l.phase);
|
|
22203
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}`;
|
|
22204
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
|
+
}
|
|
22205
22278
|
const deployOnlyDeadlock = openLegs.length > 0 && openLegs.every((l) => l.phase === "deploy" && supersedeableDeployLegShape(l));
|
|
22206
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)` : "";
|
|
22207
22280
|
return `diagnose the failed leg, then ${ledgerLaneResumeCommand(ledger)} once its cause is fixed (an abort only with fresh approval)${supersession}${tails}`;
|
|
@@ -22251,6 +22324,10 @@ async function healStaleAlignmentLeg(deps, cwd, expected) {
|
|
|
22251
22324
|
return prior.tag;
|
|
22252
22325
|
});
|
|
22253
22326
|
}
|
|
22327
|
+
function rcPromotedAt(ledger) {
|
|
22328
|
+
const at = Date.parse(ledger.phases.promotion.updatedAt);
|
|
22329
|
+
return Number.isFinite(at) ? at : void 0;
|
|
22330
|
+
}
|
|
22254
22331
|
async function healUnfoldedRunLegs(deps, cwd, model) {
|
|
22255
22332
|
const path2 = releaseLedgerPath(cwd);
|
|
22256
22333
|
let prior;
|
|
@@ -22282,15 +22359,16 @@ async function healUnfoldedRunLegs(deps, cwd, model) {
|
|
|
22282
22359
|
}
|
|
22283
22360
|
const targets = prodReleaseTargets(model);
|
|
22284
22361
|
const runless = legs.filter((phase) => openLeg(current.phases[phase]) && current.phases[phase].runId == null);
|
|
22285
|
-
if (runless.length > 0 && targets.length > 0) {
|
|
22362
|
+
if (runless.length > 0 && (targets.length > 0 || model === "vercel")) {
|
|
22286
22363
|
let rows;
|
|
22287
22364
|
try {
|
|
22288
|
-
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);
|
|
22289
22366
|
} catch {
|
|
22290
22367
|
rows = [];
|
|
22291
22368
|
}
|
|
22292
22369
|
const inputs = phaseInputsFromRunRows(model, rows, prior.tagSha, { repo: prior.repo });
|
|
22293
|
-
const
|
|
22370
|
+
const correlated = (entry) => entry.runId != null || entry.workflow === "vercel";
|
|
22371
|
+
const foldable = runless.filter((phase) => correlated(inputs[phase]) && inputs[phase].state !== "pending");
|
|
22294
22372
|
if (foldable.length > 0) {
|
|
22295
22373
|
const written = await withReleaseLedger(path2, anchors, (ledger) => {
|
|
22296
22374
|
if (!ledger) return { value: [] };
|
|
@@ -23188,6 +23266,13 @@ async function runTrainDoctor(input) {
|
|
|
23188
23266
|
unverified(`required status checks on ${stage}`, e);
|
|
23189
23267
|
}
|
|
23190
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
|
+
}
|
|
23191
23276
|
let ledgerTag;
|
|
23192
23277
|
let ledgerPath;
|
|
23193
23278
|
try {
|
|
@@ -24152,10 +24237,15 @@ async function recoverFailedDispatchPhase(deps, ledger, anchors, phase, dispatch
|
|
|
24152
24237
|
note: notes.length > 0 ? `${dispatch.note}; ${notes.join("; ")}` : dispatch.note
|
|
24153
24238
|
};
|
|
24154
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
|
+
}
|
|
24155
24245
|
async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts = {}) {
|
|
24156
24246
|
const sha = anchors.tagSha;
|
|
24157
24247
|
const landed = "the immutable rc tag, the green wall, origin/rc, and the rc deploy dispatch";
|
|
24158
|
-
const
|
|
24248
|
+
const recovered = await recoverFailedDispatchPhase(
|
|
24159
24249
|
deps,
|
|
24160
24250
|
ledger,
|
|
24161
24251
|
anchors,
|
|
@@ -24168,6 +24258,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
24168
24258
|
workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "rc deploy workflow"
|
|
24169
24259
|
}
|
|
24170
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;
|
|
24171
24262
|
const unverifiable = !isCentralDispatchModel(deployModel) && dispatch.deployStatus === "pending" && dispatch.runId == null && !dispatch.workflowRuns?.length;
|
|
24172
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 });
|
|
24173
24264
|
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployEntry), { ...opts, landed: "the immutable rc tag, the green wall, and origin/rc" });
|
|
@@ -24176,7 +24267,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
24176
24267
|
return dispatch;
|
|
24177
24268
|
}
|
|
24178
24269
|
function releaseAnnounceDeferral(model, summaryFile) {
|
|
24179
|
-
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";
|
|
24180
24271
|
}
|
|
24181
24272
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
|
|
24182
24273
|
const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
|
|
@@ -24463,7 +24554,8 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
|
24463
24554
|
const terminal = deployLeg.state === "complete" || deployLeg.state === "skipped";
|
|
24464
24555
|
const legStatus = deployLeg.state === "failed" ? "failure" : terminal ? "success" : "pending";
|
|
24465
24556
|
const hasDispatchEvidence = deployLeg.runId != null || deployLeg.workflow != null || deployLeg.updatedAt !== "never";
|
|
24466
|
-
const
|
|
24557
|
+
const vercelReread = deployModel === "vercel" && !terminal;
|
|
24558
|
+
const dispatch02 = (terminal || hasDispatchEvidence) && !vercelReread ? {
|
|
24467
24559
|
note: deployLeg.note ?? deployLeg.error ?? `persisted rc deploy leg is ${deployLeg.state}`,
|
|
24468
24560
|
deployStatus: legStatus,
|
|
24469
24561
|
...deployLeg.runId != null ? { runId: deployLeg.runId } : {},
|
|
@@ -24475,7 +24567,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
|
24475
24567
|
...deployLeg.event ? { event: deployLeg.event } : {},
|
|
24476
24568
|
conclusion: legStatus
|
|
24477
24569
|
}] } : {}
|
|
24478
|
-
} : 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");
|
|
24479
24571
|
const dispatch2 = terminal ? dispatch02 : await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch02, { strict: true });
|
|
24480
24572
|
const ledgerReport2 = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24481
24573
|
return {
|
|
@@ -24541,10 +24633,11 @@ ${spent.note}`);
|
|
|
24541
24633
|
}
|
|
24542
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" });
|
|
24543
24635
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
24636
|
+
deps.warn?.("Release: Promoting the verified release candidate");
|
|
24544
24637
|
await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/rc`]);
|
|
24545
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" });
|
|
24546
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" });
|
|
24547
|
-
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");
|
|
24548
24641
|
const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch0, { strict });
|
|
24549
24642
|
const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24550
24643
|
return {
|
|
@@ -24566,6 +24659,64 @@ ${spent.note}`);
|
|
|
24566
24659
|
note: `reused immutable ${tag}; pushed its exact SHA to rc and dispatched the rc deploy`
|
|
24567
24660
|
};
|
|
24568
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
|
+
}
|
|
24569
24720
|
async function runReleaseResumePipeline(deps, options, prepared) {
|
|
24570
24721
|
const watch = options.watch ?? false;
|
|
24571
24722
|
const { ctx, meta, branchHints, directTrack, deployModel } = prepared;
|
|
@@ -24629,8 +24780,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24629
24780
|
deps.warn?.("Release: Checking deployment and publishing progress");
|
|
24630
24781
|
const historicalTargets = prodReleaseTargets(deployModel);
|
|
24631
24782
|
const historicalRepo = releaseRunRepoFor(deployModel, ctx.repo);
|
|
24632
|
-
|
|
24633
|
-
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";
|
|
24634
24786
|
let historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}`;
|
|
24635
24787
|
if (historicalDeploy === "failure") {
|
|
24636
24788
|
const recovered = await recoverFailedDispatchPhase(
|
|
@@ -24642,7 +24794,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24642
24794
|
{ repo: historicalRepo, sha: tagSha, watched: false, workflow: "historical release workflow" }
|
|
24643
24795
|
);
|
|
24644
24796
|
historicalRows = recovered.workflowRuns ?? historicalRows;
|
|
24645
|
-
historicalDeploy = historicalTargets.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
|
|
24797
|
+
historicalDeploy = historicalTargets.length || vercel && historicalRows.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
|
|
24646
24798
|
historicalNote = recovered.note;
|
|
24647
24799
|
}
|
|
24648
24800
|
const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
|
|
@@ -24680,7 +24832,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24680
24832
|
Object.assign(dispatch2, shippedDispatch);
|
|
24681
24833
|
if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
|
|
24682
24834
|
const settledBySupersede = persisted?.phases.deploy.superseded === true || persisted?.phases.publish.superseded === true;
|
|
24683
|
-
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(
|
|
24684
24836
|
deps,
|
|
24685
24837
|
{ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
|
|
24686
24838
|
deployModel,
|
|
@@ -25128,6 +25280,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
25128
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)`);
|
|
25129
25281
|
ledgerAnchors2 = anchors2;
|
|
25130
25282
|
}
|
|
25283
|
+
deps.warn?.("Release: Publishing the release candidate tag");
|
|
25131
25284
|
tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
25132
25285
|
} catch (e) {
|
|
25133
25286
|
if (ledger && ledgerAnchors2) {
|
|
@@ -25164,6 +25317,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
25164
25317
|
}
|
|
25165
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) });
|
|
25166
25319
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
25320
|
+
deps.warn?.("Release: Promoting the verified release candidate");
|
|
25167
25321
|
try {
|
|
25168
25322
|
await runGitPush(deps, ["push", "origin", "rc"]);
|
|
25169
25323
|
} catch (e) {
|
|
@@ -25172,7 +25326,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
25172
25326
|
}
|
|
25173
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" });
|
|
25174
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" });
|
|
25175
|
-
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");
|
|
25176
25330
|
const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
|
|
25177
25331
|
const rcLedgerReport = releaseLedgerReport(await ledger?.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
25178
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 } : {} };
|
|
@@ -25462,7 +25616,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
25462
25616
|
if (directTrack) {
|
|
25463
25617
|
throw new Error("direct-track repos release straight to main (no rc); run `mmi-cli devops release --apply` from development instead of rcand");
|
|
25464
25618
|
}
|
|
25465
|
-
return runTrainApplyPipeline("rcand", pipelineInput);
|
|
25619
|
+
return withCheckoutRestoredAfterRelease(deps, await runTrainApplyPipeline("rcand", pipelineInput), "development");
|
|
25466
25620
|
}
|
|
25467
25621
|
if (directTrack) {
|
|
25468
25622
|
return withCheckoutRestoredAfterRelease(
|
|
@@ -38194,8 +38348,9 @@ function registerStageCommands(program3) {
|
|
|
38194
38348
|
const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
38195
38349
|
const repo = Array.isArray(project2.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : await repoSlug();
|
|
38196
38350
|
try {
|
|
38351
|
+
const sha = await gitOut(["rev-parse", `origin/${branch}`]).catch(() => gitOut(["rev-parse", "HEAD"]));
|
|
38197
38352
|
const gh = defaultGitHubClient();
|
|
38198
|
-
const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo,
|
|
38353
|
+
const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo, sha, "preview");
|
|
38199
38354
|
return { branch, ...deployment ? { deployment } : {} };
|
|
38200
38355
|
} catch (e) {
|
|
38201
38356
|
return { branch, error: e.message };
|
|
@@ -38636,7 +38791,7 @@ function renderVerifySecrets(body) {
|
|
|
38636
38791
|
|
|
38637
38792
|
// src/command-register-collaboration.ts
|
|
38638
38793
|
var import_node_child_process15 = require("node:child_process");
|
|
38639
|
-
var
|
|
38794
|
+
var import_node_fs43 = require("node:fs");
|
|
38640
38795
|
var import_promises7 = require("node:fs/promises");
|
|
38641
38796
|
init_clean_exit();
|
|
38642
38797
|
init_cli_shared();
|
|
@@ -38660,7 +38815,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
38660
38815
|
}
|
|
38661
38816
|
|
|
38662
38817
|
// src/command-register-collaboration.ts
|
|
38663
|
-
var
|
|
38818
|
+
var import_node_path41 = require("node:path");
|
|
38664
38819
|
|
|
38665
38820
|
// src/attach-to-project.ts
|
|
38666
38821
|
async function attachViaHubApp(cfg, contentNodeId) {
|
|
@@ -39744,9 +39899,9 @@ function changedFilesSince(base, cwd) {
|
|
|
39744
39899
|
const raw = git2(["diff", "--name-status", "-M", base, "--"], cwd).trim();
|
|
39745
39900
|
const changed = raw ? raw.split("\n").map((line) => {
|
|
39746
39901
|
const parts = line.split(" ");
|
|
39747
|
-
const
|
|
39748
|
-
const row = { status: parts[0][0], path:
|
|
39749
|
-
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]);
|
|
39750
39905
|
return row;
|
|
39751
39906
|
}) : [];
|
|
39752
39907
|
const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
|
|
@@ -40809,6 +40964,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
40809
40964
|
return void 0;
|
|
40810
40965
|
}
|
|
40811
40966
|
|
|
40967
|
+
// src/worktree-residue-sweep.ts
|
|
40968
|
+
var import_node_fs42 = require("node:fs");
|
|
40969
|
+
var import_node_path40 = require("node:path");
|
|
40970
|
+
|
|
40812
40971
|
// src/worktree-merge-cleanup.ts
|
|
40813
40972
|
var import_node_fs41 = require("node:fs");
|
|
40814
40973
|
var import_node_path39 = require("node:path");
|
|
@@ -41654,6 +41813,122 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
41654
41813
|
return lines2;
|
|
41655
41814
|
}
|
|
41656
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
|
+
|
|
41657
41932
|
// src/pr-deploy-dev.ts
|
|
41658
41933
|
function slugOfRepo(repo) {
|
|
41659
41934
|
return repo.split("/")[1]?.toLowerCase() ?? "";
|
|
@@ -41688,10 +41963,10 @@ function argvWantsJson2() {
|
|
|
41688
41963
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
41689
41964
|
}
|
|
41690
41965
|
function hubRoot() {
|
|
41691
|
-
const fromPkg = (0,
|
|
41966
|
+
const fromPkg = (0, import_node_path41.join)(__dirname, "..", "..");
|
|
41692
41967
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
41693
|
-
if ((0,
|
|
41694
|
-
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();
|
|
41695
41970
|
return null;
|
|
41696
41971
|
}
|
|
41697
41972
|
function ciAuditDeps() {
|
|
@@ -41703,8 +41978,8 @@ function ciAuditDeps() {
|
|
|
41703
41978
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
41704
41979
|
readSeedFile: (path2) => {
|
|
41705
41980
|
if (!root) return null;
|
|
41706
|
-
const fullPath = (0,
|
|
41707
|
-
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;
|
|
41708
41983
|
}
|
|
41709
41984
|
};
|
|
41710
41985
|
}
|
|
@@ -42429,11 +42704,11 @@ ${list}`);
|
|
|
42429
42704
|
}
|
|
42430
42705
|
});
|
|
42431
42706
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
42432
|
-
const wfDir = (0,
|
|
42433
|
-
if (!(0,
|
|
42434
|
-
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) => {
|
|
42435
42710
|
try {
|
|
42436
|
-
return workflowReportsPrChecks((0,
|
|
42711
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path41.join)(wfDir, name), "utf8"));
|
|
42437
42712
|
} catch {
|
|
42438
42713
|
return true;
|
|
42439
42714
|
}
|
|
@@ -42629,7 +42904,7 @@ ${list}`);
|
|
|
42629
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) => {
|
|
42630
42905
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
42631
42906
|
const verdict = o.verdict;
|
|
42632
|
-
const findings = o.findingsFile ? (0,
|
|
42907
|
+
const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
42633
42908
|
let patch;
|
|
42634
42909
|
let head;
|
|
42635
42910
|
try {
|
|
@@ -42903,7 +43178,7 @@ ${list}`);
|
|
|
42903
43178
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
42904
43179
|
closingGuardInput,
|
|
42905
43180
|
method === "--squash",
|
|
42906
|
-
o.squashBodyFile ? (0,
|
|
43181
|
+
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
42907
43182
|
);
|
|
42908
43183
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
42909
43184
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -42935,7 +43210,7 @@ ${list}`);
|
|
|
42935
43210
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
42936
43211
|
let foreignCheckout;
|
|
42937
43212
|
if (foreignCwd) {
|
|
42938
|
-
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]);
|
|
42939
43214
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
42940
43215
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
42941
43216
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -43033,7 +43308,7 @@ ${list}`);
|
|
|
43033
43308
|
}
|
|
43034
43309
|
if (!repoForPostCleanup) throw e;
|
|
43035
43310
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
43036
|
-
const commitMessage = bodyFile ? (0,
|
|
43311
|
+
const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
|
|
43037
43312
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
43038
43313
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
43039
43314
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -43140,6 +43415,15 @@ ${list}`);
|
|
|
43140
43415
|
return;
|
|
43141
43416
|
}
|
|
43142
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
|
+
}
|
|
43143
43427
|
let localCleanup;
|
|
43144
43428
|
if (foreignCwd && !foreignCheckout) {
|
|
43145
43429
|
localCleanup = { branch: headRef, localBranch: { name: headRef, status: "not-attempted", reason: "skipped-foreign-cwd" } };
|
|
@@ -43153,7 +43437,7 @@ ${list}`);
|
|
|
43153
43437
|
preserveWorktree: o.preserveWorktree,
|
|
43154
43438
|
gcAcknowledged: o.gc,
|
|
43155
43439
|
expectedHeadOid: headRefOid,
|
|
43156
|
-
pathExists: (p) => (0,
|
|
43440
|
+
pathExists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
43157
43441
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
43158
43442
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
43159
43443
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -43184,6 +43468,7 @@ ${list}`);
|
|
|
43184
43468
|
remote
|
|
43185
43469
|
});
|
|
43186
43470
|
const worktree = localCleanup?.worktree;
|
|
43471
|
+
if (gitCommonDir && worktree?.path && worktree.residue === "left") recordResidue(gitCommonDir, worktree.path);
|
|
43187
43472
|
const worktreePartial = isPrMergeWorktreePartial(localCleanup) && worktree?.path ? {
|
|
43188
43473
|
kind: "worktree-directory",
|
|
43189
43474
|
path: worktree.path,
|
|
@@ -43248,6 +43533,7 @@ ${list}`);
|
|
|
43248
43533
|
...partialCleanup.length ? { cleanupStatus: "partial", partialCleanup } : {},
|
|
43249
43534
|
...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
|
|
43250
43535
|
...localCleanup?.localBranch ? { localBranch: localCleanup.localBranch } : {},
|
|
43536
|
+
...residueSweep && (residueSweep.swept.length || residueSweep.emptyRemoved.length || residueSweep.kept.length || residueSweep.unrecorded.length) ? { residueSweep } : {},
|
|
43251
43537
|
// `boardAdvance` keeps its published array shape; `boardAdvanceStatus` is the field a caller reads to
|
|
43252
43538
|
// tell "merged clean" from "merged, but the board did not follow" without guessing from the exit code.
|
|
43253
43539
|
...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
|
|
@@ -43257,6 +43543,7 @@ ${list}`);
|
|
|
43257
43543
|
...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
|
|
43258
43544
|
}));
|
|
43259
43545
|
for (const line of renderPrMergeCleanupLines(localCleanup)) console.warn(line);
|
|
43546
|
+
for (const line of renderResidueSweepLines(residueSweep)) console.warn(line);
|
|
43260
43547
|
if (devDeploy && !devDeploy.ok) {
|
|
43261
43548
|
console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
|
|
43262
43549
|
}
|
|
@@ -43274,7 +43561,7 @@ ${list}`);
|
|
|
43274
43561
|
}
|
|
43275
43562
|
|
|
43276
43563
|
// src/command-register-developer.ts
|
|
43277
|
-
var
|
|
43564
|
+
var import_node_fs48 = require("node:fs");
|
|
43278
43565
|
init_clean_exit();
|
|
43279
43566
|
init_cli_shared();
|
|
43280
43567
|
init_error_codes();
|
|
@@ -43307,7 +43594,7 @@ async function resolveWhoami(deps) {
|
|
|
43307
43594
|
}
|
|
43308
43595
|
|
|
43309
43596
|
// src/command-register-developer.ts
|
|
43310
|
-
var
|
|
43597
|
+
var import_node_path46 = require("node:path");
|
|
43311
43598
|
init_house_map();
|
|
43312
43599
|
init_hub_url();
|
|
43313
43600
|
|
|
@@ -43340,7 +43627,7 @@ async function executeWaveLand(plan, deps) {
|
|
|
43340
43627
|
}
|
|
43341
43628
|
|
|
43342
43629
|
// src/box-commands.ts
|
|
43343
|
-
var
|
|
43630
|
+
var import_node_fs44 = require("node:fs");
|
|
43344
43631
|
init_clean_exit();
|
|
43345
43632
|
|
|
43346
43633
|
// src/box.ts
|
|
@@ -43552,7 +43839,7 @@ function registerBoxCommands(program3) {
|
|
|
43552
43839
|
}
|
|
43553
43840
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
43554
43841
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
43555
|
-
if (wroteScript) (0,
|
|
43842
|
+
if (wroteScript) (0, import_node_fs44.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
43556
43843
|
if (o.json) {
|
|
43557
43844
|
console.log(JSON.stringify({
|
|
43558
43845
|
box: found,
|
|
@@ -43574,19 +43861,19 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
43574
43861
|
// src/dist-drift.ts
|
|
43575
43862
|
var import_node_child_process16 = require("node:child_process");
|
|
43576
43863
|
var import_node_crypto13 = require("node:crypto");
|
|
43577
|
-
var
|
|
43864
|
+
var import_node_fs46 = require("node:fs");
|
|
43578
43865
|
var import_node_os21 = require("node:os");
|
|
43579
|
-
var
|
|
43866
|
+
var import_node_path43 = require("node:path");
|
|
43580
43867
|
|
|
43581
43868
|
// ../scripts/distribution-digest.mjs
|
|
43582
43869
|
var import_node_crypto12 = require("node:crypto");
|
|
43583
|
-
var
|
|
43584
|
-
var
|
|
43870
|
+
var import_node_fs45 = require("node:fs");
|
|
43871
|
+
var import_node_path42 = require("node:path");
|
|
43585
43872
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
43586
43873
|
function repoPath(root, declaredPath, label) {
|
|
43587
|
-
const absoluteRoot = (0,
|
|
43588
|
-
const target = (0,
|
|
43589
|
-
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}`)) {
|
|
43590
43877
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
43591
43878
|
}
|
|
43592
43879
|
return target;
|
|
@@ -43594,7 +43881,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
43594
43881
|
function digestFiles(files) {
|
|
43595
43882
|
const hash = (0, import_node_crypto12.createHash)("sha256");
|
|
43596
43883
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
43597
|
-
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);
|
|
43598
43885
|
hash.update(file.relative, "utf8");
|
|
43599
43886
|
hash.update("\0");
|
|
43600
43887
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -43609,8 +43896,8 @@ function digestFiles(files) {
|
|
|
43609
43896
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
43610
43897
|
return digestFiles(packedFiles.map((path2) => {
|
|
43611
43898
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
43612
|
-
if (!(0,
|
|
43613
|
-
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) };
|
|
43614
43901
|
}));
|
|
43615
43902
|
}
|
|
43616
43903
|
|
|
@@ -43694,13 +43981,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
43694
43981
|
return lines2;
|
|
43695
43982
|
}
|
|
43696
43983
|
function readOrNull(path2) {
|
|
43697
|
-
return (0,
|
|
43984
|
+
return (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2) : null;
|
|
43698
43985
|
}
|
|
43699
43986
|
function walkFiles(root) {
|
|
43700
43987
|
const files = [];
|
|
43701
43988
|
const walk2 = (directory) => {
|
|
43702
|
-
for (const entry of (0,
|
|
43703
|
-
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);
|
|
43704
43991
|
if (entry.isDirectory()) walk2(child2);
|
|
43705
43992
|
else files.push(child2);
|
|
43706
43993
|
}
|
|
@@ -43710,10 +43997,10 @@ function walkFiles(root) {
|
|
|
43710
43997
|
}
|
|
43711
43998
|
function bomPathFor(root) {
|
|
43712
43999
|
try {
|
|
43713
|
-
const registry2 = JSON.parse((0,
|
|
43714
|
-
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");
|
|
43715
44002
|
} catch {
|
|
43716
|
-
return (0,
|
|
44003
|
+
return (0, import_node_path43.join)(root, "distribution-bom.json");
|
|
43717
44004
|
}
|
|
43718
44005
|
}
|
|
43719
44006
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -43726,35 +44013,35 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
43726
44013
|
});
|
|
43727
44014
|
}
|
|
43728
44015
|
function runDistStatus(root) {
|
|
43729
|
-
const stage = (0,
|
|
44016
|
+
const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
|
|
43730
44017
|
let overlayCount = 0;
|
|
43731
44018
|
try {
|
|
43732
|
-
const cliOut = (0,
|
|
43733
|
-
const hubOut = (0,
|
|
43734
|
-
rebuildTo((0,
|
|
43735
|
-
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);
|
|
43736
44023
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
43737
44024
|
const rebuilt = (path2) => {
|
|
43738
44025
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
43739
|
-
return spec ? readOrNull((0,
|
|
44026
|
+
return spec ? readOrNull((0, import_node_path43.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
43740
44027
|
};
|
|
43741
|
-
const committed = (path2) => readOrNull((0,
|
|
43742
|
-
const tree = (path2) => readOrNull((0,
|
|
43743
|
-
const distRoot = (0,
|
|
43744
|
-
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0,
|
|
43745
|
-
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"));
|
|
43746
44033
|
const digest = (entries) => {
|
|
43747
|
-
const overlay = (0,
|
|
44034
|
+
const overlay = (0, import_node_path43.join)(stage, `overlay-${overlayCount++}`);
|
|
43748
44035
|
for (const entry of entries) {
|
|
43749
|
-
const target = (0,
|
|
43750
|
-
(0,
|
|
43751
|
-
(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);
|
|
43752
44039
|
}
|
|
43753
44040
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
43754
44041
|
};
|
|
43755
44042
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
43756
44043
|
} finally {
|
|
43757
|
-
(0,
|
|
44044
|
+
(0, import_node_fs46.rmSync)(stage, { recursive: true, force: true });
|
|
43758
44045
|
}
|
|
43759
44046
|
}
|
|
43760
44047
|
|
|
@@ -43833,7 +44120,7 @@ init_hub_auth();
|
|
|
43833
44120
|
|
|
43834
44121
|
// src/schedules-lift-command.ts
|
|
43835
44122
|
var import_promises8 = require("node:fs/promises");
|
|
43836
|
-
var
|
|
44123
|
+
var import_node_path44 = require("node:path");
|
|
43837
44124
|
init_clean_exit();
|
|
43838
44125
|
init_cli_shared();
|
|
43839
44126
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -43862,7 +44149,7 @@ async function readWorkflowFiles(dir) {
|
|
|
43862
44149
|
const files = [];
|
|
43863
44150
|
for (const name of names.sort()) {
|
|
43864
44151
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
43865
|
-
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") });
|
|
43866
44153
|
}
|
|
43867
44154
|
return files;
|
|
43868
44155
|
}
|
|
@@ -43945,8 +44232,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
43945
44232
|
|
|
43946
44233
|
// src/spawn-policy-core.ts
|
|
43947
44234
|
var import_node_child_process17 = require("node:child_process");
|
|
43948
|
-
var
|
|
43949
|
-
var
|
|
44235
|
+
var import_node_fs47 = require("node:fs");
|
|
44236
|
+
var import_node_path45 = require("node:path");
|
|
43950
44237
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
43951
44238
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
43952
44239
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -44032,7 +44319,7 @@ function runSpawnPolicy(root) {
|
|
|
44032
44319
|
for (const file of files) {
|
|
44033
44320
|
let raw;
|
|
44034
44321
|
try {
|
|
44035
|
-
raw = (0,
|
|
44322
|
+
raw = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)(root, file), "utf8");
|
|
44036
44323
|
} catch {
|
|
44037
44324
|
continue;
|
|
44038
44325
|
}
|
|
@@ -44052,19 +44339,19 @@ function runSpawnPolicy(root) {
|
|
|
44052
44339
|
function registerDeveloperCommands(program3) {
|
|
44053
44340
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
44054
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) => {
|
|
44055
|
-
const path2 = (0,
|
|
44056
|
-
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;
|
|
44057
44344
|
const plan = planManagedGitignore(current);
|
|
44058
44345
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
44059
44346
|
if (opts.json) {
|
|
44060
|
-
if (opts.write && plan.changed) (0,
|
|
44347
|
+
if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
44061
44348
|
console.log(JSON.stringify(plan, null, 2));
|
|
44062
44349
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
44063
44350
|
return;
|
|
44064
44351
|
}
|
|
44065
44352
|
if (opts.write) {
|
|
44066
44353
|
if (plan.changed) {
|
|
44067
|
-
(0,
|
|
44354
|
+
(0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
44068
44355
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
44069
44356
|
} else {
|
|
44070
44357
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -45295,6 +45582,10 @@ ${decision.note}`);
|
|
|
45295
45582
|
deployNote = dispatch.note;
|
|
45296
45583
|
runs.push(hotfixWorkflowRunFromDispatch("tenant-deploy.yml", deploy));
|
|
45297
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;
|
|
45298
45589
|
} else {
|
|
45299
45590
|
deployNote = `no hotfix deploy dispatch for deployModel=${deployModel} \u2014 prod deploy is repo-specific`;
|
|
45300
45591
|
}
|
|
@@ -45804,8 +46095,8 @@ function checkHotfixCarries(options) {
|
|
|
45804
46095
|
}
|
|
45805
46096
|
|
|
45806
46097
|
// src/train-commands.ts
|
|
45807
|
-
var
|
|
45808
|
-
var
|
|
46098
|
+
var import_node_fs49 = require("node:fs");
|
|
46099
|
+
var import_node_path47 = require("node:path");
|
|
45809
46100
|
init_cli_shared();
|
|
45810
46101
|
init_clean_exit();
|
|
45811
46102
|
init_client_version();
|
|
@@ -45820,7 +46111,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
45820
46111
|
}
|
|
45821
46112
|
function readRepoVersion() {
|
|
45822
46113
|
try {
|
|
45823
|
-
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;
|
|
45824
46115
|
} catch {
|
|
45825
46116
|
return void 0;
|
|
45826
46117
|
}
|
|
@@ -45922,8 +46213,8 @@ async function withReleaseProgress(deps, initial, action) {
|
|
|
45922
46213
|
clearInterval(heartbeat);
|
|
45923
46214
|
}
|
|
45924
46215
|
}
|
|
45925
|
-
function releaseProgressVerdict(status) {
|
|
45926
|
-
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`;
|
|
45927
46218
|
}
|
|
45928
46219
|
|
|
45929
46220
|
// src/command-register-train-operations.ts
|
|
@@ -46160,8 +46451,31 @@ function renderReleasePublishRetry(r) {
|
|
|
46160
46451
|
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
46161
46452
|
}
|
|
46162
46453
|
function renderRcandResume(r) {
|
|
46163
|
-
const
|
|
46164
|
-
|
|
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");
|
|
46165
46479
|
}
|
|
46166
46480
|
function renderAlignment(label, alignment) {
|
|
46167
46481
|
if (alignment.status !== "pr-pending") return `${label}: ${alignment.note}`;
|
|
@@ -46269,16 +46583,17 @@ function buildReleaseVerdict(commandName, result, projectInfoSync) {
|
|
|
46269
46583
|
alignment: alignmentPending ? "unresolved" : "ok",
|
|
46270
46584
|
foldPort: "ok"
|
|
46271
46585
|
});
|
|
46586
|
+
const legs = releaseFollowUpLegs(result, projectInfoSync);
|
|
46272
46587
|
return {
|
|
46273
46588
|
followUpStatus,
|
|
46274
|
-
releaseVerdict:
|
|
46589
|
+
releaseVerdict: {
|
|
46275
46590
|
releaseStatus: "succeeded",
|
|
46276
46591
|
followUpStatus,
|
|
46277
46592
|
promoted: result.promoted,
|
|
46278
46593
|
tag: result.tag,
|
|
46279
46594
|
releaseUrl: result.release?.url ?? null,
|
|
46280
|
-
legs:
|
|
46281
|
-
}
|
|
46595
|
+
legs: commandName === "rcand" ? legs.filter((leg) => leg.leg !== "project-info") : legs
|
|
46596
|
+
}
|
|
46282
46597
|
};
|
|
46283
46598
|
}
|
|
46284
46599
|
function buildResumeReleaseVerdict(result, projectInfoSync) {
|
|
@@ -46331,22 +46646,7 @@ function renderTrainApply(commandName, r) {
|
|
|
46331
46646
|
base = `${base}; project info: ${r.projectInfoSync.note}`;
|
|
46332
46647
|
}
|
|
46333
46648
|
if (r.announceNote) base = `${base}; announce: ${r.announceNote}`;
|
|
46334
|
-
|
|
46335
|
-
const v = r.releaseVerdict;
|
|
46336
|
-
const verdict = v.followUpStatus === "failed" ? "PROMOTED, FOLLOW-UP FAILED" : v.followUpStatus === "pending" ? "PROMOTED, FOLLOW-UP PENDING" : "SUCCEEDED";
|
|
46337
|
-
const failedLegs = v.legs.filter((leg) => leg.status === "failed");
|
|
46338
|
-
const followUpNote = trainFollowUpNote(v.followUpStatus);
|
|
46339
|
-
return [
|
|
46340
|
-
`${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}${followUpNote ? ` (${followUpNote})` : ""}`,
|
|
46341
|
-
...failedLegs.map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`),
|
|
46342
|
-
// #5987: the durable ledger's phase legs + derived typed verdict ride the same receipt.
|
|
46343
|
-
...r.ledger ? formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`) : []
|
|
46344
|
-
].join("\n");
|
|
46345
|
-
}
|
|
46346
|
-
if (r.ledger) {
|
|
46347
|
-
return [base, ...formatReleaseLedgerReport(r.ledger).map((line) => ` ${line}`)].join("\n");
|
|
46348
|
-
}
|
|
46349
|
-
return base;
|
|
46649
|
+
return appendVerdictAndLedger(base, r.releaseVerdict, r.ledger);
|
|
46350
46650
|
}
|
|
46351
46651
|
function applyTrainFollowUpExit(status) {
|
|
46352
46652
|
const code = trainFollowUpExitCode(status);
|
|
@@ -46396,6 +46696,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46396
46696
|
trainCmd.addOption(new Option(f.flags, f.description).hideHelp());
|
|
46397
46697
|
}
|
|
46398
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());
|
|
46399
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) => {
|
|
46400
46702
|
try {
|
|
46401
46703
|
await requireFreshTrainCli(commandName);
|
|
@@ -46423,8 +46725,30 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46423
46725
|
if (o.retryPublish && commandName !== "release") {
|
|
46424
46726
|
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
|
|
46425
46727
|
}
|
|
46426
|
-
if (o.
|
|
46427
|
-
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
|
+
}
|
|
46428
46752
|
}
|
|
46429
46753
|
if (o.supersedeDeploy && o.supersedePublish) {
|
|
46430
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");
|
|
@@ -46528,17 +46852,12 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46528
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`);
|
|
46529
46853
|
try {
|
|
46530
46854
|
if (commandName === "rcand") {
|
|
46531
|
-
const raw2 = await
|
|
46532
|
-
|
|
46533
|
-
|
|
46534
|
-
|
|
46535
|
-
|
|
46536
|
-
|
|
46537
|
-
rcRetirement: "ok",
|
|
46538
|
-
alignment: "ok",
|
|
46539
|
-
foldPort: "ok"
|
|
46540
|
-
}));
|
|
46541
|
-
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);
|
|
46542
46861
|
}
|
|
46543
46862
|
const raw = await withReleaseProgress(
|
|
46544
46863
|
trainApplyDeps(),
|
|
@@ -46559,7 +46878,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46559
46878
|
return;
|
|
46560
46879
|
} catch (e) {
|
|
46561
46880
|
applyTrainFollowUpExit("failed");
|
|
46562
|
-
|
|
46881
|
+
consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
|
|
46563
46882
|
return failGraceful(`${commandName} --resume: ${e.message}`);
|
|
46564
46883
|
}
|
|
46565
46884
|
}
|
|
@@ -46603,7 +46922,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46603
46922
|
try {
|
|
46604
46923
|
const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
46605
46924
|
const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev });
|
|
46606
|
-
const raw = commandName === "release" ?
|
|
46925
|
+
const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
|
|
46607
46926
|
const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
|
|
46608
46927
|
let projectInfoSync;
|
|
46609
46928
|
if (commandName === "release") {
|
|
@@ -46621,14 +46940,14 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46621
46940
|
};
|
|
46622
46941
|
const output = o.json ? JSON.stringify(reported, null, 2) : renderTrainApply(commandName, reported);
|
|
46623
46942
|
emitTrainResult(`${commandName} apply`, output, o.out);
|
|
46624
|
-
|
|
46943
|
+
consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus, progressSubject(commandName))}`);
|
|
46625
46944
|
applyTrainFollowUpExit(followUpStatus);
|
|
46626
46945
|
return;
|
|
46627
46946
|
} catch (e) {
|
|
46628
46947
|
const error = e;
|
|
46629
46948
|
writeTrainFailureReceipt(commandName, o.json === true, o.out, error);
|
|
46630
46949
|
process.exitCode = 1;
|
|
46631
|
-
|
|
46950
|
+
consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
|
|
46632
46951
|
return await failGraceful(`${commandName}: ${error.message}`);
|
|
46633
46952
|
}
|
|
46634
46953
|
}
|
|
@@ -46808,7 +47127,7 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
46808
47127
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
46809
47128
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
46810
47129
|
function envHealLockPath(home) {
|
|
46811
|
-
return (0,
|
|
47130
|
+
return (0, import_node_path48.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
46812
47131
|
}
|
|
46813
47132
|
async function withEnvHealLock(what, run) {
|
|
46814
47133
|
try {
|
|
@@ -46945,7 +47264,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46945
47264
|
);
|
|
46946
47265
|
const result = applyPluginCachePlan(
|
|
46947
47266
|
plan,
|
|
46948
|
-
(p) => (0,
|
|
47267
|
+
(p) => (0, import_node_fs50.rmSync)(p, { recursive: true }),
|
|
46949
47268
|
stagingApplyFsGuard(configRoot)
|
|
46950
47269
|
);
|
|
46951
47270
|
return {
|
|
@@ -46980,7 +47299,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46980
47299
|
// has no generated routing index would
|
|
46981
47300
|
// get a permanent — demanding an artifact it never asked for.
|
|
46982
47301
|
docsIndexState: (root) => {
|
|
46983
|
-
if (!(0,
|
|
47302
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
46984
47303
|
const real = createDocsIndexDeps(root);
|
|
46985
47304
|
let docs;
|
|
46986
47305
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -46989,7 +47308,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46989
47308
|
},
|
|
46990
47309
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
46991
47310
|
healDocsIndex: (root) => {
|
|
46992
|
-
if (!(0,
|
|
47311
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
46993
47312
|
const real = createDocsIndexDeps(root);
|
|
46994
47313
|
let docs;
|
|
46995
47314
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -47615,7 +47934,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
47615
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`);
|
|
47616
47935
|
if (o.secretsFile) {
|
|
47617
47936
|
try {
|
|
47618
|
-
vars.push(`secrets=${(0,
|
|
47937
|
+
vars.push(`secrets=${(0, import_node_fs50.readFileSync)(o.secretsFile, "utf8")}`);
|
|
47619
47938
|
} catch (e) {
|
|
47620
47939
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
47621
47940
|
}
|
|
@@ -47948,16 +48267,16 @@ function ciAuditDeps2() {
|
|
|
47948
48267
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
47949
48268
|
readSeedFile: (path2) => {
|
|
47950
48269
|
if (!root) return null;
|
|
47951
|
-
const fullPath = (0,
|
|
47952
|
-
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;
|
|
47953
48272
|
}
|
|
47954
48273
|
};
|
|
47955
48274
|
}
|
|
47956
48275
|
function hubRoot2() {
|
|
47957
|
-
const fromPkg = (0,
|
|
48276
|
+
const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
|
|
47958
48277
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
47959
|
-
if ((0,
|
|
47960
|
-
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();
|
|
47961
48280
|
return null;
|
|
47962
48281
|
}
|
|
47963
48282
|
registerQueryCommands(program2);
|
|
@@ -48057,10 +48376,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
48057
48376
|
targets = resolution.targets;
|
|
48058
48377
|
}
|
|
48059
48378
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
48060
|
-
const fileMatrix = (0,
|
|
48379
|
+
const fileMatrix = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
48061
48380
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
48062
48381
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
48063
|
-
const sanctioned = (0,
|
|
48382
|
+
const sanctioned = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
48064
48383
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
48065
48384
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
48066
48385
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -48091,16 +48410,16 @@ function directoryBytes(path2) {
|
|
|
48091
48410
|
let total = 0;
|
|
48092
48411
|
let entries;
|
|
48093
48412
|
try {
|
|
48094
|
-
entries = (0,
|
|
48413
|
+
entries = (0, import_node_fs50.readdirSync)(path2, { withFileTypes: true });
|
|
48095
48414
|
} catch {
|
|
48096
48415
|
return 0;
|
|
48097
48416
|
}
|
|
48098
48417
|
for (const entry of entries) {
|
|
48099
|
-
const child2 = (0,
|
|
48418
|
+
const child2 = (0, import_node_path48.join)(path2, entry.name);
|
|
48100
48419
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
48101
48420
|
else {
|
|
48102
48421
|
try {
|
|
48103
|
-
total += (0,
|
|
48422
|
+
total += (0, import_node_fs50.statSync)(child2).size;
|
|
48104
48423
|
} catch {
|
|
48105
48424
|
}
|
|
48106
48425
|
}
|
|
@@ -48108,25 +48427,25 @@ function directoryBytes(path2) {
|
|
|
48108
48427
|
return total;
|
|
48109
48428
|
}
|
|
48110
48429
|
function listDirEntries(dir) {
|
|
48111
|
-
return (0,
|
|
48430
|
+
return (0, import_node_fs50.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
48112
48431
|
}
|
|
48113
48432
|
function readInstalledPluginRefs(configRoot) {
|
|
48114
48433
|
const p = installedPluginsPathForConfig(configRoot);
|
|
48115
|
-
if (!(0,
|
|
48434
|
+
if (!(0, import_node_fs50.existsSync)(p)) return [];
|
|
48116
48435
|
try {
|
|
48117
|
-
return installedPluginPaths((0,
|
|
48436
|
+
return installedPluginPaths((0, import_node_fs50.readFileSync)(p, "utf8"));
|
|
48118
48437
|
} catch {
|
|
48119
48438
|
return null;
|
|
48120
48439
|
}
|
|
48121
48440
|
}
|
|
48122
48441
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
48123
48442
|
return {
|
|
48124
|
-
exists: (p) => (0,
|
|
48125
|
-
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),
|
|
48126
48445
|
dirBytes,
|
|
48127
|
-
listStagingDirs: (root) => (0,
|
|
48446
|
+
listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
48128
48447
|
try {
|
|
48129
|
-
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) };
|
|
48130
48449
|
} catch {
|
|
48131
48450
|
return { name: d.name, mtimeMs: Date.now() };
|
|
48132
48451
|
}
|
|
@@ -48140,10 +48459,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
48140
48459
|
return {
|
|
48141
48460
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
48142
48461
|
mtimeMs: (name) => {
|
|
48143
|
-
const p = (0,
|
|
48144
|
-
if (!(0,
|
|
48462
|
+
const p = (0, import_node_path48.join)(stagingRoot, name);
|
|
48463
|
+
if (!(0, import_node_fs50.existsSync)(p)) return null;
|
|
48145
48464
|
try {
|
|
48146
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
48465
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);
|
|
48147
48466
|
} catch {
|
|
48148
48467
|
return null;
|
|
48149
48468
|
}
|
|
@@ -48169,7 +48488,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
48169
48488
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
48170
48489
|
);
|
|
48171
48490
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
48172
|
-
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;
|
|
48173
48492
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
48174
48493
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
48175
48494
|
else console.log(renderPluginCachePlan(plan, result));
|