@mutmutco/cli 4.5.31 → 4.5.32

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.
Files changed (2) hide show
  1. package/dist/main.cjs +124 -37
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -362,6 +362,16 @@ var init_error_codes = __esm({
362
362
  });
363
363
 
364
364
  // src/github-client.ts
365
+ var github_client_exports = {};
366
+ __export(github_client_exports, {
367
+ GitHubApiError: () => GitHubApiError,
368
+ classifyGhTokenExecFailure: () => classifyGhTokenExecFailure,
369
+ createGitHubClient: () => createGitHubClient,
370
+ defaultGitHubClient: () => defaultGitHubClient,
371
+ githubToken: () => githubToken,
372
+ githubTokenRead: () => githubTokenRead,
373
+ rateLimitResetNote: () => rateLimitResetNote
374
+ });
365
375
  function classifyGhTokenExecFailure(e) {
366
376
  const err = e;
367
377
  const stderr = typeof err?.stderr === "string" ? err.stderr.trim() : "";
@@ -403,7 +413,7 @@ function rateLimitFromResponse(res, detail) {
403
413
  const resetHeader = res.headers.get("x-ratelimit-reset");
404
414
  const reset = resetHeader && /^\d+$/.test(resetHeader) ? Number(resetHeader) : void 0;
405
415
  const rateLimited = (res.status === 403 || res.status === 429) && remaining === "0" || /rate limit|secondary rate|abuse detection/i.test(detail);
406
- return { rateLimited, ...rateLimited && reset !== void 0 ? { rateLimitReset: reset } : {} };
416
+ return { rateLimited, ...remaining === "0" && !/secondary|abuse/i.test(detail) && res.headers.get("x-ratelimit-resource") ? { rateLimitResource: res.headers.get("x-ratelimit-resource") } : {}, ...rateLimited && reset !== void 0 ? { rateLimitReset: reset } : {} };
407
417
  }
408
418
  function joinUrl(base, path2) {
409
419
  if (path2.startsWith("http://") || path2.startsWith("https://")) return path2;
@@ -456,7 +466,7 @@ async function errorFromResponse(res) {
456
466
  const budgetNote = limit.rateLimited ? ` \u2014 ${rateLimitResetNote(limit.rateLimitReset)}` : "";
457
467
  return new GitHubApiError(`HTTP ${res.status}${suffix}${budgetNote} (${res.url})`, {
458
468
  status: res.status,
459
- ...limit.rateLimited ? { rateLimited: true, rateLimitReset: limit.rateLimitReset } : {}
469
+ ...limit.rateLimited ? { rateLimited: true, rateLimitReset: limit.rateLimitReset, rateLimitResource: limit.rateLimitResource } : {}
460
470
  });
461
471
  }
462
472
  function createGitHubClient(options = {}) {
@@ -530,6 +540,7 @@ function createGitHubClient(options = {}) {
530
540
  status: 200,
531
541
  graphqlErrors: parsed.errors,
532
542
  rateLimited: true,
543
+ ...!/secondary|abuse/i.test(message2) && parsed.errors.some((e) => e.type === "RATE_LIMITED") && (!res.headers.get("x-ratelimit-resource") || res.headers.get("x-ratelimit-resource") === "graphql") ? { rateLimitResource: "graphql" } : {},
533
544
  rateLimitReset: reset
534
545
  });
535
546
  }
@@ -560,6 +571,7 @@ var init_github_client = __esm({
560
571
  rateLimited;
561
572
  /** Epoch seconds when the exhausted pool resets (`X-Ratelimit-Reset` of the refusing response). */
562
573
  rateLimitReset;
574
+ rateLimitResource;
563
575
  constructor(message2, opts = {}) {
564
576
  super(message2);
565
577
  this.name = "GitHubApiError";
@@ -568,6 +580,7 @@ var init_github_client = __esm({
568
580
  this.graphqlErrors = opts.graphqlErrors;
569
581
  this.rateLimited = opts.rateLimited;
570
582
  this.rateLimitReset = opts.rateLimitReset;
583
+ this.rateLimitResource = opts.rateLimitResource;
571
584
  }
572
585
  };
573
586
  DEFAULT_TIMEOUT_MS = 2e4;
@@ -5470,7 +5483,7 @@ function isSnapshotShape(body) {
5470
5483
  const b = body;
5471
5484
  const rate = b?.github?.rateLimit;
5472
5485
  const staleValid = b?.stale === void 0 || typeof b.stale === "object" && b.stale !== null && Number.isFinite(b.stale.ageMs) && b.stale.ageMs >= 0;
5473
- const githubValid = b?.github === void 0 || b.github.credential === "app_installation" && Number.isFinite(rate?.limit) && Number.isFinite(rate?.remaining) && Number.isFinite(rate?.cost) && typeof rate?.resetAt === "string";
5486
+ const githubValid = b?.github === void 0 || b.github.credential === "app_installation" && (rate === void 0 || Number.isFinite(rate?.limit) && Number.isFinite(rate?.remaining) && Number.isFinite(rate?.cost) && typeof rate?.resetAt === "string");
5474
5487
  return Boolean(
5475
5488
  b && typeof b === "object" && typeof b.project?.id === "string" && typeof b.project?.title === "string" && typeof b.viewer === "string" && b.viewer.length > 0 && Array.isArray(b.nodes) && Array.isArray(b.writableRepos) && Array.isArray(b.unreadableRepos) && Array.isArray(b.pullRequests) && Array.isArray(b.warnings) && typeof b.partial === "boolean" && githubValid && staleValid
5476
5489
  );
@@ -5511,7 +5524,11 @@ async function fetchHubBoardSnapshot(request, deps) {
5511
5524
  const body2 = await res.json().catch(() => null);
5512
5525
  if (body2?.error === "rate_limited") {
5513
5526
  const reset = typeof body2.resetAt === "string" ? `; resets ${body2.resetAt}` : "; reset time unavailable";
5514
- return { state: "unavailable", reason: `GitHub App quota exhausted${reset}` };
5527
+ return {
5528
+ state: "unavailable",
5529
+ reason: `GitHub App quota exhausted${reset}`,
5530
+ ...body2.github?.credential === "app_installation" && body2.github.resource === "graphql" && /^github-app-v1:[a-f0-9]{64}$/.test(body2.github.quotaScope ?? "") ? { quota: { status: "rate_limited", ...typeof body2.resetAt === "string" ? { resetAt: body2.resetAt } : {}, github: { credential: "app_installation", resource: "graphql", quotaScope: body2.github.quotaScope } } } : {}
5531
+ };
5515
5532
  }
5516
5533
  }
5517
5534
  if (!res.ok) return { state: "unavailable", reason: `board snapshot HTTP ${res.status}` };
@@ -12280,7 +12297,7 @@ function renderBoardItem(item) {
12280
12297
  }
12281
12298
  function renderBoardReport(report) {
12282
12299
  const lines2 = [`Board \xB7 ${report.project.title} \xB7 @${report.viewer}`, renderBoardSource()];
12283
- if (report.github) {
12300
+ if (report.github?.rateLimit) {
12284
12301
  const { limit, remaining, cost, resetAt } = report.github.rateLimit;
12285
12302
  lines2.push(`github: app installation \xB7 GraphQL ${remaining}/${limit} remaining \xB7 cost ${cost} \xB7 resets ${resetAt}`);
12286
12303
  }
@@ -12510,7 +12527,7 @@ async function collectBoardPreferringSnapshot(cfg, options, deps) {
12510
12527
  };
12511
12528
  }
12512
12529
  if (options.snapshotOnly && attempt?.state === "unavailable") {
12513
- throw new Error(`Hub board snapshot unavailable (${attempt.reason}) \u2014 --snapshot-only refuses the direct user-auth fallback`);
12530
+ throw Object.assign(new Error(`Hub board snapshot unavailable (${attempt.reason}) \u2014 --snapshot-only refuses the direct user-auth fallback`), { quota: attempt.quota });
12514
12531
  }
12515
12532
  const collected = await collectBoardItems(cfg, { repo: options.repo, allowPartial: options.allowPartial, activeOnly: true }, deps);
12516
12533
  if (attempt?.state === "unavailable") {
@@ -12535,7 +12552,7 @@ async function readBoard(options, deps = {}) {
12535
12552
  unknown: new Set(snapshot.unreadableRepos.map((entry) => entry.repo.toLowerCase()))
12536
12553
  };
12537
12554
  pullRequests = snapshot.pullRequests;
12538
- github = snapshot.github;
12555
+ github = snapshot.github && { ...snapshot.github, ...snapshot.quotaResetAt ? { quotaResetAt: snapshot.quotaResetAt } : {} };
12539
12556
  } else {
12540
12557
  const probed = await resolveWritableReposForClaimables(collected.items, client);
12541
12558
  collected.warnings.push(...probed.warnings);
@@ -16820,10 +16837,10 @@ var rollout_plan_default = {
16820
16837
  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)."
16821
16838
  },
16822
16839
  baseline: {
16823
- version: "4.5.31",
16824
- tag: "v4.5.31",
16825
- commit: "9e03ef1880b6",
16826
- npm: "@mutmutco/cli@4.5.31"
16840
+ version: "4.5.32",
16841
+ tag: "v4.5.32",
16842
+ commit: "7c2492e36877",
16843
+ npm: "@mutmutco/cli@4.5.32"
16827
16844
  },
16828
16845
  exitCriterion: "fleet-n-of-n",
16829
16846
  hubOnlyShortcut: "forbidden",
@@ -16840,14 +16857,14 @@ var rollout_plan_default = {
16840
16857
  repo: "mutmutco/mmi-hub",
16841
16858
  role: "canary",
16842
16859
  schedule: "train",
16843
- v3Target: "v4.5.31"
16860
+ v3Target: "v4.5.32"
16844
16861
  }
16845
16862
  ],
16846
16863
  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.",
16847
16864
  rollback: {
16848
16865
  independent: true,
16849
- mechanism: "npm dist-tag latest -> 4.5.31 and redeploy the Hub Lambda from tag v4.5.31 (9e03ef1880b6); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16850
- v3Target: "v4.5.31 (@mutmutco/cli@4.5.31, tag commit 9e03ef1880b6 \u2014 last known-good release carrying the repo-index v4-only contract)"
16866
+ mechanism: "npm dist-tag latest -> 4.5.32 and redeploy the Hub Lambda from tag v4.5.32 (7c2492e36877); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
16867
+ v3Target: "v4.5.32 (@mutmutco/cli@4.5.32, tag commit 7c2492e36877 \u2014 last known-good release carrying the repo-index v4-only contract)"
16851
16868
  }
16852
16869
  },
16853
16870
  {
@@ -17946,6 +17963,51 @@ function registerQueryCommands(program3) {
17946
17963
  }
17947
17964
  });
17948
17965
  }
17966
+ async function readPrQuotaContext(repo, number, quotaScope, client) {
17967
+ if (!quotaScope) throw new Error("PR quota context requires the verified App actor");
17968
+ const [owner, name] = repo.split("/");
17969
+ const github = { credential: "app_installation", quotaScope, resource: "graphql" };
17970
+ try {
17971
+ const issues = [];
17972
+ const checks = [];
17973
+ let issuesCursor;
17974
+ let checksCursor;
17975
+ let readIssues = true;
17976
+ let readChecks = true;
17977
+ for (let page = 0; page < 100; page++) {
17978
+ const data = await client.graphql(`query($owner:String!,$name:String!,$number:Int!,$issuesCursor:String,$checksCursor:String,$readIssues:Boolean!,$readChecks:Boolean!) {
17979
+ rateLimit { limit remaining resetAt }
17980
+ repository(owner:$owner,name:$name) { pullRequest(number:$number) {
17981
+ number closingIssuesReferences(first:100,after:$issuesCursor) @include(if:$readIssues) { nodes { number } pageInfo { hasNextPage endCursor } }
17982
+ commits(last:1) @include(if:$readChecks) { nodes { commit { statusCheckRollup { contexts(first:100,after:$checksCursor) {
17983
+ nodes { ... on CheckRun { status conclusion } ... on StatusContext { state } }
17984
+ pageInfo { hasNextPage endCursor }
17985
+ } } } } }
17986
+ } }
17987
+ }`, { owner, name, number, issuesCursor, checksCursor, readIssues, readChecks });
17988
+ const pr = data.repository?.pullRequest;
17989
+ if (!pr) throw new Error("PR quota context could not resolve the PR");
17990
+ const issuePage = pr.closingIssuesReferences;
17991
+ const checkPage = pr.commits?.nodes[0]?.commit.statusCheckRollup?.contexts;
17992
+ if (readIssues && !issuePage) throw new Error("PR closing references unreadable");
17993
+ if (readIssues) issues.push(...issuePage.nodes);
17994
+ if (readChecks) checks.push(...checkPage?.nodes ?? []);
17995
+ readIssues = readIssues && issuePage.pageInfo.hasNextPage;
17996
+ readChecks = readChecks && (checkPage?.pageInfo.hasNextPage ?? false);
17997
+ if (!readIssues && !readChecks) return { number: pr.number, closingIssuesReferences: issues, statusCheckRollup: checks, github: { ...github, rateLimit: data.rateLimit } };
17998
+ const nextIssues = issuePage?.pageInfo.endCursor;
17999
+ const nextChecks = checkPage?.pageInfo.endCursor;
18000
+ if (readIssues && (!nextIssues || nextIssues === issuesCursor) || readChecks && (!nextChecks || nextChecks === checksCursor)) throw new Error("PR quota context pagination cursor did not advance");
18001
+ issuesCursor = nextIssues;
18002
+ checksCursor = nextChecks;
18003
+ }
18004
+ throw new Error("PR quota context pagination exceeded 100 pages");
18005
+ } catch (error) {
18006
+ const { GitHubApiError: GitHubApiError2 } = await Promise.resolve().then(() => (init_github_client(), github_client_exports));
18007
+ if (!(error instanceof GitHubApiError2) || !error.rateLimited || error.rateLimitResource !== "graphql") throw error;
18008
+ return { status: "rate_limited", github, ...error.rateLimitReset === void 0 ? {} : { resetAt: new Date(error.rateLimitReset * 1e3).toISOString() } };
18009
+ }
18010
+ }
17949
18011
 
17950
18012
  // src/fleet-track-inventory.ts
17951
18013
  var NO_DEPLOY_SURFACE_MODELS = /* @__PURE__ */ new Set(["none", "content"]);
@@ -28647,8 +28709,14 @@ function renderPluginCachePlan(plan, applied) {
28647
28709
  return lines2.join("\n");
28648
28710
  }
28649
28711
 
28650
- // src/app-actor.ts
28712
+ // ../shared/github-quota-scope.ts
28651
28713
  var import_node_crypto6 = require("node:crypto");
28714
+ function appQuotaScope(appId, installationId, origin = "https://api.github.com", resource = "graphql") {
28715
+ return `github-app-v1:${(0, import_node_crypto6.createHash)("sha256").update(JSON.stringify([new URL(origin).origin, appId.trim(), installationId.trim(), resource])).digest("hex")}`;
28716
+ }
28717
+
28718
+ // src/app-actor.ts
28719
+ var import_node_crypto7 = require("node:crypto");
28652
28720
  var APP_ACTOR_ENV = "MMI_ACTOR";
28653
28721
  var APP_VAULT_REPO = "mutmutco/MMI-Hub";
28654
28722
  var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
@@ -28699,10 +28767,14 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
28699
28767
  exp: now + APP_JWT_TTL_S,
28700
28768
  iss: appId
28701
28769
  }));
