@mutmutco/cli 3.68.0 → 3.70.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 +267 -106
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -33,9 +33,11 @@ __export(index_exports, {
|
|
|
33
33
|
DEFAULT_PRIORITY: () => DEFAULT_PRIORITY,
|
|
34
34
|
awsCallerArn: () => awsCallerArn,
|
|
35
35
|
classifyParseError: () => classifyParseError,
|
|
36
|
+
commandOwnLongFlags: () => commandOwnLongFlags,
|
|
36
37
|
envHealLockPath: () => envHealLockPath,
|
|
37
38
|
gcPlan: () => gcPlan,
|
|
38
39
|
isOrgRegisteredRepo: () => isOrgRegisteredRepo,
|
|
40
|
+
positionalTargetForm: () => positionalTargetForm,
|
|
39
41
|
registryClientDeps: () => registryClientDeps,
|
|
40
42
|
repoSlug: () => repoSlug,
|
|
41
43
|
shouldMarkWorktreeActivity: () => shouldMarkWorktreeActivity,
|
|
@@ -5003,6 +5005,7 @@ function buildPrMergeResultPayload(input) {
|
|
|
5003
5005
|
function buildPrMergeArgs(input) {
|
|
5004
5006
|
const args = ["pr", "merge", input.number, ...input.repoArgs, input.method];
|
|
5005
5007
|
if (input.deleteBranch !== false) args.push("--delete-branch");
|
|
5008
|
+
if (input.bodyFile) args.push("--body-file", input.bodyFile);
|
|
5006
5009
|
if (input.auto) args.push("--auto");
|
|
5007
5010
|
return args;
|
|
5008
5011
|
}
|
|
@@ -7235,7 +7238,7 @@ function checkGateBudget(files, opts = {}) {
|
|
|
7235
7238
|
}
|
|
7236
7239
|
|
|
7237
7240
|
// src/project-model.ts
|
|
7238
|
-
var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "non-deployable", "cli-tool", "worker"];
|
|
7241
|
+
var PROJECT_TYPES = ["web-app", "hub-service", "content", "desktop-app", "desktop-game", "mobile-app", "non-deployable", "cli-tool", "worker"];
|
|
7239
7242
|
var DEPLOY_MODELS = ["hub-serverless", "serverless", "tenant-container", "solo-container", "registry-publish", "content", "none"];
|
|
7240
7243
|
var RELEASE_TRACKS = ["full", "direct", "trunk"];
|
|
7241
7244
|
var PROJECT_TYPE_SET = new Set(PROJECT_TYPES);
|
|
@@ -7272,7 +7275,7 @@ function resolveDeployModel(meta, repo) {
|
|
|
7272
7275
|
const projectType = resolveProjectType(meta, repo);
|
|
7273
7276
|
if (projectType === "content" || meta?.class === "content") return "content";
|
|
7274
7277
|
if (projectType === "hub-service" || repoIsHub(repo)) return "hub-serverless";
|
|
7275
|
-
if (projectType === "desktop-app" || projectType === "desktop-game" || projectType === "non-deployable") return "none";
|
|
7278
|
+
if (projectType === "desktop-app" || projectType === "desktop-game" || projectType === "mobile-app" || projectType === "non-deployable") return "none";
|
|
7276
7279
|
if (projectType === "cli-tool") return "registry-publish";
|
|
7277
7280
|
return "tenant-container";
|
|
7278
7281
|
}
|
|
@@ -7751,7 +7754,13 @@ function collectRegistryRepos(projects) {
|
|
|
7751
7754
|
if (![...seen].some((r) => r.toLowerCase() === HUB_REPO.toLowerCase())) seen.add(HUB_REPO);
|
|
7752
7755
|
return [...seen].sort((a, b) => a.localeCompare(b));
|
|
7753
7756
|
}
|
|
7754
|
-
async function
|
|
7757
|
+
async function prTriggeredWorkflowsOnRef(deps, repo, branch, repoClass) {
|
|
7758
|
+
const hasGate = repoClass === "hub" ? true : await contentExists(deps, repo, branch, PRODUCT_GATE_PATH);
|
|
7759
|
+
const workflowPaths = repoClass === "hub" ? [".github/workflows/gate.yml"] : hasGate ? [PRODUCT_GATE_PATH] : await listWorkflowPaths(deps, repo, branch);
|
|
7760
|
+
if (workflowPaths === void 0) return void 0;
|
|
7761
|
+
return filterPullRequestTriggered(deps, repo, branch, workflowPaths);
|
|
7762
|
+
}
|
|
7763
|
+
async function resolveRepoMergeCiPolicy(repo, deps, headRef) {
|
|
7755
7764
|
const meta = await deps.getProjectMeta(slugFromRepo(repo));
|
|
7756
7765
|
const repoClass = classifyRepo(repo, meta);
|
|
7757
7766
|
if (repoClass === "content") {
|
|
@@ -7762,17 +7771,25 @@ async function resolveRepoMergeCiPolicy(repo, deps) {
|
|
|
7762
7771
|
});
|
|
7763
7772
|
}
|
|
7764
7773
|
const baseBranch = "development";
|
|
7765
|
-
const
|
|
7766
|
-
|
|
7767
|
-
if (workflowPaths === void 0) {
|
|
7774
|
+
const prTriggered = await prTriggeredWorkflowsOnRef(deps, repo, baseBranch, repoClass);
|
|
7775
|
+
if (prTriggered === void 0) {
|
|
7768
7776
|
return { policy: "wait-for-checks", reason: "workflows listing unreadable (API error) \u2014 failing safe to wait-for-checks" };
|
|
7769
7777
|
}
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
7778
|
+
let effective = prTriggered;
|
|
7779
|
+
let headReason = "";
|
|
7780
|
+
if (!effective.length && headRef && headRef !== baseBranch) {
|
|
7781
|
+
const onHead = await prTriggeredWorkflowsOnRef(deps, repo, headRef, repoClass);
|
|
7782
|
+
if (onHead?.length) {
|
|
7783
|
+
effective = onHead;
|
|
7784
|
+
headReason = ` (on PR head ${headRef}; absent on ${baseBranch})`;
|
|
7785
|
+
}
|
|
7786
|
+
}
|
|
7787
|
+
const resolved = resolveMergeCiPolicy({
|
|
7788
|
+
workflowPaths: effective,
|
|
7773
7789
|
registryCi: meta?.ci,
|
|
7774
7790
|
registryRequiredChecks: meta?.requiredChecks
|
|
7775
7791
|
});
|
|
7792
|
+
return headReason ? { ...resolved, reason: `${resolved.reason}${headReason}` } : resolved;
|
|
7776
7793
|
}
|
|
7777
7794
|
async function filterPullRequestTriggered(deps, repo, branch, paths) {
|
|
7778
7795
|
const kept = [];
|
|
@@ -8970,7 +8987,7 @@ async function executeWaveLand(plan, deps, opts = { preserveWorktree: true }) {
|
|
|
8970
8987
|
}
|
|
8971
8988
|
|
|
8972
8989
|
// src/index.ts
|
|
8973
|
-
var
|
|
8990
|
+
var import_node_os11 = require("node:os");
|
|
8974
8991
|
|
|
8975
8992
|
// src/board.ts
|
|
8976
8993
|
var import_node_child_process7 = require("node:child_process");
|
|
@@ -9464,7 +9481,8 @@ var OWNER = "mutmutco";
|
|
|
9464
9481
|
var SSM_ROOT = "/mmi-future";
|
|
9465
9482
|
var PROJECT_TIER_SEGMENT = "dev";
|
|
9466
9483
|
var ORG_INFRA_SLUG = "_org";
|
|
9467
|
-
var
|
|
9484
|
+
var KEY_MAX_PATH_SEGMENTS = 4;
|
|
9485
|
+
var KEY_RE = new RegExp(`^(?:[a-z0-9][a-z0-9-]*/){0,${KEY_MAX_PATH_SEGMENTS}}[A-Za-z][A-Za-z0-9_]*$`);
|
|
9468
9486
|
function isValidSecretKey(key) {
|
|
9469
9487
|
if (!key || key.length > 256) return false;
|
|
9470
9488
|
if (key.includes("..") || key.startsWith("/") || key.includes("*")) return false;
|
|
@@ -9663,7 +9681,7 @@ function formatCapabilities(r) {
|
|
|
9663
9681
|
no vault credentials visible`;
|
|
9664
9682
|
const width = Math.max(...items.map((i) => i.scope.length));
|
|
9665
9683
|
const lines = items.map(
|
|
9666
|
-
(i) => `${i.accessible ? "+" : " "} ${i.scope.padEnd(width)} ${i.tier.padEnd(7)} ${i.reason}`
|
|
9684
|
+
(i) => `${i.accessible ? "+" : " "} ${i.scope.padEnd(width)} ${i.tier.padEnd(7)} ${i.grantScope ? `${i.reason} (${i.grantScope})` : i.reason}`
|
|
9667
9685
|
);
|
|
9668
9686
|
const reachable = items.filter((i) => i.accessible).length;
|
|
9669
9687
|
return [
|
|
@@ -9674,6 +9692,7 @@ no vault credentials visible`;
|
|
|
9674
9692
|
...lines,
|
|
9675
9693
|
"",
|
|
9676
9694
|
"+ = usable now. Consume keyless: `mmi-cli secrets use <KEY> -- <cmd>` (inject, never printed); validate: `mmi-cli secrets verify <KEY>`. No command prints a raw value (#2844).",
|
|
9695
|
+
" `grant (read)` = keyless use only \u2014 rotating or removing that key still needs an rw grant (#3652).",
|
|
9677
9696
|
` Org-infra (#3463): \`--slug\` is ALWAYS \`${ORG_INFRA_SLUG}\`; the provider is the key's FIRST SEGMENT \u2014 \`mmi-cli secrets use cloudflare/GLOBAL_API_KEY --slug ${ORG_INFRA_SLUG} -- <cmd>\` (master-gated).`
|
|
9678
9697
|
].join("\n");
|
|
9679
9698
|
}
|
|
@@ -10338,11 +10357,29 @@ async function secretsRemove(deps, key, opts) {
|
|
|
10338
10357
|
}
|
|
10339
10358
|
deps.log(`removed ${key}`);
|
|
10340
10359
|
}
|
|
10341
|
-
|
|
10360
|
+
var WILDCARD_GRANT_KEY_RE = /^(?:[a-z][a-z0-9-]*\/)?\*$/;
|
|
10361
|
+
function isWildcardGrantKey(key) {
|
|
10362
|
+
return WILDCARD_GRANT_KEY_RE.test(key);
|
|
10363
|
+
}
|
|
10364
|
+
function isValidGrantKey(key) {
|
|
10365
|
+
if (!key || key.length > 256) return false;
|
|
10366
|
+
if (key.includes("..") || key.startsWith("/")) return false;
|
|
10367
|
+
return isWildcardGrantKey(key) || isValidSecretKey(key);
|
|
10368
|
+
}
|
|
10369
|
+
async function secretsGrant(deps, repo, login, key, opts) {
|
|
10370
|
+
if (!isValidGrantKey(key)) {
|
|
10371
|
+
deps.err(`invalid grant key ${JSON.stringify(key)} \u2014 expected <provider>/<KEY>, or a read-only wildcard \`*\` / \`<provider>/*\``);
|
|
10372
|
+
return;
|
|
10373
|
+
}
|
|
10374
|
+
if (isWildcardGrantKey(key) && !opts.read) {
|
|
10375
|
+
deps.err(`secrets grant: ${key} is a wildcard \u2014 pass --read. A wildcard never confers write, so granting it rw is refused rather than silently narrowed.`);
|
|
10376
|
+
return;
|
|
10377
|
+
}
|
|
10378
|
+
const scope = opts.read ? "read" : "rw";
|
|
10342
10379
|
const res = await deps.fetch(`${deps.apiUrl}/secrets/grant`, {
|
|
10343
10380
|
method: "POST",
|
|
10344
10381
|
headers: await deps.headers({ "content-type": "application/json" }),
|
|
10345
|
-
body: JSON.stringify({ repo, login, key }),
|
|
10382
|
+
body: JSON.stringify({ repo, login, key, scope }),
|
|
10346
10383
|
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
10347
10384
|
});
|
|
10348
10385
|
if (!res.ok) {
|
|
@@ -10351,7 +10388,7 @@ async function secretsGrant(deps, repo, login, key, _opts) {
|
|
|
10351
10388
|
);
|
|
10352
10389
|
return;
|
|
10353
10390
|
}
|
|
10354
|
-
deps.log(`granted @${login} access to ${key} in ${repo}`);
|
|
10391
|
+
deps.log(`granted @${login} ${scope === "read" ? "read-only" : "read/write"} access to ${key} in ${repo}`);
|
|
10355
10392
|
}
|
|
10356
10393
|
async function secretsRevoke(deps, repo, login, key, _opts) {
|
|
10357
10394
|
const res = await deps.fetch(`${deps.apiUrl}/secrets/revoke`, {
|
|
@@ -15943,12 +15980,15 @@ async function resolveOwners(deps) {
|
|
|
15943
15980
|
function collaboratorRole(c) {
|
|
15944
15981
|
return c.role_name ?? (c.permissions?.admin ? "admin" : c.permissions?.maintain ? "maintain" : "write");
|
|
15945
15982
|
}
|
|
15946
|
-
async function auditRepoCollaborators(repo, owners, deps) {
|
|
15983
|
+
async function auditRepoCollaborators(repo, owners, deps, projectAdmins = /* @__PURE__ */ new Set(), sanctionedAdmins = /* @__PURE__ */ new Set()) {
|
|
15947
15984
|
const collabs = await restPagedJson(deps, `repos/${repo}/collaborators?affiliation=direct`, []);
|
|
15948
15985
|
const findings = [];
|
|
15986
|
+
const writeOrBetter = /* @__PURE__ */ new Set();
|
|
15949
15987
|
for (const c of collabs) {
|
|
15950
15988
|
if (owners.has(c.login)) continue;
|
|
15951
15989
|
const role = collaboratorRole(c);
|
|
15990
|
+
writeOrBetter.add(c.login);
|
|
15991
|
+
if (OVERGRANT_ROLES.has(role) && sanctionedAdmins.has(c.login)) continue;
|
|
15952
15992
|
if (OVERGRANT_ROLES.has(role)) {
|
|
15953
15993
|
findings.push({
|
|
15954
15994
|
repo,
|
|
@@ -15960,6 +16000,17 @@ async function auditRepoCollaborators(repo, owners, deps) {
|
|
|
15960
16000
|
});
|
|
15961
16001
|
}
|
|
15962
16002
|
}
|
|
16003
|
+
for (const login of projectAdmins) {
|
|
16004
|
+
if (owners.has(login) || writeOrBetter.has(login)) continue;
|
|
16005
|
+
findings.push({
|
|
16006
|
+
repo,
|
|
16007
|
+
kind: "collaborator-undergrant",
|
|
16008
|
+
severity: "medium",
|
|
16009
|
+
actor: login,
|
|
16010
|
+
detail: `@${login} is a declared project-admin of ${repo} in the registry but holds no direct collaborator role; secrets self-serve and train authority fail closed for them`,
|
|
16011
|
+
remediation: `gh api -X PUT repos/${repo}/collaborators/${login} -f permission=push`
|
|
16012
|
+
});
|
|
16013
|
+
}
|
|
15963
16014
|
return findings;
|
|
15964
16015
|
}
|
|
15965
16016
|
async function auditTrainBranch(repo, branch, owners, deps, projectAdmins = /* @__PURE__ */ new Set()) {
|
|
@@ -16007,6 +16058,18 @@ async function auditTrainBranch(repo, branch, owners, deps, projectAdmins = /* @
|
|
|
16007
16058
|
});
|
|
16008
16059
|
}
|
|
16009
16060
|
}
|
|
16061
|
+
for (const login of projectAdmins) {
|
|
16062
|
+
if (owners.has(login) || users.includes(login)) continue;
|
|
16063
|
+
findings.push({
|
|
16064
|
+
repo,
|
|
16065
|
+
branch,
|
|
16066
|
+
kind: "train-allowlist-missing",
|
|
16067
|
+
severity: "medium",
|
|
16068
|
+
actor: login,
|
|
16069
|
+
detail: `declared project-admin @${login} is missing from the ${branch} push allowlist \u2014 the registry grants them train authority the branch lock denies`,
|
|
16070
|
+
remediation: `gh api -X POST repos/${repo}/branches/${branch}/protection/restrictions/users -f users[]="${login}"`
|
|
16071
|
+
});
|
|
16072
|
+
}
|
|
16010
16073
|
const apps = (restrictions.apps ?? []).map((a) => a.slug);
|
|
16011
16074
|
if (!apps.includes(LOCKED_APP)) {
|
|
16012
16075
|
findings.push({
|
|
@@ -16039,9 +16102,9 @@ function auditDataAccessContracts(repo, contracts = { consumers: {} }) {
|
|
|
16039
16102
|
}
|
|
16040
16103
|
return findings;
|
|
16041
16104
|
}
|
|
16042
|
-
async function auditRepoAccess(repo, repoClass, owners, deps, projectAdmins = /* @__PURE__ */ new Set(), dataAccess, releaseTrack) {
|
|
16105
|
+
async function auditRepoAccess(repo, repoClass, owners, deps, projectAdmins = /* @__PURE__ */ new Set(), dataAccess, releaseTrack, sanctionedAdmins = /* @__PURE__ */ new Set()) {
|
|
16043
16106
|
const findings = [];
|
|
16044
|
-
findings.push(...await auditRepoCollaborators(repo, owners, deps));
|
|
16107
|
+
findings.push(...await auditRepoCollaborators(repo, owners, deps, projectAdmins, sanctionedAdmins));
|
|
16045
16108
|
if (dataAccess) findings.push(...auditDataAccessContracts(repo, dataAccess));
|
|
16046
16109
|
const track = releaseTrack ?? (repoClass === "content" ? "trunk" : void 0);
|
|
16047
16110
|
for (const branch of lockedBranches(repoClass, track)) {
|
|
@@ -16081,7 +16144,7 @@ async function auditPluginReadAccess(owners, projectAdmins, deps) {
|
|
|
16081
16144
|
}
|
|
16082
16145
|
return findings;
|
|
16083
16146
|
}
|
|
16084
|
-
async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
16147
|
+
async function auditOrgAccess(targets, deps, matrix = {}, dataAccess, sanctioned = {}) {
|
|
16085
16148
|
if (targets.length === 0) {
|
|
16086
16149
|
return {
|
|
16087
16150
|
ok: false,
|
|
@@ -16118,7 +16181,16 @@ async function auditOrgAccess(targets, deps, matrix = {}, dataAccess) {
|
|
|
16118
16181
|
orgFindings.push(...await auditPluginReadAccess(owners, allProjectAdmins, deps));
|
|
16119
16182
|
const repos = [];
|
|
16120
16183
|
for (const target of targets) {
|
|
16121
|
-
repos.push(await auditRepoAccess(
|
|
16184
|
+
repos.push(await auditRepoAccess(
|
|
16185
|
+
target.repo,
|
|
16186
|
+
target.class,
|
|
16187
|
+
owners,
|
|
16188
|
+
deps,
|
|
16189
|
+
new Set(entriesValueByCanonicalRepo(matrix, target.repo) ?? []),
|
|
16190
|
+
dataAccess,
|
|
16191
|
+
target.releaseTrack,
|
|
16192
|
+
new Set(entriesValueByCanonicalRepo(sanctioned, target.repo) ?? [])
|
|
16193
|
+
));
|
|
16122
16194
|
}
|
|
16123
16195
|
const ok = orgFindings.every((f) => f.severity !== "high") && repos.every((r) => r.ok);
|
|
16124
16196
|
return { ok, owners: [...owners], orgFindings, repos };
|
|
@@ -16157,6 +16229,10 @@ function loadAccessMatrix(matrixJson) {
|
|
|
16157
16229
|
if (!matrixJson) return {};
|
|
16158
16230
|
return safeJson(matrixJson, {}).projectAdmins ?? {};
|
|
16159
16231
|
}
|
|
16232
|
+
function loadSanctionedAdmins(matrixJson) {
|
|
16233
|
+
if (!matrixJson) return {};
|
|
16234
|
+
return safeJson(matrixJson, {}).sanctionedAdmins ?? {};
|
|
16235
|
+
}
|
|
16160
16236
|
function loadDataAccessContracts(dataAccessJson) {
|
|
16161
16237
|
if (!dataAccessJson) return { consumers: {} };
|
|
16162
16238
|
const parsed = safeJson(dataAccessJson, { consumers: {} });
|
|
@@ -16548,6 +16624,43 @@ function evaluate(changed, policy, present = () => false) {
|
|
|
16548
16624
|
function git(args, cwd) {
|
|
16549
16625
|
return (0, import_node_child_process10.execFileSync)("git", args, { cwd, encoding: "utf8", maxBuffer: 32 * 1024 * 1024 });
|
|
16550
16626
|
}
|
|
16627
|
+
var COAUTHOR_KEY = "Co-authored-by";
|
|
16628
|
+
var GH_MESSAGE_SEPARATOR = /^-{5,}$/;
|
|
16629
|
+
var LIFTED_KEYS = new RegExp(`^(?:${TRAILER_KEY}|${COAUTHOR_KEY}):`, "i");
|
|
16630
|
+
function parseTrailers(message, cwd) {
|
|
16631
|
+
try {
|
|
16632
|
+
return (0, import_node_child_process10.execFileSync)("git", ["interpret-trailers", "--parse", "--unfold"], {
|
|
16633
|
+
cwd,
|
|
16634
|
+
input: message,
|
|
16635
|
+
encoding: "utf8",
|
|
16636
|
+
maxBuffer: 4 * 1024 * 1024
|
|
16637
|
+
}).split("\n").map((l) => l.trim()).filter(Boolean);
|
|
16638
|
+
} catch {
|
|
16639
|
+
return [];
|
|
16640
|
+
}
|
|
16641
|
+
}
|
|
16642
|
+
function squashBodyWithOverride(commits, cwd) {
|
|
16643
|
+
const overrides = [];
|
|
16644
|
+
const coauthors = [];
|
|
16645
|
+
const bodies = [];
|
|
16646
|
+
for (const commit of commits) {
|
|
16647
|
+
const message = `${commit.headline}
|
|
16648
|
+
|
|
16649
|
+
${commit.body}`.trim();
|
|
16650
|
+
for (const trailer of parseTrailers(message, cwd)) {
|
|
16651
|
+
if (trailer.toLowerCase().startsWith(`${TRAILER_KEY.toLowerCase()}:`)) {
|
|
16652
|
+
if (!overrides.includes(trailer)) overrides.push(trailer);
|
|
16653
|
+
} else if (trailer.toLowerCase().startsWith(`${COAUTHOR_KEY.toLowerCase()}:`)) {
|
|
16654
|
+
if (!coauthors.some((c) => c.toLowerCase() === trailer.toLowerCase())) coauthors.push(trailer);
|
|
16655
|
+
}
|
|
16656
|
+
}
|
|
16657
|
+
const source = commits.length > 1 ? message : commit.body;
|
|
16658
|
+
const kept = source.split("\n").filter((line) => !LIFTED_KEYS.test(line.trim()) && !GH_MESSAGE_SEPARATOR.test(line.trim())).join("\n").trim();
|
|
16659
|
+
if (kept) bodies.push(kept);
|
|
16660
|
+
}
|
|
16661
|
+
if (!overrides.length) return null;
|
|
16662
|
+
return [...bodies, [...overrides, ...coauthors].join("\n")].join("\n\n");
|
|
16663
|
+
}
|
|
16551
16664
|
function resolveBase(cwd, explicit) {
|
|
16552
16665
|
const candidates = [explicit, process.env.TEST_POLICY_BASE, "origin/development", "origin/main"].filter(Boolean);
|
|
16553
16666
|
for (const ref of candidates) {
|
|
@@ -18035,8 +18148,8 @@ function registerSecretsCommands(program3) {
|
|
|
18035
18148
|
if (ok === false) process.exitCode = 1;
|
|
18036
18149
|
});
|
|
18037
18150
|
});
|
|
18038
|
-
secrets.command("grant <repo> <login> <key>").description("MASTER-ONLY: grant a project-admin standing access to
|
|
18039
|
-
secrets.command("revoke <repo> <login> <key>").description("MASTER-ONLY: withdraw a previously granted org-infra secret access").action((repo, login, key) => withSecrets((d) => secretsRevoke(d, repo, login, key, {})));
|
|
18151
|
+
secrets.command("grant <repo> <login> <key>").description("MASTER-ONLY: grant a project-admin standing access to an org-infra secret. Default is read/write on one exact key; --read grants keyless USE only, and only --read accepts a wildcard key (`*` = the whole namespace, `<provider>/*` = one provider group) (#3652)").option("--read", "read-only: permits keyless `secrets use`, never `set`/`rm`. Required for a wildcard key.").action((repo, login, key, o) => withSecrets((d) => secretsGrant(d, repo, login, key, { read: o.read })));
|
|
18152
|
+
secrets.command("revoke <repo> <login> <key>").description("MASTER-ONLY: withdraw a previously granted org-infra secret access (pass the grant key exactly as granted, wildcard included)").action((repo, login, key) => withSecrets((d) => secretsRevoke(d, repo, login, key, {})));
|
|
18040
18153
|
}
|
|
18041
18154
|
|
|
18042
18155
|
// src/app-actor.ts
|
|
@@ -21350,6 +21463,7 @@ function registerBoardCommands(program3) {
|
|
|
21350
21463
|
var import_node_fs27 = require("node:fs");
|
|
21351
21464
|
var import_promises6 = require("node:fs/promises");
|
|
21352
21465
|
var import_node_path26 = require("node:path");
|
|
21466
|
+
var import_node_os8 = require("node:os");
|
|
21353
21467
|
var import_node_child_process12 = require("node:child_process");
|
|
21354
21468
|
|
|
21355
21469
|
// src/board-advance.ts
|
|
@@ -22258,8 +22372,36 @@ ${err?.stdout ?? ""}`.split("\n").map((line) => line.trim()).find((line) => line
|
|
|
22258
22372
|
var defaultGhMergeAutoIo = {
|
|
22259
22373
|
gh: async (args, timeoutMs) => (await execFileP2("gh", args, { timeout: timeoutMs })).stdout
|
|
22260
22374
|
};
|
|
22375
|
+
async function composeOverrideBodyFile(prNumber, repoArgs, gh) {
|
|
22376
|
+
try {
|
|
22377
|
+
const raw = await gh(["pr", "view", prNumber, ...repoArgs, "--json", "commits"], GC_GH_TIMEOUT_MS2);
|
|
22378
|
+
const commits = JSON.parse(raw).commits ?? [];
|
|
22379
|
+
const body = squashBodyWithOverride(commits.map((c) => ({ headline: c.messageHeadline ?? "", body: c.messageBody ?? "" })), process.cwd());
|
|
22380
|
+
if (!body) return void 0;
|
|
22381
|
+
const dir = (0, import_node_fs27.mkdtempSync)((0, import_node_path26.join)((0, import_node_os8.tmpdir)(), "mmi-squash-body-"));
|
|
22382
|
+
const path2 = (0, import_node_path26.join)(dir, "body.txt");
|
|
22383
|
+
(0, import_node_fs27.writeFileSync)(path2, `${body}
|
|
22384
|
+
`, "utf8");
|
|
22385
|
+
return { path: path2, cleanup: () => {
|
|
22386
|
+
try {
|
|
22387
|
+
(0, import_node_fs27.rmSync)(dir, { recursive: true, force: true });
|
|
22388
|
+
} catch {
|
|
22389
|
+
}
|
|
22390
|
+
} };
|
|
22391
|
+
} catch {
|
|
22392
|
+
return void 0;
|
|
22393
|
+
}
|
|
22394
|
+
}
|
|
22261
22395
|
async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAutoIo) {
|
|
22262
22396
|
const args = repo ? ["--repo", repo] : [];
|
|
22397
|
+
const overrideBody = await composeOverrideBodyFile(prNumber, args, io.gh);
|
|
22398
|
+
try {
|
|
22399
|
+
return await mergeAutoEnqueueWithBody(prNumber, args, method, io, overrideBody?.path);
|
|
22400
|
+
} finally {
|
|
22401
|
+
overrideBody?.cleanup();
|
|
22402
|
+
}
|
|
22403
|
+
}
|
|
22404
|
+
async function mergeAutoEnqueueWithBody(prNumber, args, method, io, bodyFile) {
|
|
22263
22405
|
const headRef = (await io.gh(["pr", "view", prNumber, ...args, "--json", "headRefName", "--jq", ".headRefName"], GC_GH_TIMEOUT_MS2).catch(() => "")).trim();
|
|
22264
22406
|
const deleteBranch = !isProtectedBranch(headRef);
|
|
22265
22407
|
const readMergeState = () => readGhPrStateWithRetry(
|
|
@@ -22278,7 +22420,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
|
|
|
22278
22420
|
return s.ok && s.state === "MERGED";
|
|
22279
22421
|
},
|
|
22280
22422
|
reEnqueue: async () => {
|
|
22281
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
|
|
22423
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
22282
22424
|
}
|
|
22283
22425
|
});
|
|
22284
22426
|
if (confirmation === "merged") return { mergeStatus: "merged" };
|
|
@@ -22286,7 +22428,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
|
|
|
22286
22428
|
return { mergeStatus: "failed", error: "auto-merge did not stick \u2014 GitHub reported no autoMergeRequest after enqueue; retry once the PR has a pending check" };
|
|
22287
22429
|
};
|
|
22288
22430
|
try {
|
|
22289
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
|
|
22431
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
22290
22432
|
} catch (e) {
|
|
22291
22433
|
const message = String(e.message || "");
|
|
22292
22434
|
if (/already been merged/i.test(message)) return { mergeStatus: "merged" };
|
|
@@ -22294,7 +22436,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
|
|
|
22294
22436
|
if (note) return { mergeStatus: "failed", error: note };
|
|
22295
22437
|
if (mergeAutoRejectedPrAlreadyClean(message)) {
|
|
22296
22438
|
try {
|
|
22297
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
|
|
22439
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: false, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
22298
22440
|
} catch (e2) {
|
|
22299
22441
|
const m2 = String(e2.message || "");
|
|
22300
22442
|
if (/already been merged/i.test(m2)) return { mergeStatus: "merged" };
|
|
@@ -22305,7 +22447,7 @@ async function ghMergeAutoEnqueue(prNumber, repo, method, io = defaultGhMergeAut
|
|
|
22305
22447
|
return { mergeStatus: "failed", error: `could not confirm PR state after clean-status fallback: ${afterDirect.error}` };
|
|
22306
22448
|
}
|
|
22307
22449
|
try {
|
|
22308
|
-
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch }), GH_MUTATION_TIMEOUT_MS);
|
|
22450
|
+
await io.gh(buildPrMergeArgs({ number: prNumber, repoArgs: args, method, auto: true, deleteBranch, bodyFile }), GH_MUTATION_TIMEOUT_MS);
|
|
22309
22451
|
} catch (e3) {
|
|
22310
22452
|
const m3 = String(e3.message || "");
|
|
22311
22453
|
if (/already been merged/i.test(m3)) return { mergeStatus: "merged" };
|
|
@@ -23829,7 +23971,7 @@ function registerDeployCommands(program3) {
|
|
|
23829
23971
|
|
|
23830
23972
|
// src/discovery-commands.ts
|
|
23831
23973
|
var import_node_fs31 = require("node:fs");
|
|
23832
|
-
var
|
|
23974
|
+
var import_node_os9 = require("node:os");
|
|
23833
23975
|
var import_node_path29 = require("node:path");
|
|
23834
23976
|
var GC_GH_TIMEOUT_MS3 = 2e4;
|
|
23835
23977
|
async function collectStatus() {
|
|
@@ -24005,7 +24147,7 @@ async function collectOnboardStatus() {
|
|
|
24005
24147
|
else if (top) nextCommand = `mmi-cli board claim ${top.number} # ${top.title}`;
|
|
24006
24148
|
else nextCommand = "mmi-cli board read \u2014 no claimable items found";
|
|
24007
24149
|
}
|
|
24008
|
-
const home = (0,
|
|
24150
|
+
const home = (0, import_node_os9.homedir)();
|
|
24009
24151
|
const plugin = onboardPluginGate({
|
|
24010
24152
|
readKnown: () => readFileSyncSafe((0, import_node_path29.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs31.readFileSync),
|
|
24011
24153
|
readSettings: () => readFileSyncSafe((0, import_node_path29.join)(home, ".claude", "settings.json"), import_node_fs31.readFileSync)
|
|
@@ -25388,17 +25530,17 @@ function parseOriginRepo(remoteUrl) {
|
|
|
25388
25530
|
}
|
|
25389
25531
|
function ghHostsConfigPath(env, platform2) {
|
|
25390
25532
|
const sep2 = platform2 === "win32" ? "\\" : "/";
|
|
25391
|
-
const
|
|
25533
|
+
const join27 = (...parts) => parts.join(sep2);
|
|
25392
25534
|
const explicit = env.GH_CONFIG_DIR?.trim();
|
|
25393
|
-
if (explicit) return
|
|
25535
|
+
if (explicit) return join27(explicit, "hosts.yml");
|
|
25394
25536
|
if (platform2 === "win32") {
|
|
25395
25537
|
const appData = (env.AppData ?? env.APPDATA)?.trim();
|
|
25396
|
-
return appData ?
|
|
25538
|
+
return appData ? join27(appData, "GitHub CLI", "hosts.yml") : void 0;
|
|
25397
25539
|
}
|
|
25398
25540
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
25399
|
-
if (xdg) return
|
|
25541
|
+
if (xdg) return join27(xdg, "gh", "hosts.yml");
|
|
25400
25542
|
const home = env.HOME?.trim();
|
|
25401
|
-
return home ?
|
|
25543
|
+
return home ? join27(home, ".config", "gh", "hosts.yml") : void 0;
|
|
25402
25544
|
}
|
|
25403
25545
|
function parseGhHostsAccounts(yaml, host = "github.com") {
|
|
25404
25546
|
let hostIndent = null;
|
|
@@ -25449,7 +25591,7 @@ function ghAccountCaveat(announcedLogin, accounts) {
|
|
|
25449
25591
|
|
|
25450
25592
|
// src/doctor-io.ts
|
|
25451
25593
|
var import_node_fs32 = require("node:fs");
|
|
25452
|
-
var
|
|
25594
|
+
var import_node_os10 = require("node:os");
|
|
25453
25595
|
var import_node_path30 = require("node:path");
|
|
25454
25596
|
var import_node_child_process13 = require("node:child_process");
|
|
25455
25597
|
var import_node_util8 = require("node:util");
|
|
@@ -25458,7 +25600,7 @@ var MMI_PLUGIN_ID2 = "mmi@mutmutco";
|
|
|
25458
25600
|
function installedClaudePluginVersion() {
|
|
25459
25601
|
try {
|
|
25460
25602
|
const file = JSON.parse(
|
|
25461
|
-
(0, import_node_fs32.readFileSync)((0, import_node_path30.join)((0,
|
|
25603
|
+
(0, import_node_fs32.readFileSync)((0, import_node_path30.join)((0, import_node_os10.homedir)(), ".claude", "plugins", "installed_plugins.json"), "utf8")
|
|
25462
25604
|
);
|
|
25463
25605
|
const versions = (file.plugins?.[MMI_PLUGIN_ID2] ?? []).map((r) => r.version).filter((v) => Boolean(v));
|
|
25464
25606
|
if (versions.length === 0) return void 0;
|
|
@@ -25567,7 +25709,7 @@ function envHealLockPath(home) {
|
|
|
25567
25709
|
async function withEnvHealLock(what, run) {
|
|
25568
25710
|
try {
|
|
25569
25711
|
return await withFileLock(
|
|
25570
|
-
envHealLockPath((0,
|
|
25712
|
+
envHealLockPath((0, import_node_os11.homedir)()),
|
|
25571
25713
|
{ staleMs: ENV_HEAL_LOCK_STALE_MS, maxWaitMs: ENV_HEAL_LOCK_MAX_WAIT_MS, label: "mmi env-heal lock" },
|
|
25572
25714
|
run
|
|
25573
25715
|
);
|
|
@@ -25662,9 +25804,9 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
25662
25804
|
},
|
|
25663
25805
|
pluginCache: () => {
|
|
25664
25806
|
const plan = buildPluginCachePlan(
|
|
25665
|
-
(0,
|
|
25807
|
+
(0, import_node_os11.homedir)(),
|
|
25666
25808
|
runningPluginVersion(process.env, resolveClientVersion()),
|
|
25667
|
-
pluginCacheFsDeps((0,
|
|
25809
|
+
pluginCacheFsDeps((0, import_node_os11.homedir)(), () => 0)
|
|
25668
25810
|
);
|
|
25669
25811
|
return {
|
|
25670
25812
|
stale: plan.prune,
|
|
@@ -25713,7 +25855,7 @@ function mmiDoctorDeps(opts = {}) {
|
|
|
25713
25855
|
// which branch it would pick one up from. Two local file reads, no network, fail-soft to no rows.
|
|
25714
25856
|
marketplaceRows: () => {
|
|
25715
25857
|
try {
|
|
25716
|
-
const home = (0,
|
|
25858
|
+
const home = (0, import_node_os11.homedir)();
|
|
25717
25859
|
return marketplaceRows(
|
|
25718
25860
|
MMI_MARKETPLACE_NAME,
|
|
25719
25861
|
readFileSyncSafe((0, import_node_path31.join)(home, ...KNOWN_MARKETPLACES_RELATIVE), import_node_fs33.readFileSync),
|
|
@@ -25734,23 +25876,6 @@ async function requireFreshTrainCli(commandName) {
|
|
|
25734
25876
|
if (report.ok) return;
|
|
25735
25877
|
throw new Error(staleTrainCliMessage(report, commandName));
|
|
25736
25878
|
}
|
|
25737
|
-
var cachedLongFlags;
|
|
25738
|
-
function allLongFlags() {
|
|
25739
|
-
if (cachedLongFlags) return cachedLongFlags;
|
|
25740
|
-
const acc = /* @__PURE__ */ new Set();
|
|
25741
|
-
const walk2 = (node) => {
|
|
25742
|
-
if (!isCanonicalSuggestion(node)) return;
|
|
25743
|
-
for (const opt of node.options) {
|
|
25744
|
-
if (opt.discovery) continue;
|
|
25745
|
-
const m = /--[\w-]+/.exec(opt.flags);
|
|
25746
|
-
if (m) acc.add(m[0]);
|
|
25747
|
-
}
|
|
25748
|
-
for (const child2 of node.subcommands) walk2(child2);
|
|
25749
|
-
};
|
|
25750
|
-
walk2(buildCommandManifest(program2).tree);
|
|
25751
|
-
cachedLongFlags = [...acc];
|
|
25752
|
-
return cachedLongFlags;
|
|
25753
|
-
}
|
|
25754
25879
|
var cachedCommandPaths;
|
|
25755
25880
|
function allCommandPaths() {
|
|
25756
25881
|
if (cachedCommandPaths) return cachedCommandPaths;
|
|
@@ -25817,6 +25942,22 @@ function commandPath2(cmd) {
|
|
|
25817
25942
|
}
|
|
25818
25943
|
return parts.join(" ");
|
|
25819
25944
|
}
|
|
25945
|
+
function commandOwnLongFlags(cmd) {
|
|
25946
|
+
if (!cmd) return [];
|
|
25947
|
+
const flags = /* @__PURE__ */ new Set();
|
|
25948
|
+
for (const opt of cmd.options) {
|
|
25949
|
+
if (opt.long) flags.add(opt.long);
|
|
25950
|
+
}
|
|
25951
|
+
return [...flags];
|
|
25952
|
+
}
|
|
25953
|
+
var TARGET_SELECTOR_FLAGS = /* @__PURE__ */ new Set(["--repo", "--ref", "--target", "--project"]);
|
|
25954
|
+
function positionalTargetForm(cmd) {
|
|
25955
|
+
if (!cmd) return void 0;
|
|
25956
|
+
const args = cmd.registeredArguments ?? [];
|
|
25957
|
+
const first = args[0];
|
|
25958
|
+
if (!first) return void 0;
|
|
25959
|
+
return `mmi-cli ${commandPath2(cmd)} <${first.name()}>`;
|
|
25960
|
+
}
|
|
25820
25961
|
function resolveParseHint() {
|
|
25821
25962
|
if (lastParseErrorKind === "unknown-command") {
|
|
25822
25963
|
const path3 = lastUnknownCommand ? suggestCommandPath(lastUnknownCommand, allCommandPaths()) : void 0;
|
|
@@ -25845,10 +25986,13 @@ function envelopeAwareWriteErr(str) {
|
|
|
25845
25986
|
if (match) {
|
|
25846
25987
|
const flag = match[1];
|
|
25847
25988
|
if (argvWantsJson2()) {
|
|
25848
|
-
const
|
|
25989
|
+
const invoked = resolveCommandFromArgv(program2, process.argv.slice(2));
|
|
25990
|
+
const suggestion = didYouMean(flag, commandOwnLongFlags(invoked));
|
|
25849
25991
|
const corrected = suggestion ? `mmi-cli ${process.argv.slice(2).map((a) => a === flag ? suggestion : a).join(" ")}` : void 0;
|
|
25992
|
+
const positional = !suggestion && TARGET_SELECTOR_FLAGS.has(flag) ? positionalTargetForm(invoked) : void 0;
|
|
25993
|
+
const message = positional ? `unknown option '${flag}' \u2014 this command takes its target as a positional: ${positional}` : `unknown option '${flag}'`;
|
|
25850
25994
|
process.stderr.write(
|
|
25851
|
-
formatErrorEnvelope(
|
|
25995
|
+
formatErrorEnvelope(message, {
|
|
25852
25996
|
code: ERROR_CODES.ERR_UNKNOWN_FLAG,
|
|
25853
25997
|
offending_flag: flag,
|
|
25854
25998
|
...suggestion ? { did_you_mean: suggestion } : {},
|
|
@@ -27304,14 +27448,19 @@ async function listCiWorkflowPaths(cwd = process.cwd()) {
|
|
|
27304
27448
|
}
|
|
27305
27449
|
}).map((name) => `.github/workflows/${name}`);
|
|
27306
27450
|
}
|
|
27307
|
-
async function resolveMergeCiPolicyForCheckout(repoOpt) {
|
|
27451
|
+
async function resolveMergeCiPolicyForCheckout(repoOpt, headRef) {
|
|
27308
27452
|
const repo = repoOpt ?? await resolveRepo();
|
|
27309
27453
|
if (repo) {
|
|
27310
|
-
return resolveRepoMergeCiPolicy(repo, ciAuditDeps());
|
|
27454
|
+
return resolveRepoMergeCiPolicy(repo, ciAuditDeps(), headRef);
|
|
27311
27455
|
}
|
|
27312
27456
|
const workflowPaths = await listCiWorkflowPaths();
|
|
27313
27457
|
return resolveMergeCiPolicy({ workflowPaths });
|
|
27314
27458
|
}
|
|
27459
|
+
async function prHeadRefForCiProbe(prNumber, repo) {
|
|
27460
|
+
const snapshot = await fetchRestPrSnapshot(prNumber, repo).catch(() => null);
|
|
27461
|
+
if (!snapshot || snapshot.headIsFork || !snapshot.headRef) return void 0;
|
|
27462
|
+
return snapshot.headRef;
|
|
27463
|
+
}
|
|
27315
27464
|
function ciAuditDeps() {
|
|
27316
27465
|
const cfgPromise = loadConfig();
|
|
27317
27466
|
const root = hubRoot();
|
|
@@ -27350,9 +27499,11 @@ pr.command("checks-wait <number>").description(`bounded wait for PR checks; skip
|
|
|
27350
27499
|
timeoutMs = Math.round(minutes * 6e4);
|
|
27351
27500
|
}
|
|
27352
27501
|
const repo = await requireRepo(o.repo);
|
|
27353
|
-
const
|
|
27502
|
+
const snapshot = await fetchRestPrSnapshot(number, repo).catch(() => null);
|
|
27503
|
+
const baseBranch = snapshot?.baseRef ?? "development";
|
|
27504
|
+
const ciHeadRef = snapshot && !snapshot.headIsFork && snapshot.headRef ? snapshot.headRef : void 0;
|
|
27354
27505
|
const result = await waitForPrChecks({
|
|
27355
|
-
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo),
|
|
27506
|
+
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
|
|
27356
27507
|
pollChecks: () => pollRestPrChecks(number, repo),
|
|
27357
27508
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
27358
27509
|
pollRateLimit: () => fetchRestCorePool(),
|
|
@@ -27497,12 +27648,13 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
27497
27648
|
const beforeWorktrees = parseWorktreePorcelain(
|
|
27498
27649
|
(await execFileP2("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS }).catch(() => ({ stdout: "" }))).stdout
|
|
27499
27650
|
);
|
|
27500
|
-
const
|
|
27651
|
+
const ciHeadRef = repoForPostCleanup ? await prHeadRefForCiProbe(number, repoForPostCleanup) : void 0;
|
|
27652
|
+
const ciPolicy = await resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef);
|
|
27501
27653
|
if (o.wait) {
|
|
27502
27654
|
const repo = await requireRepo(o.repo);
|
|
27503
27655
|
const baseBranch = await fetchRestPrSnapshot(number, repo).then((s) => s.baseRef).catch(() => "development");
|
|
27504
27656
|
const wait = await waitForPrChecks({
|
|
27505
|
-
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo),
|
|
27657
|
+
resolvePolicy: () => resolveMergeCiPolicyForCheckout(o.repo, ciHeadRef),
|
|
27506
27658
|
pollChecks: () => pollRestPrChecks(number, repo),
|
|
27507
27659
|
pollMergeable: () => pollRestPrMergeable(number, repo),
|
|
27508
27660
|
pollRateLimit: () => fetchRestCorePool(),
|
|
@@ -27527,44 +27679,50 @@ jsonParity(pr.command("merge <number>").description("merge a PR (squash by defau
|
|
|
27527
27679
|
let remoteDeleteAttempted = false;
|
|
27528
27680
|
let upgradedToAuto = false;
|
|
27529
27681
|
let remoteNotAttemptedReason = headIsProtected ? "protected-branch" : void 0;
|
|
27530
|
-
await
|
|
27531
|
-
|
|
27532
|
-
|
|
27533
|
-
|
|
27534
|
-
|
|
27535
|
-
|
|
27536
|
-
|
|
27537
|
-
|
|
27538
|
-
|
|
27539
|
-
|
|
27540
|
-
|
|
27541
|
-
|
|
27542
|
-
|
|
27543
|
-
|
|
27544
|
-
|
|
27545
|
-
|
|
27546
|
-
|
|
27547
|
-
|
|
27548
|
-
|
|
27549
|
-
|
|
27550
|
-
|
|
27551
|
-
|
|
27552
|
-
|
|
27553
|
-
|
|
27554
|
-
|
|
27555
|
-
|
|
27556
|
-
|
|
27557
|
-
|
|
27558
|
-
|
|
27559
|
-
|
|
27560
|
-
|
|
27561
|
-
|
|
27562
|
-
|
|
27563
|
-
|
|
27564
|
-
|
|
27565
|
-
|
|
27566
|
-
|
|
27567
|
-
|
|
27682
|
+
const overrideBody = await composeOverrideBodyFile(number, repoArgs, async (a, t) => (await execFileP2("gh", a, { timeout: t })).stdout);
|
|
27683
|
+
const bodyFile = overrideBody?.path;
|
|
27684
|
+
try {
|
|
27685
|
+
await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: o.auto, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch(async (e) => {
|
|
27686
|
+
const message = String(e.message || "");
|
|
27687
|
+
if (/already been merged/i.test(message)) {
|
|
27688
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
27689
|
+
return;
|
|
27690
|
+
}
|
|
27691
|
+
const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
|
|
27692
|
+
if (note) throw new Error(`gh pr merge ${number}: ${note}`);
|
|
27693
|
+
if (o.auto && mergeAutoRejectedPrAlreadyClean(message)) {
|
|
27694
|
+
await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: false, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
27695
|
+
const m2 = String(e2.message || "");
|
|
27696
|
+
if (/already been merged/i.test(m2)) {
|
|
27697
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
27698
|
+
return;
|
|
27699
|
+
}
|
|
27700
|
+
const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
|
|
27701
|
+
if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
|
|
27702
|
+
if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
|
|
27703
|
+
});
|
|
27704
|
+
return;
|
|
27705
|
+
}
|
|
27706
|
+
if (!o.auto && basePolicyBlocksImmediateMerge(message)) {
|
|
27707
|
+
console.warn(`pr merge: the base-branch policy blocks an immediate merge \u2014 upgrading to --auto (merges once required checks pass).`);
|
|
27708
|
+
upgradedToAuto = true;
|
|
27709
|
+
await execFileP2("gh", buildPrMergeArgs({ number, repoArgs, method, auto: true, deleteBranch: !headIsProtected, bodyFile }), { timeout: GH_MUTATION_TIMEOUT_MS }).catch((e2) => {
|
|
27710
|
+
const m2 = String(e2.message || "");
|
|
27711
|
+
if (/already been merged/i.test(m2)) {
|
|
27712
|
+
remoteNotAttemptedReason = "pr-already-merged";
|
|
27713
|
+
return;
|
|
27714
|
+
}
|
|
27715
|
+
const note2 = timeoutKillNote(e2, GH_MUTATION_TIMEOUT_MS);
|
|
27716
|
+
if (note2) throw new Error(`gh pr merge ${number}: ${note2}`);
|
|
27717
|
+
if (!ghPrMergeLocalBranchDeleteWarning(m2)) throw e2;
|
|
27718
|
+
});
|
|
27719
|
+
return;
|
|
27720
|
+
}
|
|
27721
|
+
if (!ghPrMergeLocalBranchDeleteWarning(message)) throw e;
|
|
27722
|
+
});
|
|
27723
|
+
} finally {
|
|
27724
|
+
overrideBody?.cleanup();
|
|
27725
|
+
}
|
|
27568
27726
|
const stateRead = await readGhPrStateWithRetry(
|
|
27569
27727
|
() => execFileP2("gh", ["pr", "view", number, ...repoArgs, "--json", "state", "--jq", ".state"], { timeout: GC_GH_TIMEOUT_MS2 }).then((r) => r.stdout)
|
|
27570
27728
|
);
|
|
@@ -27958,7 +28116,8 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
27958
28116
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
27959
28117
|
const fileContracts = (0, import_node_fs33.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs33.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
27960
28118
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
27961
|
-
const
|
|
28119
|
+
const sanctioned = (0, import_node_fs33.existsSync)("access-matrix.json") ? loadSanctionedAdmins((0, import_node_fs33.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
28120
|
+
const report = await auditOrgAccess(targets, deps, matrix, dataAccess, sanctioned);
|
|
27962
28121
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
27963
28122
|
if (!report.ok) process.exitCode = 1;
|
|
27964
28123
|
});
|
|
@@ -28052,13 +28211,13 @@ function stagingApplyFsGuard(home) {
|
|
|
28052
28211
|
}
|
|
28053
28212
|
program2.command("plugin-prune").description(`prune stale cached MMI plugin versions (keeps running + newest, ${PLUGIN_CACHE_KEEP} total) and orphaned temp_git_* staging dirs; dry-run unless --apply (#2903, #2990)`).option("--apply", "actually delete the stale version dirs + orphaned staging dirs (default: report only)").option("--json", "machine-readable output").action((o) => {
|
|
28054
28213
|
const plan = buildPluginCachePlan(
|
|
28055
|
-
(0,
|
|
28214
|
+
(0, import_node_os11.homedir)(),
|
|
28056
28215
|
runningPluginVersion(process.env, resolveClientVersion()),
|
|
28057
|
-
pluginCacheFsDeps((0,
|
|
28216
|
+
pluginCacheFsDeps((0, import_node_os11.homedir)(), directoryBytes),
|
|
28058
28217
|
{ withBytes: true }
|
|
28059
28218
|
);
|
|
28060
28219
|
const anythingToDelete = plan.prune.length > 0 || plan.staging.length > 0;
|
|
28061
|
-
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0,
|
|
28220
|
+
const result = o.apply && anythingToDelete ? applyPluginCachePlan(plan, (p) => (0, import_node_fs33.rmSync)(p, { recursive: true, force: true }), stagingApplyFsGuard((0, import_node_os11.homedir)())) : void 0;
|
|
28062
28221
|
const warnings = plan.prune.length > 0 ? [CONCURRENT_SESSION_WARNING] : [];
|
|
28063
28222
|
if (o.json) console.log(JSON.stringify({ ...plan, warnings, applied: result ?? null }));
|
|
28064
28223
|
else console.log(renderPluginCachePlan(plan, result));
|
|
@@ -28141,9 +28300,11 @@ program2.parseAsync(process.argv).then(() => finishCliRun()).catch((e) => failGr
|
|
|
28141
28300
|
DEFAULT_PRIORITY,
|
|
28142
28301
|
awsCallerArn,
|
|
28143
28302
|
classifyParseError,
|
|
28303
|
+
commandOwnLongFlags,
|
|
28144
28304
|
envHealLockPath,
|
|
28145
28305
|
gcPlan,
|
|
28146
28306
|
isOrgRegisteredRepo,
|
|
28307
|
+
positionalTargetForm,
|
|
28147
28308
|
registryClientDeps,
|
|
28148
28309
|
repoSlug,
|
|
28149
28310
|
shouldMarkWorktreeActivity,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.70.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",
|