@mutmutco/cli 3.48.0 → 3.49.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 +124 -29
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -6754,11 +6754,17 @@ async function secretsVerify(deps, key, opts) {
|
|
|
6754
6754
|
deps.err(`invalid secret key ${JSON.stringify(key)}`);
|
|
6755
6755
|
return false;
|
|
6756
6756
|
}
|
|
6757
|
-
|
|
6758
|
-
deps.err(`no provider verifier configured for ${secretLeafName(key)}`);
|
|
6759
|
-
return false;
|
|
6760
|
-
}
|
|
6757
|
+
const hasProviderVerifier = Boolean(providerForSecretKey(key));
|
|
6761
6758
|
const value = await fetchSecretValue(deps, key, opts);
|
|
6759
|
+
if (!hasProviderVerifier) {
|
|
6760
|
+
if (!value) {
|
|
6761
|
+
deps.err(`${key}: ABSENT or unreachable at this coordinate \u2014 no value could be read`);
|
|
6762
|
+
deps.err(`Locate a key across every vault you can reach: mmi-cli org access capabilities${opts.repo ? ` --repo ${opts.repo}` : ""}`);
|
|
6763
|
+
return false;
|
|
6764
|
+
}
|
|
6765
|
+
deps.log(`${key}: PRESENT (${value.length} chars) \u2014 reachability only; no provider verifier is configured for ${secretLeafName(key)}, so the credential itself was not validated`);
|
|
6766
|
+
return true;
|
|
6767
|
+
}
|
|
6762
6768
|
if (!value) {
|
|
6763
6769
|
deps.err(`secrets verify: could not read ${key}; no value was printed`);
|
|
6764
6770
|
return false;
|
|
@@ -6903,6 +6909,11 @@ async function secretsDeclare(deps, key, opts) {
|
|
|
6903
6909
|
const body = await readJsonBody(res);
|
|
6904
6910
|
if (!res.ok) {
|
|
6905
6911
|
deps.err(await upgradeMessage(res, body) ?? `secrets declare failed: HTTP ${res.status}${errorDetail(body)}`);
|
|
6912
|
+
if (res.status === 404 && !errorDetail(body)) {
|
|
6913
|
+
deps.err(
|
|
6914
|
+
`The Hub did not recognize the declare route (no error body \u2014 an API-gateway 404, not an authority refusal). Fall back to the master-side catalog write: mmi-cli org project set ${repo} --secrets-file <catalog.json> (shape: keyed by env name; each entry {key,purpose,group,owner,stages[],consumers[]} \u2014 see docs/Guides/oauth-provision.md \xA7 Cred catalog shape).`
|
|
6915
|
+
);
|
|
6916
|
+
}
|
|
6906
6917
|
return false;
|
|
6907
6918
|
}
|
|
6908
6919
|
if (opts.json) {
|
|
@@ -7115,7 +7126,12 @@ async function secretsUse(deps, key, opts) {
|
|
|
7115
7126
|
" \u2022 Runtime: the central deploy injects the declared stage env; broker consumers use the workload's scoped runtime token. Never bake a value into an image or commit it.",
|
|
7116
7127
|
` \u2022 CI (GitHub Actions): the workflow assumes its OIDC role and runs \`aws ssm get-parameter --with-decryption --name ${path2}\` \u2014 no GitHub secret.`,
|
|
7117
7128
|
" \u2022 Own project: the stageless canonical and any declared dev/rc/main overrides are project-admin self-service.",
|
|
7118
|
-
" \u2022 Org-infra/cross-slug keys remain master-gated unless exactly granted."
|
|
7129
|
+
" \u2022 Org-infra/cross-slug keys remain master-gated unless exactly granted.",
|
|
7130
|
+
"",
|
|
7131
|
+
"Answer it without consuming it (#3436):",
|
|
7132
|
+
" \u2022 WHERE does a key live, across every vault you can reach: `mmi-cli org access capabilities` (names + tier + scope, never values). `secrets list` is this repo only and cannot see _org.",
|
|
7133
|
+
` \u2022 DOES it exist here: \`mmi-cli secrets verify ${key}${opts.slug ? ` --slug ${opts.slug}` : ""}\` \u2014 PRESENT/ABSENT even with no provider verifier configured.`,
|
|
7134
|
+
" \u2022 PowerShell note: `mmi-cli` resolves to npm's mmi-cli.ps1, whose binder eats the `--` separator. The wrapped command works either way; if a flag still misbinds, call `mmi-cli.cmd` explicitly."
|
|
7119
7135
|
].join("\n")
|
|
7120
7136
|
);
|
|
7121
7137
|
return;
|
|
@@ -9179,8 +9195,11 @@ async function waitForPrChecks(deps) {
|
|
|
9179
9195
|
failureStreak += 1;
|
|
9180
9196
|
if (failureStreak >= PR_CHECKS_FAILURE_CONFIRMATIONS) {
|
|
9181
9197
|
const diagnosis = deps.diagnoseFailure ? await deps.diagnoseFailure().catch(() => null) : null;
|
|
9182
|
-
if (diagnosis?.cause === "
|
|
9183
|
-
return { policy, status: "failure", reason: diagnosis.reason, detail: "
|
|
9198
|
+
if (diagnosis?.cause === "stale-head") {
|
|
9199
|
+
return { policy, status: "failure", reason: diagnosis.reason, detail: "stale-head" };
|
|
9200
|
+
}
|
|
9201
|
+
if (diagnosis?.cause === "runner-infra") {
|
|
9202
|
+
return { policy, status: "failure", reason: diagnosis.reason, detail: "runner-infra" };
|
|
9184
9203
|
}
|
|
9185
9204
|
return { policy, status: "failure", reason, detail: "checks-failure" };
|
|
9186
9205
|
}
|
|
@@ -11808,27 +11827,36 @@ function setCommandMetadata(command, metadata) {
|
|
|
11808
11827
|
function commandMetadata(command) {
|
|
11809
11828
|
return command[COMMAND_METADATA];
|
|
11810
11829
|
}
|
|
11811
|
-
|
|
11812
|
-
|
|
11830
|
+
var REQUIRED_OPTION_HELP = {
|
|
11831
|
+
optionDescription(option) {
|
|
11832
|
+
const described = Help.prototype.optionDescription.call(this, option);
|
|
11833
|
+
return option.mandatory ? `(required) ${described}` : described;
|
|
11834
|
+
}
|
|
11835
|
+
};
|
|
11836
|
+
function classifyTree(command, path2, inherited, hideFromParent) {
|
|
11837
|
+
const override = PATH_OVERRIDES[path2];
|
|
11838
|
+
const metadata = override ?? inherited;
|
|
11813
11839
|
setCommandMetadata(command, metadata);
|
|
11814
|
-
if (metadata.discovery !== "primary") {
|
|
11840
|
+
if ((override !== void 0 || hideFromParent) && metadata.discovery !== "primary") {
|
|
11815
11841
|
command._hidden = true;
|
|
11816
11842
|
}
|
|
11817
11843
|
for (const option of command.options) {
|
|
11818
11844
|
if ((HIDDEN_OPTIONS[path2] ?? []).includes(option.long ?? option.short ?? "")) option.hideHelp();
|
|
11819
11845
|
}
|
|
11846
|
+
command.configureHelp(REQUIRED_OPTION_HELP);
|
|
11820
11847
|
for (const child2 of command.commands) {
|
|
11821
11848
|
const childPath = path2 ? `${path2} ${child2.name()}` : child2.name();
|
|
11822
|
-
classifyTree(child2, childPath,
|
|
11849
|
+
classifyTree(child2, childPath, metadata, false);
|
|
11823
11850
|
}
|
|
11824
11851
|
}
|
|
11825
11852
|
function applyCommandTaxonomy(program3) {
|
|
11826
11853
|
for (const command of program3.commands) {
|
|
11827
11854
|
const metadata = topLevelMetadata(command.name());
|
|
11828
11855
|
command.helpGroup(metadata.help_group);
|
|
11829
|
-
classifyTree(command, command.name(), metadata);
|
|
11856
|
+
classifyTree(command, command.name(), metadata, true);
|
|
11830
11857
|
}
|
|
11831
11858
|
program3.configureHelp({
|
|
11859
|
+
...REQUIRED_OPTION_HELP,
|
|
11832
11860
|
visibleCommands(command) {
|
|
11833
11861
|
const visible = command.commands.filter((child2) => !child2._hidden);
|
|
11834
11862
|
const helpCommand = command._getHelpCommand();
|
|
@@ -14506,6 +14534,24 @@ async function deriveHotfixVersion(deps) {
|
|
|
14506
14534
|
function hotfixBranch(tag) {
|
|
14507
14535
|
return `hotfix/${tag}`;
|
|
14508
14536
|
}
|
|
14537
|
+
async function restoreAfterFailedPort(deps, opts) {
|
|
14538
|
+
const { startBranch, branch, created } = opts;
|
|
14539
|
+
if (!startBranch || startBranch === "HEAD" || startBranch === branch) {
|
|
14540
|
+
return `Local state left as-is (could not determine the branch to return to).`;
|
|
14541
|
+
}
|
|
14542
|
+
try {
|
|
14543
|
+
await deps.run("git", ["checkout", startBranch]);
|
|
14544
|
+
} catch {
|
|
14545
|
+
return `Could NOT return to ${startBranch} \u2014 the checkout is still on ${branch} (origin/main content); switch back before editing anything.`;
|
|
14546
|
+
}
|
|
14547
|
+
if (!created) return `Returned to ${startBranch}; the pre-existing local ${branch} was left in place.`;
|
|
14548
|
+
try {
|
|
14549
|
+
await deps.run("git", ["branch", "-D", branch]);
|
|
14550
|
+
return `Returned to ${startBranch} and removed the local ${branch}.`;
|
|
14551
|
+
} catch {
|
|
14552
|
+
return `Returned to ${startBranch}; the local ${branch} could not be removed \u2014 delete it by hand.`;
|
|
14553
|
+
}
|
|
14554
|
+
}
|
|
14509
14555
|
async function resolveHotfixDeployModel(deps, ctx) {
|
|
14510
14556
|
const load = await loadProjectMeta(deps, ctx);
|
|
14511
14557
|
const meta = requireProjectMetaForTrain(load, ctx.repo);
|
|
@@ -14604,12 +14650,17 @@ async function runHotfixStart(deps, options) {
|
|
|
14604
14650
|
await deps.run("git", ["pull", "--ff-only", "origin", branch]);
|
|
14605
14651
|
notes.push(`branch ${branch} already on origin \u2014 reused (cherry-pick/bump assumed present; PR step resumes)`);
|
|
14606
14652
|
} else {
|
|
14653
|
+
const startBranch = clean3(await deps.run("git", ["rev-parse", "--abbrev-ref", "HEAD"]));
|
|
14654
|
+
const preexistingLocal = clean3(await deps.run("git", ["branch", "--list", branch]));
|
|
14607
14655
|
await deps.run("git", ["checkout", "-B", branch, "origin/main"]);
|
|
14608
14656
|
try {
|
|
14609
14657
|
await deps.run("git", ["cherry-pick", "-x", sha]);
|
|
14610
14658
|
} catch (e) {
|
|
14611
14659
|
await deps.run("git", ["cherry-pick", "--abort"]);
|
|
14612
|
-
|
|
14660
|
+
const recovery = await restoreAfterFailedPort(deps, { startBranch, branch, created: !preexistingLocal });
|
|
14661
|
+
throw new Error(
|
|
14662
|
+
`cherry-pick of ${label} onto ${branch} conflicted \u2014 aborted; nothing was pushed. Land the conflict resolution on development through a normal PR, then rerun \`mmi-cli hotfix start --from <that PR's merge sha>\` \u2014 never hand-resolve the port onto main. ${recovery} (${e.message ?? e})`
|
|
14663
|
+
);
|
|
14613
14664
|
}
|
|
14614
14665
|
notes.push(`cherry-picked ${label} onto ${branch} (from origin/main, -x trailer recorded)`);
|
|
14615
14666
|
if (deployModel === "hub-serverless") {
|
|
@@ -16849,7 +16900,7 @@ async function withSecrets(run) {
|
|
|
16849
16900
|
function registerSecretsCommands(program3) {
|
|
16850
16901
|
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");
|
|
16851
16902
|
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)));
|
|
16852
|
-
secrets.command("list").description("list secret NAMES + tier for
|
|
16903
|
+
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 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)));
|
|
16853
16904
|
secrets.command("find <intent>").description("resolve a plain-language intent to canonical secret names + the exact keyless-use command (master-only, #2244)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action((intent, o) => withSecrets(async (d) => {
|
|
16854
16905
|
if (!await secretsFind(d, intent, o)) process.exitCode = 1;
|
|
16855
16906
|
}));
|
|
@@ -16972,7 +17023,7 @@ function registerSecretsCommands(program3) {
|
|
|
16972
17023
|
});
|
|
16973
17024
|
if (!ok) process.exitCode = 1;
|
|
16974
17025
|
}));
|
|
16975
|
-
secrets.command("verify <key>").description("validate a known provider secret without printing its value").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((key, o) => withSecrets(async (d) => {
|
|
17026
|
+
secrets.command("verify <key>").description("validate a known provider secret without printing its value; a key with no configured provider verifier falls back to a value-free PRESENT/ABSENT reachability check (#3436)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "org-infra namespace (e.g. _org) for a granted org secret").action((key, o) => withSecrets(async (d) => {
|
|
16976
17027
|
const ok = await secretsVerify(d, key, o);
|
|
16977
17028
|
if (!ok) process.exitCode = 1;
|
|
16978
17029
|
}));
|
|
@@ -17022,7 +17073,7 @@ function registerSecretsCommands(program3) {
|
|
|
17022
17073
|
if (!ok) process.exitCode = 1;
|
|
17023
17074
|
}));
|
|
17024
17075
|
secrets.command("rm <key>").description("remove a secret from your own full project vault; org-infra requires master or an exact grant. --slug _org removes an org-infra <provider>/<KEY> value (#3315)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "org-infra namespace (e.g. _org) for the value removal \u2014 master or an exact grant (#3315)").action((key, o) => withSecrets((d) => secretsRemove(d, key, o)));
|
|
17025
|
-
secrets.command("use <key> [command...]").description("consume a secret KEYLESS: own-project full tree or an exactly granted org-infra key; injects into command env and never prints it").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "org-infra namespace (e.g. _org) for a granted org secret").option("--name <ENVVAR>", "env var name to inject under (default: the key leaf, UPPER_SNAKE)").action((key, command, o) => withSecrets(async (d) => {
|
|
17076
|
+
secrets.command("use <key> [command...]").description("consume a secret KEYLESS: own-project full tree or an exactly granted org-infra key; injects into command env and never prints it. The wrapped command keeps its OWN flags (`-- node -e \u2026`, `-- curl -H \u2026`) with or without the `--`, because npm's PowerShell shim swallows `--` before the CLI sees it (#3436)").allowUnknownOption().option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "org-infra namespace (e.g. _org) for a granted org secret").option("--name <ENVVAR>", "env var name to inject under (default: the key leaf, UPPER_SNAKE)").action((key, command, o) => withSecrets(async (d) => {
|
|
17026
17077
|
const ok = await secretsUse(d, key, { ...o, command });
|
|
17027
17078
|
if (ok === false) process.exitCode = 1;
|
|
17028
17079
|
}));
|
|
@@ -20725,14 +20776,30 @@ function parseRestPrSnapshot(json) {
|
|
|
20725
20776
|
const pr2 = json ?? {};
|
|
20726
20777
|
const headSha = pr2.head?.sha ?? "";
|
|
20727
20778
|
if (!headSha) throw new Error("pulls endpoint returned no head SHA");
|
|
20779
|
+
const headRepo = pr2.head?.repo?.full_name;
|
|
20780
|
+
const baseRepo = pr2.base?.repo?.full_name;
|
|
20728
20781
|
return {
|
|
20729
20782
|
headSha,
|
|
20783
|
+
headRef: pr2.head?.ref ?? "",
|
|
20784
|
+
// Only call it a fork when BOTH names are known and differ — an absent name (private/deleted fork)
|
|
20785
|
+
// must not read as same-repo and send a git-ref probe at a branch the base repo does not have.
|
|
20786
|
+
headIsFork: Boolean(headRepo && baseRepo && headRepo !== baseRepo),
|
|
20730
20787
|
mergeable: pr2.mergeable === true ? "MERGEABLE" : pr2.mergeable === false ? "CONFLICTING" : "UNKNOWN",
|
|
20731
20788
|
baseRef: pr2.base?.ref ?? "development",
|
|
20732
20789
|
merged: pr2.merged === true,
|
|
20733
20790
|
state: pr2.state ?? ""
|
|
20734
20791
|
};
|
|
20735
20792
|
}
|
|
20793
|
+
async function fetchBranchTipSha(repo, ref, gh = defaultGhApi) {
|
|
20794
|
+
if (!ref) return null;
|
|
20795
|
+
try {
|
|
20796
|
+
const parsed = JSON.parse(await gh([`repos/${repo}/git/ref/heads/${ref}`]));
|
|
20797
|
+
const sha = parsed.object?.sha;
|
|
20798
|
+
return typeof sha === "string" && sha ? sha : null;
|
|
20799
|
+
} catch {
|
|
20800
|
+
return null;
|
|
20801
|
+
}
|
|
20802
|
+
}
|
|
20736
20803
|
async function fetchRestPrSnapshot(prNumber, repo, gh = defaultGhApi) {
|
|
20737
20804
|
return parseRestPrSnapshot(JSON.parse(await gh([`repos/${repo}/pulls/${prNumber}`])));
|
|
20738
20805
|
}
|
|
@@ -20767,36 +20834,54 @@ async function pollRestPrMerged(prNumber, repo, gh = defaultGhApi) {
|
|
|
20767
20834
|
}
|
|
20768
20835
|
}
|
|
20769
20836
|
var CI_BUDGET_ANNOTATION_TITLE = "CI budget exceeded";
|
|
20837
|
+
var CI_DISK_ANNOTATION_TITLE = "CI runner disk";
|
|
20838
|
+
var CI_TOOLCHAIN_ANNOTATION_TITLE = "CI runner toolchain";
|
|
20839
|
+
var RUNNER_INFRA_ANNOTATION_TITLES = /* @__PURE__ */ new Set([
|
|
20840
|
+
CI_BUDGET_ANNOTATION_TITLE,
|
|
20841
|
+
CI_DISK_ANNOTATION_TITLE,
|
|
20842
|
+
CI_TOOLCHAIN_ANNOTATION_TITLE
|
|
20843
|
+
]);
|
|
20770
20844
|
function isErrorAnnotation(a) {
|
|
20771
20845
|
const level = a.annotation_level?.toLowerCase();
|
|
20772
20846
|
return level === "failure" || level === "error";
|
|
20773
20847
|
}
|
|
20774
20848
|
function classifyFailedChecks(failing) {
|
|
20775
|
-
const
|
|
20849
|
+
const infraFailures = [];
|
|
20776
20850
|
const otherFailures = [];
|
|
20777
20851
|
for (const check of failing) {
|
|
20778
20852
|
const errors = check.annotations.filter(isErrorAnnotation);
|
|
20779
|
-
const
|
|
20780
|
-
(
|
|
20853
|
+
const allInfra = errors.length > 0 && errors.every((a) => RUNNER_INFRA_ANNOTATION_TITLES.has(a.title?.trim() ?? ""));
|
|
20854
|
+
(allInfra ? infraFailures : otherFailures).push(check.name);
|
|
20781
20855
|
}
|
|
20782
|
-
if (
|
|
20856
|
+
if (infraFailures.length && !otherFailures.length) {
|
|
20783
20857
|
return {
|
|
20784
|
-
cause: "
|
|
20785
|
-
|
|
20858
|
+
cause: "runner-infra",
|
|
20859
|
+
infraFailures,
|
|
20786
20860
|
otherFailures,
|
|
20787
|
-
reason: `${
|
|
20861
|
+
reason: `${infraFailures.length} check(s) failed on RUNNER INFRASTRUCTURE, not your diff: ${infraFailures.join(", ")}. No test failed \u2014 the shared runner hit a wall-clock budget kill, ran out of disk, or was missing a toolchain (the check annotation names which). Re-run once the runner drains/heals (gh run rerun --failed); do not debug the diff.`
|
|
20788
20862
|
};
|
|
20789
20863
|
}
|
|
20790
20864
|
return {
|
|
20791
20865
|
cause: "checks-failure",
|
|
20792
|
-
|
|
20866
|
+
infraFailures,
|
|
20793
20867
|
otherFailures,
|
|
20794
|
-
reason:
|
|
20868
|
+
reason: infraFailures.length ? `checks failed: ${otherFailures.join(", ")}; separately, ${infraFailures.join(", ")} failed on runner infrastructure, not a test.` : `checks failed: ${otherFailures.join(", ") || "unknown"}.`
|
|
20795
20869
|
};
|
|
20796
20870
|
}
|
|
20797
20871
|
async function diagnoseFailedRestChecks(prNumber, repo, gh = defaultGhApi) {
|
|
20798
20872
|
try {
|
|
20799
20873
|
const snapshot = await fetchRestPrSnapshot(prNumber, repo, gh);
|
|
20874
|
+
if (!snapshot.headIsFork) {
|
|
20875
|
+
const branchTip = await fetchBranchTipSha(repo, snapshot.headRef, gh);
|
|
20876
|
+
if (branchTip && branchTip !== snapshot.headSha) {
|
|
20877
|
+
return {
|
|
20878
|
+
cause: "stale-head",
|
|
20879
|
+
infraFailures: [],
|
|
20880
|
+
otherFailures: [],
|
|
20881
|
+
reason: `the PR head (${snapshot.headSha.slice(0, 7)}) is BEHIND the branch tip (${branchTip.slice(0, 7)}) \u2014 GitHub likely dropped a \`synchronize\` event, so the reported red is a superseded commit and NO run exists for your current push. This is not your diff failing. Re-push (an empty commit is enough) or close and reopen the PR to re-sync the head and trigger a fresh run.`
|
|
20882
|
+
};
|
|
20883
|
+
}
|
|
20884
|
+
}
|
|
20800
20885
|
const failing = (await fetchHeadCheckRuns(snapshot.headSha, repo, gh)).filter((run) => classifyCheckRun(run) === "fail" && typeof run.id === "number");
|
|
20801
20886
|
if (!failing.length) return null;
|
|
20802
20887
|
const annotated = await Promise.all(failing.map(async (run) => ({
|
|
@@ -24525,7 +24610,7 @@ projectDeploy.command("get [owner/repo]").description("read nonsecret DEPLOY# fa
|
|
|
24525
24610
|
const payload = stage ? { slug: out.slug, stage, deploy: out.stages[stage] ?? null } : out;
|
|
24526
24611
|
console.log(JSON.stringify(payload));
|
|
24527
24612
|
});
|
|
24528
|
-
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").option("--class <class>", "deployable | content").option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>",
|
|
24613
|
+
project.command("set [owner/repo]").description("upsert project META (idempotent merge; master-gated)").option("--class <class>", "deployable | content").option("--project-type <type>", `${PROJECT_TYPES.join(" | ")} (capability shape)`).option("--deploy-model <model>", `${DEPLOY_MODELS.join(" | ")} (release/deploy path; none means no Hub deploy registration)`).option("--release-track <track>", `${RELEASE_TRACKS.join(" | ")} (branch topology; direct skips rc)`).option("--var <KEY=VALUE...>", settableVarHelp()).option("--set <KEY=VALUE...>", "alias of --var (one KEY=VALUE per flag; repeat the flag to set several)").option("--secrets-file <path>", 'read the #2244 secrets catalog map (JSON) from a file and merge it per entry into the existing catalog (clear all with --unset secrets). SHAPE: a JSON object keyed by env name, each entry {"key":"UPPER_SNAKE" (repeat the env name), "purpose":"<non-empty>", "group":"<e.g. auth|database>", "owner":"<github login>", "stages":[] (empty = the one stageless shared value; else any of dev/rc/main), "consumers":["runtime"|"box"|\u2026], "provider":"<optional>"}').option("--unset <KEY...>", "META field to remove (repeatable): oauth, requiredRuntimeSecrets, requiredBuildSecrets, secrets, edgeDomains, requiredGcpApis, publishRequired").option("--clear-web-profile", "remove web-only registry fields (oauth, edgeDomains) for non-web/content projects").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
24529
24614
|
const cfg = await loadConfig();
|
|
24530
24615
|
let target;
|
|
24531
24616
|
try {
|
|
@@ -24712,7 +24797,15 @@ oauth.command("plan", { isDefault: true }).description("print the canonical JS o
|
|
|
24712
24797
|
try {
|
|
24713
24798
|
oc = parseOauthConfig(meta ?? {}, slug);
|
|
24714
24799
|
} catch (e) {
|
|
24715
|
-
|
|
24800
|
+
const message = e.message;
|
|
24801
|
+
if (/^oauth is not configured for /.test(message)) {
|
|
24802
|
+
return failGraceful(
|
|
24803
|
+
`org oauth plan: ${message}. Declare it in the registry META first:
|
|
24804
|
+
mmi-cli org project set ${o.repo ?? `mutmutco/${slug}`} --var 'oauth={"subdomains":["${defaultSubdomain(slug)}"],"callbackPath":"${DEFAULT_CALLBACK_PATH}"}'
|
|
24805
|
+
New repo? The GCP project and its Google Auth Platform consent screen must exist too \u2014 docs/Guides/oauth-provision.md \xA7 New repo.`
|
|
24806
|
+
);
|
|
24807
|
+
}
|
|
24808
|
+
return failGraceful(`org oauth plan: ${message}`);
|
|
24716
24809
|
}
|
|
24717
24810
|
const origins = expectedJsOrigins(oc);
|
|
24718
24811
|
const redirects = expectedRedirectUris(oc);
|
|
@@ -25269,8 +25362,10 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
25269
25362
|
printLine(`pr checks-wait: timeout \u2014 waited ${Math.round((result.waitedMs ?? 0) / 6e4)}m, last state: ${result.detail ?? "pending"}. No check failed; re-run to keep waiting, or pass --timeout <minutes>.`);
|
|
25270
25363
|
} else if (result.status === "rate-limited") {
|
|
25271
25364
|
printLine(`pr checks-wait: rate-limited \u2014 ${result.reason ?? "REST pool below floor"}. No check failed; re-run after the pool resets.`);
|
|
25272
|
-
} else if (result.detail === "
|
|
25273
|
-
printLine(`pr checks-wait: failure (
|
|
25365
|
+
} else if (result.detail === "runner-infra") {
|
|
25366
|
+
printLine(`pr checks-wait: failure (runner infrastructure, NOT a test failure) \u2014 ${result.reason}`);
|
|
25367
|
+
} else if (result.detail === "stale-head") {
|
|
25368
|
+
printLine(`pr checks-wait: failure (stale PR head, NOT a test failure) \u2014 ${result.reason}`);
|
|
25274
25369
|
} else printLine(`pr checks-wait: ${result.status}${result.reason ? ` \u2014 ${result.reason}` : ""}${result.detail ? ` (${result.detail})` : ""}`);
|
|
25275
25370
|
if (result.status === "failure" || result.status === "conflicting") process.exitCode = 1;
|
|
25276
25371
|
if (result.status === "timeout" || result.status === "rate-limited") process.exitCode = PR_CHECKS_TIMEOUT_EXIT_CODE;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.49.0",
|
|
4
4
|
"description": "MMI Future CLI — the org dev toolbox (board, registry, keyless secrets, release train, bootstrap, doctor) and the cross-IDE engine the plugin's session-start hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|