@mutmutco/cli 4.5.36 → 4.5.37
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 +270 -57
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -863,6 +863,9 @@ var init_house_map = __esm({
|
|
|
863
863
|
});
|
|
864
864
|
|
|
865
865
|
// src/stdin-inject.ts
|
|
866
|
+
function setInjectedStdin(payload) {
|
|
867
|
+
injectedStdin = payload;
|
|
868
|
+
}
|
|
866
869
|
function stdinHasPipedInput(statFd = () => (0, import_node_fs4.fstatSync)(0), getIsTTY = () => process.stdin.isTTY) {
|
|
867
870
|
try {
|
|
868
871
|
const stat4 = statFd();
|
|
@@ -922,6 +925,34 @@ var init_stdin_inject = __esm({
|
|
|
922
925
|
});
|
|
923
926
|
|
|
924
927
|
// src/cli-shared.ts
|
|
928
|
+
var cli_shared_exports = {};
|
|
929
|
+
__export(cli_shared_exports, {
|
|
930
|
+
DEFAULT_EXEC_TIMEOUT_MS: () => DEFAULT_EXEC_TIMEOUT_MS,
|
|
931
|
+
ExecDeadlineError: () => ExecDeadlineError,
|
|
932
|
+
GIT_TIMEOUT_MS: () => GIT_TIMEOUT_MS,
|
|
933
|
+
consoleIo: () => consoleIo,
|
|
934
|
+
execFileHard: () => execFileHard,
|
|
935
|
+
execFileP: () => execFileP,
|
|
936
|
+
fail: () => fail,
|
|
937
|
+
failGraceful: () => failGraceful,
|
|
938
|
+
failGracefulEnvelope: () => failGracefulEnvelope,
|
|
939
|
+
gitOut: () => gitOut,
|
|
940
|
+
githubLogin: () => githubLogin,
|
|
941
|
+
hubHeaders: () => hubHeaders,
|
|
942
|
+
installBrokenPipeGuard: () => installBrokenPipeGuard,
|
|
943
|
+
installProcessBackstop: () => installProcessBackstop,
|
|
944
|
+
isOrgRepoRoot: () => isOrgRepoRoot,
|
|
945
|
+
jsonParity: () => jsonParity,
|
|
946
|
+
loadConfig: () => loadConfig,
|
|
947
|
+
mutating: () => mutating,
|
|
948
|
+
planMutation: () => planMutation,
|
|
949
|
+
printLine: () => printLine,
|
|
950
|
+
rawFlag: () => rawFlag,
|
|
951
|
+
rawValue: () => rawValue,
|
|
952
|
+
readStdin: () => readStdin,
|
|
953
|
+
repoProject: () => repoProject,
|
|
954
|
+
setInjectedStdin: () => setInjectedStdin
|
|
955
|
+
});
|
|
925
956
|
function killProcessTree(pid) {
|
|
926
957
|
try {
|
|
927
958
|
if (process.platform === "win32") {
|
|
@@ -11654,11 +11685,11 @@ function writeMarketplacePinsOnDisk(path2, pins, succeeded, failedVerb, declineW
|
|
|
11654
11685
|
return `could NOT ${failedVerb} ${[...pins.keys()].join(", ")} \u2014 set it by hand`;
|
|
11655
11686
|
}
|
|
11656
11687
|
const verify = readKnownMarketplacesFile(path2);
|
|
11657
|
-
const
|
|
11688
|
+
const failed2 = [...pins].filter(([name, want]) => {
|
|
11658
11689
|
const got = readKnownMarketplace(verify, name);
|
|
11659
11690
|
return typeof want.autoUpdate === "boolean" && got.declared !== want.autoUpdate || want.ref !== void 0 && got.ref !== want.ref;
|
|
11660
11691
|
});
|
|
11661
|
-
return
|
|
11692
|
+
return failed2.length ? `${failedVerb} did NOT take for ${failed2.map(([n]) => n).join(", ")} \u2014 set it by hand` : succeeded(pins);
|
|
11662
11693
|
});
|
|
11663
11694
|
} catch (error) {
|
|
11664
11695
|
if (error instanceof Error && error.message.startsWith("shared-document lock timeout:")) return error.message;
|
|
@@ -16840,10 +16871,10 @@ var rollout_plan_default = {
|
|
|
16840
16871
|
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)."
|
|
16841
16872
|
},
|
|
16842
16873
|
baseline: {
|
|
16843
|
-
version: "4.5.
|
|
16844
|
-
tag: "v4.5.
|
|
16845
|
-
commit: "
|
|
16846
|
-
npm: "@mutmutco/cli@4.5.
|
|
16874
|
+
version: "4.5.37",
|
|
16875
|
+
tag: "v4.5.37",
|
|
16876
|
+
commit: "75086128c3ba",
|
|
16877
|
+
npm: "@mutmutco/cli@4.5.37"
|
|
16847
16878
|
},
|
|
16848
16879
|
exitCriterion: "fleet-n-of-n",
|
|
16849
16880
|
hubOnlyShortcut: "forbidden",
|
|
@@ -16860,14 +16891,14 @@ var rollout_plan_default = {
|
|
|
16860
16891
|
repo: "mutmutco/mmi-hub",
|
|
16861
16892
|
role: "canary",
|
|
16862
16893
|
schedule: "train",
|
|
16863
|
-
v3Target: "v4.5.
|
|
16894
|
+
v3Target: "v4.5.37"
|
|
16864
16895
|
}
|
|
16865
16896
|
],
|
|
16866
16897
|
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.",
|
|
16867
16898
|
rollback: {
|
|
16868
16899
|
independent: true,
|
|
16869
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
16870
|
-
v3Target: "v4.5.
|
|
16900
|
+
mechanism: "npm dist-tag latest -> 4.5.37 and redeploy the Hub Lambda from tag v4.5.37 (75086128c3ba); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16901
|
+
v3Target: "v4.5.37 (@mutmutco/cli@4.5.37, tag commit 75086128c3ba \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
16871
16902
|
}
|
|
16872
16903
|
},
|
|
16873
16904
|
{
|
|
@@ -19633,9 +19664,9 @@ async function waitForRequiredTrainChecks(deps, ctx, sha, required, freshSince)
|
|
|
19633
19664
|
});
|
|
19634
19665
|
const satisfiedNote = autoSatisfied.size ? `, auto-satisfied (never runs on a tag, see #2404): ${[...autoSatisfied].join(", ")}` : "";
|
|
19635
19666
|
lastStatus = `${states.map(([c, s]) => `${c}=${s}`).join(", ")}${satisfiedNote}`;
|
|
19636
|
-
const
|
|
19637
|
-
if (
|
|
19638
|
-
throw new Error(`required train check failed: ${
|
|
19667
|
+
const failed2 = states.filter(([, s]) => s === "failed").map(([c]) => c);
|
|
19668
|
+
if (failed2.length > 0) {
|
|
19669
|
+
throw new Error(`required train check failed: ${failed2.join(", ")} (${lastStatus}${fleetNote})`);
|
|
19639
19670
|
}
|
|
19640
19671
|
if (states.every(([, s]) => s === "success")) {
|
|
19641
19672
|
return `required checks passed: ${pending.join(", ") || "(none pending)"}${satisfiedNote}${fleetNote}`;
|
|
@@ -19937,9 +19968,9 @@ async function readVercelDeployment(api, repo, sha, target, since) {
|
|
|
19937
19968
|
}
|
|
19938
19969
|
if (newest.size === 0) return void 0;
|
|
19939
19970
|
const reads = await Promise.all([...newest].map(async ([env, id]) => ({ env, id, ...await readOne(api, repo, id, since) })));
|
|
19940
|
-
const
|
|
19971
|
+
const failed2 = reads.find((r) => FAILED.has(r.state));
|
|
19941
19972
|
const unfinished = reads.find((r) => !isTerminalVercelState(r.state));
|
|
19942
|
-
const pick =
|
|
19973
|
+
const pick = failed2 ?? unfinished ?? reads[0];
|
|
19943
19974
|
return {
|
|
19944
19975
|
id: pick.id,
|
|
19945
19976
|
environment: reads.map((r) => r.env).join(", "),
|
|
@@ -20332,11 +20363,11 @@ async function appendServedChannelProof(deps, input, dispatch) {
|
|
|
20332
20363
|
const channel = servedChannelFor(input.repo, input.installer);
|
|
20333
20364
|
if (!channel) return dispatch;
|
|
20334
20365
|
const expected = input.tag.replace(/^v/, "");
|
|
20335
|
-
const withProof = (conclusion, evidence, note,
|
|
20366
|
+
const withProof = (conclusion, evidence, note, failed2 = false) => ({
|
|
20336
20367
|
...dispatch,
|
|
20337
20368
|
note: `${dispatch.note}; ${note}`,
|
|
20338
20369
|
workflowRuns: [...dispatch.workflowRuns ?? [], servedChannelRow(channel.product, conclusion, evidence)],
|
|
20339
|
-
...
|
|
20370
|
+
...failed2 ? { deployStatus: "failure" } : {}
|
|
20340
20371
|
});
|
|
20341
20372
|
if (!input.shipped) {
|
|
20342
20373
|
return withProof("pending", `${channel.url} not probed`, `served channel ${channel.url} not probed \u2014 deploy/publish are not proven green, so it is not expected to have moved`);
|
|
@@ -20611,8 +20642,8 @@ function shipLegsGreen(deploy, publish) {
|
|
|
20611
20642
|
async function announceShippedRelease(deps, args, model, legs, deferred) {
|
|
20612
20643
|
if (!deps.announce) return void 0;
|
|
20613
20644
|
if (trainObservesShipment(model) && !shipLegsGreen(legs.deploy, legs.publish)) {
|
|
20614
|
-
const
|
|
20615
|
-
return `not announced: deploy/publish ${
|
|
20645
|
+
const failed2 = legs.deploy.state === "failed" || legs.publish.state === "failed";
|
|
20646
|
+
return `not announced: deploy/publish ${failed2 ? "FAILED" : "not proven green"} \u2014 ${deferred}`;
|
|
20616
20647
|
}
|
|
20617
20648
|
return (await deps.announce(args)).note;
|
|
20618
20649
|
}
|
|
@@ -23378,8 +23409,8 @@ function ledgerFailedRemedy(ledger) {
|
|
|
23378
23409
|
const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
|
|
23379
23410
|
const tails = formatFailedLegTails(ledger);
|
|
23380
23411
|
if (hotfixRetryDeployShape(ledger)) {
|
|
23381
|
-
const
|
|
23382
|
-
return `the published ${ledger.tag} hotfix can retry the failed ${joinLegNames(
|
|
23412
|
+
const failed2 = openLegs.filter((l) => l.state === "failed").map((l) => l.phase);
|
|
23413
|
+
return `the published ${ledger.tag} hotfix can retry the failed ${joinLegNames(failed2)} 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}`;
|
|
23383
23414
|
}
|
|
23384
23415
|
if (rcandRetryDeployShape(ledger)) {
|
|
23385
23416
|
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}`;
|
|
@@ -23547,8 +23578,8 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
|
|
|
23547
23578
|
`release --supersede-deploy: origin reports ${prior.tag} at ${liveSha.slice(0, 12) || "no tag"} but the ledger anchors ${prior.tagSha.slice(0, 12)} \u2014 an absent or divergent release is NOT supersedeable (release --abort --apply is the unpublished candidate's own exit); nothing was written (#6553)`
|
|
23548
23579
|
);
|
|
23549
23580
|
}
|
|
23550
|
-
const
|
|
23551
|
-
const note =
|
|
23581
|
+
const failed2 = prior.phases.deploy.state === "failed";
|
|
23582
|
+
const note = failed2 ? `superseded: content-caused deploy failure on the immutable promoted ${prior.tag} \u2014 the cause fix rides the next patch train (master-approved closure, #6553)` : `superseded: ${prior.tag} deploy outcome uncorrelatable from the release SHA (no failure recorded) \u2014 closed on the owner's evidence (master-approved closure, #6553, #7327)`;
|
|
23552
23583
|
await fence(() => atomicWriteJson(path2, {
|
|
23553
23584
|
...prior,
|
|
23554
23585
|
phases: {
|
|
@@ -23562,7 +23593,7 @@ async function supersedeContentCausedDeployLeg(deps, cwd) {
|
|
|
23562
23593
|
repo: prior.repo,
|
|
23563
23594
|
tag: prior.tag,
|
|
23564
23595
|
tagSha: prior.tagSha,
|
|
23565
|
-
note:
|
|
23596
|
+
note: failed2 ? `closed the ${prior.tag} deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm, then run the next patch train (hotfix or release) to carry the cause fix` : `closed the ${prior.tag} uncorrelatable deploy leg as superseded (skipped) \u2014 the ledger is phases-green; rerun the train doctor to confirm`
|
|
23566
23597
|
};
|
|
23567
23598
|
});
|
|
23568
23599
|
}
|
|
@@ -24537,17 +24568,17 @@ async function runTrainDoctor(input) {
|
|
|
24537
24568
|
if (derived.phaseStatus !== "phases-green") {
|
|
24538
24569
|
const openLegs = derived.legs.filter((l) => l.state === "pending" || l.state === "failed");
|
|
24539
24570
|
const open2 = openLegs.map((l) => `${l.phase}=${l.state}`).join(", ");
|
|
24540
|
-
const
|
|
24571
|
+
const failed2 = derived.phaseStatus === "phases-failed";
|
|
24541
24572
|
const deployOnlyDeadlock = openLegs.length > 0 && openLegs.every((l) => l.phase === "deploy" && supersedeableDeployLegShape(l));
|
|
24542
24573
|
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)` : "";
|
|
24543
24574
|
add({
|
|
24544
|
-
code:
|
|
24575
|
+
code: failed2 ? "ledger-failed" : "ledger-pending",
|
|
24545
24576
|
severity: "blocker",
|
|
24546
24577
|
source: "local",
|
|
24547
24578
|
// #6429: each lane finishes its own run (#6068) — an rcand-lane ledger named as a release
|
|
24548
24579
|
// ledger, with `release --resume` as its remedy, sends the operator to a verb that refuses it.
|
|
24549
24580
|
title: `a prior ${ledger.lane ?? "release"} ledger for ${ledger.tag} is ${derived.phaseStatus} (${open2}) \u2014 a new train must not stack on it (#5987)`,
|
|
24550
|
-
remedy:
|
|
24581
|
+
remedy: failed2 ? ledgerFailedRemedy(ledger) : `finish it first: ${ledgerLaneResumeCommand(ledger)} (alignment-only pending legs close themselves with \`train doctor --heal\` once the PR merges, and a deploy/publish leg whose run already concluded folds under the same heal)${supersession}`
|
|
24551
24582
|
});
|
|
24552
24583
|
} else if (newestRemoteReleaseTag && newestRemoteReleaseTag !== ledger.tag) {
|
|
24553
24584
|
add({
|
|
@@ -24594,21 +24625,21 @@ regenerate the projections (\`node scripts/check-skill-payload.mjs\`) and commit
|
|
|
24594
24625
|
const installed = [];
|
|
24595
24626
|
let pending = firstDir;
|
|
24596
24627
|
let outstanding = failure;
|
|
24597
|
-
let
|
|
24628
|
+
let failed2 = null;
|
|
24598
24629
|
while (pending && !installed.includes(pending)) {
|
|
24599
24630
|
const dir = pending;
|
|
24600
24631
|
try {
|
|
24601
24632
|
await train.run("npm", ["--prefix", dir, "ci"]);
|
|
24602
24633
|
} catch (e) {
|
|
24603
|
-
|
|
24634
|
+
failed2 = { dir, detail: `\`npm --prefix ${dir} ci\` failed: ${message(e)}` };
|
|
24604
24635
|
break;
|
|
24605
24636
|
}
|
|
24606
24637
|
installed.push(dir);
|
|
24607
24638
|
outstanding = await verifyDeps();
|
|
24608
24639
|
pending = outstanding ? /in (cli|updater)\/node_modules/u.exec(outstanding)?.[1] ?? null : null;
|
|
24609
24640
|
}
|
|
24610
|
-
if (
|
|
24611
|
-
add({ code: "hub-build-deps", severity: "blocker", source: "local", title:
|
|
24641
|
+
if (failed2) {
|
|
24642
|
+
add({ code: "hub-build-deps", severity: "blocker", source: "local", title: failed2.detail, remedy: `repair ${failed2.dir}/node_modules by hand, then rerun` });
|
|
24612
24643
|
} else if (outstanding) {
|
|
24613
24644
|
add({ code: "hub-build-deps", severity: "blocker", source: "local", title: `installing ${installed.join(", ")} from ${installed.length === 1 ? "its lockfile" : "their lockfiles"} did not resolve the build dependencies`, remedy: outstanding });
|
|
24614
24645
|
} else {
|
|
@@ -24709,6 +24740,133 @@ function formatTrainDoctor(v) {
|
|
|
24709
24740
|
return lines2.join("\n");
|
|
24710
24741
|
}
|
|
24711
24742
|
|
|
24743
|
+
// src/vercel-version-env.ts
|
|
24744
|
+
var SKIPPED = { status: "skipped", note: "" };
|
|
24745
|
+
function vercelVersionEnvAheadNote(key) {
|
|
24746
|
+
return `a failed branch push can leave ${key} ahead of what is live`;
|
|
24747
|
+
}
|
|
24748
|
+
function safeReason(reason, token) {
|
|
24749
|
+
const compact = reason.replace(/\s+/g, " ").trim().slice(0, 180);
|
|
24750
|
+
const scrubbed = token && compact.includes(token) ? compact.split(token).join("[redacted]") : compact;
|
|
24751
|
+
return scrubbed || "unknown error";
|
|
24752
|
+
}
|
|
24753
|
+
function failed(deps, key, tag, reason) {
|
|
24754
|
+
const note = `vercel version env: FAILED to set ${key}=${tag} (${reason}) \u2014 /healthz will show the commit, not the tag; fix: set it and redeploy once`;
|
|
24755
|
+
deps.warn?.(note);
|
|
24756
|
+
return { status: "failed", note };
|
|
24757
|
+
}
|
|
24758
|
+
function targetLabel(stage) {
|
|
24759
|
+
return stage === "main" ? "production" : "preview";
|
|
24760
|
+
}
|
|
24761
|
+
function envCollectionUrl(project2, team, extra) {
|
|
24762
|
+
const url = new URL(`https://api.vercel.com/v10/projects/${encodeURIComponent(project2)}/env`);
|
|
24763
|
+
if (team) url.searchParams.set("teamId", team);
|
|
24764
|
+
for (const [key, value] of Object.entries(extra ?? {})) url.searchParams.set(key, value);
|
|
24765
|
+
return url.toString();
|
|
24766
|
+
}
|
|
24767
|
+
function envItemUrl(project2, id, team) {
|
|
24768
|
+
const url = new URL(`https://api.vercel.com/v9/projects/${encodeURIComponent(project2)}/env/${encodeURIComponent(id)}`);
|
|
24769
|
+
if (team) url.searchParams.set("teamId", team);
|
|
24770
|
+
return url.toString();
|
|
24771
|
+
}
|
|
24772
|
+
function isStageEnv(row, stage, key) {
|
|
24773
|
+
if (row.key !== key) return false;
|
|
24774
|
+
const target = Array.isArray(row.target) ? row.target.filter((item) => typeof item === "string") : [];
|
|
24775
|
+
if (stage === "main") return target.length === 1 && target[0] === "production";
|
|
24776
|
+
return target.length === 1 && target[0] === "preview" && row.gitBranch === "rc";
|
|
24777
|
+
}
|
|
24778
|
+
function envsOf(body) {
|
|
24779
|
+
if (Array.isArray(body)) return body;
|
|
24780
|
+
if (body && typeof body === "object" && Array.isArray(body.envs)) {
|
|
24781
|
+
return body.envs;
|
|
24782
|
+
}
|
|
24783
|
+
return null;
|
|
24784
|
+
}
|
|
24785
|
+
async function readBody(res, token) {
|
|
24786
|
+
let raw = "";
|
|
24787
|
+
try {
|
|
24788
|
+
raw = await res.text();
|
|
24789
|
+
} catch (e) {
|
|
24790
|
+
return { ok: false, reason: safeReason(e instanceof Error ? e.message : String(e), token) };
|
|
24791
|
+
}
|
|
24792
|
+
if (!res.ok) return { ok: false, reason: `HTTP ${res.status}${raw ? `: ${safeReason(raw, token)}` : ""}` };
|
|
24793
|
+
if (!raw.trim()) return { ok: true, body: {} };
|
|
24794
|
+
try {
|
|
24795
|
+
return { ok: true, body: JSON.parse(raw) };
|
|
24796
|
+
} catch {
|
|
24797
|
+
return { ok: false, reason: "response was not JSON" };
|
|
24798
|
+
}
|
|
24799
|
+
}
|
|
24800
|
+
async function setVercelVersionEnv(deps, meta, stage, tag) {
|
|
24801
|
+
if (meta?.deployModel !== "vercel" || !meta.vercelVersionEnv) return SKIPPED;
|
|
24802
|
+
const { project: project2, key, team } = meta.vercelVersionEnv;
|
|
24803
|
+
if (!project2 || !key) return failed(deps, key || "(missing key)", tag, "vercelVersionEnv is missing project or key");
|
|
24804
|
+
let token;
|
|
24805
|
+
try {
|
|
24806
|
+
token = await deps.readToken();
|
|
24807
|
+
} catch (e) {
|
|
24808
|
+
return failed(deps, key, tag, safeReason(e instanceof Error ? e.message : String(e), null));
|
|
24809
|
+
}
|
|
24810
|
+
if (!token) return failed(deps, key, tag, "no _org/vercel/API_TOKEN");
|
|
24811
|
+
const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };
|
|
24812
|
+
let listed;
|
|
24813
|
+
try {
|
|
24814
|
+
const res = await deps.fetch(envCollectionUrl(project2, team, stage === "rc" ? { gitBranch: "rc" } : void 0), { method: "GET", headers });
|
|
24815
|
+
listed = await readBody(res, token);
|
|
24816
|
+
} catch (e) {
|
|
24817
|
+
return failed(deps, key, tag, safeReason(e instanceof Error ? e.message : String(e), token));
|
|
24818
|
+
}
|
|
24819
|
+
if (!listed.ok) return failed(deps, key, tag, listed.reason);
|
|
24820
|
+
const rows = envsOf(listed.body);
|
|
24821
|
+
if (!rows) return failed(deps, key, tag, "env list returned an unexpected shape");
|
|
24822
|
+
const existing = rows.find((row) => isStageEnv(row, stage, key) && typeof row.id === "string" && row.id.length > 0);
|
|
24823
|
+
const createBody = stage === "main" ? { key, value: tag, type: "plain", target: ["production"] } : { key, value: tag, type: "plain", target: ["preview"], gitBranch: "rc" };
|
|
24824
|
+
try {
|
|
24825
|
+
const res = existing ? await deps.fetch(envItemUrl(project2, existing.id, team), {
|
|
24826
|
+
method: "PATCH",
|
|
24827
|
+
headers,
|
|
24828
|
+
body: JSON.stringify({ value: tag })
|
|
24829
|
+
}) : await deps.fetch(envCollectionUrl(project2, team), {
|
|
24830
|
+
method: "POST",
|
|
24831
|
+
headers,
|
|
24832
|
+
body: JSON.stringify(createBody)
|
|
24833
|
+
});
|
|
24834
|
+
const written = await readBody(res, token);
|
|
24835
|
+
if (!written.ok) return failed(deps, key, tag, written.reason);
|
|
24836
|
+
} catch (e) {
|
|
24837
|
+
return failed(deps, key, tag, safeReason(e instanceof Error ? e.message : String(e), token));
|
|
24838
|
+
}
|
|
24839
|
+
const note = `vercel version env: ${key}=${tag} set on ${targetLabel(stage)}`;
|
|
24840
|
+
deps.warn?.(note);
|
|
24841
|
+
return { status: "set", note };
|
|
24842
|
+
}
|
|
24843
|
+
async function defaultVercelFetch(url, init) {
|
|
24844
|
+
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15e3) });
|
|
24845
|
+
return { ok: res.ok, status: res.status, text: () => res.text() };
|
|
24846
|
+
}
|
|
24847
|
+
async function readOrgVercelApiToken() {
|
|
24848
|
+
const { loadConfig: loadConfig2, hubHeaders: hubHeaders2, repoProject: repoProject2 } = await Promise.resolve().then(() => (init_cli_shared(), cli_shared_exports));
|
|
24849
|
+
const cfg = await loadConfig2();
|
|
24850
|
+
if (!cfg.sagaApiUrl) return null;
|
|
24851
|
+
const repo = `mutmutco/${(await repoProject2(cfg)).toLowerCase()}`;
|
|
24852
|
+
const res = await fetch(`${cfg.sagaApiUrl}/secrets/get`, {
|
|
24853
|
+
method: "POST",
|
|
24854
|
+
headers: await hubHeaders2({ "content-type": "application/json" }),
|
|
24855
|
+
body: JSON.stringify({ repo, key: "vercel/API_TOKEN", use: true, slug: "_org" }),
|
|
24856
|
+
signal: AbortSignal.timeout(8e3)
|
|
24857
|
+
});
|
|
24858
|
+
if (!res.ok) return null;
|
|
24859
|
+
const body = await res.json();
|
|
24860
|
+
return typeof body.value === "string" ? body.value : null;
|
|
24861
|
+
}
|
|
24862
|
+
function vercelVersionEnvDeps(deps) {
|
|
24863
|
+
return {
|
|
24864
|
+
fetch: deps.vercelFetch ?? defaultVercelFetch,
|
|
24865
|
+
readToken: deps.readVercelApiToken ?? readOrgVercelApiToken,
|
|
24866
|
+
warn: (message2) => deps.warn?.(message2)
|
|
24867
|
+
};
|
|
24868
|
+
}
|
|
24869
|
+
|
|
24712
24870
|
// src/train-apply-phases.ts
|
|
24713
24871
|
var TRAIN_BUMP_INTENTS = ["major", "minor", "patch"];
|
|
24714
24872
|
function readLocalGateWorkflows() {
|
|
@@ -25313,14 +25471,14 @@ async function recoverFailedDispatchPhase(deps, ledger, anchors, phase, dispatch
|
|
|
25313
25471
|
let fallbackRecovered = false;
|
|
25314
25472
|
const failedRows = workflowRuns?.filter((run) => run.conclusion === "failure") ?? [];
|
|
25315
25473
|
if (failedRows.length > 0) {
|
|
25316
|
-
for (const
|
|
25317
|
-
const rowPhase =
|
|
25474
|
+
for (const failed2 of failedRows) {
|
|
25475
|
+
const rowPhase = failed2.workflow.includes("publish") ? "publish" : phase;
|
|
25318
25476
|
if (persisted?.phases[rowPhase]?.superseded === true) continue;
|
|
25319
25477
|
const recovery = await recover(deps, ledger, anchors, rowPhase, {
|
|
25320
25478
|
repo: input.repo,
|
|
25321
25479
|
sha: input.sha,
|
|
25322
|
-
failure: `watched ${rowPhase} run failed: ${
|
|
25323
|
-
runs: [{ ...
|
|
25480
|
+
failure: `watched ${rowPhase} run failed: ${failed2.workflow} (${dispatch.note})`,
|
|
25481
|
+
runs: [{ ...failed2, repo: input.repo }],
|
|
25324
25482
|
watched: input.watched
|
|
25325
25483
|
});
|
|
25326
25484
|
if (recovery.note) notes.push(recovery.note);
|
|
@@ -25386,6 +25544,27 @@ async function recordRcandDeployLegs(ledger, deps, anchors, deployModel, d, opts
|
|
|
25386
25544
|
function releaseAnnounceDeferral(model, summaryFile) {
|
|
25387
25545
|
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";
|
|
25388
25546
|
}
|
|
25547
|
+
function annotateVersionEnvDispatch(dispatch, result) {
|
|
25548
|
+
if (!result.note) return dispatch;
|
|
25549
|
+
return { ...dispatch, note: `${dispatch.note}; ${result.note}` };
|
|
25550
|
+
}
|
|
25551
|
+
function annotateVersionEnvAnnounce(note, result) {
|
|
25552
|
+
if (!result.note) return note;
|
|
25553
|
+
return note ? `${note}; ${result.note}` : result.note;
|
|
25554
|
+
}
|
|
25555
|
+
async function pushAfterVercelVersionEnv(deps, meta, stage, tag, pushArgs) {
|
|
25556
|
+
const result = await setVercelVersionEnv(vercelVersionEnvDeps(deps), meta, stage, tag);
|
|
25557
|
+
try {
|
|
25558
|
+
await runGitPush(deps, pushArgs);
|
|
25559
|
+
} catch (e) {
|
|
25560
|
+
const base = e instanceof Error ? e.message : String(e);
|
|
25561
|
+
const parts = [base];
|
|
25562
|
+
if (result.note) parts.push(result.note);
|
|
25563
|
+
if (result.status === "set" && meta.vercelVersionEnv?.key) parts.push(vercelVersionEnvAheadNote(meta.vercelVersionEnv.key));
|
|
25564
|
+
throw new Error(parts.join(" \u2014 "));
|
|
25565
|
+
}
|
|
25566
|
+
return result;
|
|
25567
|
+
}
|
|
25389
25568
|
async function completeMainRelease(deps, ctx, meta, deployModel, watch, options, tag, releaseSha, startBranch, preFold, tagProbe, branchHints, ledger, alignEarly) {
|
|
25390
25569
|
const resumeCommand = options.dev ? "mmi-cli devops release --dev --apply" : "mmi-cli devops release --apply";
|
|
25391
25570
|
const ledgerAnchors = { repo: ctx.repo, tag, tagSha: releaseSha };
|
|
@@ -25482,9 +25661,10 @@ ${recovery.note}`), recoveryInput);
|
|
|
25482
25661
|
landed: "the immutable tag pushed at " + releaseSha.slice(0, 12)
|
|
25483
25662
|
});
|
|
25484
25663
|
if (trueMergeGateNote) checks = `${trueMergeGateNote}; ${checks}`;
|
|
25664
|
+
let versionEnv = { status: "skipped", note: "" };
|
|
25485
25665
|
try {
|
|
25486
25666
|
deps.warn?.("Release: Promoting the verified release");
|
|
25487
|
-
await
|
|
25667
|
+
versionEnv = await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", "main"]);
|
|
25488
25668
|
} catch (e) {
|
|
25489
25669
|
await recordPhase(ledger, deps, ledgerAnchors, "promotion", phaseEntry({ state: "failed", sha: releaseSha, error: `origin/main push failed after ${tag} was pushed: ${e instanceof Error ? e.message : String(e)}` }), { landed: "the immutable tag pushed at " + releaseSha.slice(0, 12) });
|
|
25490
25670
|
throw partialTrainRecoveryError(e, recoveryInput);
|
|
@@ -25517,7 +25697,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
25517
25697
|
workflow: failedReleaseRun?.workflow ?? (isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow")
|
|
25518
25698
|
}
|
|
25519
25699
|
);
|
|
25520
|
-
const deployDispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, recoveredDeployDispatch);
|
|
25700
|
+
const deployDispatch = annotateVersionEnvDispatch(await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, releaseSha, recoveredDeployDispatch), versionEnv);
|
|
25521
25701
|
const deployLeg = phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut) && ctx.slug !== "mmi-katip" ? { state: "skipped", sha: releaseSha, note: "suite bundle-only: live backend not deployed (#7263)" } : deployPhaseInput(deployModel, deployDispatch, { releaseSha }));
|
|
25522
25702
|
await recordPhase(ledger, deps, ledgerAnchors, "deploy", deployLeg, {
|
|
25523
25703
|
landed: "the immutable tag, the green required-check wall, the origin/main fast-forward, and the verified GitHub Release"
|
|
@@ -25551,7 +25731,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
25551
25731
|
appendPublishDispatch(deployDispatch, publishDispatch)
|
|
25552
25732
|
);
|
|
25553
25733
|
alignment = await aligning;
|
|
25554
|
-
const announceNote = await announcing;
|
|
25734
|
+
const announceNote = annotateVersionEnvAnnounce(await announcing, versionEnv);
|
|
25555
25735
|
return { checks, releaseUrl, announceNote, dispatch, ledgerAnchors, ...alignment ? { alignment } : {} };
|
|
25556
25736
|
}
|
|
25557
25737
|
function resumeStateOf(status) {
|
|
@@ -25754,10 +25934,10 @@ ${spent.note}`);
|
|
|
25754
25934
|
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" });
|
|
25755
25935
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
25756
25936
|
deps.warn?.("Release: Promoting the verified release candidate");
|
|
25757
|
-
await
|
|
25937
|
+
const versionEnv = await pushAfterVercelVersionEnv(deps, meta, "rc", tag, ["push", "origin", `${tagSha}:refs/heads/rc`]);
|
|
25758
25938
|
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" });
|
|
25759
25939
|
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" });
|
|
25760
|
-
const dispatch0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha, "report");
|
|
25940
|
+
const dispatch0 = annotateVersionEnvDispatch(await dispatchDeploy(deps, ctx, "rc", "rc", deployModel, watch, autoRunSince, tagSha, "report"), versionEnv);
|
|
25761
25941
|
const dispatch = await recordRcandDeployLegs(ledger, deps, anchors, deployModel, dispatch0, { strict });
|
|
25762
25942
|
const ledgerReport = releaseLedgerReport(await ledger.load(anchors).catch(() => void 0), ledger.path);
|
|
25763
25943
|
return {
|
|
@@ -26037,7 +26217,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
26037
26217
|
}
|
|
26038
26218
|
await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: tagSha, note: "re-verified the required-check wall live on this resume" }), { strict: ledgerMode === "strict", landed: "the immutable tag on origin at the resumed SHA" });
|
|
26039
26219
|
deps.warn?.("Release: Promoting the verified release");
|
|
26040
|
-
await
|
|
26220
|
+
const versionEnv = await pushAfterVercelVersionEnv(deps, meta, "main", tag, ["push", "origin", `${tagSha}:refs/heads/main`]);
|
|
26041
26221
|
steps.push(`pushed ${tagSha.slice(0, 12)} to origin/main`);
|
|
26042
26222
|
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "complete", sha: tagSha, note: `resumed: ${tagSha.slice(0, 12)} pushed to origin/main (tag preserved, never re-cut)` }), { strict: ledgerMode === "strict", landed: "the immutable tag and the re-proven required-check wall" });
|
|
26043
26223
|
deps.warn?.("Release: Creating the GitHub Release");
|
|
@@ -26067,7 +26247,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
26067
26247
|
workflow: isCentralDispatchModel(deployModel) ? "tenant-deploy.yml" : "deploy workflow"
|
|
26068
26248
|
}
|
|
26069
26249
|
);
|
|
26070
|
-
const deployDispatch = await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, recoveredDeployDispatch);
|
|
26250
|
+
const deployDispatch = annotateVersionEnvDispatch(await appendJervGatewayReleaseDeploy(deps, ctx.repo, tag, tagSha, recoveredDeployDispatch), versionEnv);
|
|
26071
26251
|
steps.push(`dispatched the ${deployModel} deploy path`);
|
|
26072
26252
|
const deployLeg = persisted?.phases.deploy.superseded === true ? persisted.phases.deploy : phaseEntry(validSuiteCutContext(ctx.repo, deps.suiteCut) && ctx.slug !== "mmi-katip" ? { state: "skipped", sha: tagSha, note: "suite bundle-only: live backend not deployed (#7263)" } : deployPhaseInput(deployModel, deployDispatch, { releaseSha: tagSha }));
|
|
26073
26253
|
if (persisted?.phases.deploy.superseded !== true) {
|
|
@@ -26108,7 +26288,7 @@ ${recovery.note}`), recoveryInput);
|
|
|
26108
26288
|
devRollForward = landed.dev;
|
|
26109
26289
|
rcAlignment = landed.rc;
|
|
26110
26290
|
}
|
|
26111
|
-
const announceNote = await announcing;
|
|
26291
|
+
const announceNote = annotateVersionEnvAnnounce(await announcing, versionEnv);
|
|
26112
26292
|
steps.push(`development roll-forward: ${devRollForward.status}`);
|
|
26113
26293
|
if (rcAlignment) steps.push(`rc alignment: ${rcAlignment.status}`);
|
|
26114
26294
|
const followUp = deriveTrainFollowUpStatus({
|
|
@@ -26448,15 +26628,16 @@ ${spent.note}`), rcandRecovery(tag2));
|
|
|
26448
26628
|
await recordPhase(ledger, deps, anchors, "ordinaryCi", phaseEntry({ state: "complete", sha: rcSha, note: checks2 }), { landed: "the immutable rc tag pushed at " + rcSha.slice(0, 12) });
|
|
26449
26629
|
const autoRunSince = (deps.now ?? Date.now)();
|
|
26450
26630
|
deps.warn?.("Release: Promoting the verified release candidate");
|
|
26631
|
+
let versionEnv = { status: "skipped", note: "" };
|
|
26451
26632
|
try {
|
|
26452
|
-
await
|
|
26633
|
+
versionEnv = await pushAfterVercelVersionEnv(deps, meta, "rc", tag2, ["push", "origin", "rc"]);
|
|
26453
26634
|
} catch (e) {
|
|
26454
26635
|
await recordPhase(ledger, deps, anchors, "promotion", phaseEntry({ state: "failed", sha: rcSha, error: `origin/rc push failed after ${tag2} was pushed: ${e instanceof Error ? e.message : String(e)}` }), { landed: "the immutable rc tag pushed at " + rcSha.slice(0, 12) });
|
|
26455
26636
|
throw partialTrainRecoveryError(e, rcandRecovery(tag2));
|
|
26456
26637
|
}
|
|
26457
26638
|
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" });
|
|
26458
26639
|
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" });
|
|
26459
|
-
const d0 = await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report");
|
|
26640
|
+
const d0 = annotateVersionEnvDispatch(await dispatchDeploy(deps, ctx, "rc", "rc", deployModel2, watch, autoRunSince, rcSha, "report"), versionEnv);
|
|
26460
26641
|
const d2 = await recordRcandDeployLegs(ledger, deps, anchors, deployModel2, d0);
|
|
26461
26642
|
const rcLedgerReport = releaseLedgerReport(await ledger?.load(anchors).catch(() => void 0), ledger?.path ?? "(no ledger)");
|
|
26462
26643
|
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 } : {} };
|
|
@@ -28643,13 +28824,13 @@ function buildPluginCachePlan(home, running, deps, opts = {}) {
|
|
|
28643
28824
|
}
|
|
28644
28825
|
function applyPluginCachePlan(plan, remove2, stagingGuard) {
|
|
28645
28826
|
const removed = [];
|
|
28646
|
-
const
|
|
28827
|
+
const failed2 = [];
|
|
28647
28828
|
for (const version of plan.prune) {
|
|
28648
28829
|
try {
|
|
28649
28830
|
remove2(`${plan.cacheRoot}/${version}`);
|
|
28650
28831
|
removed.push(version);
|
|
28651
28832
|
} catch (e) {
|
|
28652
|
-
|
|
28833
|
+
failed2.push({ version, error: e.message });
|
|
28653
28834
|
}
|
|
28654
28835
|
}
|
|
28655
28836
|
const removedStaging = [];
|
|
@@ -28682,7 +28863,7 @@ function applyPluginCachePlan(plan, remove2, stagingGuard) {
|
|
|
28682
28863
|
}
|
|
28683
28864
|
return {
|
|
28684
28865
|
removed,
|
|
28685
|
-
failed,
|
|
28866
|
+
failed: failed2,
|
|
28686
28867
|
bytes: plan.bytes,
|
|
28687
28868
|
removedStaging,
|
|
28688
28869
|
failedStaging,
|
|
@@ -29694,7 +29875,7 @@ init_hub_auth();
|
|
|
29694
29875
|
init_github_client();
|
|
29695
29876
|
|
|
29696
29877
|
// src/project-set.ts
|
|
29697
|
-
var UNSET_KEYS = ["projectAdmins", "installer", "oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary", "notifyOnDeploy"];
|
|
29878
|
+
var UNSET_KEYS = ["projectAdmins", "installer", "oauth", "requiredRuntimeSecrets", "requiredBuildSecrets", "tenantTasks", "secrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "releaseChannel", "releaseLanguage", "dsManifestPath", "consumesDesignSystem", "ci", "requiredChecks", "requiredCheckBranches", "ciExemptReason", "gate", "seedCanary", "notifyOnDeploy", "vercelVersionEnv"];
|
|
29698
29879
|
var UNSET_KEY_SET = new Set(UNSET_KEYS);
|
|
29699
29880
|
var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
|
|
29700
29881
|
var SECRET_CONSUMERS = ["runtime", "build", "lambda", "actions", "agent", "box"];
|
|
@@ -30048,6 +30229,32 @@ function parseNotifyOnDeployVar(raw) {
|
|
|
30048
30229
|
}
|
|
30049
30230
|
return parsed;
|
|
30050
30231
|
}
|
|
30232
|
+
var VERCEL_VERSION_ENV_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
30233
|
+
var VERCEL_VERSION_ENV_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
|
|
30234
|
+
function parseVercelVersionEnvVar(raw) {
|
|
30235
|
+
let parsed;
|
|
30236
|
+
try {
|
|
30237
|
+
parsed = JSON.parse(raw);
|
|
30238
|
+
} catch {
|
|
30239
|
+
throw new Error('org project set: vercelVersionEnv must be JSON, e.g. {"project":"mm-work","key":"MMW_VERSION","team":"team_xxx"}');
|
|
30240
|
+
}
|
|
30241
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
30242
|
+
throw new Error("org project set: vercelVersionEnv must be a JSON object { project, key, team? }");
|
|
30243
|
+
}
|
|
30244
|
+
const row = parsed;
|
|
30245
|
+
const unknown = Object.keys(row).filter((key) => key !== "project" && key !== "key" && key !== "team");
|
|
30246
|
+
if (unknown.length > 0) throw new Error(`org project set: vercelVersionEnv has unknown field(s): ${unknown.join(", ")}`);
|
|
30247
|
+
if (typeof row.project !== "string" || !VERCEL_VERSION_ENV_ID_RE.test(row.project)) {
|
|
30248
|
+
throw new Error("org project set: vercelVersionEnv.project must be a Vercel project id or name");
|
|
30249
|
+
}
|
|
30250
|
+
if (typeof row.key !== "string" || !VERCEL_VERSION_ENV_NAME_RE.test(row.key)) {
|
|
30251
|
+
throw new Error("org project set: vercelVersionEnv.key must be an env name (e.g. MMW_VERSION)");
|
|
30252
|
+
}
|
|
30253
|
+
if (row.team !== void 0 && (typeof row.team !== "string" || !VERCEL_VERSION_ENV_ID_RE.test(row.team))) {
|
|
30254
|
+
throw new Error("org project set: vercelVersionEnv.team must be a Vercel team id or slug");
|
|
30255
|
+
}
|
|
30256
|
+
return { project: row.project, key: row.key, ...typeof row.team === "string" ? { team: row.team } : {} };
|
|
30257
|
+
}
|
|
30051
30258
|
function parseCiExemptReasonVar(raw) {
|
|
30052
30259
|
const trimmed = raw.trim();
|
|
30053
30260
|
if (!trimmed) throw new Error("org project set: ciExemptReason must be a non-empty reason (or use --unset ciExemptReason to clear it)");
|
|
@@ -30189,7 +30396,8 @@ var SETTABLE_VAR_KEYS = [
|
|
|
30189
30396
|
"secrets",
|
|
30190
30397
|
"seedCanary",
|
|
30191
30398
|
"installer",
|
|
30192
|
-
"notifyOnDeploy"
|
|
30399
|
+
"notifyOnDeploy",
|
|
30400
|
+
"vercelVersionEnv"
|
|
30193
30401
|
];
|
|
30194
30402
|
var SETTABLE_VAR_KEY_SET = new Set(SETTABLE_VAR_KEYS);
|
|
30195
30403
|
var SETTABLE_VAR_HINTS = {
|
|
@@ -30219,6 +30427,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
30219
30427
|
requiredChecks: 'JSON array, e.g. ["gate"] or [] for no-ci',
|
|
30220
30428
|
requiredCheckBranches: "non-empty JSON branch-name array; exact product-ruleset scope (#5210)",
|
|
30221
30429
|
notifyOnDeploy: 'JSON stage array, e.g. ["dev"] \u2014 successful tenant-deploy sends <stage>-deployed to the project repo (#7358)',
|
|
30430
|
+
vercelVersionEnv: "JSON {project,key,team?} \u2014 train sets this Vercel env to the release tag before the main/rc push (#7362)",
|
|
30222
30431
|
ciExemptReason: "non-empty string \u2014 declares this deployable repo has no CI surface (#4265)",
|
|
30223
30432
|
gate: "JSON {runtime,cmd,affectedCmd,workdir,cacheDepPath,pyVersion}"
|
|
30224
30433
|
};
|
|
@@ -30364,6 +30573,8 @@ function buildProjectSetPatch(input) {
|
|
|
30364
30573
|
patch[key] = parseRequiredCheckBranchesVar(raw);
|
|
30365
30574
|
} else if (key === "notifyOnDeploy") {
|
|
30366
30575
|
patch[key] = parseNotifyOnDeployVar(raw);
|
|
30576
|
+
} else if (key === "vercelVersionEnv") {
|
|
30577
|
+
patch[key] = parseVercelVersionEnvVar(raw);
|
|
30367
30578
|
} else if (key === "ciExemptReason") {
|
|
30368
30579
|
patch[key] = parseCiExemptReasonVar(raw);
|
|
30369
30580
|
} else if (key === "gate") {
|
|
@@ -35014,7 +35225,7 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
35014
35225
|
throw new Error(`working tree must be clean before hotfix start${describeBlockingPaths(status)}`);
|
|
35015
35226
|
}
|
|
35016
35227
|
await deps.run("git", ["fetch", "origin", "--tags"]);
|
|
35017
|
-
const { deployModel } = await hotfixPreflight(deps, ctx, "start");
|
|
35228
|
+
const { deployModel, meta } = await hotfixPreflight(deps, ctx, "start");
|
|
35018
35229
|
const bumpNotes = [];
|
|
35019
35230
|
let resolved = await resolveHotfixVersion(deps);
|
|
35020
35231
|
for (; ; ) {
|
|
@@ -35136,6 +35347,8 @@ async function runHotfixStartLocked(deps, options, doctor) {
|
|
|
35136
35347
|
await assertHotfixFoldBomStamp(deps, tag);
|
|
35137
35348
|
await deps.run("git", ["push", "-u", "origin", branch]);
|
|
35138
35349
|
}
|
|
35350
|
+
const versionEnv = await setVercelVersionEnv(vercelVersionEnvDeps(deps), meta, "main", tag);
|
|
35351
|
+
if (versionEnv.note) notes.push(versionEnv.note);
|
|
35139
35352
|
const bumpNote = deployModel === "hub-serverless" ? " with the locked distribution bump" : foldPaths.length > 0 ? " with the package version bump" : "";
|
|
35140
35353
|
const prUrl = clean3(await deps.run("gh", [
|
|
35141
35354
|
"pr",
|
|
@@ -37387,12 +37600,12 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
37387
37600
|
}
|
|
37388
37601
|
function evaluatePrMergeHousekeeping(report, context, force = false) {
|
|
37389
37602
|
const scratch = report.scratch;
|
|
37390
|
-
const
|
|
37603
|
+
const failed2 = scratch?.status === "failed";
|
|
37391
37604
|
const prunable = scratch?.safeAuto ?? 0;
|
|
37392
37605
|
const unprunable = scratch?.skipped ?? 0;
|
|
37393
|
-
if (!(
|
|
37606
|
+
if (!(failed2 || prunable > 0 || unprunable > 0)) return { blocked: false };
|
|
37394
37607
|
if (force) return { blocked: false };
|
|
37395
|
-
const detail =
|
|
37608
|
+
const detail = failed2 ? `scratch housekeeping failed${scratch?.error ? `: ${scratch.error}` : ""}` : `${prunable} prunable + ${unprunable} un-prunable scratch item(s) remain after cleanup`;
|
|
37396
37609
|
return {
|
|
37397
37610
|
blocked: true,
|
|
37398
37611
|
reason: `${context} housekeeping blocked merge: ${detail}. Run host-owned scratch cleanup, then retry \u2014 or re-run \`mmi-cli ${context} --force\` to acknowledge and land anyway. (Kept advisory plans/ scratch never blocks: it is gitignored and can never reach origin.)`
|
|
@@ -45137,15 +45350,15 @@ function renderReport(checks, opts) {
|
|
|
45137
45350
|
}
|
|
45138
45351
|
function renderTally(checks) {
|
|
45139
45352
|
const total = checks.length;
|
|
45140
|
-
const
|
|
45353
|
+
const failed2 = checks.filter((c) => !c.ok).length;
|
|
45141
45354
|
const unverified = checks.filter((c) => c.ok && c.verified === false).length;
|
|
45142
45355
|
const warned = checks.filter((c) => c.ok && c.verified !== false && c.warn).length;
|
|
45143
|
-
const healthy = total -
|
|
45144
|
-
if (
|
|
45356
|
+
const healthy = total - failed2 - unverified - warned;
|
|
45357
|
+
if (failed2 === 0 && unverified === 0 && warned === 0) return `\u2713 all ${total} checks healthy`;
|
|
45145
45358
|
const parts = [`\u2713 ${healthy} verified healthy`];
|
|
45146
45359
|
if (warned > 0) parts.push(`\u26A0 ${warned} warn`);
|
|
45147
45360
|
if (unverified > 0) parts.push(`? ${unverified} unverified`);
|
|
45148
|
-
if (
|
|
45361
|
+
if (failed2 > 0) parts.push(`\u2717 ${failed2} failed`);
|
|
45149
45362
|
return `${parts.join(" \xB7 ")} \u2014 ${total} checks`;
|
|
45150
45363
|
}
|
|
45151
45364
|
function doctorReportExitCode(checks) {
|
package/package.json
CHANGED