@mutmutco/cli 4.0.14 → 4.0.16
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 +186 -19
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -10832,10 +10832,10 @@ var rollout_plan_default = {
|
|
|
10832
10832
|
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)."
|
|
10833
10833
|
},
|
|
10834
10834
|
baseline: {
|
|
10835
|
-
version: "4.0.
|
|
10836
|
-
tag: "v4.0.
|
|
10837
|
-
commit: "
|
|
10838
|
-
npm: "@mutmutco/cli@4.0.
|
|
10835
|
+
version: "4.0.16",
|
|
10836
|
+
tag: "v4.0.16",
|
|
10837
|
+
commit: "ac03c4999cb4",
|
|
10838
|
+
npm: "@mutmutco/cli@4.0.16"
|
|
10839
10839
|
},
|
|
10840
10840
|
exitCriterion: "fleet-n-of-n",
|
|
10841
10841
|
hubOnlyShortcut: "forbidden",
|
|
@@ -10852,14 +10852,14 @@ var rollout_plan_default = {
|
|
|
10852
10852
|
repo: "mutmutco/mmi-hub",
|
|
10853
10853
|
role: "canary",
|
|
10854
10854
|
schedule: "train",
|
|
10855
|
-
v3Target: "v4.0.
|
|
10855
|
+
v3Target: "v4.0.16"
|
|
10856
10856
|
}
|
|
10857
10857
|
],
|
|
10858
10858
|
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.",
|
|
10859
10859
|
rollback: {
|
|
10860
10860
|
independent: true,
|
|
10861
|
-
mechanism: "npm dist-tag latest -> 4.0.
|
|
10862
|
-
v3Target: "v4.0.
|
|
10861
|
+
mechanism: "npm dist-tag latest -> 4.0.16 and redeploy the Hub Lambda from tag v4.0.16 (ac03c4999cb4); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
10862
|
+
v3Target: "v4.0.16 (@mutmutco/cli@4.0.16, tag commit ac03c4999cb4 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
10863
10863
|
}
|
|
10864
10864
|
},
|
|
10865
10865
|
{
|
|
@@ -13926,8 +13926,39 @@ async function isStrayUnreleasedTag(deps, tag, remoteSha, repo) {
|
|
|
13926
13926
|
}
|
|
13927
13927
|
return deps.run("gh", ["release", "view", tag, "--repo", repo, "--json", "tagName"]).then(() => false).catch((e) => /not found|HTTP 404/i.test(e instanceof Error ? e.message : String(e)));
|
|
13928
13928
|
}
|
|
13929
|
+
function cmpReleaseTag(a, b) {
|
|
13930
|
+
const parse = (t) => {
|
|
13931
|
+
const m = /^v(\d+)\.(\d+)\.(\d+)$/.exec(t.trim());
|
|
13932
|
+
return m ? { major: +m[1], minor: +m[2], patch: +m[3] } : null;
|
|
13933
|
+
};
|
|
13934
|
+
const left = parse(a);
|
|
13935
|
+
const right = parse(b);
|
|
13936
|
+
if (!left || !right) throw new Error(`cmpReleaseTag expects vX.Y.Z tags, got ${a} vs ${b}`);
|
|
13937
|
+
return left.major - right.major || left.minor - right.minor || left.patch - right.patch;
|
|
13938
|
+
}
|
|
13939
|
+
async function listRemoteReleaseTags(deps) {
|
|
13940
|
+
const out = await runGitRemoteRead(deps, ["ls-remote", "--tags", "origin", "refs/tags/v*"]);
|
|
13941
|
+
const tags = /* @__PURE__ */ new Set();
|
|
13942
|
+
for (const line of clean2(out).split("\n")) {
|
|
13943
|
+
const ref = line.trim().split(/\s+/)[1] ?? "";
|
|
13944
|
+
const tag = ref.replace(/^refs\/tags\//, "").replace(/\^\{\}$/, "");
|
|
13945
|
+
if (/^v\d+\.\d+\.\d+$/.test(tag)) tags.add(tag);
|
|
13946
|
+
}
|
|
13947
|
+
return [...tags].sort(cmpReleaseTag);
|
|
13948
|
+
}
|
|
13949
|
+
async function assertComputedReleaseTagStillNext(deps, tag) {
|
|
13950
|
+
if (!/^v\d+\.\d+\.\d+$/.test(tag)) return;
|
|
13951
|
+
const remote = await listRemoteReleaseTags(deps);
|
|
13952
|
+
const newer = remote.filter((t) => cmpReleaseTag(t, tag) > 0);
|
|
13953
|
+
if (newer.length === 0) return;
|
|
13954
|
+
const latest = newer[newer.length - 1];
|
|
13955
|
+
throw new Error(
|
|
13956
|
+
`origin already has release tag ${latest} ahead of this run's computed ${tag} \u2014 another /release landed mid-flight. Refusing to mint a skip-version or hole-fill tag. Stop; let the other train finish (or abort its stray tag/Release with the authorized human's go if verify failed), then rerun mmi-cli devops release --apply so next-version re-derives from the settled tags. Do not run two /release trains on the same repo concurrently.`
|
|
13957
|
+
);
|
|
13958
|
+
}
|
|
13929
13959
|
async function ensureTagPushed(deps, tag, sha, probed, releaseRepo) {
|
|
13930
|
-
const remoteSha =
|
|
13960
|
+
const remoteSha = await probeRemoteTag(deps, tag);
|
|
13961
|
+
const planRemoteSha = probed?.remoteSha ?? "";
|
|
13931
13962
|
let localSha = "";
|
|
13932
13963
|
try {
|
|
13933
13964
|
localSha = clean2(await deps.run("git", ["rev-parse", "--verify", `refs/tags/${tag}^{commit}`]));
|
|
@@ -13936,6 +13967,11 @@ async function ensureTagPushed(deps, tag, sha, probed, releaseRepo) {
|
|
|
13936
13967
|
if (remoteSha) {
|
|
13937
13968
|
if (remoteSha !== sha) {
|
|
13938
13969
|
const mismatch = `tag ${tag} already exists on origin at ${remoteSha}, but this run intends ${sha}`;
|
|
13970
|
+
if (!planRemoteSha && releaseRepo) {
|
|
13971
|
+
throw new Error(
|
|
13972
|
+
`${mismatch}. The tag appeared on origin while this train was promoting \u2014 another /release claimed this version mid-flight. Refusing to mint a competing or skip-version tag. Stop; let the other train finish, or if its tag/Release is a stray (publish verify failed, tagged SHA lacks the fold commit), delete that stray tag and its GitHub Release with the authorized human's go, then rerun mmi-cli devops release --apply. Do not start a second /release while one is still folding or tagging.`
|
|
13973
|
+
);
|
|
13974
|
+
}
|
|
13939
13975
|
if (releaseRepo && await isStrayUnreleasedTag(deps, tag, remoteSha, releaseRepo)) {
|
|
13940
13976
|
throw new Error(
|
|
13941
13977
|
`${mismatch}. The existing tag is not reachable from origin/main and has no GitHub Release \u2014 it was pushed outside the train, not by a completed release. Sanctioned recovery: delete the stray tag (git push origin --delete ${tag}; git tag -d ${tag} if it exists locally), check the repo's Actions for any workflow the stray tag already triggered, then rerun mmi-cli devops release --apply (if a publish already ran off the stray tag, mint the next version instead of reusing this one). Never complete the release by hand \u2014 a manual GitHub Release or branch push bypasses the train and leaves main behind.`
|
|
@@ -14610,6 +14646,7 @@ async function completeMainRelease(deps, ctx, meta, deployModel, watch, options,
|
|
|
14610
14646
|
}
|
|
14611
14647
|
let tagPush;
|
|
14612
14648
|
try {
|
|
14649
|
+
await assertComputedReleaseTagStillNext(deps, tag);
|
|
14613
14650
|
tagPush = await ensureTagPushed(deps, tag, releaseSha, tagProbe, ctx.repo);
|
|
14614
14651
|
} catch (e) {
|
|
14615
14652
|
throw await recoverFailedFold(deps, e, startBranch, preFold.mainSha, resumeCommand);
|
|
@@ -17194,7 +17231,12 @@ async function runStage(config = {}, opts = {}) {
|
|
|
17194
17231
|
const ranBuild = Boolean(build);
|
|
17195
17232
|
try {
|
|
17196
17233
|
await ensureStageRuntimeEnv(config, opts, cwd);
|
|
17197
|
-
if (build)
|
|
17234
|
+
if (build) {
|
|
17235
|
+
await shell(sub(build), cwd, timeoutMs, {
|
|
17236
|
+
...stageProcessEnv(stagePort, extraEnv),
|
|
17237
|
+
...opts.buildEnvMerge ?? {}
|
|
17238
|
+
});
|
|
17239
|
+
}
|
|
17198
17240
|
} catch (e) {
|
|
17199
17241
|
(0, import_node_fs16.rmSync)(statePath2, { force: true });
|
|
17200
17242
|
if (globalStatePath && globalStatePath !== statePath2) (0, import_node_fs16.rmSync)(globalStatePath, { force: true });
|
|
@@ -29221,6 +29263,85 @@ async function runStageLiveDown(deps, t) {
|
|
|
29221
29263
|
};
|
|
29222
29264
|
}
|
|
29223
29265
|
|
|
29266
|
+
// src/stage-build-secrets.ts
|
|
29267
|
+
var GITHUB_PACKAGES_TOKEN_REF = "@github-packages-token";
|
|
29268
|
+
var ENV_ID_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
29269
|
+
var BARE_VAULT_KEY_RE = /^[A-Z_][A-Z0-9_]*$/;
|
|
29270
|
+
var CROSS_SLUG_REF_RE = /^(?:mm-fofu|_org\/[a-z0-9][a-z0-9-]*):[A-Z_][A-Z0-9_]*$/;
|
|
29271
|
+
function parseStageBuildSecretEntry(entry) {
|
|
29272
|
+
const trimmed = entry.trim();
|
|
29273
|
+
if (!trimmed) return null;
|
|
29274
|
+
const eq = trimmed.indexOf("=");
|
|
29275
|
+
if (eq === -1) {
|
|
29276
|
+
if (!BARE_VAULT_KEY_RE.test(trimmed) && !CROSS_SLUG_REF_RE.test(trimmed)) return null;
|
|
29277
|
+
const envKey2 = trimmed.includes(":") ? trimmed.slice(trimmed.lastIndexOf(":") + 1) : trimmed;
|
|
29278
|
+
return { envKey: envKey2, ref: trimmed };
|
|
29279
|
+
}
|
|
29280
|
+
const envKey = trimmed.slice(0, eq);
|
|
29281
|
+
const ref = trimmed.slice(eq + 1);
|
|
29282
|
+
if (!ENV_ID_RE.test(envKey) || !ref) return null;
|
|
29283
|
+
if (ref === GITHUB_PACKAGES_TOKEN_REF) return { envKey, ref };
|
|
29284
|
+
if (!BARE_VAULT_KEY_RE.test(ref) && !CROSS_SLUG_REF_RE.test(ref)) return null;
|
|
29285
|
+
return { envKey, ref };
|
|
29286
|
+
}
|
|
29287
|
+
async function resolveStageBuildSecrets(input) {
|
|
29288
|
+
const entries = input.requiredBuildSecrets;
|
|
29289
|
+
if (!entries?.length) return {};
|
|
29290
|
+
const env = input.env ?? process.env;
|
|
29291
|
+
const out = {};
|
|
29292
|
+
const missing = [];
|
|
29293
|
+
for (const raw of entries) {
|
|
29294
|
+
const parsed = parseStageBuildSecretEntry(raw);
|
|
29295
|
+
if (!parsed) {
|
|
29296
|
+
missing.push(`${raw} (malformed requiredBuildSecrets entry)`);
|
|
29297
|
+
continue;
|
|
29298
|
+
}
|
|
29299
|
+
const { envKey, ref } = parsed;
|
|
29300
|
+
const fromEnv = env[envKey];
|
|
29301
|
+
if (typeof fromEnv === "string" && fromEnv.length > 0) {
|
|
29302
|
+
out[envKey] = fromEnv;
|
|
29303
|
+
continue;
|
|
29304
|
+
}
|
|
29305
|
+
if (ref === GITHUB_PACKAGES_TOKEN_REF) {
|
|
29306
|
+
const fromVault = await input.fetchVault(envKey);
|
|
29307
|
+
if (fromVault) {
|
|
29308
|
+
out[envKey] = fromVault;
|
|
29309
|
+
continue;
|
|
29310
|
+
}
|
|
29311
|
+
missing.push(
|
|
29312
|
+
`${envKey}=${GITHUB_PACKAGES_TOKEN_REF} (central deploy mints this; local /stage needs process env ${envKey} or a stageless project vault secret ${envKey} \u2014 a GitHub PAT with read:packages for npm.pkg.github.com; declare+set via vault secrets, never commit the value)`
|
|
29313
|
+
);
|
|
29314
|
+
continue;
|
|
29315
|
+
}
|
|
29316
|
+
const vault = await fetchVaultRef(input.fetchVault, ref);
|
|
29317
|
+
if (vault) {
|
|
29318
|
+
out[envKey] = vault;
|
|
29319
|
+
continue;
|
|
29320
|
+
}
|
|
29321
|
+
missing.push(
|
|
29322
|
+
`${envKey}=${ref} (not in process env and vault read returned nothing \u2014 declare+set the stageless secret, or export ${envKey} before mmi-cli stage run --apply)`
|
|
29323
|
+
);
|
|
29324
|
+
}
|
|
29325
|
+
if (missing.length) {
|
|
29326
|
+
throw new Error(
|
|
29327
|
+
`stage build secrets unresolved: ${missing.join("; ")}. Compose BuildKit mounts (e.g. NODE_AUTH_TOKEN) read the build process env only.`
|
|
29328
|
+
);
|
|
29329
|
+
}
|
|
29330
|
+
return out;
|
|
29331
|
+
}
|
|
29332
|
+
async function fetchVaultRef(fetchVault, ref) {
|
|
29333
|
+
const colon = ref.indexOf(":");
|
|
29334
|
+
if (colon === -1) return fetchVault(ref);
|
|
29335
|
+
const prefix = ref.slice(0, colon);
|
|
29336
|
+
const key = ref.slice(colon + 1);
|
|
29337
|
+
if (prefix === "mm-fofu") return fetchVault(key, { slug: "mm-fofu" });
|
|
29338
|
+
if (prefix.startsWith("_org/")) {
|
|
29339
|
+
const provider = prefix.slice("_org/".length);
|
|
29340
|
+
return fetchVault(`${provider}/${key}`, { slug: "_org" });
|
|
29341
|
+
}
|
|
29342
|
+
return null;
|
|
29343
|
+
}
|
|
29344
|
+
|
|
29224
29345
|
// src/stage-commands.ts
|
|
29225
29346
|
function registerStageCommands(program3) {
|
|
29226
29347
|
function stagePortFromArgv() {
|
|
@@ -29270,6 +29391,30 @@ function registerStageCommands(program3) {
|
|
|
29270
29391
|
}
|
|
29271
29392
|
return Object.keys(merge).length ? merge : void 0;
|
|
29272
29393
|
}
|
|
29394
|
+
async function fetchStageBuildEnvMerge() {
|
|
29395
|
+
const cfg = await loadConfig();
|
|
29396
|
+
if (!cfg.sagaApiUrl) return void 0;
|
|
29397
|
+
const read = await fetchProjectBySlugChecked(await repoSlug(), registryClientDeps(cfg)).catch(() => null);
|
|
29398
|
+
if (!read?.ok || !read.project) return void 0;
|
|
29399
|
+
const required = read.project.requiredBuildSecrets;
|
|
29400
|
+
if (!required?.length) return void 0;
|
|
29401
|
+
const d = makeSecretsDeps(cfg);
|
|
29402
|
+
const merge = await resolveStageBuildSecrets({
|
|
29403
|
+
requiredBuildSecrets: required,
|
|
29404
|
+
fetchVault: (key, opts) => fetchSecretValue(d, key, opts ?? {})
|
|
29405
|
+
});
|
|
29406
|
+
return Object.keys(merge).length ? merge : void 0;
|
|
29407
|
+
}
|
|
29408
|
+
async function stageVaultOpts() {
|
|
29409
|
+
const [vaultEnvMerge, buildEnvMerge] = await Promise.all([
|
|
29410
|
+
fetchStageVaultEnvMerge(),
|
|
29411
|
+
fetchStageBuildEnvMerge()
|
|
29412
|
+
]);
|
|
29413
|
+
return {
|
|
29414
|
+
...vaultEnvMerge ? { vaultEnvMerge } : {},
|
|
29415
|
+
...buildEnvMerge ? { buildEnvMerge } : {}
|
|
29416
|
+
};
|
|
29417
|
+
}
|
|
29273
29418
|
function stageStepsFor(res, stops = true) {
|
|
29274
29419
|
if (res.source === "derived" && res.derived) return derivedStagePlan(res.derived, shellFor(), stops);
|
|
29275
29420
|
return [{ label: `no local stage to run \u2014 ${res.gap ?? "stage config gap"}` }];
|
|
@@ -29367,7 +29512,8 @@ function registerStageCommands(program3) {
|
|
|
29367
29512
|
const cfg = res.derived.config;
|
|
29368
29513
|
const hold = stageKeepAlive();
|
|
29369
29514
|
try {
|
|
29370
|
-
const
|
|
29515
|
+
const vaultOpts = await stageVaultOpts();
|
|
29516
|
+
const result = await runStage(cfg, { ...stageScopedRunOpts({ timeoutMs: o.timeoutMs }), ...vaultOpts });
|
|
29371
29517
|
const reportUrl = reportedStageUrl(res, result);
|
|
29372
29518
|
const url = reportUrl ? ` \u2014 ${reportUrl}` : "";
|
|
29373
29519
|
return printLine(o.json ? JSON.stringify({ ...result, source: res.source, url: reportUrl }) : `mmi-cli stage: ${result.message}${url}`);
|
|
@@ -29437,14 +29583,14 @@ function registerStageCommands(program3) {
|
|
|
29437
29583
|
}
|
|
29438
29584
|
if (res.source === "none") return failGraceful(`stage run: ${res.gap}`);
|
|
29439
29585
|
const cfg = res.derived.config;
|
|
29440
|
-
const
|
|
29586
|
+
const vaultOpts = await stageVaultOpts();
|
|
29441
29587
|
try {
|
|
29442
29588
|
const hold = stageKeepAlive();
|
|
29443
29589
|
let printed = false;
|
|
29444
29590
|
try {
|
|
29445
29591
|
const result = await runStage(cfg, {
|
|
29446
29592
|
...stageScopedRunOpts({ timeoutMs: o.timeoutMs, allowStaleEnv: o.allowStaleEnv }),
|
|
29447
|
-
|
|
29593
|
+
...vaultOpts,
|
|
29448
29594
|
onReady: (ready) => {
|
|
29449
29595
|
const reportUrl = reportedStageUrl(res, ready);
|
|
29450
29596
|
const url = reportUrl ? ` \u2014 ${reportUrl}` : "";
|
|
@@ -29662,13 +29808,13 @@ async function resolveBoardAdvanceForPr(prNumber, repoOption) {
|
|
|
29662
29808
|
},
|
|
29663
29809
|
// #5317: after a successful move, re-read the board to verify Done actually holds — a move the API
|
|
29664
29810
|
// reported but the board did not reflect is named as left behind, never silently dropped.
|
|
29811
|
+
// #5358: verify against the item DIRECTLY (fetchIssueProjectItem, the same read `board show` uses),
|
|
29812
|
+
// never the active-board view — a just-Done item has already left the buckets readBoard partitions,
|
|
29813
|
+
// so a board-view lookup reports every successful advance as "not found".
|
|
29665
29814
|
readIssueStatus: async (ref) => {
|
|
29666
|
-
const number = Number(ref.replace(/^#/, ""));
|
|
29667
|
-
if (!Number.isInteger(number) || number <= 0 || !repo) return { error: `unusable ref ${ref}` };
|
|
29668
29815
|
try {
|
|
29669
|
-
const
|
|
29670
|
-
const
|
|
29671
|
-
const item = buckets.find((candidate2) => candidate2.repository.toLowerCase() === repo.toLowerCase() && candidate2.number === number);
|
|
29816
|
+
const cfg = resolveBoardConfig(await loadConfigForBoardSelector2(ref, repoOption));
|
|
29817
|
+
const { item } = await fetchIssueProjectItem(defaultGitHubClient(), cfg, parseIssueSelector(ref, repo));
|
|
29672
29818
|
return item ? { status: item.status } : { error: `issue ${ref} not found on the board after advance` };
|
|
29673
29819
|
} catch (e) {
|
|
29674
29820
|
return { error: e.message };
|
|
@@ -33854,7 +34000,27 @@ var surfaces_default = {
|
|
|
33854
34000
|
kind: "npm-pack",
|
|
33855
34001
|
packagePath: "updater"
|
|
33856
34002
|
},
|
|
33857
|
-
publishVisibility: "public"
|
|
34003
|
+
publishVisibility: "public",
|
|
34004
|
+
prepare: [
|
|
34005
|
+
{
|
|
34006
|
+
command: "npm",
|
|
34007
|
+
args: [
|
|
34008
|
+
"--prefix",
|
|
34009
|
+
"updater",
|
|
34010
|
+
"run",
|
|
34011
|
+
"build"
|
|
34012
|
+
],
|
|
34013
|
+
inputs: [
|
|
34014
|
+
"updater/src",
|
|
34015
|
+
"updater/build.mjs",
|
|
34016
|
+
"updater/package.json",
|
|
34017
|
+
"updater/tsconfig.json"
|
|
34018
|
+
],
|
|
34019
|
+
outputs: [
|
|
34020
|
+
"updater/dist/index.cjs"
|
|
34021
|
+
]
|
|
34022
|
+
}
|
|
34023
|
+
]
|
|
33858
34024
|
},
|
|
33859
34025
|
{
|
|
33860
34026
|
id: "mmi-cli-lock",
|
|
@@ -38297,11 +38463,12 @@ registerStageCommands(program2);
|
|
|
38297
38463
|
var GH_TRAIN_TIMEOUT_MS = 3e4;
|
|
38298
38464
|
var GH_RUN_WATCH_TIMEOUT_MS = 20 * 6e4;
|
|
38299
38465
|
var NODE_PREPARE_TIMEOUT_MS = 10 * 6e4;
|
|
38466
|
+
var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
|
|
38300
38467
|
var NPM_TRAIN_TIMEOUT_MS = 6e4;
|
|
38301
38468
|
function trainApplyDeps() {
|
|
38302
38469
|
return {
|
|
38303
38470
|
run: async (file, args) => {
|
|
38304
|
-
const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
|
|
38471
|
+
const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
|
|
38305
38472
|
try {
|
|
38306
38473
|
return isWin2 && file === "npm" ? (await execFileP2("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP2(file, args, { timeout })).stdout;
|
|
38307
38474
|
} catch (e) {
|
package/package.json
CHANGED