@mutmutco/cli 4.5.4 → 4.5.6
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 -197
- 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");
|
|
@@ -15190,9 +15190,9 @@ async function foldReleaseVersion(deps, model, tag, foldPaths, sourceCommit = "H
|
|
|
15190
15190
|
if (restored) foldNoteExtra = `; ${restored}`;
|
|
15191
15191
|
}
|
|
15192
15192
|
} else {
|
|
15193
|
-
await installAppFoldDeps(deps);
|
|
15194
15193
|
const refresh = await refreshSharedInstallerDeps(deps);
|
|
15195
15194
|
if (refresh) foldNoteExtra = `; ${refresh}`;
|
|
15195
|
+
await installAppFoldDeps(deps);
|
|
15196
15196
|
await deps.run("npm", ["version", version, "--no-git-tag-version", "--allow-same-version"]);
|
|
15197
15197
|
const patch = Number(version.split(".")[2] ?? "0");
|
|
15198
15198
|
if (model === "registry-publish" && Number.isFinite(patch) && patch > 0) {
|
|
@@ -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.6",
|
|
16002
|
+
tag: "v4.5.6",
|
|
16003
|
+
commit: "5b38ae7f9052",
|
|
16004
|
+
npm: "@mutmutco/cli@4.5.6"
|
|
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.6"
|
|
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.6 and redeploy the Hub Lambda from tag v4.5.6 (5b38ae7f9052); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16028
|
+
v3Target: "v4.5.6 (@mutmutco/cli@4.5.6, tag commit 5b38ae7f9052 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16029
16029
|
}
|
|
16030
16030
|
},
|
|
16031
16031
|
{
|
|
@@ -18939,37 +18939,64 @@ 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 latest =
|
|
18951
|
-
const
|
|
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, since) {
|
|
18947
|
+
const statuses = await api(`repos/${repo}/deployments/${id}/statuses?per_page=10`);
|
|
18948
|
+
const all = Array.isArray(statuses) ? statuses : [];
|
|
18949
|
+
const rows = since === void 0 ? all : all.filter((st) => !(Date.parse(str(st.created_at) ?? "") < since));
|
|
18950
|
+
const latest = rows.find((s) => str(s.state) !== "inactive") ?? rows[0];
|
|
18951
|
+
const raw = str(latest?.state);
|
|
18952
|
+
const state = raw === void 0 ? "none" : raw === "inactive" ? "success" : raw;
|
|
18952
18953
|
const url = str(latest?.environment_url) ?? str(latest?.target_url);
|
|
18954
|
+
const logUrl = str(latest?.log_url);
|
|
18955
|
+
return { state, ...url ? { url } : {}, ...logUrl ? { logUrl } : {} };
|
|
18956
|
+
}
|
|
18957
|
+
var FAILED = /* @__PURE__ */ new Set(["failure", "error"]);
|
|
18958
|
+
async function readVercelDeployment(api, repo, sha, target, since) {
|
|
18959
|
+
const listed = await api(`repos/${repo}/deployments?sha=${encodeURIComponent(sha)}&per_page=30`);
|
|
18960
|
+
const rows = Array.isArray(listed) ? listed : [];
|
|
18961
|
+
const newest = /* @__PURE__ */ new Map();
|
|
18962
|
+
for (const d of rows) {
|
|
18963
|
+
const env = str(d.environment) ?? "";
|
|
18964
|
+
if (typeof d.id !== "number" || !matchesTarget(env, target) || newest.has(env)) continue;
|
|
18965
|
+
newest.set(env, d.id);
|
|
18966
|
+
}
|
|
18967
|
+
if (newest.size === 0) return void 0;
|
|
18968
|
+
const reads = await Promise.all([...newest].map(async ([env, id]) => ({ env, id, ...await readOne(api, repo, id, since) })));
|
|
18969
|
+
const failed = reads.find((r) => FAILED.has(r.state));
|
|
18970
|
+
const unfinished = reads.find((r) => !isTerminalVercelState(r.state));
|
|
18971
|
+
const pick = failed ?? unfinished ?? reads[0];
|
|
18953
18972
|
return {
|
|
18954
|
-
id:
|
|
18955
|
-
environment:
|
|
18956
|
-
sha
|
|
18957
|
-
|
|
18958
|
-
|
|
18959
|
-
...
|
|
18960
|
-
...str(latest?.log_url) ? { logUrl: str(latest?.log_url) } : {}
|
|
18973
|
+
id: pick.id,
|
|
18974
|
+
environment: reads.map((r) => r.env).join(", "),
|
|
18975
|
+
sha,
|
|
18976
|
+
state: pick.state,
|
|
18977
|
+
...reads[0].url ? { url: reads[0].url } : {},
|
|
18978
|
+
...pick.logUrl ? { logUrl: pick.logUrl } : {}
|
|
18961
18979
|
};
|
|
18962
18980
|
}
|
|
18963
18981
|
var VERCEL_WATCH_ATTEMPTS = 120;
|
|
18964
18982
|
var VERCEL_WATCH_DELAY_MS = 1e4;
|
|
18965
|
-
async function
|
|
18983
|
+
async function watchVercelDeployment(api, repo, sha, target, sleep2, onTick, since) {
|
|
18966
18984
|
let last;
|
|
18985
|
+
let readOnce = false;
|
|
18986
|
+
let lastError;
|
|
18967
18987
|
for (let attempt = 0; attempt < VERCEL_WATCH_ATTEMPTS; attempt++) {
|
|
18968
18988
|
if (attempt > 0) await sleep2(VERCEL_WATCH_DELAY_MS);
|
|
18969
|
-
|
|
18989
|
+
try {
|
|
18990
|
+
last = await readVercelDeployment(api, repo, sha, target, since);
|
|
18991
|
+
readOnce = true;
|
|
18992
|
+
} catch (e) {
|
|
18993
|
+
lastError = e;
|
|
18994
|
+
continue;
|
|
18995
|
+
}
|
|
18970
18996
|
onTick?.(attempt, last);
|
|
18971
18997
|
if (last && isTerminalVercelState(last.state)) return last;
|
|
18972
18998
|
}
|
|
18999
|
+
if (!readOnce) throw lastError;
|
|
18973
19000
|
return last;
|
|
18974
19001
|
}
|
|
18975
19002
|
async function probeVercelUrl(url, fetchImpl = fetch) {
|
|
@@ -19420,34 +19447,28 @@ function prodReleaseTargets(model) {
|
|
|
19420
19447
|
function trainObservesShipment(model) {
|
|
19421
19448
|
return isCentralDispatchModel(model) || prodReleaseTargets(model).length > 0 || model === "vercel";
|
|
19422
19449
|
}
|
|
19423
|
-
async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
|
|
19424
|
-
const
|
|
19425
|
-
|
|
19450
|
+
async function vercelDeployDispatch(deps, ctx, ref, watch, headSha, since) {
|
|
19451
|
+
const target = ref === "main" ? "production" : "preview";
|
|
19452
|
+
const plane = target === "production" ? "Production" : `${ref} (non-Production)`;
|
|
19453
|
+
const note = `no dispatch: Vercel deploys ${ref} on push (${plane}) \u2014 observed via the Vercel GitHub app Deployment`;
|
|
19426
19454
|
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";
|
|
19455
|
+
const after = target === "preview" ? rcWindow(since) : void 0;
|
|
19435
19456
|
let read;
|
|
19436
19457
|
try {
|
|
19437
19458
|
if (!watch) {
|
|
19438
19459
|
deps.warn?.("Release: Checking Vercel deployment status");
|
|
19439
|
-
read = await readVercelDeployment(
|
|
19460
|
+
read = await readVercelDeployment(vercelApi(deps), ctx.repo, headSha, target, after);
|
|
19440
19461
|
} else {
|
|
19441
|
-
deps.warn?.(
|
|
19442
|
-
read = await
|
|
19462
|
+
deps.warn?.(`Release: Waiting for Vercel to deploy ${ref}`);
|
|
19463
|
+
read = await watchVercelDeployment(vercelApi(deps), ctx.repo, headSha, target, resolveSleep(deps), (attempt, d) => {
|
|
19443
19464
|
if (attempt > 0 && attempt % 6 === 0) deps.warn?.(`Vercel deployment ${d ? d.state : "not posted yet"} \u2014 ${attempt / 6}m elapsed`);
|
|
19444
|
-
});
|
|
19465
|
+
}, after);
|
|
19445
19466
|
}
|
|
19446
19467
|
} catch (e) {
|
|
19447
|
-
return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [
|
|
19468
|
+
return { note: `${note}; the Deployment read FAILED (${describeReadError(e)}) \u2014 verify on Vercel; never re-tag`, workflowRuns: [vercelRow(void 0, "failure")], deployStatus: "failure" };
|
|
19448
19469
|
}
|
|
19449
|
-
let deployStatus =
|
|
19450
|
-
let detail = read ? `Vercel
|
|
19470
|
+
let deployStatus = vercelVerdict(read);
|
|
19471
|
+
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
19472
|
if (watch && deployStatus === "success" && read?.url) {
|
|
19452
19473
|
const probe = await (deps.probeUrl ?? probeVercelUrl)(read.url);
|
|
19453
19474
|
if (probe.ok) detail = `${detail}; URL answered HTTP ${probe.status}`;
|
|
@@ -19456,7 +19477,27 @@ async function vercelDeployDispatch(deps, ctx, ref, watch, headSha) {
|
|
|
19456
19477
|
detail = `${detail}; URL probe FAILED (${probe.error ?? `HTTP ${probe.status}`})`;
|
|
19457
19478
|
}
|
|
19458
19479
|
}
|
|
19459
|
-
return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [
|
|
19480
|
+
return { note: `${note}; ${detail}`, ...read?.logUrl ? { runUrl: read.logUrl } : {}, workflowRuns: [vercelRow(read, deployStatus)], deployStatus };
|
|
19481
|
+
}
|
|
19482
|
+
function vercelApi(deps) {
|
|
19483
|
+
return async (path2) => JSON.parse(await deps.run("gh", ["api", path2]));
|
|
19484
|
+
}
|
|
19485
|
+
function vercelVerdict(d) {
|
|
19486
|
+
return d?.state === "success" ? "success" : d && isTerminalVercelState(d.state) ? "failure" : "pending";
|
|
19487
|
+
}
|
|
19488
|
+
function vercelRow(d, conclusion) {
|
|
19489
|
+
return { workflow: "vercel", conclusion, ...d?.logUrl ? { runUrl: d.logUrl } : { runUrlNote: "no Vercel Deployment posted for this SHA yet" }, event: "push" };
|
|
19490
|
+
}
|
|
19491
|
+
async function listVercelDeployRows(deps, repo, sha, ref = "main", since) {
|
|
19492
|
+
const target = ref === "main" ? "production" : "preview";
|
|
19493
|
+
const read = await readVercelDeployment(vercelApi(deps), repo, sha, target, target === "preview" ? rcWindow(since) : void 0);
|
|
19494
|
+
if (!read) return [];
|
|
19495
|
+
const verdict = vercelVerdict(read);
|
|
19496
|
+
const probe = verdict === "success" && read.url ? await (deps.probeUrl ?? probeVercelUrl)(read.url) : void 0;
|
|
19497
|
+
return [vercelRow(read, probe && !probe.ok ? "failure" : verdict)];
|
|
19498
|
+
}
|
|
19499
|
+
function rcWindow(since) {
|
|
19500
|
+
return since === void 0 ? void 0 : since - 6e4;
|
|
19460
19501
|
}
|
|
19461
19502
|
function shipLegsGreen(deploy, publish) {
|
|
19462
19503
|
const green = (leg) => leg?.superseded !== true && (leg?.state === "complete" || leg?.state === "skipped");
|
|
@@ -19638,7 +19679,7 @@ async function dispatchDeploy(deps, ctx, stage, ref, model, watch, autoRunSince,
|
|
|
19638
19679
|
deployStatus: aggregateWorkflowRuns(workflowRuns)
|
|
19639
19680
|
};
|
|
19640
19681
|
}
|
|
19641
|
-
if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha);
|
|
19682
|
+
if (model === "vercel") return vercelDeployDispatch(deps, ctx, ref, watch, autoRunHeadSha, autoRunSince);
|
|
19642
19683
|
return { note: `no manual dispatch: ${model} repo deploys via its own push-triggered workflow`, deployStatus: "pending" };
|
|
19643
19684
|
}
|
|
19644
19685
|
async function readEdgeDomains(deps, slug) {
|
|
@@ -21907,6 +21948,16 @@ function deployPhaseInput(model, dispatch, opts = {}) {
|
|
|
21907
21948
|
...runMetadata({ runId: dispatch.runId, runUrl: dispatch.runUrl, workflow: "tenant-deploy.yml", event: "workflow_dispatch" }, runRepo, "dispatch"),
|
|
21908
21949
|
note: dispatch.note
|
|
21909
21950
|
};
|
|
21951
|
+
case "vercel": {
|
|
21952
|
+
const row = (dispatch.workflowRuns ?? []).find((r) => r.workflow === "vercel");
|
|
21953
|
+
return {
|
|
21954
|
+
state: runRowState(row, dispatch.deployStatus),
|
|
21955
|
+
...sha ? { sha } : {},
|
|
21956
|
+
workflow: "vercel",
|
|
21957
|
+
...row?.runUrl ? { runUrl: row.runUrl } : {},
|
|
21958
|
+
note: dispatch.note
|
|
21959
|
+
};
|
|
21960
|
+
}
|
|
21910
21961
|
default:
|
|
21911
21962
|
return {
|
|
21912
21963
|
state: dispatch.deployStatus === "success" ? "complete" : dispatch.deployStatus === "failure" ? "failed" : "pending",
|
|
@@ -21988,6 +22039,13 @@ function phaseInputsFromRunRows(model, rows, tagSha, opts = {}) {
|
|
|
21988
22039
|
note: `${publishRun.workflow} run on the release SHA`
|
|
21989
22040
|
} : { state: "pending", sha: tagSha, note: "publish.yml release run not yet correlated" }
|
|
21990
22041
|
};
|
|
22042
|
+
case "vercel": {
|
|
22043
|
+
const vercelRow2 = rows.find((r) => r.workflow === "vercel");
|
|
22044
|
+
return {
|
|
22045
|
+
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?)" },
|
|
22046
|
+
publish: { state: "skipped", sha: tagSha, note: "vercel has no publish leg" }
|
|
22047
|
+
};
|
|
22048
|
+
}
|
|
21991
22049
|
default:
|
|
21992
22050
|
return {
|
|
21993
22051
|
deploy: { state: "pending", sha: tagSha, note: `${model} deploys outside the release SHA's run set \u2014 historical outcome uncorrelatable` },
|
|
@@ -22194,6 +22252,17 @@ function hotfixRetryDeployShape(ledger) {
|
|
|
22194
22252
|
if (!open2.every((phase) => RETRY_DEPLOY_LEGS.has(phase))) return false;
|
|
22195
22253
|
return open2.some((phase) => ledger.phases[phase].state === "failed");
|
|
22196
22254
|
}
|
|
22255
|
+
function rcandRetryDeployShape(ledger) {
|
|
22256
|
+
if (ledger.lane !== "rcand") return false;
|
|
22257
|
+
if (ledger.phases.promotion.state !== "complete") return false;
|
|
22258
|
+
const deploy = ledger.phases.deploy;
|
|
22259
|
+
if (deploy.state !== "failed" || deploy.superseded === true || deploy.workflow !== "tenant-deploy.yml") return false;
|
|
22260
|
+
return RELEASE_LEDGER_PHASES.every((phase) => {
|
|
22261
|
+
if (phase === "deploy") return true;
|
|
22262
|
+
const state = ledger.phases[phase].state;
|
|
22263
|
+
return state === "complete" || state === "skipped";
|
|
22264
|
+
});
|
|
22265
|
+
}
|
|
22197
22266
|
function ledgerFailedRemedy(ledger) {
|
|
22198
22267
|
const derived = deriveReleasePhaseStatus(ledger);
|
|
22199
22268
|
const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
|
|
@@ -22202,6 +22271,9 @@ function ledgerFailedRemedy(ledger) {
|
|
|
22202
22271
|
const failed = openLegs.filter((l) => l.state === "failed").map((l) => l.phase);
|
|
22203
22272
|
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
22273
|
}
|
|
22274
|
+
if (rcandRetryDeployShape(ledger)) {
|
|
22275
|
+
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}`;
|
|
22276
|
+
}
|
|
22205
22277
|
const deployOnlyDeadlock = openLegs.length > 0 && openLegs.every((l) => l.phase === "deploy" && supersedeableDeployLegShape(l));
|
|
22206
22278
|
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
22279
|
return `diagnose the failed leg, then ${ledgerLaneResumeCommand(ledger)} once its cause is fixed (an abort only with fresh approval)${supersession}${tails}`;
|
|
@@ -22251,6 +22323,10 @@ async function healStaleAlignmentLeg(deps, cwd, expected) {
|
|
|
22251
22323
|
return prior.tag;
|
|
22252
22324
|
});
|
|
22253
22325
|
}
|
|
22326
|
+
function rcPromotedAt(ledger) {
|
|
22327
|
+
const at = Date.parse(ledger.phases.promotion.updatedAt);
|
|
22328
|
+
return Number.isFinite(at) ? at : void 0;
|
|
22329
|
+
}
|
|
22254
22330
|
async function healUnfoldedRunLegs(deps, cwd, model) {
|
|
22255
22331
|
const path2 = releaseLedgerPath(cwd);
|
|
22256
22332
|
let prior;
|
|
@@ -22282,15 +22358,16 @@ async function healUnfoldedRunLegs(deps, cwd, model) {
|
|
|
22282
22358
|
}
|
|
22283
22359
|
const targets = prodReleaseTargets(model);
|
|
22284
22360
|
const runless = legs.filter((phase) => openLeg(current.phases[phase]) && current.phases[phase].runId == null);
|
|
22285
|
-
if (runless.length > 0 && targets.length > 0) {
|
|
22361
|
+
if (runless.length > 0 && (targets.length > 0 || model === "vercel")) {
|
|
22286
22362
|
let rows;
|
|
22287
22363
|
try {
|
|
22288
|
-
rows = await listOwnWorkflowRuns(deps, releaseRunRepoFor(model, prior.repo), targets, prior.tagSha);
|
|
22364
|
+
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
22365
|
} catch {
|
|
22290
22366
|
rows = [];
|
|
22291
22367
|
}
|
|
22292
22368
|
const inputs = phaseInputsFromRunRows(model, rows, prior.tagSha, { repo: prior.repo });
|
|
22293
|
-
const
|
|
22369
|
+
const correlated = (entry) => entry.runId != null || entry.workflow === "vercel";
|
|
22370
|
+
const foldable = runless.filter((phase) => correlated(inputs[phase]) && inputs[phase].state !== "pending");
|
|
22294
22371
|
if (foldable.length > 0) {
|
|
22295
22372
|
const written = await withReleaseLedger(path2, anchors, (ledger) => {
|
|
22296
22373
|
if (!ledger) return { value: [] };
|
|
@@ -23188,6 +23265,13 @@ async function runTrainDoctor(input) {
|
|
|
23188
23265
|
unverified(`required status checks on ${stage}`, e);
|
|
23189
23266
|
}
|
|
23190
23267
|
}
|
|
23268
|
+
if (meta?.deployModel === "vercel") {
|
|
23269
|
+
const tip = await git3(["rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"]).catch(() => "");
|
|
23270
|
+
const rows = tip ? await listVercelDeployRows(train, repo, tip).catch(() => void 0) : void 0;
|
|
23271
|
+
if (rows && !rows.length) {
|
|
23272
|
+
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` });
|
|
23273
|
+
}
|
|
23274
|
+
}
|
|
23191
23275
|
let ledgerTag;
|
|
23192
23276
|
let ledgerPath;
|
|
23193
23277
|
try {
|
|
@@ -24152,10 +24236,15 @@ async function recoverFailedDispatchPhase(deps, ledger, anchors, phase, dispatch
|
|
|
24152
24236
|
note: notes.length > 0 ? `${dispatch.note}; ${notes.join("; ")}` : dispatch.note
|
|
24153
24237
|
};
|
|
24154
24238
|
}
|
|
24239
|
+
var RCAND_RETRY_DEPLOY_COMMAND = "mmi-cli devops rcand --retry-deploy --watch";
|
|
24240
|
+
function rcPushedAt(ledger) {
|
|
24241
|
+
const at = Date.parse(ledger?.phases.promotion.updatedAt ?? "");
|
|
24242
|
+
return Number.isFinite(at) ? at : void 0;
|
|
24243
|
+
}
|
|
24155
24244
|
async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts = {}) {
|
|
24156
24245
|
const sha = anchors.tagSha;
|
|
24157
24246
|
const landed = "the immutable rc tag, the green wall, origin/rc, and the rc deploy dispatch";
|
|
24158
|
-
const
|
|
24247
|
+
const recovered = await recoverFailedDispatchPhase(
|
|
24159
24248
|
deps,
|
|
24160
24249
|
ledger,
|
|
24161
24250
|
anchors,
|
|
@@ -24168,6 +24257,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
24168
24257
|
workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "rc deploy workflow"
|
|
24169
24258
|
}
|
|
24170
24259
|
);
|
|
24260
|
+
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
24261
|
const unverifiable = !isCentralDispatchModel(deployModel) && dispatch.deployStatus === "pending" && dispatch.runId == null && !dispatch.workflowRuns?.length;
|
|
24172
24262
|
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
24263
|
await recordPhase(ledger, deps, anchors, "deploy", phaseEntry(deployEntry), { ...opts, landed: "the immutable rc tag, the green wall, and origin/rc" });
|
|
@@ -24176,7 +24266,7 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
24176
24266
|
return dispatch;
|
|
24177
24267
|
}
|
|
24178
24268
|
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";
|
|
24269
|
+
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
24270
|
}
|
|
24181
24271
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
|
|
24182
24272
|
const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
|
|
@@ -24463,7 +24553,8 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
|
24463
24553
|
const terminal = deployLeg.state === "complete" || deployLeg.state === "skipped";
|
|
24464
24554
|
const legStatus = deployLeg.state === "failed" ? "failure" : terminal ? "success" : "pending";
|
|
24465
24555
|
const hasDispatchEvidence = deployLeg.runId != null || deployLeg.workflow != null || deployLeg.updatedAt !== "never";
|
|
24466
|
-
const
|
|
24556
|
+
const vercelReread = deployModel === "vercel" && !terminal;
|
|
24557
|
+
const dispatch02 = (terminal || hasDispatchEvidence) && !vercelReread ? {
|
|
24467
24558
|
note: deployLeg.note ?? deployLeg.error ?? `persisted rc deploy leg is ${deployLeg.state}`,
|
|
24468
24559
|
deployStatus: legStatus,
|
|
24469
24560
|
...deployLeg.runId != null ? { runId: deployLeg.runId } : {},
|
|
@@ -24475,7 +24566,7 @@ Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
|
24475
24566
|
...deployLeg.event ? { event: deployLeg.event } : {},
|
|
24476
24567
|
conclusion: legStatus
|
|
24477
24568
|
}] } : {}
|
|
24478
|
-
} : await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, (deps.now ?? Date.now)(), tagSha);
|
|
24569
|
+
} : await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, vercelReread ? rcPushedAt(persisted) : (deps.now ?? Date.now)(), tagSha, "report");
|
|
24479
24570
|
const dispatch2 = terminal ? dispatch02 : await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch02, { strict: true });
|
|
24480
24571
|
const ledgerReport2 = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24481
24572
|
return {
|
|
@@ -24541,10 +24632,11 @@ ${spent.note}`);
|
|
|
24541
24632
|
}
|
|
24542
24633
|
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
24634
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
24635
|
+
deps.warn?.("Release: Promoting the verified release candidate");
|
|
24544
24636
|
await runGitPush(deps, ["push", "origin", `${tagSha}:refs/heads/rc`]);
|
|
24545
24637
|
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
24638
|
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);
|
|
24639
|
+
const dispatch0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha, "report");
|
|
24548
24640
|
const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch0, { strict });
|
|
24549
24641
|
const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24550
24642
|
return {
|
|
@@ -24566,6 +24658,64 @@ ${spent.note}`);
|
|
|
24566
24658
|
note: `reused immutable ${tag}; pushed its exact SHA to rc and dispatched the rc deploy`
|
|
24567
24659
|
};
|
|
24568
24660
|
}
|
|
24661
|
+
async function runRcandRetryDeploy(deps, options = {}) {
|
|
24662
|
+
return withTrainWriter(deps, "rcand --retry-deploy", () => runRcandRetryDeployLocked(deps, options));
|
|
24663
|
+
}
|
|
24664
|
+
async function runRcandRetryDeployLocked(deps, options) {
|
|
24665
|
+
const label = "rcand --retry-deploy";
|
|
24666
|
+
const ctx = await buildTrainApplyContext(deps);
|
|
24667
|
+
await requireCleanTree(deps);
|
|
24668
|
+
await runGitRemoteRead(deps, ["fetch", "origin", "--tags"]);
|
|
24669
|
+
const meta = requireProjectMetaForTrain(await loadProjectMeta(deps, ctx), ctx.repo);
|
|
24670
|
+
const deployModel = await preflight(deps, ctx, "rc", meta);
|
|
24671
|
+
if (!isCentralDispatchModel(deployModel)) {
|
|
24672
|
+
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`);
|
|
24673
|
+
}
|
|
24674
|
+
const candidate = await activeRcandLedgerCandidate(deps, process.cwd());
|
|
24675
|
+
if (!candidate) {
|
|
24676
|
+
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`);
|
|
24677
|
+
}
|
|
24678
|
+
const { tag, sha: tagSha } = candidate;
|
|
24679
|
+
const ledger = openReleaseLedgerWriter(process.cwd());
|
|
24680
|
+
const anchors = { repo: ctx.repo, tag, tagSha };
|
|
24681
|
+
let persisted;
|
|
24682
|
+
try {
|
|
24683
|
+
persisted = (await reverifyPersistedRunPhases(deps, ledger.path, anchors, { sha: tagSha, repo: ctx.repo })).ledger;
|
|
24684
|
+
} catch (e) {
|
|
24685
|
+
throw new Error(`${label}: ${e instanceof Error ? e.message : String(e)}
|
|
24686
|
+
Nothing was written. Inspect ${ledger.path} before retrying.`);
|
|
24687
|
+
}
|
|
24688
|
+
if (!persisted || !rcandRetryDeployShape(persisted)) {
|
|
24689
|
+
const open2 = persisted ? deriveReleasePhaseStatus(persisted).legs.filter((l) => l.state === "pending" || l.state === "failed") : [];
|
|
24690
|
+
throw new Error(
|
|
24691
|
+
`${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`
|
|
24692
|
+
);
|
|
24693
|
+
}
|
|
24694
|
+
if (!await probeAncestor(deps, tagSha, "origin/rc", `${label}: origin/rc ancestry`)) {
|
|
24695
|
+
throw new Error(`${label}: origin/rc does not contain ${tag} (${tagSha.slice(0, 12)}) \u2014 the candidate is not promoted; nothing was written`);
|
|
24696
|
+
}
|
|
24697
|
+
const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, options.watch ?? false, (deps.now ?? Date.now)(), tagSha, "report");
|
|
24698
|
+
const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, d0, { strict: true });
|
|
24699
|
+
const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
24700
|
+
return {
|
|
24701
|
+
...ctx,
|
|
24702
|
+
command: "rcand-retry-deploy",
|
|
24703
|
+
stage: "rc",
|
|
24704
|
+
ref: "rc",
|
|
24705
|
+
tag,
|
|
24706
|
+
tagSha,
|
|
24707
|
+
deployModel,
|
|
24708
|
+
promoted: true,
|
|
24709
|
+
checks: persisted.phases.ordinaryCi.note ?? "required-check wall was completed before origin/rc promotion",
|
|
24710
|
+
dispatch: dispatch.note,
|
|
24711
|
+
runId: dispatch.runId,
|
|
24712
|
+
runUrl: dispatch.runUrl,
|
|
24713
|
+
workflowRuns: dispatch.workflowRuns,
|
|
24714
|
+
deployStatus: dispatch.deployStatus,
|
|
24715
|
+
...ledgerReport ? { ledger: ledgerReport } : {},
|
|
24716
|
+
note: `re-dispatched the failed rc deploy of immutable ${tag} at ${tagSha.slice(0, 12)}; nothing re-cut, re-tagged or re-pushed`
|
|
24717
|
+
};
|
|
24718
|
+
}
|
|
24569
24719
|
async function runReleaseResumePipeline(deps, options, prepared) {
|
|
24570
24720
|
const watch = options.watch ?? false;
|
|
24571
24721
|
const { ctx, meta, branchHints, directTrack, deployModel } = prepared;
|
|
@@ -24629,8 +24779,9 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24629
24779
|
deps.warn?.("Release: Checking deployment and publishing progress");
|
|
24630
24780
|
const historicalTargets = prodReleaseTargets(deployModel);
|
|
24631
24781
|
const historicalRepo = releaseRunRepoFor(deployModel, ctx.repo);
|
|
24632
|
-
|
|
24633
|
-
let
|
|
24782
|
+
const vercel = deployModel === "vercel";
|
|
24783
|
+
let historicalRows = vercel ? await listVercelDeployRows(deps, ctx.repo, tagSha).catch(() => []) : historicalTargets.length ? await listOwnWorkflowRuns(deps, historicalRepo, historicalTargets, tagSha) : [];
|
|
24784
|
+
let historicalDeploy = historicalTargets.length || vercel && historicalRows.length ? aggregateWorkflowRuns(historicalRows) : "pending";
|
|
24634
24785
|
let historicalNote = `historical ${deployModel} deploy/publish status: ${historicalDeploy}`;
|
|
24635
24786
|
if (historicalDeploy === "failure") {
|
|
24636
24787
|
const recovered = await recoverFailedDispatchPhase(
|
|
@@ -24642,7 +24793,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24642
24793
|
{ repo: historicalRepo, sha: tagSha, watched: false, workflow: "historical release workflow" }
|
|
24643
24794
|
);
|
|
24644
24795
|
historicalRows = recovered.workflowRuns ?? historicalRows;
|
|
24645
|
-
historicalDeploy = historicalTargets.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
|
|
24796
|
+
historicalDeploy = historicalTargets.length || vercel && historicalRows.length ? aggregateWorkflowRuns(historicalRows) : recovered.deployStatus;
|
|
24646
24797
|
historicalNote = recovered.note;
|
|
24647
24798
|
}
|
|
24648
24799
|
const dispatch2 = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, {
|
|
@@ -24680,7 +24831,7 @@ Nothing was written. Inspect ${ledger.path} (or clear it only after proving the
|
|
|
24680
24831
|
Object.assign(dispatch2, shippedDispatch);
|
|
24681
24832
|
if (isJervHubRepo(ctx.repo)) steps2.push(dispatch2.note);
|
|
24682
24833
|
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(
|
|
24834
|
+
const announceNote2 = persisted && !shippedBefore && !settledBySupersede && (prodReleaseTargets(deployModel).length > 0 || deployModel === "vercel" || companion !== null) ? await announceShippedRelease(
|
|
24684
24835
|
deps,
|
|
24685
24836
|
{ repo: ctx.repo, tag, summaryFile: options.announceSummaryFile },
|
|
24686
24837
|
deployModel,
|
|
@@ -25128,6 +25279,7 @@ async function runTrainApplyPipeline(mode, input) {
|
|
|
25128
25279
|
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
25280
|
ledgerAnchors2 = anchors2;
|
|
25130
25281
|
}
|
|
25282
|
+
deps.warn?.("Release: Publishing the release candidate tag");
|
|
25131
25283
|
tagPush = await ensureTagPushed(deps, tag2, rcSha);
|
|
25132
25284
|
} catch (e) {
|
|
25133
25285
|
if (ledger && ledgerAnchors2) {
|
|
@@ -25164,6 +25316,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
25164
25316
|
}
|
|
25165
25317
|
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
25318
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
25319
|
+
deps.warn?.("Release: Promoting the verified release candidate");
|
|
25167
25320
|
try {
|
|
25168
25321
|
await runGitPush(deps, ["push", "origin", "rc"]);
|
|
25169
25322
|
} catch (e) {
|
|
@@ -25172,7 +25325,7 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
25172
25325
|
}
|
|
25173
25326
|
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
25327
|
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);
|
|
25328
|
+
const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report");
|
|
25176
25329
|
const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
|
|
25177
25330
|
const rcLedgerReport = releaseLedgerReport(await ledger?.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
25178
25331
|
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 +25615,7 @@ async function runTrainApplyLocked(command, deps, options = {}) {
|
|
|
25462
25615
|
if (directTrack) {
|
|
25463
25616
|
throw new Error("direct-track repos release straight to main (no rc); run `mmi-cli devops release --apply` from development instead of rcand");
|
|
25464
25617
|
}
|
|
25465
|
-
return runTrainApplyPipeline("rcand", pipelineInput);
|
|
25618
|
+
return withCheckoutRestoredAfterRelease(deps, await runTrainApplyPipeline("rcand", pipelineInput), "development");
|
|
25466
25619
|
}
|
|
25467
25620
|
if (directTrack) {
|
|
25468
25621
|
return withCheckoutRestoredAfterRelease(
|
|
@@ -38194,8 +38347,9 @@ function registerStageCommands(program3) {
|
|
|
38194
38347
|
const branch = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
38195
38348
|
const repo = Array.isArray(project2.repos) && typeof project2.repos[0] === "string" ? project2.repos[0] : await repoSlug();
|
|
38196
38349
|
try {
|
|
38350
|
+
const sha = await gitOut(["rev-parse", `origin/${branch}`]).catch(() => gitOut(["rev-parse", "HEAD"]));
|
|
38197
38351
|
const gh = defaultGitHubClient();
|
|
38198
|
-
const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo,
|
|
38352
|
+
const deployment = await readVercelDeployment((path2) => gh.rest("GET", path2), repo, sha, "preview");
|
|
38199
38353
|
return { branch, ...deployment ? { deployment } : {} };
|
|
38200
38354
|
} catch (e) {
|
|
38201
38355
|
return { branch, error: e.message };
|
|
@@ -38636,7 +38790,7 @@ function renderVerifySecrets(body) {
|
|
|
38636
38790
|
|
|
38637
38791
|
// src/command-register-collaboration.ts
|
|
38638
38792
|
var import_node_child_process15 = require("node:child_process");
|
|
38639
|
-
var
|
|
38793
|
+
var import_node_fs43 = require("node:fs");
|
|
38640
38794
|
var import_promises7 = require("node:fs/promises");
|
|
38641
38795
|
init_clean_exit();
|
|
38642
38796
|
init_cli_shared();
|
|
@@ -38660,7 +38814,7 @@ function spawnDetachedSelf(args, deps, opts = {}) {
|
|
|
38660
38814
|
}
|
|
38661
38815
|
|
|
38662
38816
|
// src/command-register-collaboration.ts
|
|
38663
|
-
var
|
|
38817
|
+
var import_node_path41 = require("node:path");
|
|
38664
38818
|
|
|
38665
38819
|
// src/attach-to-project.ts
|
|
38666
38820
|
async function attachViaHubApp(cfg, contentNodeId) {
|
|
@@ -39744,9 +39898,9 @@ function changedFilesSince(base, cwd) {
|
|
|
39744
39898
|
const raw = git2(["diff", "--name-status", "-M", base, "--"], cwd).trim();
|
|
39745
39899
|
const changed = raw ? raw.split("\n").map((line) => {
|
|
39746
39900
|
const parts = line.split(" ");
|
|
39747
|
-
const
|
|
39748
|
-
const row = { status: parts[0][0], path:
|
|
39749
|
-
if (parts.length > 2) row.from =
|
|
39901
|
+
const norm2 = (p) => p.replace(/\\/g, "/");
|
|
39902
|
+
const row = { status: parts[0][0], path: norm2(parts[parts.length - 1]) };
|
|
39903
|
+
if (parts.length > 2) row.from = norm2(parts[1]);
|
|
39750
39904
|
return row;
|
|
39751
39905
|
}) : [];
|
|
39752
39906
|
const untracked = git2(["ls-files", "--others", "--exclude-standard", "-z"], cwd).split("\0").filter(Boolean).map((path2) => ({ status: "A", path: path2.replace(/\\/g, "/") }));
|
|
@@ -40809,6 +40963,10 @@ async function prCreateClaimRefusal(body, repoOption, deps = {}) {
|
|
|
40809
40963
|
return void 0;
|
|
40810
40964
|
}
|
|
40811
40965
|
|
|
40966
|
+
// src/worktree-residue-sweep.ts
|
|
40967
|
+
var import_node_fs42 = require("node:fs");
|
|
40968
|
+
var import_node_path40 = require("node:path");
|
|
40969
|
+
|
|
40812
40970
|
// src/worktree-merge-cleanup.ts
|
|
40813
40971
|
var import_node_fs41 = require("node:fs");
|
|
40814
40972
|
var import_node_path39 = require("node:path");
|
|
@@ -41654,6 +41812,122 @@ function renderPrMergeCleanupLines(cleanup) {
|
|
|
41654
41812
|
return lines2;
|
|
41655
41813
|
}
|
|
41656
41814
|
|
|
41815
|
+
// src/worktree-residue-sweep.ts
|
|
41816
|
+
var RESIDUE_LEDGER_FILE = "mmi-worktree-residue.json";
|
|
41817
|
+
function norm(p) {
|
|
41818
|
+
const s = p.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
41819
|
+
return process.platform === "win32" ? s.toLowerCase() : s;
|
|
41820
|
+
}
|
|
41821
|
+
function residueLedgerPath(gitCommonDir) {
|
|
41822
|
+
return (0, import_node_path40.join)(gitCommonDir, RESIDUE_LEDGER_FILE);
|
|
41823
|
+
}
|
|
41824
|
+
function readPaths(file, deps) {
|
|
41825
|
+
try {
|
|
41826
|
+
const parsed = JSON.parse((deps.readLedger ?? ((f) => (0, import_node_fs42.readFileSync)(f, "utf8")))(file));
|
|
41827
|
+
return Array.isArray(parsed) ? parsed.filter((p) => typeof p === "string") : [];
|
|
41828
|
+
} catch {
|
|
41829
|
+
return [];
|
|
41830
|
+
}
|
|
41831
|
+
}
|
|
41832
|
+
function writePaths(file, paths, deps) {
|
|
41833
|
+
try {
|
|
41834
|
+
const write = deps.writeLedger ?? ((f, t) => {
|
|
41835
|
+
(0, import_node_fs42.mkdirSync)((0, import_node_path40.dirname)(f), { recursive: true });
|
|
41836
|
+
(0, import_node_fs42.writeFileSync)(f, t, "utf8");
|
|
41837
|
+
});
|
|
41838
|
+
write(file, `${JSON.stringify([...new Set(paths)], null, 1)}
|
|
41839
|
+
`);
|
|
41840
|
+
} catch {
|
|
41841
|
+
}
|
|
41842
|
+
}
|
|
41843
|
+
function recordResidue(gitCommonDir, path2, deps = {}) {
|
|
41844
|
+
const file = residueLedgerPath(gitCommonDir);
|
|
41845
|
+
const paths = readPaths(file, deps);
|
|
41846
|
+
if (!paths.some((p) => norm(p) === norm(path2))) paths.push(path2);
|
|
41847
|
+
writePaths(file, paths, deps);
|
|
41848
|
+
}
|
|
41849
|
+
async function sweepWorktreeResidue(gitCommonDir, registered, deps = {}) {
|
|
41850
|
+
const exists = deps.exists ?? import_node_fs42.existsSync;
|
|
41851
|
+
const readDir = deps.readDir ?? ((p) => (0, import_node_fs42.readdirSync)(p));
|
|
41852
|
+
const isDir = deps.isDir ?? ((p) => {
|
|
41853
|
+
try {
|
|
41854
|
+
return (0, import_node_fs42.statSync)(p).isDirectory();
|
|
41855
|
+
} catch {
|
|
41856
|
+
return false;
|
|
41857
|
+
}
|
|
41858
|
+
});
|
|
41859
|
+
const removeRecorded = deps.removeRecorded ?? ((p) => removeResidueDirectory(p));
|
|
41860
|
+
const removeEmpty = deps.removeEmpty ?? ((p) => (0, import_node_fs42.rmdirSync)(p));
|
|
41861
|
+
const report = { swept: [], kept: [], dropped: [], emptyRemoved: [], unrecorded: [] };
|
|
41862
|
+
const live = new Set(registered.map(norm));
|
|
41863
|
+
const primary = registered[0] ? norm(registered[0]) : void 0;
|
|
41864
|
+
const file = residueLedgerPath(gitCommonDir);
|
|
41865
|
+
const recorded = readPaths(file, deps);
|
|
41866
|
+
const remaining = [];
|
|
41867
|
+
for (const path2 of recorded) {
|
|
41868
|
+
const key = norm(path2);
|
|
41869
|
+
if (live.has(key) || key === primary) {
|
|
41870
|
+
report.dropped.push(path2);
|
|
41871
|
+
continue;
|
|
41872
|
+
}
|
|
41873
|
+
if (!exists(path2)) {
|
|
41874
|
+
report.dropped.push(path2);
|
|
41875
|
+
continue;
|
|
41876
|
+
}
|
|
41877
|
+
const removed = await removeRecorded(path2);
|
|
41878
|
+
if (removed.ok && !exists(path2)) report.swept.push(path2);
|
|
41879
|
+
else {
|
|
41880
|
+
report.kept.push({ path: path2, error: removed.ok ? "directory remains" : removed.error });
|
|
41881
|
+
remaining.push(path2);
|
|
41882
|
+
}
|
|
41883
|
+
}
|
|
41884
|
+
if (recorded.length) writePaths(file, remaining, deps);
|
|
41885
|
+
const roots = /* @__PURE__ */ new Set();
|
|
41886
|
+
for (const p of [...registered.slice(1), ...recorded]) roots.add((0, import_node_path40.dirname)(p));
|
|
41887
|
+
const keptKeys = new Set(remaining.map(norm));
|
|
41888
|
+
for (const root of roots) {
|
|
41889
|
+
if (!isDir(root) || live.has(norm(root)) || norm(root) === primary) continue;
|
|
41890
|
+
let children;
|
|
41891
|
+
try {
|
|
41892
|
+
children = readDir(root);
|
|
41893
|
+
} catch {
|
|
41894
|
+
continue;
|
|
41895
|
+
}
|
|
41896
|
+
for (const name of children) {
|
|
41897
|
+
const child2 = (0, import_node_path40.join)(root, name);
|
|
41898
|
+
const key = norm(child2);
|
|
41899
|
+
if (live.has(key) || key === primary || keptKeys.has(key) || !isDir(child2)) continue;
|
|
41900
|
+
let entries;
|
|
41901
|
+
try {
|
|
41902
|
+
entries = readDir(child2);
|
|
41903
|
+
} catch {
|
|
41904
|
+
continue;
|
|
41905
|
+
}
|
|
41906
|
+
if (entries.length === 0) {
|
|
41907
|
+
try {
|
|
41908
|
+
removeEmpty(child2);
|
|
41909
|
+
report.emptyRemoved.push(child2);
|
|
41910
|
+
} catch {
|
|
41911
|
+
}
|
|
41912
|
+
} else {
|
|
41913
|
+
report.unrecorded.push(child2);
|
|
41914
|
+
}
|
|
41915
|
+
}
|
|
41916
|
+
}
|
|
41917
|
+
return report;
|
|
41918
|
+
}
|
|
41919
|
+
function renderResidueSweepLines(report) {
|
|
41920
|
+
if (!report) return [];
|
|
41921
|
+
const lines2 = [];
|
|
41922
|
+
const cleaned = report.swept.length + report.emptyRemoved.length;
|
|
41923
|
+
if (cleaned) lines2.push(`pr merge: removed ${cleaned} leftover worktree folder(s) from earlier merges`);
|
|
41924
|
+
for (const k of report.kept) lines2.push(`pr merge: leftover folder still locked, retried on the next merge \u2014 ${k.path}`);
|
|
41925
|
+
if (report.unrecorded.length) {
|
|
41926
|
+
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(", ")}`);
|
|
41927
|
+
}
|
|
41928
|
+
return lines2;
|
|
41929
|
+
}
|
|
41930
|
+
|
|
41657
41931
|
// src/pr-deploy-dev.ts
|
|
41658
41932
|
function slugOfRepo(repo) {
|
|
41659
41933
|
return repo.split("/")[1]?.toLowerCase() ?? "";
|
|
@@ -41688,10 +41962,10 @@ function argvWantsJson2() {
|
|
|
41688
41962
|
return process.argv.some((a) => a === "--json" || a.startsWith("--json="));
|
|
41689
41963
|
}
|
|
41690
41964
|
function hubRoot() {
|
|
41691
|
-
const fromPkg = (0,
|
|
41965
|
+
const fromPkg = (0, import_node_path41.join)(__dirname, "..", "..");
|
|
41692
41966
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
41693
|
-
if ((0,
|
|
41694
|
-
if ((0,
|
|
41967
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(fromPkg, marker))) return fromPkg;
|
|
41968
|
+
if ((0, import_node_fs43.existsSync)((0, import_node_path41.join)(process.cwd(), marker))) return process.cwd();
|
|
41695
41969
|
return null;
|
|
41696
41970
|
}
|
|
41697
41971
|
function ciAuditDeps() {
|
|
@@ -41703,8 +41977,8 @@ function ciAuditDeps() {
|
|
|
41703
41977
|
getProjectMeta: async (slug) => fetchProjectBySlug(slug, registryClientDeps(await cfgPromise)),
|
|
41704
41978
|
readSeedFile: (path2) => {
|
|
41705
41979
|
if (!root) return null;
|
|
41706
|
-
const fullPath = (0,
|
|
41707
|
-
return (0,
|
|
41980
|
+
const fullPath = (0, import_node_path41.join)(root, path2);
|
|
41981
|
+
return (0, import_node_fs43.existsSync)(fullPath) ? (0, import_node_fs43.readFileSync)(fullPath, "utf8") : null;
|
|
41708
41982
|
}
|
|
41709
41983
|
};
|
|
41710
41984
|
}
|
|
@@ -42429,11 +42703,11 @@ ${list}`);
|
|
|
42429
42703
|
}
|
|
42430
42704
|
});
|
|
42431
42705
|
async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
42432
|
-
const wfDir = (0,
|
|
42433
|
-
if (!(0,
|
|
42434
|
-
return (0,
|
|
42706
|
+
const wfDir = (0, import_node_path41.join)(cwd, ".github", "workflows");
|
|
42707
|
+
if (!(0, import_node_fs43.existsSync)(wfDir)) return [];
|
|
42708
|
+
return (0, import_node_fs43.readdirSync)(wfDir).filter((name) => /\.(ya?ml)$/i.test(name)).filter((name) => {
|
|
42435
42709
|
try {
|
|
42436
|
-
return workflowReportsPrChecks((0,
|
|
42710
|
+
return workflowReportsPrChecks((0, import_node_fs43.readFileSync)((0, import_node_path41.join)(wfDir, name), "utf8"));
|
|
42437
42711
|
} catch {
|
|
42438
42712
|
return true;
|
|
42439
42713
|
}
|
|
@@ -42629,7 +42903,7 @@ ${list}`);
|
|
|
42629
42903
|
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
42904
|
if (!isReviewVerdict(o.verdict)) return fail(`pr review-verdict: --verdict must be one of ${REVIEW_VERDICTS.join("|")} (got ${o.verdict})`);
|
|
42631
42905
|
const verdict = o.verdict;
|
|
42632
|
-
const findings = o.findingsFile ? (0,
|
|
42906
|
+
const findings = o.findingsFile ? (0, import_node_fs43.readFileSync)(o.findingsFile, "utf8") : void 0;
|
|
42633
42907
|
let patch;
|
|
42634
42908
|
let head;
|
|
42635
42909
|
try {
|
|
@@ -42903,7 +43177,7 @@ ${list}`);
|
|
|
42903
43177
|
const mergeSquashBody = squashBodyTextForMerge(
|
|
42904
43178
|
closingGuardInput,
|
|
42905
43179
|
method === "--squash",
|
|
42906
|
-
o.squashBodyFile ? (0,
|
|
43180
|
+
o.squashBodyFile ? (0, import_node_fs43.readFileSync)(o.squashBodyFile, "utf8") : void 0
|
|
42907
43181
|
);
|
|
42908
43182
|
if (!o.squashBodyFile) warnSquashBodyClosingStrip("pr merge", closingGuardInput, mergeSquashBody);
|
|
42909
43183
|
const closingGuardVerdict = evaluateClosingGuard(closingGuardInput, {
|
|
@@ -42935,7 +43209,7 @@ ${list}`);
|
|
|
42935
43209
|
const remote = foreignCwd ? `https://github.com/${targetRepo2}.git` : "origin";
|
|
42936
43210
|
let foreignCheckout;
|
|
42937
43211
|
if (foreignCwd) {
|
|
42938
|
-
const sibling = (0,
|
|
43212
|
+
const sibling = (0, import_node_path41.join)((0, import_node_path41.dirname)(beforeWorktrees[0]?.path || startingPath || process.cwd()), targetRepo2.split("/")[1]);
|
|
42939
43213
|
const siblingRepo = repoFromRemoteUrl(await gitOut(["-C", sibling, "remote", "get-url", "origin"]).catch(() => ""));
|
|
42940
43214
|
if (siblingRepo?.toLowerCase() === targetRepo2.toLowerCase()) {
|
|
42941
43215
|
const siblingWorktrees = await execFileP("git", ["-C", sibling, "worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseGitWorktreePorcelain(r.stdout)).catch(() => void 0);
|
|
@@ -43033,7 +43307,7 @@ ${list}`);
|
|
|
43033
43307
|
}
|
|
43034
43308
|
if (!repoForPostCleanup) throw e;
|
|
43035
43309
|
console.warn(`pr merge: gh GraphQL rate-limited \u2014 merging PR #${number} via REST PUT instead (#4588).`);
|
|
43036
|
-
const commitMessage = bodyFile ? (0,
|
|
43310
|
+
const commitMessage = bodyFile ? (0, import_node_fs43.readFileSync)(bodyFile, "utf8") : void 0;
|
|
43037
43311
|
await defaultGitHubClient().rest("PUT", `repos/${repoForPostCleanup}/pulls/${number}/merge`, {
|
|
43038
43312
|
body: { merge_method: method.slice(2), ...commitMessage ? { commit_message: commitMessage } : {} },
|
|
43039
43313
|
timeoutMs: GH_MUTATION_TIMEOUT_MS
|
|
@@ -43140,6 +43414,15 @@ ${list}`);
|
|
|
43140
43414
|
return;
|
|
43141
43415
|
}
|
|
43142
43416
|
const primaryRoot = beforeWorktrees[0]?.path ?? (startingPath || process.cwd());
|
|
43417
|
+
let gitCommonDir;
|
|
43418
|
+
let residueSweep;
|
|
43419
|
+
if (!(foreignCwd && !foreignCheckout)) {
|
|
43420
|
+
try {
|
|
43421
|
+
gitCommonDir = (0, import_node_path41.resolve)(primaryRoot, (await execFileP("git", ["-C", primaryRoot, "rev-parse", "--git-common-dir"], { timeout: GIT_TIMEOUT_MS })).stdout.trim());
|
|
43422
|
+
residueSweep = await sweepWorktreeResidue(gitCommonDir, beforeWorktrees.map((w) => w.path));
|
|
43423
|
+
} catch {
|
|
43424
|
+
}
|
|
43425
|
+
}
|
|
43143
43426
|
let localCleanup;
|
|
43144
43427
|
if (foreignCwd && !foreignCheckout) {
|
|
43145
43428
|
localCleanup = { branch: headRef, localBranch: { name: headRef, status: "not-attempted", reason: "skipped-foreign-cwd" } };
|
|
@@ -43153,7 +43436,7 @@ ${list}`);
|
|
|
43153
43436
|
preserveWorktree: o.preserveWorktree,
|
|
43154
43437
|
gcAcknowledged: o.gc,
|
|
43155
43438
|
expectedHeadOid: headRefOid,
|
|
43156
|
-
pathExists: (p) => (0,
|
|
43439
|
+
pathExists: (p) => (0, import_node_fs43.existsSync)(p),
|
|
43157
43440
|
// #5899: pin cleanup git calls to the main checkout — the task worktree this process may be
|
|
43158
43441
|
// standing in is removed mid-cleanup, so a cwd-relative invocation fails with
|
|
43159
43442
|
// 'fatal: not a git repository' and leaves a spurious partial-cleanup exit.
|
|
@@ -43184,6 +43467,7 @@ ${list}`);
|
|
|
43184
43467
|
remote
|
|
43185
43468
|
});
|
|
43186
43469
|
const worktree = localCleanup?.worktree;
|
|
43470
|
+
if (gitCommonDir && worktree?.path && worktree.residue === "left") recordResidue(gitCommonDir, worktree.path);
|
|
43187
43471
|
const worktreePartial = isPrMergeWorktreePartial(localCleanup) && worktree?.path ? {
|
|
43188
43472
|
kind: "worktree-directory",
|
|
43189
43473
|
path: worktree.path,
|
|
@@ -43248,6 +43532,7 @@ ${list}`);
|
|
|
43248
43532
|
...partialCleanup.length ? { cleanupStatus: "partial", partialCleanup } : {},
|
|
43249
43533
|
...localCleanup?.worktree ? { worktree: localCleanup.worktree } : {},
|
|
43250
43534
|
...localCleanup?.localBranch ? { localBranch: localCleanup.localBranch } : {},
|
|
43535
|
+
...residueSweep && (residueSweep.swept.length || residueSweep.emptyRemoved.length || residueSweep.kept.length || residueSweep.unrecorded.length) ? { residueSweep } : {},
|
|
43251
43536
|
// `boardAdvance` keeps its published array shape; `boardAdvanceStatus` is the field a caller reads to
|
|
43252
43537
|
// tell "merged clean" from "merged, but the board did not follow" without guessing from the exit code.
|
|
43253
43538
|
...boardAdvance.entries.length ? { boardAdvance: boardAdvance.entries } : {},
|
|
@@ -43257,6 +43542,7 @@ ${list}`);
|
|
|
43257
43542
|
...devDeploy ? { devDeploy: devDeploy.ok ? { dispatched: true } : { dispatched: false, manual: devDeployManual } } : {}
|
|
43258
43543
|
}));
|
|
43259
43544
|
for (const line of renderPrMergeCleanupLines(localCleanup)) console.warn(line);
|
|
43545
|
+
for (const line of renderResidueSweepLines(residueSweep)) console.warn(line);
|
|
43260
43546
|
if (devDeploy && !devDeploy.ok) {
|
|
43261
43547
|
console.error(`pr merge: ${devDeployManual} (${devDeploy.detail})`);
|
|
43262
43548
|
}
|
|
@@ -43274,7 +43560,7 @@ ${list}`);
|
|
|
43274
43560
|
}
|
|
43275
43561
|
|
|
43276
43562
|
// src/command-register-developer.ts
|
|
43277
|
-
var
|
|
43563
|
+
var import_node_fs48 = require("node:fs");
|
|
43278
43564
|
init_clean_exit();
|
|
43279
43565
|
init_cli_shared();
|
|
43280
43566
|
init_error_codes();
|
|
@@ -43307,7 +43593,7 @@ async function resolveWhoami(deps) {
|
|
|
43307
43593
|
}
|
|
43308
43594
|
|
|
43309
43595
|
// src/command-register-developer.ts
|
|
43310
|
-
var
|
|
43596
|
+
var import_node_path46 = require("node:path");
|
|
43311
43597
|
init_house_map();
|
|
43312
43598
|
init_hub_url();
|
|
43313
43599
|
|
|
@@ -43340,7 +43626,7 @@ async function executeWaveLand(plan, deps) {
|
|
|
43340
43626
|
}
|
|
43341
43627
|
|
|
43342
43628
|
// src/box-commands.ts
|
|
43343
|
-
var
|
|
43629
|
+
var import_node_fs44 = require("node:fs");
|
|
43344
43630
|
init_clean_exit();
|
|
43345
43631
|
|
|
43346
43632
|
// src/box.ts
|
|
@@ -43552,7 +43838,7 @@ function registerBoxCommands(program3) {
|
|
|
43552
43838
|
}
|
|
43553
43839
|
const remote = command && command.length ? command.join(" ") : void 0;
|
|
43554
43840
|
const wroteScript = o.ssh && o.script ? o.script : null;
|
|
43555
|
-
if (wroteScript) (0,
|
|
43841
|
+
if (wroteScript) (0, import_node_fs44.writeFileSync)(wroteScript, sshRecipeScript(found, remote), "utf8");
|
|
43556
43842
|
if (o.json) {
|
|
43557
43843
|
console.log(JSON.stringify({
|
|
43558
43844
|
box: found,
|
|
@@ -43574,19 +43860,19 @@ ${SSH_RECIPE_AGENT_NOTE}`);
|
|
|
43574
43860
|
// src/dist-drift.ts
|
|
43575
43861
|
var import_node_child_process16 = require("node:child_process");
|
|
43576
43862
|
var import_node_crypto13 = require("node:crypto");
|
|
43577
|
-
var
|
|
43863
|
+
var import_node_fs46 = require("node:fs");
|
|
43578
43864
|
var import_node_os21 = require("node:os");
|
|
43579
|
-
var
|
|
43865
|
+
var import_node_path43 = require("node:path");
|
|
43580
43866
|
|
|
43581
43867
|
// ../scripts/distribution-digest.mjs
|
|
43582
43868
|
var import_node_crypto12 = require("node:crypto");
|
|
43583
|
-
var
|
|
43584
|
-
var
|
|
43869
|
+
var import_node_fs45 = require("node:fs");
|
|
43870
|
+
var import_node_path42 = require("node:path");
|
|
43585
43871
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
43586
43872
|
function repoPath(root, declaredPath, label) {
|
|
43587
|
-
const absoluteRoot = (0,
|
|
43588
|
-
const target = (0,
|
|
43589
|
-
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${
|
|
43873
|
+
const absoluteRoot = (0, import_node_path42.resolve)(root);
|
|
43874
|
+
const target = (0, import_node_path42.resolve)(root, declaredPath);
|
|
43875
|
+
if (target !== absoluteRoot && !target.startsWith(`${absoluteRoot}${import_node_path42.sep}`)) {
|
|
43590
43876
|
throw new Error(`${label} ${declaredPath} escapes the repository root`);
|
|
43591
43877
|
}
|
|
43592
43878
|
return target;
|
|
@@ -43594,7 +43880,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
43594
43880
|
function digestFiles(files) {
|
|
43595
43881
|
const hash = (0, import_node_crypto12.createHash)("sha256");
|
|
43596
43882
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
43597
|
-
const content = file.stat.isSymbolicLink() ? Buffer.from((0,
|
|
43883
|
+
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs45.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs45.readFileSync)(file.absolute);
|
|
43598
43884
|
hash.update(file.relative, "utf8");
|
|
43599
43885
|
hash.update("\0");
|
|
43600
43886
|
hash.update(file.stat.isSymbolicLink() ? "symlink" : "file", "utf8");
|
|
@@ -43609,8 +43895,8 @@ function digestFiles(files) {
|
|
|
43609
43895
|
function digestPackedFiles(packageRoot, packedFiles) {
|
|
43610
43896
|
return digestFiles(packedFiles.map((path2) => {
|
|
43611
43897
|
const absolute = repoPath(packageRoot, path2, "packed artifact identity path");
|
|
43612
|
-
if (!(0,
|
|
43613
|
-
return { absolute, relative: slash(path2), stat: (0,
|
|
43898
|
+
if (!(0, import_node_fs45.existsSync)(absolute)) throw new Error(`packed artifact identity path ${path2} does not exist`);
|
|
43899
|
+
return { absolute, relative: slash(path2), stat: (0, import_node_fs45.lstatSync)(absolute) };
|
|
43614
43900
|
}));
|
|
43615
43901
|
}
|
|
43616
43902
|
|
|
@@ -43694,13 +43980,13 @@ function renderDistDriftReceipt(receipt) {
|
|
|
43694
43980
|
return lines2;
|
|
43695
43981
|
}
|
|
43696
43982
|
function readOrNull(path2) {
|
|
43697
|
-
return (0,
|
|
43983
|
+
return (0, import_node_fs46.existsSync)(path2) ? (0, import_node_fs46.readFileSync)(path2) : null;
|
|
43698
43984
|
}
|
|
43699
43985
|
function walkFiles(root) {
|
|
43700
43986
|
const files = [];
|
|
43701
43987
|
const walk2 = (directory) => {
|
|
43702
|
-
for (const entry of (0,
|
|
43703
|
-
const child2 = (0,
|
|
43988
|
+
for (const entry of (0, import_node_fs46.readdirSync)(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
43989
|
+
const child2 = (0, import_node_path43.join)(directory, entry.name);
|
|
43704
43990
|
if (entry.isDirectory()) walk2(child2);
|
|
43705
43991
|
else files.push(child2);
|
|
43706
43992
|
}
|
|
@@ -43710,10 +43996,10 @@ function walkFiles(root) {
|
|
|
43710
43996
|
}
|
|
43711
43997
|
function bomPathFor(root) {
|
|
43712
43998
|
try {
|
|
43713
|
-
const registry2 = JSON.parse((0,
|
|
43714
|
-
return (0,
|
|
43999
|
+
const registry2 = JSON.parse((0, import_node_fs46.readFileSync)((0, import_node_path43.join)(root, "surfaces.json"), "utf8"));
|
|
44000
|
+
return (0, import_node_path43.join)(root, registry2?.sharedAgentCore?.releaseMetadata?.bomPath ?? "distribution-bom.json");
|
|
43715
44001
|
} catch {
|
|
43716
|
-
return (0,
|
|
44002
|
+
return (0, import_node_path43.join)(root, "distribution-bom.json");
|
|
43717
44003
|
}
|
|
43718
44004
|
}
|
|
43719
44005
|
function rebuildTo(packageRoot, outDir) {
|
|
@@ -43726,35 +44012,35 @@ function rebuildTo(packageRoot, outDir) {
|
|
|
43726
44012
|
});
|
|
43727
44013
|
}
|
|
43728
44014
|
function runDistStatus(root) {
|
|
43729
|
-
const stage = (0,
|
|
44015
|
+
const stage = (0, import_node_fs46.mkdtempSync)((0, import_node_path43.join)((0, import_node_os21.tmpdir)(), "mmi-dist-drift-"));
|
|
43730
44016
|
let overlayCount = 0;
|
|
43731
44017
|
try {
|
|
43732
|
-
const cliOut = (0,
|
|
43733
|
-
const hubOut = (0,
|
|
43734
|
-
rebuildTo((0,
|
|
43735
|
-
rebuildTo((0,
|
|
44018
|
+
const cliOut = (0, import_node_path43.join)(stage, "cli-dist");
|
|
44019
|
+
const hubOut = (0, import_node_path43.join)(stage, "hub-dist");
|
|
44020
|
+
rebuildTo((0, import_node_path43.join)(root, "cli"), cliOut);
|
|
44021
|
+
rebuildTo((0, import_node_path43.join)(root, "updater"), hubOut);
|
|
43736
44022
|
const outDirFor = (packageDir) => packageDir === "cli" ? cliOut : hubOut;
|
|
43737
44023
|
const rebuilt = (path2) => {
|
|
43738
44024
|
const spec = DIST_ARTIFACTS.find((entry) => entry.path === path2);
|
|
43739
|
-
return spec ? readOrNull((0,
|
|
44025
|
+
return spec ? readOrNull((0, import_node_path43.join)(outDirFor(spec.packageDir), spec.output)) : null;
|
|
43740
44026
|
};
|
|
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,
|
|
44027
|
+
const committed = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
|
|
44028
|
+
const tree = (path2) => readOrNull((0, import_node_path43.join)(root, path2));
|
|
44029
|
+
const distRoot = (0, import_node_path43.join)(root, "cli", "dist");
|
|
44030
|
+
const distTree = () => walkFiles(distRoot).map((absolute) => `cli/dist/${(0, import_node_path43.relative)(distRoot, absolute).replaceAll("\\", "/")}`);
|
|
44031
|
+
const bom = JSON.parse((0, import_node_fs46.readFileSync)(bomPathFor(root), "utf8"));
|
|
43746
44032
|
const digest = (entries) => {
|
|
43747
|
-
const overlay = (0,
|
|
44033
|
+
const overlay = (0, import_node_path43.join)(stage, `overlay-${overlayCount++}`);
|
|
43748
44034
|
for (const entry of entries) {
|
|
43749
|
-
const target = (0,
|
|
43750
|
-
(0,
|
|
43751
|
-
(0,
|
|
44035
|
+
const target = (0, import_node_path43.join)(overlay, entry.path);
|
|
44036
|
+
(0, import_node_fs46.mkdirSync)((0, import_node_path43.dirname)(target), { recursive: true });
|
|
44037
|
+
(0, import_node_fs46.writeFileSync)(target, entry.bytes);
|
|
43752
44038
|
}
|
|
43753
44039
|
return digestPackedFiles(overlay, entries.map((entry) => entry.path));
|
|
43754
44040
|
};
|
|
43755
44041
|
return computeDistDriftReceipt({ committed, tree, rebuilt, distTree, bom, digest });
|
|
43756
44042
|
} finally {
|
|
43757
|
-
(0,
|
|
44043
|
+
(0, import_node_fs46.rmSync)(stage, { recursive: true, force: true });
|
|
43758
44044
|
}
|
|
43759
44045
|
}
|
|
43760
44046
|
|
|
@@ -43833,7 +44119,7 @@ init_hub_auth();
|
|
|
43833
44119
|
|
|
43834
44120
|
// src/schedules-lift-command.ts
|
|
43835
44121
|
var import_promises8 = require("node:fs/promises");
|
|
43836
|
-
var
|
|
44122
|
+
var import_node_path44 = require("node:path");
|
|
43837
44123
|
init_clean_exit();
|
|
43838
44124
|
init_cli_shared();
|
|
43839
44125
|
var DEFAULT_WORKFLOWS_DIR = ".github/workflows";
|
|
@@ -43862,7 +44148,7 @@ async function readWorkflowFiles(dir) {
|
|
|
43862
44148
|
const files = [];
|
|
43863
44149
|
for (const name of names.sort()) {
|
|
43864
44150
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
43865
|
-
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0,
|
|
44151
|
+
files.push({ path: `.github/workflows/${name}`, text: await (0, import_promises8.readFile)((0, import_node_path44.join)(dir, name), "utf8") });
|
|
43866
44152
|
}
|
|
43867
44153
|
return files;
|
|
43868
44154
|
}
|
|
@@ -43945,8 +44231,8 @@ function registerSchedulesLiftCommand(program3, deps = {}) {
|
|
|
43945
44231
|
|
|
43946
44232
|
// src/spawn-policy-core.ts
|
|
43947
44233
|
var import_node_child_process17 = require("node:child_process");
|
|
43948
|
-
var
|
|
43949
|
-
var
|
|
44234
|
+
var import_node_fs47 = require("node:fs");
|
|
44235
|
+
var import_node_path45 = require("node:path");
|
|
43950
44236
|
var SPAWNERS = ["spawn", "spawnSync", "exec", "execSync", "execFile", "execFileSync"];
|
|
43951
44237
|
var CALL_SOURCE = String.raw`(^|[^.\w$])(${SPAWNERS.join("|")})\s*\(`;
|
|
43952
44238
|
var SOURCE_EXT = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
@@ -44032,7 +44318,7 @@ function runSpawnPolicy(root) {
|
|
|
44032
44318
|
for (const file of files) {
|
|
44033
44319
|
let raw;
|
|
44034
44320
|
try {
|
|
44035
|
-
raw = (0,
|
|
44321
|
+
raw = (0, import_node_fs47.readFileSync)((0, import_node_path45.join)(root, file), "utf8");
|
|
44036
44322
|
} catch {
|
|
44037
44323
|
continue;
|
|
44038
44324
|
}
|
|
@@ -44052,19 +44338,19 @@ function runSpawnPolicy(root) {
|
|
|
44052
44338
|
function registerDeveloperCommands(program3) {
|
|
44053
44339
|
const rules = program3.command("rules").description("org-managed .gitignore delivery");
|
|
44054
44340
|
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,
|
|
44341
|
+
const path2 = (0, import_node_path46.join)(process.cwd(), ".gitignore");
|
|
44342
|
+
const current = (0, import_node_fs48.existsSync)(path2) ? (0, import_node_fs48.readFileSync)(path2, "utf8") : null;
|
|
44057
44343
|
const plan = planManagedGitignore(current);
|
|
44058
44344
|
const drift = [...plan.added.map((l) => `+${l}`), ...plan.removed.map((l) => `-${l}`)].join(", ") || "block normalize";
|
|
44059
44345
|
if (opts.json) {
|
|
44060
|
-
if (opts.write && plan.changed) (0,
|
|
44346
|
+
if (opts.write && plan.changed) (0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
44061
44347
|
console.log(JSON.stringify(plan, null, 2));
|
|
44062
44348
|
if (!opts.write && plan.changed) process.exitCode = 1;
|
|
44063
44349
|
return;
|
|
44064
44350
|
}
|
|
44065
44351
|
if (opts.write) {
|
|
44066
44352
|
if (plan.changed) {
|
|
44067
|
-
(0,
|
|
44353
|
+
(0, import_node_fs48.writeFileSync)(path2, plan.content, "utf8");
|
|
44068
44354
|
console.log(`mmi-cli devops org rules gitignore: updated .gitignore (${drift})`);
|
|
44069
44355
|
} else {
|
|
44070
44356
|
console.log("mmi-cli devops org rules gitignore: up to date");
|
|
@@ -45295,6 +45581,10 @@ ${decision.note}`);
|
|
|
45295
45581
|
deployNote = dispatch.note;
|
|
45296
45582
|
runs.push(hotfixWorkflowRunFromDispatch("tenant-deploy.yml", deploy));
|
|
45297
45583
|
if (publish) runs.push(hotfixWorkflowRunFromDispatch("publish.yml", publish));
|
|
45584
|
+
} else if (deployModel === "vercel") {
|
|
45585
|
+
const vercel = await dispatchDeploy(deps, ctx, "main", "main", deployModel, true, (deps.now ?? Date.now)(), mergedSha, "report");
|
|
45586
|
+
runs.push(hotfixWorkflowRunFromDispatch("vercel", vercel));
|
|
45587
|
+
deployNote = vercel.note;
|
|
45298
45588
|
} else {
|
|
45299
45589
|
deployNote = `no hotfix deploy dispatch for deployModel=${deployModel} \u2014 prod deploy is repo-specific`;
|
|
45300
45590
|
}
|
|
@@ -45804,8 +46094,8 @@ function checkHotfixCarries(options) {
|
|
|
45804
46094
|
}
|
|
45805
46095
|
|
|
45806
46096
|
// src/train-commands.ts
|
|
45807
|
-
var
|
|
45808
|
-
var
|
|
46097
|
+
var import_node_fs49 = require("node:fs");
|
|
46098
|
+
var import_node_path47 = require("node:path");
|
|
45809
46099
|
init_cli_shared();
|
|
45810
46100
|
init_clean_exit();
|
|
45811
46101
|
init_client_version();
|
|
@@ -45820,7 +46110,7 @@ function resolveReleaseBumpIntent(raw) {
|
|
|
45820
46110
|
}
|
|
45821
46111
|
function readRepoVersion() {
|
|
45822
46112
|
try {
|
|
45823
|
-
return JSON.parse((0,
|
|
46113
|
+
return JSON.parse((0, import_node_fs49.readFileSync)((0, import_node_path47.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
45824
46114
|
} catch {
|
|
45825
46115
|
return void 0;
|
|
45826
46116
|
}
|
|
@@ -45922,8 +46212,8 @@ async function withReleaseProgress(deps, initial, action) {
|
|
|
45922
46212
|
clearInterval(heartbeat);
|
|
45923
46213
|
}
|
|
45924
46214
|
}
|
|
45925
|
-
function releaseProgressVerdict(status) {
|
|
45926
|
-
return status === "complete" ?
|
|
46215
|
+
function releaseProgressVerdict(status, subject = "Release") {
|
|
46216
|
+
return status === "complete" ? `${subject} complete` : status === "failed" ? `${subject} failed \u2014 a follow-up needs attention` : `${subject} pending \u2014 waiting for unfinished follow-ups`;
|
|
45927
46217
|
}
|
|
45928
46218
|
|
|
45929
46219
|
// src/command-register-train-operations.ts
|
|
@@ -46160,8 +46450,31 @@ function renderReleasePublishRetry(r) {
|
|
|
46160
46450
|
return `mmi-cli devops release --retry-publish: ${r.repo} ${r.tag} (${r.tagSha.slice(0, 12)}) \u2014 ${r.note}; ${r.runUrl}`;
|
|
46161
46451
|
}
|
|
46162
46452
|
function renderRcandResume(r) {
|
|
46163
|
-
const
|
|
46164
|
-
|
|
46453
|
+
const verb = r.command === "rcand-retry-deploy" ? "retry-deploy" : "resume";
|
|
46454
|
+
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}`;
|
|
46455
|
+
return appendVerdictAndLedger(base, r.releaseVerdict, r.ledger);
|
|
46456
|
+
}
|
|
46457
|
+
function progressSubject(commandName) {
|
|
46458
|
+
return commandName === "rcand" ? "Release candidate" : "Release";
|
|
46459
|
+
}
|
|
46460
|
+
function emitRcandContinuation(label, raw, o) {
|
|
46461
|
+
const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
|
|
46462
|
+
const { followUpStatus, releaseVerdict } = buildReleaseVerdict("rcand", result, void 0);
|
|
46463
|
+
const reported = { ...result, releaseVerdict };
|
|
46464
|
+
emitTrainResult(label, o.json ? JSON.stringify(reported, null, 2) : renderRcandResume(reported), o.out);
|
|
46465
|
+
consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus, progressSubject("rcand"))}`);
|
|
46466
|
+
applyTrainFollowUpExit(followUpStatus);
|
|
46467
|
+
}
|
|
46468
|
+
function appendVerdictAndLedger(base, v, ledger) {
|
|
46469
|
+
const lines2 = [base];
|
|
46470
|
+
if (v) {
|
|
46471
|
+
const verdict = v.followUpStatus === "failed" ? "PROMOTED, FOLLOW-UP FAILED" : v.followUpStatus === "pending" ? "PROMOTED, FOLLOW-UP PENDING" : "SUCCEEDED";
|
|
46472
|
+
const followUpNote = trainFollowUpNote(v.followUpStatus);
|
|
46473
|
+
lines2[0] = `${base}; release verdict: ${verdict}; promoted=${v.promoted}; tag=${v.tag}; release=${v.releaseUrl ?? "unreported"}; follow-up: ${v.followUpStatus.toUpperCase()}${followUpNote ? ` (${followUpNote})` : ""}`;
|
|
46474
|
+
lines2.push(...v.legs.filter((leg) => leg.status === "failed").map((leg) => ` - follow-up leg ${leg.leg}: FAILED \u2014 ${leg.error ?? "no diagnostic recorded"}`));
|
|
46475
|
+
}
|
|
46476
|
+
if (ledger) lines2.push(...formatReleaseLedgerReport(ledger).map((line) => ` ${line}`));
|
|
46477
|
+
return lines2.join("\n");
|
|
46165
46478
|
}
|
|
46166
46479
|
function renderAlignment(label, alignment) {
|
|
46167
46480
|
if (alignment.status !== "pr-pending") return `${label}: ${alignment.note}`;
|
|
@@ -46269,16 +46582,17 @@ function buildReleaseVerdict(commandName, result, projectInfoSync) {
|
|
|
46269
46582
|
alignment: alignmentPending ? "unresolved" : "ok",
|
|
46270
46583
|
foldPort: "ok"
|
|
46271
46584
|
});
|
|
46585
|
+
const legs = releaseFollowUpLegs(result, projectInfoSync);
|
|
46272
46586
|
return {
|
|
46273
46587
|
followUpStatus,
|
|
46274
|
-
releaseVerdict:
|
|
46588
|
+
releaseVerdict: {
|
|
46275
46589
|
releaseStatus: "succeeded",
|
|
46276
46590
|
followUpStatus,
|
|
46277
46591
|
promoted: result.promoted,
|
|
46278
46592
|
tag: result.tag,
|
|
46279
46593
|
releaseUrl: result.release?.url ?? null,
|
|
46280
|
-
legs:
|
|
46281
|
-
}
|
|
46594
|
+
legs: commandName === "rcand" ? legs.filter((leg) => leg.leg !== "project-info") : legs
|
|
46595
|
+
}
|
|
46282
46596
|
};
|
|
46283
46597
|
}
|
|
46284
46598
|
function buildResumeReleaseVerdict(result, projectInfoSync) {
|
|
@@ -46331,22 +46645,7 @@ function renderTrainApply(commandName, r) {
|
|
|
46331
46645
|
base = `${base}; project info: ${r.projectInfoSync.note}`;
|
|
46332
46646
|
}
|
|
46333
46647
|
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;
|
|
46648
|
+
return appendVerdictAndLedger(base, r.releaseVerdict, r.ledger);
|
|
46350
46649
|
}
|
|
46351
46650
|
function applyTrainFollowUpExit(status) {
|
|
46352
46651
|
const code = trainFollowUpExitCode(status);
|
|
@@ -46396,6 +46695,8 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46396
46695
|
trainCmd.addOption(new Option(f.flags, f.description).hideHelp());
|
|
46397
46696
|
}
|
|
46398
46697
|
}
|
|
46698
|
+
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)");
|
|
46699
|
+
trainCmd.addOption(commandName === "rcand" ? retryDeploy : retryDeploy.hideHelp());
|
|
46399
46700
|
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
46701
|
try {
|
|
46401
46702
|
await requireFreshTrainCli(commandName);
|
|
@@ -46423,8 +46724,30 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46423
46724
|
if (o.retryPublish && commandName !== "release") {
|
|
46424
46725
|
return fail(`${commandName}: --retry-publish applies only to release. Run: mmi-cli devops release --retry-publish <run-id> --apply --watch`);
|
|
46425
46726
|
}
|
|
46426
|
-
if (o.
|
|
46427
|
-
return fail(`${commandName}: --
|
|
46727
|
+
if (o.retryDeploy && commandName !== "rcand") {
|
|
46728
|
+
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)`);
|
|
46729
|
+
}
|
|
46730
|
+
if (o.out && !o.apply && !o.resume && !o.retryDeploy) {
|
|
46731
|
+
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`);
|
|
46732
|
+
}
|
|
46733
|
+
if (o.retryDeploy) {
|
|
46734
|
+
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");
|
|
46735
|
+
if (o.repo) {
|
|
46736
|
+
const guard = planTrainApplyRepoGuard(o.repo, await resolveRepo(), `mmi-cli devops rcand --retry-deploy${o.watch ? " --watch" : ""}`);
|
|
46737
|
+
if (!guard.ok) return fail(`rcand: ${guard.message}`);
|
|
46738
|
+
}
|
|
46739
|
+
try {
|
|
46740
|
+
const raw = await withReleaseProgress(
|
|
46741
|
+
trainApplyDeps(),
|
|
46742
|
+
"Checking the failed release candidate deploy",
|
|
46743
|
+
(deps) => runRcandRetryDeploy(deps, { watch: o.watch })
|
|
46744
|
+
);
|
|
46745
|
+
return emitRcandContinuation("rcand --retry-deploy", raw, o);
|
|
46746
|
+
} catch (e) {
|
|
46747
|
+
applyTrainFollowUpExit("failed");
|
|
46748
|
+
consoleIo.err("mmi-cli: Release: Release candidate blocked \u2014 see the error below");
|
|
46749
|
+
return failGraceful(`rcand --retry-deploy: ${e.message}`);
|
|
46750
|
+
}
|
|
46428
46751
|
}
|
|
46429
46752
|
if (o.supersedeDeploy && o.supersedePublish) {
|
|
46430
46753
|
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 +46851,12 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46528
46851
|
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
46852
|
try {
|
|
46530
46853
|
if (commandName === "rcand") {
|
|
46531
|
-
const raw2 = await
|
|
46532
|
-
|
|
46533
|
-
|
|
46534
|
-
|
|
46535
|
-
|
|
46536
|
-
|
|
46537
|
-
rcRetirement: "ok",
|
|
46538
|
-
alignment: "ok",
|
|
46539
|
-
foldPort: "ok"
|
|
46540
|
-
}));
|
|
46541
|
-
return;
|
|
46854
|
+
const raw2 = await withReleaseProgress(
|
|
46855
|
+
trainApplyDeps(),
|
|
46856
|
+
"Checking the existing release candidate",
|
|
46857
|
+
(deps) => runRcandResume(deps, { watch: o.watch })
|
|
46858
|
+
);
|
|
46859
|
+
return emitRcandContinuation("rcand --resume", raw2, o);
|
|
46542
46860
|
}
|
|
46543
46861
|
const raw = await withReleaseProgress(
|
|
46544
46862
|
trainApplyDeps(),
|
|
@@ -46559,7 +46877,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46559
46877
|
return;
|
|
46560
46878
|
} catch (e) {
|
|
46561
46879
|
applyTrainFollowUpExit("failed");
|
|
46562
|
-
|
|
46880
|
+
consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
|
|
46563
46881
|
return failGraceful(`${commandName} --resume: ${e.message}`);
|
|
46564
46882
|
}
|
|
46565
46883
|
}
|
|
@@ -46603,7 +46921,7 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46603
46921
|
try {
|
|
46604
46922
|
const ack = (o.ack ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
46605
46923
|
const apply = (deps) => runTrainApply(commandName, deps, { watch: o.watch, announceSummaryFile: o.announceSummaryFile, ack, dev: o.dev });
|
|
46606
|
-
const raw = commandName === "release" ?
|
|
46924
|
+
const raw = await withReleaseProgress(trainApplyDeps(), commandName === "release" ? "Preparing release" : "Preparing release candidate", apply);
|
|
46607
46925
|
const result = raw.workflowRuns ? { ...raw, workflowRuns: raw.workflowRuns.map(workflowRunWithEvidence) } : raw;
|
|
46608
46926
|
let projectInfoSync;
|
|
46609
46927
|
if (commandName === "release") {
|
|
@@ -46621,14 +46939,14 @@ function registerTrainOperationsCommands(program3, runProjectInfoSyncCallback) {
|
|
|
46621
46939
|
};
|
|
46622
46940
|
const output = o.json ? JSON.stringify(reported, null, 2) : renderTrainApply(commandName, reported);
|
|
46623
46941
|
emitTrainResult(`${commandName} apply`, output, o.out);
|
|
46624
|
-
|
|
46942
|
+
consoleIo.err(`mmi-cli: Release: ${releaseProgressVerdict(followUpStatus, progressSubject(commandName))}`);
|
|
46625
46943
|
applyTrainFollowUpExit(followUpStatus);
|
|
46626
46944
|
return;
|
|
46627
46945
|
} catch (e) {
|
|
46628
46946
|
const error = e;
|
|
46629
46947
|
writeTrainFailureReceipt(commandName, o.json === true, o.out, error);
|
|
46630
46948
|
process.exitCode = 1;
|
|
46631
|
-
|
|
46949
|
+
consoleIo.err(`mmi-cli: Release: ${progressSubject(commandName)} blocked \u2014 see the error below`);
|
|
46632
46950
|
return await failGraceful(`${commandName}: ${error.message}`);
|
|
46633
46951
|
}
|
|
46634
46952
|
}
|
|
@@ -46808,7 +47126,7 @@ ${r.stderr ?? ""}`).catch(() => "");
|
|
|
46808
47126
|
var ENV_HEAL_LOCK_STALE_MS = 10 * 6e4;
|
|
46809
47127
|
var ENV_HEAL_LOCK_MAX_WAIT_MS = 2 * 6e4;
|
|
46810
47128
|
function envHealLockPath(home) {
|
|
46811
|
-
return (0,
|
|
47129
|
+
return (0, import_node_path48.join)(home, ".claude", "plugins", ".mmi-env-heal.lock");
|
|
46812
47130
|
}
|
|
46813
47131
|
async function withEnvHealLock(what, run) {
|
|
46814
47132
|
try {
|
|
@@ -46945,7 +47263,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46945
47263
|
);
|
|
46946
47264
|
const result = applyPluginCachePlan(
|
|
46947
47265
|
plan,
|
|
46948
|
-
(p) => (0,
|
|
47266
|
+
(p) => (0, import_node_fs50.rmSync)(p, { recursive: true }),
|
|
46949
47267
|
stagingApplyFsGuard(configRoot)
|
|
46950
47268
|
);
|
|
46951
47269
|
return {
|
|
@@ -46980,7 +47298,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46980
47298
|
// has no generated routing index would
|
|
46981
47299
|
// get a permanent — demanding an artifact it never asked for.
|
|
46982
47300
|
docsIndexState: (root) => {
|
|
46983
|
-
if (!(0,
|
|
47301
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return void 0;
|
|
46984
47302
|
const real = createDocsIndexDeps(root);
|
|
46985
47303
|
let docs;
|
|
46986
47304
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -46989,7 +47307,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
46989
47307
|
},
|
|
46990
47308
|
// #4168: working-tree heal — write if drifted, then re-check. Commit remains the operator's step.
|
|
46991
47309
|
healDocsIndex: (root) => {
|
|
46992
|
-
if (!(0,
|
|
47310
|
+
if (!(0, import_node_fs50.existsSync)((0, import_node_path48.join)(root, DOCS_INDEX_PATH))) return { drift: false, docCount: 0 };
|
|
46993
47311
|
const real = createDocsIndexDeps(root);
|
|
46994
47312
|
let docs;
|
|
46995
47313
|
const listDocs = () => docs ??= real.listDocs();
|
|
@@ -47615,7 +47933,7 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
47615
47933
|
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
47934
|
if (o.secretsFile) {
|
|
47617
47935
|
try {
|
|
47618
|
-
vars.push(`secrets=${(0,
|
|
47936
|
+
vars.push(`secrets=${(0, import_node_fs50.readFileSync)(o.secretsFile, "utf8")}`);
|
|
47619
47937
|
} catch (e) {
|
|
47620
47938
|
return fail(`org project set: cannot read --secrets-file ${o.secretsFile}: ${e.message}`);
|
|
47621
47939
|
}
|
|
@@ -47948,16 +48266,16 @@ function ciAuditDeps2() {
|
|
|
47948
48266
|
// gate re-seed step is skipped gracefully rather than failing mid-run.
|
|
47949
48267
|
readSeedFile: (path2) => {
|
|
47950
48268
|
if (!root) return null;
|
|
47951
|
-
const fullPath = (0,
|
|
47952
|
-
return (0,
|
|
48269
|
+
const fullPath = (0, import_node_path48.join)(root, path2);
|
|
48270
|
+
return (0, import_node_fs50.existsSync)(fullPath) ? (0, import_node_fs50.readFileSync)(fullPath, "utf8") : null;
|
|
47953
48271
|
}
|
|
47954
48272
|
};
|
|
47955
48273
|
}
|
|
47956
48274
|
function hubRoot2() {
|
|
47957
|
-
const fromPkg = (0,
|
|
48275
|
+
const fromPkg = (0, import_node_path48.join)(__dirname, "..", "..");
|
|
47958
48276
|
const marker = "skills/bootstrap/seeds/manifest.json";
|
|
47959
|
-
if ((0,
|
|
47960
|
-
if ((0,
|
|
48277
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(fromPkg, marker))) return fromPkg;
|
|
48278
|
+
if ((0, import_node_fs50.existsSync)((0, import_node_path48.join)(process.cwd(), marker))) return process.cwd();
|
|
47961
48279
|
return null;
|
|
47962
48280
|
}
|
|
47963
48281
|
registerQueryCommands(program2);
|
|
@@ -48057,10 +48375,10 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
48057
48375
|
targets = resolution.targets;
|
|
48058
48376
|
}
|
|
48059
48377
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
48060
|
-
const fileMatrix = (0,
|
|
48378
|
+
const fileMatrix = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
48061
48379
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
48062
48380
|
const dataAccess = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
48063
|
-
const sanctioned = (0,
|
|
48381
|
+
const sanctioned = (0, import_node_fs50.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs50.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
48064
48382
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
48065
48383
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
48066
48384
|
if (!report.ok) process.exitCode = 1;
|
|
@@ -48091,16 +48409,16 @@ function directoryBytes(path2) {
|
|
|
48091
48409
|
let total = 0;
|
|
48092
48410
|
let entries;
|
|
48093
48411
|
try {
|
|
48094
|
-
entries = (0,
|
|
48412
|
+
entries = (0, import_node_fs50.readdirSync)(path2, { withFileTypes: true });
|
|
48095
48413
|
} catch {
|
|
48096
48414
|
return 0;
|
|
48097
48415
|
}
|
|
48098
48416
|
for (const entry of entries) {
|
|
48099
|
-
const child2 = (0,
|
|
48417
|
+
const child2 = (0, import_node_path48.join)(path2, entry.name);
|
|
48100
48418
|
if (entry.isDirectory()) total += directoryBytes(child2);
|
|
48101
48419
|
else {
|
|
48102
48420
|
try {
|
|
48103
|
-
total += (0,
|
|
48421
|
+
total += (0, import_node_fs50.statSync)(child2).size;
|
|
48104
48422
|
} catch {
|
|
48105
48423
|
}
|
|
48106
48424
|
}
|
|
@@ -48108,25 +48426,25 @@ function directoryBytes(path2) {
|
|
|
48108
48426
|
return total;
|
|
48109
48427
|
}
|
|
48110
48428
|
function listDirEntries(dir) {
|
|
48111
|
-
return (0,
|
|
48429
|
+
return (0, import_node_fs50.readdirSync)(dir, { withFileTypes: true }).map((d) => ({ name: d.name, isDirectory: d.isDirectory() }));
|
|
48112
48430
|
}
|
|
48113
48431
|
function readInstalledPluginRefs(configRoot) {
|
|
48114
48432
|
const p = installedPluginsPathForConfig(configRoot);
|
|
48115
|
-
if (!(0,
|
|
48433
|
+
if (!(0, import_node_fs50.existsSync)(p)) return [];
|
|
48116
48434
|
try {
|
|
48117
|
-
return installedPluginPaths((0,
|
|
48435
|
+
return installedPluginPaths((0, import_node_fs50.readFileSync)(p, "utf8"));
|
|
48118
48436
|
} catch {
|
|
48119
48437
|
return null;
|
|
48120
48438
|
}
|
|
48121
48439
|
}
|
|
48122
48440
|
function pluginCacheFsDeps(configRoot, dirBytes) {
|
|
48123
48441
|
return {
|
|
48124
|
-
exists: (p) => (0,
|
|
48125
|
-
listVersionDirs: (root) => (0,
|
|
48442
|
+
exists: (p) => (0, import_node_fs50.existsSync)(p),
|
|
48443
|
+
listVersionDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name),
|
|
48126
48444
|
dirBytes,
|
|
48127
|
-
listStagingDirs: (root) => (0,
|
|
48445
|
+
listStagingDirs: (root) => (0, import_node_fs50.readdirSync)(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => {
|
|
48128
48446
|
try {
|
|
48129
|
-
return { name: d.name, mtimeMs: newestMtimeMs((0,
|
|
48447
|
+
return { name: d.name, mtimeMs: newestMtimeMs((0, import_node_path48.join)(root, d.name), listDirEntries, (p) => (0, import_node_fs50.statSync)(p).mtimeMs) };
|
|
48130
48448
|
} catch {
|
|
48131
48449
|
return { name: d.name, mtimeMs: Date.now() };
|
|
48132
48450
|
}
|
|
@@ -48140,10 +48458,10 @@ function stagingApplyFsGuard(configRoot) {
|
|
|
48140
48458
|
return {
|
|
48141
48459
|
referencedPaths: () => readInstalledPluginRefs(configRoot),
|
|
48142
48460
|
mtimeMs: (name) => {
|
|
48143
|
-
const p = (0,
|
|
48144
|
-
if (!(0,
|
|
48461
|
+
const p = (0, import_node_path48.join)(stagingRoot, name);
|
|
48462
|
+
if (!(0, import_node_fs50.existsSync)(p)) return null;
|
|
48145
48463
|
try {
|
|
48146
|
-
return newestMtimeMs(p, listDirEntries, (q) => (0,
|
|
48464
|
+
return newestMtimeMs(p, listDirEntries, (q) => (0, import_node_fs50.statSync)(q).mtimeMs);
|
|
48147
48465
|
} catch {
|
|
48148
48466
|
return null;
|
|
48149
48467
|
}
|
|
@@ -48169,7 +48487,7 @@ program2.command("plugin-prune").description(`prune stale cached MMI plugin vers
|
|
|
48169
48487
|
{ withBytes: true, configRoot, includeStaging: surface !== "codex" }
|
|
48170
48488
|
);
|
|
48171
48489
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
48172
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0,
|
|
48490
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs50.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard(configRoot)) : void 0;
|
|
48173
48491
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
48174
48492
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
48175
48493
|
else console.log(renderPluginCachePlan(plan, result));
|