28702
- const signer = (0, import_node_crypto6.createSign)("RSA-SHA256");
28770
+ const signer = (0, import_node_crypto7.createSign)("RSA-SHA256");
28703
28771
  signer.update(`${header}.${payload}`);
28704
28772
  return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
28705
28773
  }
28774
+ var activeQuotaScope;
28775
+ function activeAppQuotaScope() {
28776
+ return activeQuotaScope;
28777
+ }
28706
28778
  async function mintInstallationToken(deps) {
28707
28779
  const [appId, installationId, privateKey] = await Promise.all(APP_VAULT_KEYS.map((k) => deps.fetchSecret(k)));
28708
28780
  if (!appId || !installationId || !privateKey) {
@@ -28748,7 +28820,7 @@ async function mintInstallationToken(deps) {
28748
28820
  if (!body.token || !body.expires_at) {
28749
28821
  throw new AppActorError("app actor: mint response carried no token \u2014 automation fails closed");
28750
28822
  }
28751
- return { token: body.token, expiresAt: body.expires_at };
28823
+ return { token: body.token, expiresAt: body.expires_at, quotaScope: appQuotaScope(appId, installationId, process.env.GITHUB_API_URL) };
28752
28824
  }
28753
28825
  async function pollTokenOrUndefined(deps) {
28754
28826
  try {
@@ -28760,6 +28832,7 @@ async function pollTokenOrUndefined(deps) {
28760
28832
  var notedIneligibleCommand = false;
28761
28833
  async function activateAppActor(commandPath3, env, mint, note = (line) => process.stderr.write(`${line}
28762
28834
  `)) {
28835
+ activeQuotaScope = void 0;
28763
28836
  const requested = (env[APP_ACTOR_ENV] ?? "").trim();
28764
28837
  if (!requested) return "personal";
28765
28838
  if (requested !== "app") {
@@ -28776,6 +28849,7 @@ async function activateAppActor(commandPath3, env, mint, note = (line) => proces
28776
28849
  delete env.GITHUB_TOKEN;
28777
28850
  const minted = await mint();
28778
28851
  env.GH_TOKEN = minted.token;
28852
+ activeQuotaScope = minted.quotaScope;
28779
28853
  return "app";
28780
28854
  }
28781
28855
 
@@ -28991,6 +29065,7 @@ function registerBoardCommands(program3) {
28991
29065
  if (!o.out) console.log(output);
28992
29066
  else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
28993
29067
  } catch (e) {
29068
+ if (o.json && e && typeof e === "object" && "quota" in e && e.quota) console.log(JSON.stringify(e.quota));
28994
29069
  return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
28995
29070
  }
28996
29071
  }
@@ -31221,7 +31296,7 @@ function renderDeployPortDoctor(report) {
31221
31296
  }
31222
31297
 
31223
31298
  // src/bootstrap-drift.ts
31224
- var import_node_crypto7 = require("node:crypto");
31299
+ var import_node_crypto8 = require("node:crypto");
31225
31300
  function byteComparableSeeds(manifest, cls) {
31226
31301
  return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
31227
31302
  }
@@ -31241,7 +31316,7 @@ function compareSeedBytes(hubContent, repoContent) {
31241
31316
  return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
31242
31317
  }
31243
31318
  function seedContentHash(content) {
31244
- return (0, import_node_crypto7.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
31319
+ return (0, import_node_crypto8.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
31245
31320
  }
31246
31321
  function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
31247
31322
  const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
@@ -31403,7 +31478,7 @@ function renderPropagationReport(plan) {
31403
31478
  }
31404
31479
 
31405
31480
  // src/bootstrap-propagation-identity.ts
31406
- var import_node_crypto8 = require("node:crypto");
31481
+ var import_node_crypto9 = require("node:crypto");
31407
31482
  var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
31408
31483
  var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
31409
31484
  function safeBranchPart(value, maxLength, fallback) {
@@ -31416,7 +31491,7 @@ function repoSlug2(repo) {
31416
31491
  function propagationBranch(repo, target) {
31417
31492
  const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
31418
31493
  const targetPart = safeBranchPart(target, 48, "target");
31419
- const hash = (0, import_node_crypto8.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
31494
+ const hash = (0, import_node_crypto9.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
31420
31495
  return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
31421
31496
  }
31422
31497
  function legacyPropagationBranch(repo) {
@@ -32572,7 +32647,7 @@ function renderBootstrapVerifyReport(report) {
32572
32647
  }
32573
32648
 
32574
32649
  // src/bootstrap-seed-delivery.ts
32575
- var import_node_crypto9 = require("node:crypto");
32650
+ var import_node_crypto10 = require("node:crypto");
32576
32651
  init_github_client();
32577
32652
  var SHA = /^[a-f0-9]{40}$/;
32578
32653
  async function readSeedFile(repo, target, ref, client) {
@@ -32605,7 +32680,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
32605
32680
  }
32606
32681
  const baseSha = base?.object?.sha;
32607
32682
  if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
32608
- const identity = (0, import_node_crypto9.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
32683
+ const identity = (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
32609
32684
  return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
32610
32685
  }
32611
32686
  async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
@@ -34173,12 +34248,13 @@ async function stageStatus(member, stage, deps) {
34173
34248
  }
34174
34249
  }
34175
34250
  }
34251
+ const lag = commit && head !== "unknown" ? commit.toLowerCase() !== head.toLowerCase() : null;
34176
34252
  return {
34177
34253
  stage,
34178
34254
  head,
34179
34255
  served,
34180
34256
  source,
34181
- lag: commit && head !== "unknown" ? commit.toLowerCase() !== head.toLowerCase() : null,
34257
+ lag: source === "registry-pin" && lag === false ? null : lag,
34182
34258
  ...deployStatus ? { deployStatus } : {}
34183
34259
  };
34184
34260
  }
@@ -34208,7 +34284,7 @@ function formatSuiteStatus(report) {
34208
34284
  // src/suite-train.ts
34209
34285
  var import_node_fs29 = require("node:fs");
34210
34286
  var import_node_path28 = require("node:path");
34211
- var import_node_crypto10 = require("node:crypto");
34287
+ var import_node_crypto11 = require("node:crypto");
34212
34288
  var SCHEMA = "mmi-suite-cut/v1";
34213
34289
  var SHA2 = /^[a-f0-9]{40}$/i;
34214
34290
  var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
@@ -34243,7 +34319,7 @@ function readSuiteCut(path2) {
34243
34319
  }
34244
34320
  function writeSuiteCut(path2, record) {
34245
34321
  (0, import_node_fs29.mkdirSync)((0, import_node_path28.dirname)(path2), { recursive: true });
34246
- const temp = `${path2}.${(0, import_node_crypto10.randomBytes)(8).toString("hex")}.tmp`;
34322
+ const temp = `${path2}.${(0, import_node_crypto11.randomBytes)(8).toString("hex")}.tmp`;
34247
34323
  try {
34248
34324
  (0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
34249
34325
  (0, import_node_fs29.renameSync)(temp, path2);
@@ -37866,7 +37942,7 @@ init_github_client();
37866
37942
 
37867
37943
  // src/issue-commands.ts
37868
37944
  var import_node_fs34 = require("node:fs");
37869
- var import_node_crypto11 = require("node:crypto");
37945
+ var import_node_crypto12 = require("node:crypto");
37870
37946
  init_cli_shared();
37871
37947
  init_clean_exit();
37872
37948
  init_error_codes();
@@ -38339,7 +38415,7 @@ function rowIdempotencyKey(batchKey, spec) {
38339
38415
  const identity = `${spec.type}
38340
38416
  ${spec.title.trim()}
38341
38417
  ${spec.body ?? ""}`;
38342
- const hash = (0, import_node_crypto11.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
38418
+ const hash = (0, import_node_crypto12.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
38343
38419
  return `${batchKey}:${hash}`;
38344
38420
  }
38345
38421
  var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
@@ -42316,7 +42392,7 @@ ${list}`);
42316
42392
  'Keep closing keywords on a dedicated line ("Closes #N") \u2014 GitHub parses close/fix/resolve + #N even mid-sentence, so pr create rewrites such prose to "part of #N" (#6039).',
42317
42393
  'When acceptance is only provable AFTER deploy, link with "Part of #N", never "Closes" \u2014 a closing reference closes the issue and advances its board card to Done on merge, before the acceptance evidence exists. Close it on that evidence instead (#6407).'
42318
42394
  ]);
42319
- pr.command("view <ref>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) \u2014 read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").action(async (ref, o) => {
42395
+ pr.command("view <ref>").description("read a PR as structured JSON (merged state, head/base, URL, merge commit) \u2014 the mmi-cli read path (#2347). --comments folds in every comment; --context also adds linkedIssues (the issues it closes/references, #2894)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json [fields...]", 'gh --json field list (overrides the default field set). Accepts commas, spaces, or repeated --json flags \u2014 in PowerShell an unquoted comma list is an array literal, so QUOTE it: --json "state,baseRefName,mergeCommit"').option("--comments", "include every comment (body + comments in one call) \u2014 read the whole PR before landing it (#2894)").option("--context", "full working context in one call: implies --comments and also adds linkedIssues (the issues the PR closes/references) (#2894)").option("--quota-context", "fixed App-only PR check read with verified quota scope and reset for harnesses").action(async (ref, o) => {
42320
42396
  let parsed;
42321
42397
  try {
42322
42398
  parsed = parseIssueRef(ref, o.repo);
@@ -42329,6 +42405,13 @@ ${list}`);
42329
42405
  const defaultPrFields = "number,title,state,url,isDraft,mergeable,mergedAt,mergeCommit,headRefName,baseRefName,author,labels";
42330
42406
  const effective = resolvePrViewFields(o.json, defaultPrFields);
42331
42407
  try {
42408
+ if (o.quotaContext) {
42409
+ if (o.comments || o.context || o.json) return fail("pr view: --quota-context cannot be combined with --comments, --context or --json");
42410
+ const data2 = await readPrQuotaContext(repo, n, activeAppQuotaScope(), defaultGitHubClient());
42411
+ console.log(JSON.stringify(data2));
42412
+ if (data2.status === "rate_limited") process.exitCode = 1;
42413
+ return;
42414
+ }
42332
42415
  if (o.comments || o.context) {
42333
42416
  const data2 = await runPrViewContext(queryDeps(), { number: n, repo, comments: Boolean(o.comments), context: Boolean(o.context), jsonFields: effective });
42334
42417
  console.log(JSON.stringify(data2));
@@ -43244,6 +43327,10 @@ var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
43244
43327
  var NPM_TRAIN_TIMEOUT_MS = 6e4;
43245
43328
  var NPM_FOLD_TIMEOUT_MS = NODE_PREPARE_TIMEOUT_MS;
43246
43329
  var JERV_GATEWAY_DEPLOY_TIMEOUT_MS = 20 * 6e4;
43330
+ var GIT_COMMIT_TIMEOUT_MS = 6e4;
43331
+ function trainGitArgs(args) {
43332
+ return ["-c", "gc.auto=0", "-c", "maintenance.auto=false", ...args];
43333
+ }
43247
43334
  async function announceSummaryRefusal(path2, isHub = false) {
43248
43335
  let lines2;
43249
43336
  try {
@@ -43283,10 +43370,10 @@ function trainApplyDeps() {
43283
43370
  // #7027: every mutating train verb holds this checkout's train writer lock for its whole run.
43284
43371
  trainWriter: (verb, cwd) => acquireTrainWriterLock(cwd, `mmi-cli devops ${verb}`),
43285
43372
  run: async (file, args) => {
43286
- const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" && (args[0] === "version" || args[0] === "ci" || args[0] === "install") ? NPM_FOLD_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && /jerv-gateway-release-deploy\.sh$/.test(args[0] ?? "") ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43373
+ const timeout = file === "node" && args[1] === "prepare" ? NODE_PREPARE_TIMEOUT_MS : file === "node" && args[1] === "verify" ? NODE_VERIFY_TIMEOUT_MS : file === "npm" && (args[0] === "version" || args[0] === "ci" || args[0] === "install") ? NPM_FOLD_TIMEOUT_MS : file === "npm" ? NPM_TRAIN_TIMEOUT_MS : file === "bash" && /jerv-gateway-release-deploy\.sh$/.test(args[0] ?? "") ? JERV_GATEWAY_DEPLOY_TIMEOUT_MS : file === "git" && args[0] === "commit" ? GIT_COMMIT_TIMEOUT_MS : file !== "gh" ? GIT_TIMEOUT_MS : args[0] === "run" && args[1] === "watch" ? GH_RUN_WATCH_TIMEOUT_MS : GH_TRAIN_TIMEOUT_MS;
43287
43374
  const watchToken = file === "gh" && args[0] === "run" && args[1] === "watch" ? await pollToken() : void 0;
43288
43375
  try {
43289
- return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, args, { timeout, ...watchToken ? { env: { ...process.env, GH_TOKEN: watchToken } } : {} })).stdout;
43376
+ return isWin3 && file === "npm" ? (await execFileP("cmd.exe", ["/c", "npm", ...args], { timeout })).stdout : (await execFileP(file, file === "git" ? trainGitArgs(args) : args, { timeout, ...watchToken ? { env: { ...process.env, GH_TOKEN: watchToken } } : {} })).stdout;
43290
43377
  } catch (e) {
43291
43378
  throw surfaceTrainSubprocessFailure(e, file, args);
43292
43379
  }
@@ -48343,14 +48430,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
48343
48430
  }
48344
48431
 
48345
48432
  // src/tenant-artifact.ts
48346
- var import_node_crypto12 = require("node:crypto");
48433
+ var import_node_crypto13 = require("node:crypto");
48347
48434
  var import_node_fs48 = require("node:fs");
48348
48435
  var import_promises10 = require("node:fs/promises");
48349
48436
  var import_node_path45 = require("node:path");
48350
48437
  var ARTIFACT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
48351
48438
  var MAX_BYTES = 5 * 1024 * 1024 * 1024;
48352
48439
  async function sha256File(path2) {
48353
- const hash = (0, import_node_crypto12.createHash)("sha256");
48440
+ const hash = (0, import_node_crypto13.createHash)("sha256");
48354
48441
  for await (const chunk of (0, import_node_fs48.createReadStream)(path2)) hash.update(chunk);
48355
48442
  return hash.digest("hex");
48356
48443
  }
@@ -48580,13 +48667,13 @@ async function executeWaveLand(plan, deps) {
48580
48667
 
48581
48668
  // src/dist-drift.ts
48582
48669
  var import_node_child_process17 = require("node:child_process");
48583
- var import_node_crypto14 = require("node:crypto");
48670
+ var import_node_crypto15 = require("node:crypto");
48584
48671
  var import_node_fs50 = require("node:fs");
48585
48672
  var import_node_os21 = require("node:os");
48586
48673
  var import_node_path47 = require("node:path");
48587
48674
 
48588
48675
  // ../scripts/distribution-digest.mjs
48589
- var import_node_crypto13 = require("node:crypto");
48676
+ var import_node_crypto14 = require("node:crypto");
48590
48677
  var import_node_fs49 = require("node:fs");
48591
48678
  var import_node_path46 = require("node:path");
48592
48679
  var slash = (value) => value.replaceAll("\\", "/");
@@ -48599,7 +48686,7 @@ function repoPath(root, declaredPath, label) {
48599
48686
  return target;
48600
48687
  }
48601
48688
  function digestFiles(files) {
48602
- const hash = (0, import_node_crypto13.createHash)("sha256");
48689
+ const hash = (0, import_node_crypto14.createHash)("sha256");
48603
48690
  for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
48604
48691
  const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs49.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs49.readFileSync)(file.absolute);
48605
48692
  hash.update(file.relative, "utf8");
@@ -48629,7 +48716,7 @@ var DIST_ARTIFACTS = [
48629
48716
  ];
48630
48717
  var BOM_DIST_TREE_ID = "mmi-cli-dist";
48631
48718
  var ABSENT = "absent";
48632
- var sha256 = (bytes) => `sha256:${(0, import_node_crypto14.createHash)("sha256").update(bytes).digest("hex")}`;
48719
+ var sha256 = (bytes) => `sha256:${(0, import_node_crypto15.createHash)("sha256").update(bytes).digest("hex")}`;
48633
48720
  function artifactDrift(path2, committedBytes, rebuiltBytes) {
48634
48721
  const committed = committedBytes ? sha256(committedBytes) : ABSENT;
48635
48722
  const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.31",
3
+ "version": "4.5.32",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",