@mutmutco/cli 4.5.102 → 4.5.103
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 +128 -22
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -6053,6 +6053,16 @@ function isRetryableGitHubWaitReadError(e) {
|
|
|
6053
6053
|
}
|
|
6054
6054
|
return /Something went wrong while executing your query/.test(text) || /^\s*unexpected end of JSON input\s*$/m.test(text);
|
|
6055
6055
|
}
|
|
6056
|
+
function refusalWaitMs(e, nowMs) {
|
|
6057
|
+
const err = e;
|
|
6058
|
+
const text = [err?.stdout, err?.stderr, e instanceof Error ? e.message : ""].filter((t) => typeof t === "string").join("\n");
|
|
6059
|
+
const header = (name) => new RegExp(`^${name}:\\s*(\\d+)\\s*$`, "im").exec(text)?.[1];
|
|
6060
|
+
const retryAfter = header("retry-after");
|
|
6061
|
+
if (retryAfter !== void 0) return Number(retryAfter) * 1e3;
|
|
6062
|
+
const reset = header("x-ratelimit-remaining") === "0" ? Number(header("x-ratelimit-reset")) : typeof err?.rateLimitReset === "number" ? err.rateLimitReset : NaN;
|
|
6063
|
+
if (reset * 1e3 > nowMs) return reset * 1e3 - nowMs;
|
|
6064
|
+
return /secondary rate|abuse detection/i.test(text) ? 6e4 : void 0;
|
|
6065
|
+
}
|
|
6056
6066
|
function waitReadFailureMessage(e) {
|
|
6057
6067
|
const err = e;
|
|
6058
6068
|
const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
|
|
@@ -6180,9 +6190,21 @@ async function waitForPrChecks(deps) {
|
|
|
6180
6190
|
return { policy, status: "failure", reason: waitReadFailureMessage(e), detail: "github-read-failed", waitedMs: now() - started };
|
|
6181
6191
|
}
|
|
6182
6192
|
lastDetail = `github-read-transient (${waitReadFailureMessage(e)})`;
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6193
|
+
const askedMs = refusalWaitMs(e, now());
|
|
6194
|
+
const jitter = deps.jitterMs ?? (() => Math.floor(Math.random() * PR_CHECKS_RATELIMIT_JITTER_MAX_MS));
|
|
6195
|
+
const waitMs = askedMs !== void 0 && askedMs > PR_CHECKS_POLL_MS ? askedMs + jitter() : PR_CHECKS_POLL_MS;
|
|
6196
|
+
if (askedMs !== void 0 && now() + waitMs >= deadline) {
|
|
6197
|
+
return {
|
|
6198
|
+
policy,
|
|
6199
|
+
status: "rate-limited",
|
|
6200
|
+
reason: `GitHub refused the read for rate limit and asked for ${Math.round(askedMs / 1e3)}s, past the wait deadline (${lastDetail})`,
|
|
6201
|
+
detail: "rate-limited",
|
|
6202
|
+
waitedMs: now() - started
|
|
6203
|
+
};
|
|
6204
|
+
}
|
|
6205
|
+
deps.log?.(`transient GitHub read failure \u2014 retrying in ${Math.round(waitMs / 1e3)}s within the wait budget: ${lastDetail}`);
|
|
6206
|
+
report(askedMs !== void 0 ? "rate-limited" : "pending");
|
|
6207
|
+
await deps.sleep(waitMs);
|
|
6186
6208
|
continue;
|
|
6187
6209
|
}
|
|
6188
6210
|
report(state);
|
|
@@ -6300,6 +6322,7 @@ function setPollTokenProvider(provider) {
|
|
|
6300
6322
|
pollTokenOnce = void 0;
|
|
6301
6323
|
resetAppPollGuard();
|
|
6302
6324
|
lastPollSource = void 0;
|
|
6325
|
+
observedPool.clear();
|
|
6303
6326
|
}
|
|
6304
6327
|
async function pollToken() {
|
|
6305
6328
|
if (!pollTokenProvider) return void 0;
|
|
@@ -6351,6 +6374,19 @@ function appPoolDetail() {
|
|
|
6351
6374
|
const counts = guard.remaining !== void 0 && guard.limit !== void 0 ? `App pool at ${guard.remaining}/${guard.limit}` : "App pool headroom unverified";
|
|
6352
6375
|
return guard.resetAtMs !== void 0 ? `${counts}, resets ${new Date(guard.resetAtMs).toISOString()}` : counts;
|
|
6353
6376
|
}
|
|
6377
|
+
var observedPool = /* @__PURE__ */ new Map();
|
|
6378
|
+
var OBSERVED_POOL_MAX_AGE_MS = 3 * 6e4;
|
|
6379
|
+
function notePoolHeaders(source, out, nowMs) {
|
|
6380
|
+
const { remaining, resetSec } = parseRateLimitHeaders(out);
|
|
6381
|
+
const resource = /^x-ratelimit-resource:\s*(\S+)\s*$/im.exec(out)?.[1];
|
|
6382
|
+
if (remaining !== void 0 && resetSec !== void 0 && (resource ?? "core") === "core") {
|
|
6383
|
+
observedPool.set(source, { remaining, reset: resetSec, atMs: nowMs });
|
|
6384
|
+
}
|
|
6385
|
+
}
|
|
6386
|
+
function observedCorePool(source, nowMs) {
|
|
6387
|
+
const seen = source ? observedPool.get(source) : void 0;
|
|
6388
|
+
return seen && nowMs - seen.atMs <= OBSERVED_POOL_MAX_AGE_MS && seen.reset * 1e3 > nowMs ? { remaining: seen.remaining, reset: seen.reset } : void 0;
|
|
6389
|
+
}
|
|
6354
6390
|
async function defaultGhApi(args) {
|
|
6355
6391
|
const appToken = await pollToken();
|
|
6356
6392
|
let source = appToken && appPollAllowsApp(Date.now()) ? "app" : "user";
|
|
@@ -6372,6 +6408,7 @@ async function defaultGhApi(args) {
|
|
|
6372
6408
|
const out = e.stdout;
|
|
6373
6409
|
const errOut = typeof out === "string" ? out : void 0;
|
|
6374
6410
|
const status = errOut !== void 0 ? parseIncludedOutput(errOut).status : void 0;
|
|
6411
|
+
if (errOut !== void 0) notePoolHeaders(source, errOut, Date.now());
|
|
6375
6412
|
if (cached && status === 304) {
|
|
6376
6413
|
lastPollSource = source;
|
|
6377
6414
|
return cached.body;
|
|
@@ -6386,6 +6423,7 @@ async function defaultGhApi(args) {
|
|
|
6386
6423
|
throw e;
|
|
6387
6424
|
}
|
|
6388
6425
|
const { body, etag, pages } = parseIncludedOutput(stdout);
|
|
6426
|
+
notePoolHeaders(source, stdout, Date.now());
|
|
6389
6427
|
if (source === "app") noteAppPollResponse(parseRateLimitHeaders(stdout), false, Date.now());
|
|
6390
6428
|
lastPollSource = source;
|
|
6391
6429
|
if (etag && pages === 1) etagCache.set(key, { etag, body });
|
|
@@ -6858,7 +6896,9 @@ async function fetchRequiredCheckContexts(repo, branch, gh2 = defaultGhApi) {
|
|
|
6858
6896
|
}
|
|
6859
6897
|
return contexts;
|
|
6860
6898
|
}
|
|
6861
|
-
async function fetchRestCorePool(gh2 = defaultGhApi) {
|
|
6899
|
+
async function fetchRestCorePool(gh2 = defaultGhApi, nowMs = Date.now()) {
|
|
6900
|
+
const seen = gh2 === defaultGhApi ? observedCorePool(await pollToken() && appPollAllowsApp(nowMs) ? "app" : "user", nowMs) : void 0;
|
|
6901
|
+
if (seen) return { state: "ok", ...seen };
|
|
6862
6902
|
let core;
|
|
6863
6903
|
try {
|
|
6864
6904
|
core = JSON.parse(await gh2(["rate_limit"])).resources?.core;
|
|
@@ -16126,6 +16166,39 @@ function gateConfigToVars(gate) {
|
|
|
16126
16166
|
if (extras.steps?.length) out.GATE_EXTRA_STEPS_YAML = gateExtraStepsYaml(extras.steps);
|
|
16127
16167
|
return out;
|
|
16128
16168
|
}
|
|
16169
|
+
function suiteModuleSeeds(seeds, suite, deployModel) {
|
|
16170
|
+
if (suite !== "mm-work" || deployModel !== "tenant-container") return seeds;
|
|
16171
|
+
const replacements = {
|
|
16172
|
+
Dockerfile: "suite-Dockerfile.template",
|
|
16173
|
+
"docker-compose.yml": "suite-compose.template.yml",
|
|
16174
|
+
".github/workflows/gate.yml": "suite-gate.template.yml"
|
|
16175
|
+
};
|
|
16176
|
+
const additions = {
|
|
16177
|
+
"src/index.ts": "suite-server.template.ts",
|
|
16178
|
+
"package.json": "suite-package.template.json",
|
|
16179
|
+
"package-lock.json": "suite-package-lock.template.json",
|
|
16180
|
+
"tsconfig.json": "suite-tsconfig.template.json",
|
|
16181
|
+
"module/package.json": "suite-module-package.template.json",
|
|
16182
|
+
"module/package-lock.json": "suite-module-package-lock.template.json",
|
|
16183
|
+
"module/.npmrc": "suite-npmrc.template",
|
|
16184
|
+
"module/src/module.jsx": "suite-module.template.jsx",
|
|
16185
|
+
"module/vite.config.mjs": "suite-vite.template.mjs",
|
|
16186
|
+
"module/screens/screens.json": "suite-screens.template.json",
|
|
16187
|
+
"module/screens/files.json": "suite-files.template.json",
|
|
16188
|
+
".github/workflows/screens.yml": "suite-screens.template.yml",
|
|
16189
|
+
"docs/Guides/mm-work-module.md": "suite-guide.template.md"
|
|
16190
|
+
};
|
|
16191
|
+
return [
|
|
16192
|
+
...seeds.filter((s) => s.target !== "docs/Guides/google-login.md").map((s) => replacements[s.target] ? { ...s, source: `seed:${replacements[s.target]}` } : s),
|
|
16193
|
+
...Object.entries(additions).map(([target, source]) => ({
|
|
16194
|
+
target,
|
|
16195
|
+
source: `seed:${source}`,
|
|
16196
|
+
ownership: "repo",
|
|
16197
|
+
classes: ["deployable"],
|
|
16198
|
+
deployModels: ["tenant-container"]
|
|
16199
|
+
}))
|
|
16200
|
+
];
|
|
16201
|
+
}
|
|
16129
16202
|
function seedMatchesDeployModel(seed, deployModel) {
|
|
16130
16203
|
if (!seed.deployModels?.length) return true;
|
|
16131
16204
|
return deployModel != null && seed.deployModels.includes(deployModel);
|
|
@@ -16307,7 +16380,11 @@ function buildRegisterPayload(repo, cls, vars, options = {}) {
|
|
|
16307
16380
|
// Identity. name/division default off the repo name when the skill didn't pass them.
|
|
16308
16381
|
name: vars.NAME || parsedRepo.name,
|
|
16309
16382
|
division: vars.DIVISION || parsedRepo.name.split("-")[0] || void 0,
|
|
16310
|
-
suite
|
|
16383
|
+
suite,
|
|
16384
|
+
...suite === "mm-work" && deployModel === "tenant-container" ? {
|
|
16385
|
+
requiredBuildSecrets: ["NODE_AUTH_TOKEN=@github-packages-token"],
|
|
16386
|
+
requiredChecks: ["gate", "screens / screens"]
|
|
16387
|
+
} : {},
|
|
16311
16388
|
repos: [`${parsedRepo.owner}/${slug}`],
|
|
16312
16389
|
branch: vars.BRANCH || (cls === "content" ? "main" : "development"),
|
|
16313
16390
|
class: cls,
|
|
@@ -17622,10 +17699,10 @@ var rollout_plan_default = {
|
|
|
17622
17699
|
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)."
|
|
17623
17700
|
},
|
|
17624
17701
|
baseline: {
|
|
17625
|
-
version: "4.5.
|
|
17626
|
-
tag: "v4.5.
|
|
17627
|
-
commit: "
|
|
17628
|
-
npm: "@mutmutco/cli@4.5.
|
|
17702
|
+
version: "4.5.103",
|
|
17703
|
+
tag: "v4.5.103",
|
|
17704
|
+
commit: "c2dd99cf0d72",
|
|
17705
|
+
npm: "@mutmutco/cli@4.5.103"
|
|
17629
17706
|
},
|
|
17630
17707
|
exitCriterion: "fleet-n-of-n",
|
|
17631
17708
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17642,14 +17719,14 @@ var rollout_plan_default = {
|
|
|
17642
17719
|
repo: "mutmutco/mmi-hub",
|
|
17643
17720
|
role: "canary",
|
|
17644
17721
|
schedule: "train",
|
|
17645
|
-
v3Target: "v4.5.
|
|
17722
|
+
v3Target: "v4.5.103"
|
|
17646
17723
|
}
|
|
17647
17724
|
],
|
|
17648
17725
|
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.",
|
|
17649
17726
|
rollback: {
|
|
17650
17727
|
independent: true,
|
|
17651
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17652
|
-
v3Target: "v4.5.
|
|
17728
|
+
mechanism: "npm dist-tag latest -> 4.5.103 and redeploy the Hub Lambda from tag v4.5.103 (c2dd99cf0d72); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17729
|
+
v3Target: "v4.5.103 (@mutmutco/cli@4.5.103, tag commit c2dd99cf0d72 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17653
17730
|
}
|
|
17654
17731
|
},
|
|
17655
17732
|
{
|
|
@@ -33937,14 +34014,15 @@ function deployRowChecks(repo, deployModel, releaseTrack, facts, factsRead, suit
|
|
|
33937
34014
|
};
|
|
33938
34015
|
});
|
|
33939
34016
|
}
|
|
33940
|
-
function edgeCertificateChecks(results) {
|
|
34017
|
+
function edgeCertificateChecks(results, repo) {
|
|
33941
34018
|
return results.flatMap((result) => {
|
|
33942
34019
|
if (!result) return [];
|
|
33943
34020
|
const { stage, status, error } = result;
|
|
34021
|
+
const fix = status?.missing && !status.pending ? ` \u2014 run: mmi-cli devops runtime edge cert ensure ${repo} ${stage}` : "";
|
|
33944
34022
|
return [{
|
|
33945
34023
|
ok: !error && (status?.required === false || status?.required === true && status.missing === false),
|
|
33946
34024
|
label: `Cloudflare edge certificate: ${stage}`,
|
|
33947
|
-
detail: error ? `certificate coverage unreadable (${error}); cannot verify TLS` : status?.hint
|
|
34025
|
+
detail: error ? `certificate coverage unreadable (${error}); cannot verify TLS` : status?.hint && `${status.hint}${fix}`
|
|
33948
34026
|
}];
|
|
33949
34027
|
});
|
|
33950
34028
|
}
|
|
@@ -34008,6 +34086,8 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
34008
34086
|
detail: exactAllowlist ? void 0 : ownersUnverified ? "org owner baseline is unreadable or empty; the exact restriction is unverified" : deps.pushAllowlistOwnersRead === true ? "restriction must contain exactly the org owners + mmi-github-app and no teams" : "restrictions.users is empty or unset"
|
|
34009
34087
|
});
|
|
34010
34088
|
}
|
|
34089
|
+
const packageAccess = suiteModulePackageAccessCheck(deps.projectMeta?.suite, deps.deployModel);
|
|
34090
|
+
if (packageAccess) checks.push(packageAccess);
|
|
34011
34091
|
let environmentDrift2 = [];
|
|
34012
34092
|
try {
|
|
34013
34093
|
environmentDrift2 = await verifyRepoEnvironments(repo, effectiveTrack, deps.client, deps.projectMeta?.suite);
|
|
@@ -34091,7 +34171,7 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
34091
34171
|
const check = npmrcGitHubPackagesAuthCheck(path2, await contentText(deps, repo, baseBranch, path2));
|
|
34092
34172
|
if (check) checks.push(check);
|
|
34093
34173
|
}
|
|
34094
|
-
checks.push(...edgeCertificateChecks(deps.edgeCertificates ?? []));
|
|
34174
|
+
checks.push(...edgeCertificateChecks(deps.edgeCertificates ?? [], repo));
|
|
34095
34175
|
checks.push(...edgeDnsChecks(deps.edgeDns ?? [], repo));
|
|
34096
34176
|
const portRangeCheck = centralContainerPortRangeCheck(deps.deployModel, deps.projectMeta?.portRange, repo);
|
|
34097
34177
|
if (portRangeCheck) checks.push(portRangeCheck);
|
|
@@ -34349,6 +34429,14 @@ function laneNotEvaluatedFromThisBranch(doctor) {
|
|
|
34349
34429
|
const only = reasons.length === 1 ? reasons[0] : void 0;
|
|
34350
34430
|
return only?.code === "branch-mismatch" ? only.title : void 0;
|
|
34351
34431
|
}
|
|
34432
|
+
function suiteModulePackageAccessCheck(suite, deployModel) {
|
|
34433
|
+
if (suite !== "mm-work" || deployModel !== "tenant-container") return void 0;
|
|
34434
|
+
return {
|
|
34435
|
+
ok: true,
|
|
34436
|
+
label: "mmwork-ds package access (manual org-admin step; unverified)",
|
|
34437
|
+
detail: "An org admin must add this repository with Read under @mutmutco/mmwork-ds \u2192 Package settings \u2192 Manage Actions access. No GitHub REST API can verify or grant this; without it CI fails with 403 read_package."
|
|
34438
|
+
};
|
|
34439
|
+
}
|
|
34352
34440
|
function attachTrainDoctorVerdicts(report, trainDoctors) {
|
|
34353
34441
|
const marked = trainDoctors.map((doctor) => {
|
|
34354
34442
|
const notEvaluatedFromThisBranch = laneNotEvaluatedFromThisBranch(doctor);
|
|
@@ -34837,13 +34925,6 @@ function registerBootstrapCommands(program3) {
|
|
|
34837
34925
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
34838
34926
|
const slug = parsedRepo.slug;
|
|
34839
34927
|
const onlyTarget = o.fromConfig && o.only.trim() === "gate.yml" ? ".github/workflows/gate.yml" : o.only.trim();
|
|
34840
|
-
const seedsToApply = onlyTarget ? manifest.seeds.filter((s) => s.target === onlyTarget || s.target.replace("{{REPO_SLUG}}", slug) === onlyTarget) : manifest.seeds;
|
|
34841
|
-
if (onlyTarget && !seedsToApply.length) {
|
|
34842
|
-
const known = manifest.seeds.map((s) => s.target).join("\n ");
|
|
34843
|
-
return fail(`bootstrap apply: --only '${onlyTarget}' names no seed in ${manifestPath}. Declared targets:
|
|
34844
|
-
${known}`);
|
|
34845
|
-
}
|
|
34846
|
-
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
34847
34928
|
const gh2 = async (args) => execFileP("gh", args, { timeout: 2e4 });
|
|
34848
34929
|
const readFile10 = (p) => (0, import_node_fs28.existsSync)(p) ? (0, import_node_fs28.readFileSync)(p, "utf8") : null;
|
|
34849
34930
|
const enc = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
@@ -34913,6 +34994,20 @@ function registerBootstrapCommands(program3) {
|
|
|
34913
34994
|
applyProjectType = o.projectType || registryMeta?.projectType || payload.projectType;
|
|
34914
34995
|
} catch {
|
|
34915
34996
|
}
|
|
34997
|
+
const availableSeeds = suiteModuleSeeds(manifest.seeds, vars.SUITE || registryMeta?.suite, applyDeployModel);
|
|
34998
|
+
const seedsToApply = onlyTarget ? availableSeeds.filter((s) => s.target === onlyTarget || s.target.replace("{{REPO_SLUG}}", slug) === onlyTarget) : availableSeeds;
|
|
34999
|
+
if (onlyTarget && !seedsToApply.length) {
|
|
35000
|
+
return fail(`bootstrap apply: --only '${onlyTarget}' names no seed for this project. Declared targets:
|
|
35001
|
+
${availableSeeds.map((s) => s.target).join("\n ")}`);
|
|
35002
|
+
}
|
|
35003
|
+
const onlyManagedBlock = onlyTarget ? seedsToApply[0]?.managedBlock != null : false;
|
|
35004
|
+
if (vars.SUITE === "mm-work" || registryMeta?.suite === "mm-work") {
|
|
35005
|
+
if (applyDeployModel === "tenant-container") {
|
|
35006
|
+
const moduleGate = withDerivedRepoVars({}, parsedRepo, o.class, effectiveTrack, registryRequiredCheckBranches, ["gate", "screens / screens"]);
|
|
35007
|
+
vars.GATE_RULESET_CONTEXTS_JSON = moduleGate.GATE_RULESET_CONTEXTS_JSON;
|
|
35008
|
+
if (registryMeta) registryMeta = { ...registryMeta, requiredChecks: ["gate", "screens / screens"] };
|
|
35009
|
+
}
|
|
35010
|
+
}
|
|
34916
35011
|
const controlClient = defaultGitHubClient();
|
|
34917
35012
|
let controlOwners = [];
|
|
34918
35013
|
let controlDeployFacts = null;
|
|
@@ -53292,6 +53387,17 @@ function registerEdgeCommands(program3) {
|
|
|
53292
53387
|
fail(`runtime edge dns: ${e.message}`);
|
|
53293
53388
|
}
|
|
53294
53389
|
});
|
|
53390
|
+
edge.command("cert <action> <owner/repo> <stage>").description("read (`status`) or order-when-missing (`ensure`, master-only) the Cloudflare advanced edge certificate pack (<x> + *.<x>) a dev/rc host needs").action(async (action, repo, stage) => {
|
|
53391
|
+
if (action !== "status" && action !== "ensure") return fail("runtime edge cert: <action> must be status or ensure");
|
|
53392
|
+
if (stage !== "dev" && stage !== "rc") return fail("runtime edge cert: <stage> must be dev or rc (main is covered by the zone certificate)");
|
|
53393
|
+
try {
|
|
53394
|
+
const result = await edgeCertificateForStage(slugOf(repo), stage, registryClientDeps(await loadConfig()), action === "ensure");
|
|
53395
|
+
console.log(JSON.stringify(result, null, 2));
|
|
53396
|
+
if (result.required && result.missing) process.exitCode = 1;
|
|
53397
|
+
} catch (e) {
|
|
53398
|
+
fail(`runtime edge cert: ${e.message}`);
|
|
53399
|
+
}
|
|
53400
|
+
});
|
|
53295
53401
|
}
|
|
53296
53402
|
|
|
53297
53403
|
// src/command-register-developer.ts
|
package/package.json
CHANGED