@mutmutco/cli 3.92.0 → 3.94.0
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 +98 -49
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -4304,6 +4304,24 @@ async function surfaceLabelApplies(repo, deps = {}) {
|
|
|
4304
4304
|
return (known?.length ?? 0) > 0;
|
|
4305
4305
|
}
|
|
4306
4306
|
|
|
4307
|
+
// src/parse-error-hints.ts
|
|
4308
|
+
var TARGET_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["--repo", "--ref", "--target", "--project", "--issue"]);
|
|
4309
|
+
function targetFlagValueFromArgv(flag, argv) {
|
|
4310
|
+
const i = argv.indexOf(flag);
|
|
4311
|
+
if (i < 0) return void 0;
|
|
4312
|
+
const next = argv[i + 1];
|
|
4313
|
+
if (!next || next.startsWith("-")) return void 0;
|
|
4314
|
+
return next;
|
|
4315
|
+
}
|
|
4316
|
+
function formatPositionalTarget(commandPath3, argName, opts = {}) {
|
|
4317
|
+
const concrete = opts.flag && opts.argv ? targetFlagValueFromArgv(opts.flag, opts.argv) : void 0;
|
|
4318
|
+
if (concrete) return `mmi-cli ${commandPath3} ${concrete}`;
|
|
4319
|
+
return `mmi-cli ${commandPath3} <${argName}>`;
|
|
4320
|
+
}
|
|
4321
|
+
function unknownTargetFlagMessage(flag, positional) {
|
|
4322
|
+
return `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}`;
|
|
4323
|
+
}
|
|
4324
|
+
|
|
4307
4325
|
// src/session-start.ts
|
|
4308
4326
|
var import_node_fs8 = require("node:fs");
|
|
4309
4327
|
var import_node_path6 = require("node:path");
|
|
@@ -11688,6 +11706,29 @@ function parseAuthoritativeRuleset(raw, meta, repo) {
|
|
|
11688
11706
|
function sameContexts(left, right) {
|
|
11689
11707
|
return left.length === right.length && left.every((context, index) => context === right[index]);
|
|
11690
11708
|
}
|
|
11709
|
+
function resolveProductRulesetReconcilePlan(input) {
|
|
11710
|
+
const liveNeedsContextConvergence = !sameContexts(input.liveContexts, input.authorityContexts);
|
|
11711
|
+
const liveIsActive = input.liveEnforcement === "active";
|
|
11712
|
+
if (liveIsActive && !liveNeedsContextConvergence) {
|
|
11713
|
+
return { shouldActivate: false, targetEnforcement: "active" };
|
|
11714
|
+
}
|
|
11715
|
+
if (input.unsafeContexts.length > 0) {
|
|
11716
|
+
const unsafe = [...input.unsafeContexts];
|
|
11717
|
+
return {
|
|
11718
|
+
shouldActivate: false,
|
|
11719
|
+
targetEnforcement: "disabled",
|
|
11720
|
+
holdReason: `product ruleset left non-enforcing \u2014 [${unsafe.join(", ")}] ${unsafe.length === 1 ? "is" : "are"} emitted ONLY by path-filtered workflow(s), so the context is not reported on a PR outside those paths and requiring it would block that PR forever (#3836). Give the gate a companion job that runs unconditionally, then re-run this command`
|
|
11721
|
+
};
|
|
11722
|
+
}
|
|
11723
|
+
if (!input.gateProvenGreen) {
|
|
11724
|
+
return {
|
|
11725
|
+
shouldActivate: false,
|
|
11726
|
+
targetEnforcement: "disabled",
|
|
11727
|
+
holdReason: "product ruleset left non-enforcing \u2014 the gate is not green on development; activating it would block every PR (#3694)"
|
|
11728
|
+
};
|
|
11729
|
+
}
|
|
11730
|
+
return { shouldActivate: true, targetEnforcement: "active" };
|
|
11731
|
+
}
|
|
11691
11732
|
async function contentExists(deps, repo, branch, path2) {
|
|
11692
11733
|
try {
|
|
11693
11734
|
const encodedPath = path2.split("/").map(encodeURIComponent).join("/");
|
|
@@ -12391,46 +12432,39 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
12391
12432
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
12392
12433
|
}
|
|
12393
12434
|
const liveContexts = live == null ? [] : sortedUnique(rulesetRequiredContexts(live));
|
|
12394
|
-
const
|
|
12395
|
-
const
|
|
12396
|
-
|
|
12397
|
-
|
|
12398
|
-
|
|
12399
|
-
const
|
|
12400
|
-
|
|
12401
|
-
|
|
12402
|
-
|
|
12403
|
-
|
|
12404
|
-
|
|
12405
|
-
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12409
|
-
|
|
12410
|
-
|
|
12411
|
-
|
|
12412
|
-
|
|
12413
|
-
if (
|
|
12414
|
-
|
|
12415
|
-
result.
|
|
12416
|
-
return finalizeCiReconcile(repo, deps, result, report, reason);
|
|
12417
|
-
}
|
|
12418
|
-
}
|
|
12419
|
-
if (liveNeedsConvergence) {
|
|
12420
|
-
try {
|
|
12421
|
-
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, enforcement);
|
|
12422
|
-
if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
|
|
12423
|
-
else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
12424
|
-
} catch (e) {
|
|
12425
|
-
result.errors.push(e.message);
|
|
12426
|
-
return finalizeCiReconcile(repo, deps, result, report);
|
|
12435
|
+
const prWorkflows = await prTriggeredWorkflowsOnRef(deps, repo, baseBranch, "deployable") ?? [];
|
|
12436
|
+
const gateFiles = gateWorkflowFiles(prWorkflows);
|
|
12437
|
+
const allPrWorkflows = await listWorkflowPaths(deps, repo, baseBranch) ?? [];
|
|
12438
|
+
const bodies = [];
|
|
12439
|
+
for (const path2 of allPrWorkflows) {
|
|
12440
|
+
const body = await fetchFileContent(deps, repo, baseBranch, path2);
|
|
12441
|
+
if (body) bodies.push({ path: path2, body });
|
|
12442
|
+
}
|
|
12443
|
+
const filteredPaths = new Set(pathFilteredPullRequestWorkflows(bodies).filter((p) => !p.endsWith("/agent-pr.yml")));
|
|
12444
|
+
const safeContexts = new Set(collectPullRequestWorkflowContexts(bodies.filter((b) => !filteredPaths.has(b.path))));
|
|
12445
|
+
const unsafe = collectPullRequestWorkflowContexts(bodies.filter((b) => filteredPaths.has(b.path))).filter((c) => authority.contexts.includes(c) && !safeContexts.has(c));
|
|
12446
|
+
const plan = resolveProductRulesetReconcilePlan({
|
|
12447
|
+
liveEnforcement: live?.enforcement,
|
|
12448
|
+
liveContexts,
|
|
12449
|
+
authorityContexts: authority.contexts,
|
|
12450
|
+
gateProvenGreen: await gateIsProvenGreen(repo, deps.client, baseBranch, gateFiles),
|
|
12451
|
+
unsafeContexts: unsafe
|
|
12452
|
+
});
|
|
12453
|
+
if (!plan.shouldActivate) {
|
|
12454
|
+
if (plan.holdReason) {
|
|
12455
|
+
result.skipped.push(plan.holdReason);
|
|
12456
|
+
return finalizeCiReconcile(repo, deps, result, report, plan.holdReason);
|
|
12427
12457
|
}
|
|
12428
|
-
} else {
|
|
12429
12458
|
result.skipped.push(`live ${PRODUCT_RULESET_NAME} contexts already match ${authority.source}`);
|
|
12459
|
+
return finalizeCiReconcile(repo, deps, result, report);
|
|
12430
12460
|
}
|
|
12431
|
-
|
|
12432
|
-
const
|
|
12433
|
-
|
|
12461
|
+
try {
|
|
12462
|
+
const activation = await activateProductRuleset(repo, authority.apiPayload, deps.client, plan.targetEnforcement);
|
|
12463
|
+
if (activation.action === "skipped") result.skipped.push(activation.detail ?? "product ruleset");
|
|
12464
|
+
else result.applied.push(`product ruleset ${activation.action}${activation.detail ? `: ${activation.detail}` : ""}`);
|
|
12465
|
+
} catch (e) {
|
|
12466
|
+
result.errors.push(e.message);
|
|
12467
|
+
return finalizeCiReconcile(repo, deps, result, report);
|
|
12434
12468
|
}
|
|
12435
12469
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
12436
12470
|
}
|
|
@@ -23427,6 +23461,12 @@ async function verifyBootstrap(repo, repoClass, deps, releaseTrack) {
|
|
|
23427
23461
|
detail: optionDetail(missing)
|
|
23428
23462
|
});
|
|
23429
23463
|
} else if (repoClass === "deployable") {
|
|
23464
|
+
const productRuleset = rulesets.find((r) => r.name === PRODUCT_RULESET_NAME);
|
|
23465
|
+
checks.push({
|
|
23466
|
+
ok: productRuleset?.enforcement === "active",
|
|
23467
|
+
label: "product required-check ruleset enforcement active",
|
|
23468
|
+
detail: productRuleset?.enforcement !== "active" ? `${PRODUCT_RULESET_NAME} is ${productRuleset?.enforcement ?? "missing"} \u2014 run mmi-cli ci reconcile --apply --repo ${repo} once the gate is green` : void 0
|
|
23469
|
+
});
|
|
23430
23470
|
const statusChecks = rulesetStatusChecks2(rulesets.filter((r) => r.target === "branch" && r.enforcement === "active"));
|
|
23431
23471
|
const missing = requiredProductStatusChecks.filter((check) => !statusChecks.has(check));
|
|
23432
23472
|
checks.push({
|
|
@@ -28840,7 +28880,8 @@ function diagnoseSurface(evidence) {
|
|
|
28840
28880
|
return { ...base, state: "repair-failed", repairDetail: evidence.repair.detail };
|
|
28841
28881
|
}
|
|
28842
28882
|
if (evidence.repair?.attempted && evidence.repair.ok) {
|
|
28843
|
-
|
|
28883
|
+
const behindReleased = evidence.installedVersion && evidence.releasedVersion && compareVersions(evidence.installedVersion, evidence.releasedVersion) < 0;
|
|
28884
|
+
return { ...base, state: behindReleased ? "pending-reload" : "clean", repairDetail: evidence.repair.detail };
|
|
28844
28885
|
}
|
|
28845
28886
|
if (!evidence.installRecordPresent) return { ...base, state: "missing" };
|
|
28846
28887
|
if (!evidence.deliveryPresent || !evidence.payloadPresent || evidence.manifest === "missing") {
|
|
@@ -28858,7 +28899,7 @@ function reloadInstruction(descriptor) {
|
|
|
28858
28899
|
return `${verb} ${descriptor.displayName}`;
|
|
28859
28900
|
}
|
|
28860
28901
|
function planSurfaceRepair(diagnosis) {
|
|
28861
|
-
if (diagnosis.state === "clean" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
28902
|
+
if (diagnosis.state === "clean" || diagnosis.state === "pending-reload" || diagnosis.state === "skipped" || diagnosis.state === "freshness-unknown") return null;
|
|
28862
28903
|
const descriptor = diagnosis.descriptor;
|
|
28863
28904
|
if (descriptor.repairOwner !== "mmi-cli") {
|
|
28864
28905
|
return {
|
|
@@ -28883,6 +28924,7 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
28883
28924
|
const detailByState = {
|
|
28884
28925
|
skipped: "skipped \u2014 host not active",
|
|
28885
28926
|
clean: diagnosis.repairDetail ? `clean \u2014 repaired and verified (${diagnosis.repairDetail})` : `clean${versions ? ` \u2014 ${versions}` : ""}`,
|
|
28927
|
+
"pending-reload": `pending ${descriptor.reload === "workspace" ? "reload" : "restart"} \u2014 repaired to ${diagnosis.releasedVersion}, but ${diagnosis.installedVersion} is still the version this host has loaded${diagnosis.repairDetail ? ` (${diagnosis.repairDetail})` : ""}`,
|
|
28886
28928
|
"freshness-unknown": `${diagnosis.installedVersion ?? "installed"} \u2014 freshness UNKNOWN, the published version could not be read`,
|
|
28887
28929
|
stale: `stale${versions ? ` \u2014 ${versions}` : ""}`,
|
|
28888
28930
|
missing: "missing \u2014 no install record",
|
|
@@ -28894,11 +28936,14 @@ function buildSurfaceDoctorCheck(diagnosis) {
|
|
|
28894
28936
|
id: `${descriptor.token}-plugin`,
|
|
28895
28937
|
surface: descriptor.token,
|
|
28896
28938
|
state,
|
|
28897
|
-
ok: state === "clean" || state === "skipped",
|
|
28939
|
+
ok: state === "clean" || state === "skipped" || state === "pending-reload",
|
|
28898
28940
|
...state === "freshness-unknown" ? { reportOnly: true } : {},
|
|
28941
|
+
// Nothing is broken, so this is not a ✗ — but it is the one OK row an operator must act on, and the
|
|
28942
|
+
// short default lane and the SessionStart banner both filter to failures unless a row says otherwise.
|
|
28943
|
+
...state === "pending-reload" ? { warn: true } : {},
|
|
28899
28944
|
label: `${descriptor.displayName} plugin`,
|
|
28900
28945
|
detail: detailByState[state],
|
|
28901
|
-
...plan ? { fix: plan.instruction } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
28946
|
+
...plan ? { fix: plan.instruction } : state === "pending-reload" ? { fix: reloadInstruction(descriptor) } : state === "freshness-unknown" ? { fix: "check `mmi-cli --version` against `npm view @mutmutco/cli version`, then rerun doctor" } : {},
|
|
28902
28947
|
verbose: [
|
|
28903
28948
|
// The three numbers the legacy builder used to print. They belong here now that this is the only
|
|
28904
28949
|
// plugin row, and a report that names a state without naming the versions behind it is not evidence.
|
|
@@ -30000,13 +30045,12 @@ function commandOwnLongFlags(cmd) {
|
|
|
30000
30045
|
}
|
|
30001
30046
|
return [...flags];
|
|
30002
30047
|
}
|
|
30003
|
-
|
|
30004
|
-
function positionalTargetForm(cmd) {
|
|
30048
|
+
function positionalTargetForm(cmd, opts = {}) {
|
|
30005
30049
|
if (!cmd) return void 0;
|
|
30006
30050
|
const args = cmd.registeredArguments ?? [];
|
|
30007
30051
|
const first = args[0];
|
|
30008
30052
|
if (!first) return void 0;
|
|
30009
|
-
return
|
|
30053
|
+
return formatPositionalTarget(commandPath2(cmd), first.name(), opts);
|
|
30010
30054
|
}
|
|
30011
30055
|
function resolveParseHint() {
|
|
30012
30056
|
if (lastParseErrorKind === "unknown-command") {
|
|
@@ -30051,12 +30095,13 @@ function envelopeAwareWriteErr(str) {
|
|
|
30051
30095
|
const match = /unknown option '([^']+)'/.exec(plain);
|
|
30052
30096
|
if (match) {
|
|
30053
30097
|
const flag = match[1];
|
|
30098
|
+
const argv = process.argv.slice(2);
|
|
30099
|
+
const invoked = resolveCommandFromArgv(program2, argv);
|
|
30100
|
+
const positional = TARGET_SELECTOR_FLAGS.has(flag) ? positionalTargetForm(invoked, { flag, argv }) : void 0;
|
|
30054
30101
|
if (argvWantsJson2()) {
|
|
30055
|
-
const
|
|
30056
|
-
const
|
|
30057
|
-
const
|
|
30058
|
-
const positional = !suggestion && TARGET_SELECTOR_FLAGS.has(flag) ? positionalTargetForm(invoked) : void 0;
|
|
30059
|
-
const message = positional ? `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}` : `unknown option '${flag}'`;
|
|
30102
|
+
const suggestion = positional ? void 0 : didYouMean(flag, commandOwnLongFlags(invoked));
|
|
30103
|
+
const corrected = suggestion ? `mmi-cli ${argv.map((a) => a === flag ? suggestion : a).join(" ")}` : void 0;
|
|
30104
|
+
const message = positional ? unknownTargetFlagMessage(flag, positional) : `unknown option '${flag}'`;
|
|
30060
30105
|
process.stderr.write(
|
|
30061
30106
|
formatErrorEnvelope(message, {
|
|
30062
30107
|
code: ERROR_CODES.ERR_UNKNOWN_FLAG,
|
|
@@ -30068,6 +30113,10 @@ function envelopeAwareWriteErr(str) {
|
|
|
30068
30113
|
unknownFlagJsonHandled = true;
|
|
30069
30114
|
return;
|
|
30070
30115
|
}
|
|
30116
|
+
if (positional) {
|
|
30117
|
+
process.stderr.write(str.replace(/unknown option '[^']+'/, unknownTargetFlagMessage(flag, positional)));
|
|
30118
|
+
return;
|
|
30119
|
+
}
|
|
30071
30120
|
process.stderr.write(str);
|
|
30072
30121
|
return;
|
|
30073
30122
|
}
|
package/package.json
CHANGED