@mutmutco/cli 4.3.38 → 4.3.39
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 -89
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -4149,6 +4149,20 @@ var HOUSE_MAP = {
|
|
|
4149
4149
|
onboard: "core"
|
|
4150
4150
|
// repo-readiness orientation (front door — torn toward oracle, kept core)
|
|
4151
4151
|
};
|
|
4152
|
+
var HOUSE_ALIASES = {
|
|
4153
|
+
issue: ["devops"],
|
|
4154
|
+
// filing/managing issues is oracle, but reads as shipping work
|
|
4155
|
+
"issue create": ["devops"]
|
|
4156
|
+
};
|
|
4157
|
+
function isDeclaredHouseAlias(houseToken, lookupPath) {
|
|
4158
|
+
const segments = lookupPath.split(" ");
|
|
4159
|
+
for (let end = segments.length; end > 0; end -= 1) {
|
|
4160
|
+
const key = segments.slice(0, end).join(" ");
|
|
4161
|
+
const aliases = HOUSE_ALIASES[key];
|
|
4162
|
+
if (aliases) return aliases.includes(houseToken);
|
|
4163
|
+
}
|
|
4164
|
+
return false;
|
|
4165
|
+
}
|
|
4152
4166
|
function houseForPath(path2) {
|
|
4153
4167
|
if (path2 === "") return "core";
|
|
4154
4168
|
const segments = path2.split(" ");
|
|
@@ -5256,6 +5270,39 @@ function unknownCommandDomainGuide(parentPath, token) {
|
|
|
5256
5270
|
return GUIDES.get(`${parentPath}:${token}`);
|
|
5257
5271
|
}
|
|
5258
5272
|
|
|
5273
|
+
// src/verb-alias.ts
|
|
5274
|
+
var VERB_ALIAS_TABLE = [
|
|
5275
|
+
// #6489: `board` has no `status` leaf (read|claim|show|move|done|doctor only); the read that
|
|
5276
|
+
// answers "what is this issue/PR's board column" is `board show <ref>`.
|
|
5277
|
+
{ typed: ["oracle", "board", "status"], canonical: ["oracle", "board", "show"] },
|
|
5278
|
+
{ typed: ["board", "status"], canonical: ["board", "show"] },
|
|
5279
|
+
// #6494: there is no top-level or housed `registry` command; org/registry project metadata is
|
|
5280
|
+
// `oracle org project get`.
|
|
5281
|
+
{ typed: ["registry"], canonical: ["oracle", "org", "project", "get"] },
|
|
5282
|
+
// #6486: filing/managing an issue reads as devops work (it ships something) but issues are oracle's
|
|
5283
|
+
// live truth; the real command is `oracle issue create`. (A narrower house-token-only reroute for
|
|
5284
|
+
// this same case may also exist elsewhere — this entry keeps the general table a complete answer on
|
|
5285
|
+
// its own, not dependent on that mechanism landing.)
|
|
5286
|
+
{ typed: ["devops", "issue", "create"], canonical: ["oracle", "issue", "create"] }
|
|
5287
|
+
];
|
|
5288
|
+
var TABLE_BY_LENGTH = [...VERB_ALIAS_TABLE].sort((a, b) => b.typed.length - a.typed.length);
|
|
5289
|
+
function matchesAt(argv, start, tokens2) {
|
|
5290
|
+
if (start + tokens2.length > argv.length) return false;
|
|
5291
|
+
return tokens2.every((tok, i) => argv[start + i] === tok);
|
|
5292
|
+
}
|
|
5293
|
+
function applyVerbAlias(argv) {
|
|
5294
|
+
const entry = TABLE_BY_LENGTH.find((e) => matchesAt(argv, 2, e.typed));
|
|
5295
|
+
if (!entry) return void 0;
|
|
5296
|
+
argv.splice(2, entry.typed.length, ...entry.canonical);
|
|
5297
|
+
return { typed: entry.typed.join(" "), canonical: entry.canonical.join(" ") };
|
|
5298
|
+
}
|
|
5299
|
+
function resolveVerbAliasShim(argv) {
|
|
5300
|
+
const hit = applyVerbAlias(argv);
|
|
5301
|
+
if (!hit) return;
|
|
5302
|
+
process.stderr.write(`mmi-cli: '${hit.typed}' is '${hit.canonical}' \u2014 ran it for you
|
|
5303
|
+
`);
|
|
5304
|
+
}
|
|
5305
|
+
|
|
5259
5306
|
// src/command-composition.ts
|
|
5260
5307
|
var import_node_os22 = require("node:os");
|
|
5261
5308
|
var import_node_path47 = require("node:path");
|
|
@@ -7195,6 +7242,50 @@ async function readRepoSurfaceLabels(repo, deps = {}) {
|
|
|
7195
7242
|
return void 0;
|
|
7196
7243
|
}
|
|
7197
7244
|
}
|
|
7245
|
+
function surfaceWords(label) {
|
|
7246
|
+
return label.slice(SURFACE_PREFIX.length).toLowerCase().split(/[-_]+/).filter(Boolean);
|
|
7247
|
+
}
|
|
7248
|
+
function pathWords(text) {
|
|
7249
|
+
const words = [];
|
|
7250
|
+
for (const hit of text.match(/[\w.-]+(?:\/[\w.-]+)+/g) ?? []) {
|
|
7251
|
+
for (const segment of hit.split("/")) words.push(...segment.toLowerCase().split(/[-_.]+/).filter(Boolean));
|
|
7252
|
+
}
|
|
7253
|
+
return words;
|
|
7254
|
+
}
|
|
7255
|
+
function proseWords(text) {
|
|
7256
|
+
return text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
7257
|
+
}
|
|
7258
|
+
function inferSurface(candidates, context = {}) {
|
|
7259
|
+
const sorted = [...candidates].sort();
|
|
7260
|
+
if (sorted.length === 0) throw new Error("inferSurface: no surface:* candidates to choose from");
|
|
7261
|
+
const pWords = new Set(pathWords(context.body ?? ""));
|
|
7262
|
+
const tWords = /* @__PURE__ */ new Set([...proseWords(context.title ?? ""), ...proseWords(context.body ?? "")]);
|
|
7263
|
+
let best = sorted[0];
|
|
7264
|
+
let bestScore = 0;
|
|
7265
|
+
let bestFromPath = false;
|
|
7266
|
+
for (const label of sorted) {
|
|
7267
|
+
const words = surfaceWords(label);
|
|
7268
|
+
if (!words.length) continue;
|
|
7269
|
+
const pathHits = words.filter((w) => pWords.has(w)).length;
|
|
7270
|
+
const proseHits = words.filter((w) => tWords.has(w)).length;
|
|
7271
|
+
const score = pathHits * 2 + proseHits;
|
|
7272
|
+
if (score > bestScore) {
|
|
7273
|
+
bestScore = score;
|
|
7274
|
+
best = label;
|
|
7275
|
+
bestFromPath = pathHits > 0;
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
if (bestScore === 0) {
|
|
7279
|
+
return {
|
|
7280
|
+
label: sorted[0],
|
|
7281
|
+
reason: `no path or title/body word matched any of ${sorted.length} surface label(s) \u2014 defaulted to the alphabetically-first`
|
|
7282
|
+
};
|
|
7283
|
+
}
|
|
7284
|
+
return {
|
|
7285
|
+
label: best,
|
|
7286
|
+
reason: bestFromPath ? "matched a path mentioned in the issue body" : "matched a word in the issue title/body"
|
|
7287
|
+
};
|
|
7288
|
+
}
|
|
7198
7289
|
async function checkSurfaceRequirement(input, deps = {}) {
|
|
7199
7290
|
if (input.waiver) {
|
|
7200
7291
|
const reason = input.waiver.reason.trim();
|
|
@@ -7226,20 +7317,7 @@ async function checkSurfaceRequirement(input, deps = {}) {
|
|
|
7226
7317
|
}
|
|
7227
7318
|
if (known.length === 0) return { enforcing: false, taxonomyAbsent: true };
|
|
7228
7319
|
if (labelsCarrySurface(input.labels)) return { enforcing: true };
|
|
7229
|
-
|
|
7230
|
-
const where = input.rowLabel ? `${input.rowLabel}: ` : "";
|
|
7231
|
-
return {
|
|
7232
|
-
enforcing: true,
|
|
7233
|
-
refusal: {
|
|
7234
|
-
message: `${command}: ${where}${input.repo} requires every open issue to carry exactly one surface:* label, and this one sets none \u2014 filing it unlabeled reds the board check on every open PR in that repo. Pass --surface <value>, or --no-surface if this filing is genuinely exempt`,
|
|
7235
|
-
payload: {
|
|
7236
|
-
code: ERROR_CODES.ERR_MISSING_FLAG,
|
|
7237
|
-
offending_flag: "--surface",
|
|
7238
|
-
// Advisory, not a closed enum: the board rule is "exactly one surface:* label", whatever its value.
|
|
7239
|
-
expected: [...known].sort()
|
|
7240
|
-
}
|
|
7241
|
-
}
|
|
7242
|
-
};
|
|
7320
|
+
return { enforcing: true, inferred: inferSurface(known, { title: input.title, body: input.body }) };
|
|
7243
7321
|
}
|
|
7244
7322
|
function conflictingSurfaceInputs(surfaceFlag, labels) {
|
|
7245
7323
|
if (!surfaceFlag) return void 0;
|
|
@@ -15617,10 +15695,10 @@ var rollout_plan_default = {
|
|
|
15617
15695
|
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)."
|
|
15618
15696
|
},
|
|
15619
15697
|
baseline: {
|
|
15620
|
-
version: "4.3.
|
|
15621
|
-
tag: "v4.3.
|
|
15622
|
-
commit: "
|
|
15623
|
-
npm: "@mutmutco/cli@4.3.
|
|
15698
|
+
version: "4.3.39",
|
|
15699
|
+
tag: "v4.3.39",
|
|
15700
|
+
commit: "cc67ff24bbec",
|
|
15701
|
+
npm: "@mutmutco/cli@4.3.39"
|
|
15624
15702
|
},
|
|
15625
15703
|
exitCriterion: "fleet-n-of-n",
|
|
15626
15704
|
hubOnlyShortcut: "forbidden",
|
|
@@ -15637,14 +15715,14 @@ var rollout_plan_default = {
|
|
|
15637
15715
|
repo: "mutmutco/mmi-hub",
|
|
15638
15716
|
role: "canary",
|
|
15639
15717
|
schedule: "train",
|
|
15640
|
-
v3Target: "v4.3.
|
|
15718
|
+
v3Target: "v4.3.39"
|
|
15641
15719
|
}
|
|
15642
15720
|
],
|
|
15643
15721
|
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.",
|
|
15644
15722
|
rollback: {
|
|
15645
15723
|
independent: true,
|
|
15646
|
-
mechanism: "npm dist-tag latest -> 4.3.
|
|
15647
|
-
v3Target: "v4.3.
|
|
15724
|
+
mechanism: "npm dist-tag latest -> 4.3.39 and redeploy the Hub Lambda from tag v4.3.39 (cc67ff24bbec); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
15725
|
+
v3Target: "v4.3.39 (@mutmutco/cli@4.3.39, tag commit cc67ff24bbec \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
15648
15726
|
}
|
|
15649
15727
|
},
|
|
15650
15728
|
{
|
|
@@ -21645,13 +21723,9 @@ async function runTrainDoctor(input) {
|
|
|
21645
21723
|
add(linked ? { code: "worktree-isolated", severity: "blocker", source: "local", title: `this is a linked worktree on ${currentBranch2 || "(detached)"}, not the lane's start branch ${startBranch} (#2770)`, remedy: `run the ${lane} lane from the primary checkout on ${startBranch}; a linked worktree never carries the train` } : { code: "branch-mismatch", severity: "blocker", source: "local", title: `on ${currentBranch2 || "(detached)"}; the ${lane} lane starts from ${startBranch}`, remedy: `git checkout ${startBranch}, then rerun; --heal never switches branches \u2014 the branch you stand on names the lane you meant, and healing it would run the train off a branch you did not pick (#6432)` });
|
|
21646
21724
|
}
|
|
21647
21725
|
const stage = laneStage(lane);
|
|
21648
|
-
|
|
21649
|
-
const workflows = deps.readWorkflows(cwd, workflowsRef);
|
|
21650
|
-
if (workflows === null) {
|
|
21651
|
-
add({ code: "workflows-unreadable", severity: "blocker", source: "local", title: `.github/workflows at ${workflowsRef} could not be read from ${cwd} \u2014 the tag-addressability (#5428) and npm-major (#5666) preflights did not run`, remedy: `run from a checkout whose git can read ${workflowsRef} (fetch origin first), then rerun; an unread workflow set is unverified, not green` });
|
|
21652
|
-
}
|
|
21726
|
+
let missing = [];
|
|
21653
21727
|
if (hints) {
|
|
21654
|
-
|
|
21728
|
+
missing = [
|
|
21655
21729
|
...!hints.hasDevelopmentBranch && lane !== "hotfix" ? ["development"] : [],
|
|
21656
21730
|
...!hints.hasMainBranch ? ["main"] : [],
|
|
21657
21731
|
// #6318: a `--dev` release never reads, merges or tags rc — a missing rc branch cannot block it.
|
|
@@ -21659,30 +21733,38 @@ async function runTrainDoctor(input) {
|
|
|
21659
21733
|
];
|
|
21660
21734
|
if (missing.length) add({ code: "bootstrap-gap", severity: "blocker", source: "origin", title: `train branch(es) missing on origin: ${missing.join(", ")}`, remedy: `bootstrap the repo train: \`mmi-cli devops bootstrap apply ${repo} --execute\` (from the MMI-Hub root), then rerun` });
|
|
21661
21735
|
}
|
|
21662
|
-
|
|
21663
|
-
|
|
21664
|
-
|
|
21665
|
-
|
|
21666
|
-
}
|
|
21667
|
-
|
|
21668
|
-
|
|
21669
|
-
|
|
21670
|
-
|
|
21671
|
-
|
|
21672
|
-
|
|
21673
|
-
|
|
21674
|
-
|
|
21675
|
-
|
|
21676
|
-
|
|
21677
|
-
|
|
21678
|
-
|
|
21679
|
-
|
|
21680
|
-
|
|
21681
|
-
|
|
21736
|
+
const startBranchMissing = startBranch != null && missing.includes(startBranch);
|
|
21737
|
+
const workflowsRef = `origin/${startBranch ?? "main"}`;
|
|
21738
|
+
const workflows = startBranchMissing ? null : deps.readWorkflows(cwd, workflowsRef);
|
|
21739
|
+
if (workflows === null && !startBranchMissing) {
|
|
21740
|
+
add({ code: "workflows-unreadable", severity: "blocker", source: "local", title: `.github/workflows at ${workflowsRef} could not be read from ${cwd} \u2014 the tag-addressability (#5428) and npm-major (#5666) preflights did not run`, remedy: `run from a checkout whose git can read ${workflowsRef} (fetch origin first), then rerun; an unread workflow set is unverified, not green` });
|
|
21741
|
+
}
|
|
21742
|
+
if (!startBranchMissing) {
|
|
21743
|
+
try {
|
|
21744
|
+
const required = await discoverRequiredCheckContexts(train, ctx, stage);
|
|
21745
|
+
if (required.length === 0) {
|
|
21746
|
+
add({ code: "bootstrap-gap", severity: "warning", source: "origin", title: `no ruleset requires a status check on ${stage} \u2014 the train tags without a check wait (the GitHub push gate is the backstop)`, remedy: `activate the product ruleset: \`mmi-cli devops bootstrap apply ${repo} --execute\` (from the MMI-Hub root), or \`mmi-cli devops ci audit --repo ${repo}\`` });
|
|
21747
|
+
} else {
|
|
21748
|
+
try {
|
|
21749
|
+
assertTagAddressableRequiredContexts({ readWorkflows: () => workflows }, required, repo);
|
|
21750
|
+
} catch (e) {
|
|
21751
|
+
add({ code: "contexts-not-tag-addressable", severity: "blocker", source: "local", title: `required ${stage} check(s) cannot materialize on a tag push (#5428)`, remedy: message(e) });
|
|
21752
|
+
}
|
|
21753
|
+
if (startBranch) {
|
|
21754
|
+
const tip = await git3(["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${startBranch}`]);
|
|
21755
|
+
const checkRuns = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/check-runs`, "--jq", TRAIN_CHECK_RUNS_JQ]));
|
|
21756
|
+
const statuses = JSON.parse(await train.run("gh", ["api", `repos/${repo}/commits/${tip}/status`, "--jq", TRAIN_COMMIT_STATUS_JQ]));
|
|
21757
|
+
const observed = required.filter((c) => checkRuns.some((r) => r.name === c) || statuses.some((s) => s.context === c));
|
|
21758
|
+
const red = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "failed");
|
|
21759
|
+
const inFlight = observed.filter((c) => resolveContextState(c, checkRuns, statuses) === "pending");
|
|
21760
|
+
const at = `${startBranch}@${tip.slice(0, 12)}`;
|
|
21761
|
+
if (red.length) add({ code: "required-check-red", severity: "blocker", source: "origin", title: `required ${stage} check(s) failed on the lane's start branch tip: ${red.join(", ")} at ${at}`, remedy: `--heal never clears this one \u2014 a red trunk needs a fix, not a heal: land the failure's fix on ${startBranch} through a CI-gated PR, then rerun once its run is green (#6461)` });
|
|
21762
|
+
else if (inFlight.length) add({ code: "required-check-red", severity: "warning", source: "origin", title: `required ${stage} check(s) still in flight on the lane's start branch tip: ${inFlight.join(", ")} at ${at}`, remedy: `wait for the run to conclude and rerun \u2014 the tip's verdict is not known yet, and the train would tag before it is` });
|
|
21763
|
+
}
|
|
21682
21764
|
}
|
|
21765
|
+
} catch (e) {
|
|
21766
|
+
unverified(`required status checks on ${stage}`, e);
|
|
21683
21767
|
}
|
|
21684
|
-
} catch (e) {
|
|
21685
|
-
unverified(`required status checks on ${stage}`, e);
|
|
21686
21768
|
}
|
|
21687
21769
|
let ledgerTag;
|
|
21688
21770
|
let ledgerPath;
|
|
@@ -24606,7 +24688,7 @@ function ciReconcileExitFailed(input) {
|
|
|
24606
24688
|
}
|
|
24607
24689
|
return input.applyResults.some((result) => result.errors.length > 0 || result.postApply == null || result.postApply.state !== "clean" && result.postApply.state !== "repaired");
|
|
24608
24690
|
}
|
|
24609
|
-
async function authoritativeSafetyHold(authority, deps, repo, baseBranch) {
|
|
24691
|
+
async function authoritativeSafetyHold(authority, deps, repo, baseBranch, meta) {
|
|
24610
24692
|
if (authority.coversReleaseBranches) {
|
|
24611
24693
|
const prOnly = authority.contexts.filter((context) => TRAIN_PR_ONLY_CONTEXTS.has(context));
|
|
24612
24694
|
if (prOnly.length > 0) {
|
|
@@ -24620,6 +24702,22 @@ async function authoritativeSafetyHold(authority, deps, repo, baseBranch) {
|
|
|
24620
24702
|
if (missing.length > 0) {
|
|
24621
24703
|
return `${authority.source} requires context(s) with no pull_request workflow emitter [${missing.join(", ")}]; no source or live mutation was made`;
|
|
24622
24704
|
}
|
|
24705
|
+
if (authority.coversReleaseBranches) {
|
|
24706
|
+
const track = resolveReleaseTrack(meta, void 0, repo);
|
|
24707
|
+
const releaseBranches = branchesForTrack(track).filter((b) => b !== baseBranch);
|
|
24708
|
+
const ignore = /* @__PURE__ */ new Set([...AGENT_PR_BOOKKEEPING_CONTEXTS, ...TRAIN_PR_ONLY_CONTEXTS]);
|
|
24709
|
+
const requiredForReleaseBranches = authority.contexts.filter((context) => !ignore.has(context));
|
|
24710
|
+
for (const branch of releaseBranches) {
|
|
24711
|
+
if (await branchPresence(deps, repo, branch) !== true) continue;
|
|
24712
|
+
const emittedThere = await resolveEmittedPrContexts(deps, repo, branch);
|
|
24713
|
+
if (emittedThere === void 0) continue;
|
|
24714
|
+
const emittedThereSet = new Set(emittedThere);
|
|
24715
|
+
const unreachable = requiredForReleaseBranches.filter((context) => !emittedThereSet.has(context));
|
|
24716
|
+
if (unreachable.length > 0) {
|
|
24717
|
+
return `${authority.source} requires context(s) [${unreachable.join(", ")}] on ${branch}, but ${branch}'s own workflows emit none of them \u2014 a base-${branch} PR (e.g. a hotfix) could never satisfy the gate; no source or live mutation was made. Remove [${unreachable.join(", ")}] from ${branch} requirements, or add the emitting workflow to ${branch}`;
|
|
24718
|
+
}
|
|
24719
|
+
}
|
|
24720
|
+
}
|
|
24623
24721
|
return null;
|
|
24624
24722
|
}
|
|
24625
24723
|
async function applyCiReconcileRepo(repo, deps) {
|
|
@@ -24636,7 +24734,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24636
24734
|
empty.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
24637
24735
|
return finalizeCiReconcile(repo, deps, empty, report);
|
|
24638
24736
|
}
|
|
24639
|
-
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch);
|
|
24737
|
+
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
24640
24738
|
if (hold) {
|
|
24641
24739
|
empty.skipped.push(hold);
|
|
24642
24740
|
return finalizeCiReconcile(repo, deps, empty, report, hold);
|
|
@@ -24644,7 +24742,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24644
24742
|
} else if (report.class === "deployable" && !report.explicitNoCi) {
|
|
24645
24743
|
const registryAuthority = registryAuthorityWithoutReference(meta, repo);
|
|
24646
24744
|
if (registryAuthority) {
|
|
24647
|
-
const hold = await authoritativeSafetyHold(registryAuthority, deps, repo, baseBranch);
|
|
24745
|
+
const hold = await authoritativeSafetyHold(registryAuthority, deps, repo, baseBranch, meta);
|
|
24648
24746
|
if (hold) {
|
|
24649
24747
|
empty.skipped.push(hold);
|
|
24650
24748
|
return finalizeCiReconcile(repo, deps, empty, report, hold);
|
|
@@ -24673,7 +24771,7 @@ async function applyCiReconcileRepo(repo, deps) {
|
|
|
24673
24771
|
result.errors.push(`${PRODUCT_RULESET_REF} is not parseable: ${e.message}`);
|
|
24674
24772
|
return finalizeCiReconcile(repo, deps, result, report);
|
|
24675
24773
|
}
|
|
24676
|
-
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch);
|
|
24774
|
+
const hold = await authoritativeSafetyHold(authority, deps, repo, baseBranch, meta);
|
|
24677
24775
|
if (hold) {
|
|
24678
24776
|
result.skipped.push(hold);
|
|
24679
24777
|
return finalizeCiReconcile(repo, deps, result, report, hold);
|
|
@@ -29253,8 +29351,11 @@ function parseOauthVar(raw) {
|
|
|
29253
29351
|
const out = {};
|
|
29254
29352
|
for (const [key, value] of Object.entries(map)) {
|
|
29255
29353
|
if (key === "subdomains" || key === "domains") {
|
|
29256
|
-
|
|
29257
|
-
|
|
29354
|
+
const allowEmpty = key === "subdomains";
|
|
29355
|
+
if (!Array.isArray(value) || value.some((v) => typeof v !== "string" || v.trim() === "" && !(allowEmpty && v === ""))) {
|
|
29356
|
+
throw new Error(
|
|
29357
|
+
allowEmpty ? `org project set: oauth.${key} must be an array of strings ("" selects the apex \u2014 the domain itself)` : `org project set: oauth.${key} must be an array of non-empty strings`
|
|
29358
|
+
);
|
|
29258
29359
|
}
|
|
29259
29360
|
out[key] = value.map((v) => v.trim());
|
|
29260
29361
|
} else if (key === "callbackPath") {
|
|
@@ -33492,18 +33593,33 @@ async function preflightBatchSurfaces(validated, rowRepo, options) {
|
|
|
33492
33593
|
}
|
|
33493
33594
|
if (options.noSurface) return [];
|
|
33494
33595
|
const errors = [];
|
|
33495
|
-
const
|
|
33596
|
+
const censusCache = /* @__PURE__ */ new Map();
|
|
33597
|
+
const censusFor = (repo) => {
|
|
33598
|
+
let pending = censusCache.get(repo);
|
|
33599
|
+
if (!pending) {
|
|
33600
|
+
pending = readRepoSurfaceLabels(repo);
|
|
33601
|
+
censusCache.set(repo, pending);
|
|
33602
|
+
}
|
|
33603
|
+
return pending;
|
|
33604
|
+
};
|
|
33496
33605
|
for (const { row, spec } of validated) {
|
|
33606
|
+
if (labelsCarrySurface(spec.labels)) continue;
|
|
33497
33607
|
const repo = rowRepo(spec);
|
|
33498
|
-
const
|
|
33499
|
-
|
|
33500
|
-
|
|
33501
|
-
|
|
33502
|
-
|
|
33503
|
-
|
|
33504
|
-
|
|
33608
|
+
const known = await censusFor(repo);
|
|
33609
|
+
if (known === void 0) {
|
|
33610
|
+
process.stderr.write(
|
|
33611
|
+
`warning: could not read ${repo}'s labels, so row ${row}'s surface-label requirement was not checked; if that board enforces one surface:* label per open issue, add one with \`mmi-cli oracle issue edit <n> --add-label surface:<value>\`
|
|
33612
|
+
`
|
|
33613
|
+
);
|
|
33614
|
+
continue;
|
|
33505
33615
|
}
|
|
33506
|
-
if (
|
|
33616
|
+
if (known.length === 0) continue;
|
|
33617
|
+
const inferred = inferSurface(known, { title: spec.title, body: spec.body });
|
|
33618
|
+
spec.labels = [...spec.labels ?? [], inferred.label];
|
|
33619
|
+
process.stderr.write(
|
|
33620
|
+
`mmi-cli: no --surface given \u2014 row ${row} filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
|
|
33621
|
+
`
|
|
33622
|
+
);
|
|
33507
33623
|
}
|
|
33508
33624
|
return errors;
|
|
33509
33625
|
}
|
|
@@ -35597,8 +35713,8 @@ function registerSecretsCommands(program3) {
|
|
|
35597
35713
|
const secrets = program3.command("secrets").description("project vault \u2014 project-admins self-serve their own repo's full tree (stageless + dev/rc/main); org-infra namespaces are master-gated");
|
|
35598
35714
|
secrets.command("where").description("print where this repo's secrets live \u2014 the two-tier vault layout + well-known keys (no values)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsWhere(d, o)));
|
|
35599
35715
|
secrets.command("list").description("list secret NAMES + tier for THIS repo (never values). To locate a key you cannot place \u2014 including org-infra (_org) \u2014 use the cross-slug view: `mmi-cli oracle org access capabilities` (#3436)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsList(d, o)));
|
|
35600
|
-
secrets.command("find <intent
|
|
35601
|
-
if (!await secretsFind(d,
|
|
35716
|
+
secrets.command("find <intent...>").description("resolve a plain-language intent to canonical secret names + the exact keyless-use command (master-only, #2244); an unquoted multi-word intent is joined with spaces (#6495)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((intentWords, o) => withSecrets(async (d) => {
|
|
35717
|
+
if (!await secretsFind(d, intentWords.join(" "), o)) process.exitCode = 1;
|
|
35602
35718
|
}));
|
|
35603
35719
|
secrets.command("catalog").description("the secret catalog \u2014 grouped, with each secret's exact keyless-use command (master-only, #2244)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--drift", "include the bidirectional reconciler drift report").option("--json", "machine-readable output").action((o) => withSecrets((d) => secretsCatalog(d, o)));
|
|
35604
35720
|
secrets.command("doctor").description("vault drift report \u2014 declared-missing / orphan / off-scheme / duplicate (master-only, #2244)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((o) => withSecrets(async (d) => {
|
|
@@ -37080,8 +37196,20 @@ function matchedMandatoryGlobs(paths, mandatory) {
|
|
|
37080
37196
|
return list.some((path2) => re.test(path2));
|
|
37081
37197
|
});
|
|
37082
37198
|
}
|
|
37083
|
-
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null, addedPaths = [] } = {}) {
|
|
37199
|
+
function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, override = null, addedPaths = [], structuralRefusal = false } = {}) {
|
|
37084
37200
|
const configuredMandatoryCount = mandatoryGlobList(mandatory).length;
|
|
37201
|
+
if (structuralRefusal) {
|
|
37202
|
+
return {
|
|
37203
|
+
configuredMandatoryCount,
|
|
37204
|
+
matchedMandatoryGlobs: [],
|
|
37205
|
+
matchedMandatoryCount: 0,
|
|
37206
|
+
testCommandsAllowed: false,
|
|
37207
|
+
editsExistingTest: false,
|
|
37208
|
+
reasonId: "test-command-outside-mandatory-zone",
|
|
37209
|
+
testCommandNote: null,
|
|
37210
|
+
commandClasses: { allowed: [], refused: [TEST_COMMAND_CLASS] }
|
|
37211
|
+
};
|
|
37212
|
+
}
|
|
37085
37213
|
if (!regulated) {
|
|
37086
37214
|
return {
|
|
37087
37215
|
configuredMandatoryCount: 0,
|
|
@@ -37090,25 +37218,30 @@ function evaluateTestCommandPolicy({ paths, mandatory, regulated = true, overrid
|
|
|
37090
37218
|
testCommandsAllowed: true,
|
|
37091
37219
|
editsExistingTest: false,
|
|
37092
37220
|
reasonId: null,
|
|
37221
|
+
testCommandNote: null,
|
|
37093
37222
|
commandClasses: { allowed: [TEST_COMMAND_CLASS], refused: [] }
|
|
37094
37223
|
};
|
|
37095
37224
|
}
|
|
37096
37225
|
const matched = matchedMandatoryGlobs(paths, mandatory);
|
|
37097
37226
|
const overrideAuthorizes = Array.isArray(override?.kinds) && override.kinds.includes(TEST_WORK_KIND);
|
|
37098
37227
|
const existingTestEdited = editsExistingTest(paths, addedPaths);
|
|
37099
|
-
const
|
|
37228
|
+
const inMandatoryZone = matched.length > 0 || overrideAuthorizes || existingTestEdited;
|
|
37229
|
+
const testCommandsAllowed = true;
|
|
37100
37230
|
return {
|
|
37101
37231
|
configuredMandatoryCount,
|
|
37102
37232
|
matchedMandatoryGlobs: matched,
|
|
37103
37233
|
matchedMandatoryCount: matched.length,
|
|
37104
37234
|
testCommandsAllowed,
|
|
37105
|
-
/** #5842: true when the diff edits a test file that already existed — one of the facts that
|
|
37106
|
-
* authorize the class,
|
|
37235
|
+
/** #5842: true when the diff edits a test file that already existed — one of the facts that used
|
|
37236
|
+
* to authorize the class, kept for callers that still branch on it. */
|
|
37107
37237
|
editsExistingTest: existingTestEdited,
|
|
37108
|
-
reasonId:
|
|
37238
|
+
reasonId: null,
|
|
37239
|
+
/** Informational only (#1166): set when the diff is outside the mandatory zone, so a caller can
|
|
37240
|
+
* note that CI does not require a test here — never a reason to refuse the run. */
|
|
37241
|
+
testCommandNote: inMandatoryZone ? null : "test-command-outside-mandatory-zone",
|
|
37109
37242
|
commandClasses: {
|
|
37110
|
-
allowed:
|
|
37111
|
-
refused:
|
|
37243
|
+
allowed: [TEST_COMMAND_CLASS],
|
|
37244
|
+
refused: []
|
|
37112
37245
|
}
|
|
37113
37246
|
};
|
|
37114
37247
|
}
|
|
@@ -37778,7 +37911,12 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37778
37911
|
addedPaths: changed.filter((f) => f.status === "A" || f.status === "R").map((f) => f.path),
|
|
37779
37912
|
mandatory: policy.mandatory,
|
|
37780
37913
|
regulated: policy.declared !== false || policyRefusals.length > 0,
|
|
37781
|
-
override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null
|
|
37914
|
+
override: override && lookup.refusals.length === 0 ? { kinds: override.kinds } : null,
|
|
37915
|
+
// #1166: a still-blocking pre-diff finding (untrusted range, unresolvable base, a missing policy
|
|
37916
|
+
// on the hotfix lane, a malformed trailer, an unwaived stale entry) means the diff itself was
|
|
37917
|
+
// never evaluated — the mandatory-zone question this evaluator now answers permissively was
|
|
37918
|
+
// never reached, so the refusal stands exactly as before.
|
|
37919
|
+
structuralRefusal: blocking.length > 0
|
|
37782
37920
|
});
|
|
37783
37921
|
const result = {
|
|
37784
37922
|
ok: findings.length === 0,
|
|
@@ -37792,6 +37930,7 @@ function runTestPolicy(root, deps = {}) {
|
|
|
37792
37930
|
matchedMandatoryCount: commandPolicy.matchedMandatoryCount,
|
|
37793
37931
|
testCommandsAllowed: commandPolicy.testCommandsAllowed,
|
|
37794
37932
|
testCommandReasonId: commandPolicy.reasonId,
|
|
37933
|
+
testCommandNote: commandPolicy.testCommandNote,
|
|
37795
37934
|
commandClasses: commandPolicy.commandClasses
|
|
37796
37935
|
};
|
|
37797
37936
|
if (override) {
|
|
@@ -39808,7 +39947,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39808
39947
|
}
|
|
39809
39948
|
const issue = program3.command("issue").description("issues \u2014 create and view with structured JSON (view; show, get and read alias view for board-verb/gh-shaped callers). Claims are board mutations: use `oracle board claim`, never `oracle issue claim` or the flat `board claim`");
|
|
39810
39949
|
withExamples(mutating(
|
|
39811
|
-
issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789).
|
|
39950
|
+
issue.command("create").description("create an issue (type \u2014 label) and print {number,url,label} JSON").addOption(new Option("--type <type>", "bug | feature | task (sets the matching label; required unless --batch)").choices([...ISSUE_TYPES])).option("--title <title>", "issue title").option("--title-file <path|->", "read the issue title from a UTF-8 file, or from stdin with -").option("--body <body>", "issue body (markdown)").option("--body-file <path|->", "read issue body from a UTF-8 file. `-` (stdin) needs a heredoc, which the agent inline-body guard denies (#1473/#2125) \u2014 prefer a real path; a title with backticks needs --title-file for the same reason (#3381)").option("--priority <priority>", "urgent | high | medium | low (defaults to medium; sets the board Priority field only \u2014 never a priority:* label, #416)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--label <label...>", "extra label(s) to attach (repeatable; auto-created if missing)").option("--surface <surface>", "issue surface, with or without the surface: prefix (#3789). Any value satisfies the one-surface-label board rule, so this is not a closed enum. Omit it on an enforcing repo and one is inferred from the title/body (#1164) \u2014 the pick is printed and included in --json as surface_inferred/surface/surface_reason").option("--no-surface", "file without a surface label on a repo that requires one \u2014 for a genuinely exempt filing (e.g. a coop proof issue that spans every surface). Suppresses inference too").option("--parent <ref>", "file as a native sub-issue of this parent (#123, owner/repo#123, or URL)").option("--no-related", "skip the auto related-issues comment"),
|
|
39812
39951
|
// --dry-run/--validate-only plan: resolve the same title source and validate the same type, priority,
|
|
39813
39952
|
// and surface contract as the real action. A plan that echoes the title-file PATH instead of its value
|
|
39814
39953
|
// is not a plan of the mutation that will run (#3914).
|
|
@@ -39832,14 +39971,24 @@ function registerCollaborationCommands(program3) {
|
|
|
39832
39971
|
if (clash) fail(clash.message, clash.payload);
|
|
39833
39972
|
const planRepo = opts.batch || surfaceWaived() ? void 0 : await resolveRepo(opts.repo);
|
|
39834
39973
|
const surface = resolveCreateSurface(opts);
|
|
39974
|
+
let planInferred;
|
|
39835
39975
|
if (planRepo) {
|
|
39836
|
-
const { refusal, warn } = await checkSurfaceRequirement({
|
|
39976
|
+
const { refusal, warn, inferred } = await checkSurfaceRequirement({
|
|
39837
39977
|
repo: planRepo,
|
|
39838
|
-
labels: [...planLabels, ...surface ? [surface] : []]
|
|
39978
|
+
labels: [...planLabels, ...surface ? [surface] : []],
|
|
39979
|
+
title,
|
|
39980
|
+
body: opts.body
|
|
39839
39981
|
});
|
|
39840
39982
|
if (warn) process.stderr.write(`${warn}
|
|
39841
39983
|
`);
|
|
39842
39984
|
if (refusal) fail(refusal.message, refusal.payload);
|
|
39985
|
+
if (inferred && !surface) {
|
|
39986
|
+
planInferred = inferred;
|
|
39987
|
+
process.stderr.write(
|
|
39988
|
+
`mmi-cli: no --surface given \u2014 filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
|
|
39989
|
+
`
|
|
39990
|
+
);
|
|
39991
|
+
}
|
|
39843
39992
|
}
|
|
39844
39993
|
return {
|
|
39845
39994
|
command: "issue create",
|
|
@@ -39847,7 +39996,8 @@ function registerCollaborationCommands(program3) {
|
|
|
39847
39996
|
title,
|
|
39848
39997
|
priority,
|
|
39849
39998
|
repo: opts.repo,
|
|
39850
|
-
...surface ? { surface } : {}
|
|
39999
|
+
...surface ? { surface } : {},
|
|
40000
|
+
...planInferred ? { surface_inferred: true, surface: planInferred.label, surface_reason: planInferred.reason } : {}
|
|
39851
40001
|
};
|
|
39852
40002
|
}
|
|
39853
40003
|
).action(async (o) => {
|
|
@@ -39859,6 +40009,7 @@ function registerCollaborationCommands(program3) {
|
|
|
39859
40009
|
let extraLabels = [];
|
|
39860
40010
|
let targetRepo2;
|
|
39861
40011
|
let surfaceFlagLabel;
|
|
40012
|
+
let surfaceInferred;
|
|
39862
40013
|
try {
|
|
39863
40014
|
issueType = resolveCreateType(o.type, "issue create", o.label);
|
|
39864
40015
|
title = await resolveIssueTitle({ title: o.title, titleFile: o.titleFile }, { readFile: import_promises7.readFile, readStdin });
|
|
@@ -39890,8 +40041,8 @@ function registerCollaborationCommands(program3) {
|
|
|
39890
40041
|
return fail(`issue create: ${e.message}`, e instanceof TextArgError ? { code: e.code, offending_flag: e.offendingFlag } : void 0);
|
|
39891
40042
|
}
|
|
39892
40043
|
{
|
|
39893
|
-
const surfaceCheck = await checkSurfaceRequirement({ repo: targetRepo2, labels: extraLabels });
|
|
39894
|
-
const { refusal, warn } = surfaceCheck;
|
|
40044
|
+
const surfaceCheck = await checkSurfaceRequirement({ repo: targetRepo2, labels: extraLabels, title, body });
|
|
40045
|
+
const { refusal, warn, inferred } = surfaceCheck;
|
|
39895
40046
|
if (warn) process.stderr.write(`${warn}
|
|
39896
40047
|
`);
|
|
39897
40048
|
if (shouldWithdrawSurfaceFlag(surfaceCheck) && surfaceFlagLabel) {
|
|
@@ -39906,6 +40057,22 @@ function registerCollaborationCommands(program3) {
|
|
|
39906
40057
|
});
|
|
39907
40058
|
process.stderr.write(
|
|
39908
40059
|
`warning: --surface ${surfaceFlagLabel} was dropped \u2014 ${targetRepo2} defines no surface:* labels, and creating one here would switch the one-surface-label rule on for every later filing in it. Use --label ${surfaceFlagLabel} if you really mean to start that taxonomy.
|
|
40060
|
+
`
|
|
40061
|
+
);
|
|
40062
|
+
}
|
|
40063
|
+
if (inferred && !surfaceFlagLabel && !surfaceWaived()) {
|
|
40064
|
+
extraLabels = [...extraLabels, inferred.label];
|
|
40065
|
+
args = buildIssueArgs({
|
|
40066
|
+
type: issueType,
|
|
40067
|
+
title,
|
|
40068
|
+
body,
|
|
40069
|
+
priority,
|
|
40070
|
+
repo: targetRepo2,
|
|
40071
|
+
labels: extraLabels.length ? extraLabels : void 0
|
|
40072
|
+
});
|
|
40073
|
+
surfaceInferred = inferred;
|
|
40074
|
+
process.stderr.write(
|
|
40075
|
+
`mmi-cli: no --surface given \u2014 filed under ${inferred.label} (${inferred.reason}). Pass --surface to choose.
|
|
39909
40076
|
`
|
|
39910
40077
|
);
|
|
39911
40078
|
}
|
|
@@ -39946,7 +40113,9 @@ function registerCollaborationCommands(program3) {
|
|
|
39946
40113
|
...attached.resetAt ? { resetAt: attached.resetAt } : {},
|
|
39947
40114
|
...attached.resetEpochSeconds !== void 0 ? { resetEpochSeconds: attached.resetEpochSeconds } : {}
|
|
39948
40115
|
} : {},
|
|
39949
|
-
...parentLinkFields(parent, parentLinkError)
|
|
40116
|
+
...parentLinkFields(parent, parentLinkError),
|
|
40117
|
+
// #1164: surfaced so a caller can see (and override with --surface) a pick it never asked for.
|
|
40118
|
+
...surfaceInferred ? { surface_inferred: true, surface: surfaceInferred.label, surface_reason: surfaceInferred.reason } : {}
|
|
39950
40119
|
}));
|
|
39951
40120
|
}), [
|
|
39952
40121
|
'mmi-cli oracle issue create --type task --title "Wire the schema"',
|
|
@@ -42091,7 +42260,7 @@ function registerDeveloperCommands(program3) {
|
|
|
42091
42260
|
}
|
|
42092
42261
|
});
|
|
42093
42262
|
const tests = program3.command("tests").description("a repo's test-policy.json \u2014 the opt-in test contract and its enforcement");
|
|
42094
|
-
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, policySource: { ref, sha, path }, root, changedCount, mandatoryCount, matchedMandatoryCount, matchedMandatoryGlobs, testCommandsAllowed, commandClasses, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").option("--policy-ref <commit>", "load test-policy.json from the exact fetched origin/development commit and audit its protected/satisfiedBy paths against that commit's tree, not this checkout; valid only with --base origin/main").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 tests policy reads the current checkout").hideHelp()).action(async (o) => {
|
|
42263
|
+
tests.command("policy").description("enforce this repo's test-policy.json against the diff: a mandatory-zone change must carry a test, an unrequested new test file is refused, a `protected` test file may not be deleted or renamed away, and a `protected` entry naming a missing file is refused. Override any of them with a `Test-Policy-Override: <reason>` commit trailer (#3605)").option("--json", "machine-readable result: { ok, base, policySource: { ref, sha, path }, root, changedCount, mandatoryCount, matchedMandatoryCount, matchedMandatoryGlobs, testCommandsAllowed, testCommandNote, commandClasses, findings[] }").option("--base <ref>", "comparison base (default: TEST_POLICY_BASE, then origin/development, then origin/main)").option("--policy-ref <commit>", "load test-policy.json from the exact fetched origin/development commit and audit its protected/satisfiedBy paths against that commit's tree, not this checkout; valid only with --base origin/main").addOption(new Option("--repo <owner/repo>", "not accepted \u2014 tests policy reads the current checkout").hideHelp()).action(async (o) => {
|
|
42095
42264
|
try {
|
|
42096
42265
|
if (o.repo) {
|
|
42097
42266
|
const rerun = `mmi-cli tests policy${o.base ? ` --base ${o.base}` : ""}${o.policyRef ? ` --policy-ref ${o.policyRef}` : ""}${o.json ? " --json" : ""}`;
|
|
@@ -42114,7 +42283,7 @@ function registerDeveloperCommands(program3) {
|
|
|
42114
42283
|
return;
|
|
42115
42284
|
}
|
|
42116
42285
|
if (result.ok) {
|
|
42117
|
-
const commandVerdict = result.testCommandsAllowed ? "test commands: allowed" : `test commands: refused [${result.testCommandReasonId}] \u2014 resolved repo: ${result.root}; if that is not the repo under test, start the command with a literal Set-Location "<your worktree>"`;
|
|
42286
|
+
const commandVerdict = result.testCommandsAllowed ? result.testCommandNote ? `test commands: allowed (informational [${result.testCommandNote}] \u2014 CI does not require a test here)` : "test commands: allowed" : `test commands: refused [${result.testCommandReasonId}] \u2014 resolved repo: ${result.root}; if that is not the repo under test, start the command with a literal Set-Location "<your worktree>"`;
|
|
42118
42287
|
console.log(
|
|
42119
42288
|
`tests policy: OK (${result.changedCount} changed file(s); ${result.matchedMandatoryCount} of ${result.mandatoryCount} mandatory glob(s) matched, ${result.protectedCount} protected file(s); ${commandVerdict}).`
|
|
42120
42289
|
);
|
|
@@ -45050,8 +45219,7 @@ project.command("sync-info [owner/repo]").description("synchronize the owning Gi
|
|
|
45050
45219
|
return failGraceful(e.message);
|
|
45051
45220
|
}
|
|
45052
45221
|
});
|
|
45053
|
-
|
|
45054
|
-
projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# facts for one project; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").choices(["dev", "rc", "main"])).option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
45222
|
+
async function runProjectDeployGet(repoOrSlug, o) {
|
|
45055
45223
|
const cfg = await loadConfig();
|
|
45056
45224
|
let target;
|
|
45057
45225
|
try {
|
|
@@ -45065,7 +45233,9 @@ projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# fa
|
|
|
45065
45233
|
if (stage && !["dev", "rc", "main"].includes(stage)) return fail("org project deploy get: --stage must be dev, rc, or main");
|
|
45066
45234
|
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
45067
45235
|
console.log(JSON.stringify(payload));
|
|
45068
|
-
}
|
|
45236
|
+
}
|
|
45237
|
+
var projectDeploy = project.command("deploy").description("read nonsecret DEPLOY# facts (domain, port, deploy path, substrate, host presence)").argument("[ownerRepo]", "shorthand for `get <owner/repo>`").addOption(new Option("--stage <stage>", "dev | rc | main").choices(["dev", "rc", "main"])).option("--json", "machine-readable output").action(async (ownerRepo, o) => runProjectDeployGet(ownerRepo, o));
|
|
45238
|
+
projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# facts for one project; defaults to the current repo").addOption(new Option("--stage <stage>", "dev | rc | main").choices(["dev", "rc", "main"])).option("--json", "machine-readable output").action(async (repoOrSlug, o) => runProjectDeployGet(repoOrSlug, o));
|
|
45069
45239
|
projectDeploy.command("doctor").description("read-only estate scan for duplicate Hetzner (sshHost, port) DEPLOY# coordinates (#5431); master-only; never auto-reassigns ports").option("--json", "machine-readable output").action(async (o) => {
|
|
45070
45240
|
const cfg = await loadConfig();
|
|
45071
45241
|
const report = await fetchDeployPortCollisions(registryClientDeps(cfg));
|
|
@@ -45313,11 +45483,13 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
45313
45483
|
oc = parseOauthConfig(meta ?? {}, slug);
|
|
45314
45484
|
} catch (e) {
|
|
45315
45485
|
const message2 = e.message;
|
|
45486
|
+
const declareCommand = `mmi-cli oracle org project set ${o.repo ?? `mutmutco/${slug}`} --var 'oauth={"subdomains":["${defaultSubdomain(slug)}"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}'`;
|
|
45316
45487
|
if (/^oauth is not configured for /.test(message2)) {
|
|
45317
|
-
return
|
|
45488
|
+
return failGracefulEnvelope(
|
|
45318
45489
|
`org oauth plan: ${message2}. Declare it in the registry META first:
|
|
45319
|
-
|
|
45320
|
-
New repo? The GCP project and its Google Auth Platform consent screen must exist too \u2014 docs/Guides/oauth-provision.md \u2014 New repo
|
|
45490
|
+
${declareCommand}
|
|
45491
|
+
New repo? The GCP project and its Google Auth Platform consent screen must exist too \u2014 docs/Guides/oauth-provision.md \u2014 New repo.`,
|
|
45492
|
+
{ code: ERROR_CODES.ERR_STATE_CONFLICT, corrected_command: declareCommand }
|
|
45321
45493
|
);
|
|
45322
45494
|
}
|
|
45323
45495
|
return failGraceful(`org oauth plan: ${message2}`);
|
|
@@ -45704,6 +45876,14 @@ function resolveHouseShim(argv) {
|
|
|
45704
45876
|
argv.splice(2, 1);
|
|
45705
45877
|
return;
|
|
45706
45878
|
}
|
|
45879
|
+
if (actual && isDeclaredHouseAlias(houseToken, lookupPath)) {
|
|
45880
|
+
process.stderr.write(
|
|
45881
|
+
`mmi-cli: '${lookupPath}' lives in house '${actual}', not '${houseToken}' \u2014 routed to \`mmi-cli ${canonicalPathFor(lookupPath)}\`
|
|
45882
|
+
`
|
|
45883
|
+
);
|
|
45884
|
+
argv.splice(2, 1);
|
|
45885
|
+
return;
|
|
45886
|
+
}
|
|
45707
45887
|
if (actual) {
|
|
45708
45888
|
refuseHouseShim(
|
|
45709
45889
|
lookupPath,
|
|
@@ -45732,6 +45912,7 @@ function printHouseRootHelp(house) {
|
|
|
45732
45912
|
);
|
|
45733
45913
|
consoleIo.log(lines2.join("\n"));
|
|
45734
45914
|
}
|
|
45915
|
+
resolveVerbAliasShim(process.argv);
|
|
45735
45916
|
resolveHouseShim(process.argv);
|
|
45736
45917
|
program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGraceful(e.message));
|
|
45737
45918
|
// Annotate the CommonJS export names for ESM import in node:
|
package/package.json
CHANGED