@mutmutco/cli 4.5.31 → 4.5.33
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 +137 -37
- 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 {
|
|
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.
|
|
16824
|
-
tag: "v4.5.
|
|
16825
|
-
commit: "
|
|
16826
|
-
npm: "@mutmutco/cli@4.5.
|
|
16840
|
+
version: "4.5.33",
|
|
16841
|
+
tag: "v4.5.33",
|
|
16842
|
+
commit: "43f256b77935",
|
|
16843
|
+
npm: "@mutmutco/cli@4.5.33"
|
|
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.
|
|
16860
|
+
v3Target: "v4.5.33"
|
|
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.
|
|
16850
|
-
v3Target: "v4.5.
|
|
16866
|
+
mechanism: "npm dist-tag latest -> 4.5.33 and redeploy the Hub Lambda from tag v4.5.33 (43f256b77935); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
16867
|
+
v3Target: "v4.5.33 (@mutmutco/cli@4.5.33, tag commit 43f256b77935 \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"]);
|
|
@@ -19292,8 +19354,21 @@ async function pushAlignmentBranch(deps, target, tag) {
|
|
|
19292
19354
|
if (!commit) throw new Error(`git commit-tree wrote no commit for the ${target} alignment of ${tag} (#6288)`);
|
|
19293
19355
|
await deps.run("git", ["push", "origin", "--delete", branch]).catch(() => void 0);
|
|
19294
19356
|
await runGitPush(deps, ["push", "origin", `${commit}:refs/heads/${branch}`]);
|
|
19357
|
+
await sweepLandedAlignmentBranches(deps, target, branch);
|
|
19295
19358
|
return branch;
|
|
19296
19359
|
}
|
|
19360
|
+
async function sweepLandedAlignmentBranches(deps, target, keep) {
|
|
19361
|
+
const prefix = `refs/heads/release-align/${target}/`;
|
|
19362
|
+
const listed = await deps.run("git", ["ls-remote", "--heads", "origin", `${prefix}*`]).catch(() => "");
|
|
19363
|
+
for (const line of clean2(listed).split("\n")) {
|
|
19364
|
+
const [sha, ref] = line.split(" ");
|
|
19365
|
+
if (!sha || !ref?.startsWith(prefix)) continue;
|
|
19366
|
+
const name = ref.slice("refs/heads/".length);
|
|
19367
|
+
if (name === keep) continue;
|
|
19368
|
+
const landed = await deps.run("git", ["merge-base", "--is-ancestor", sha, `origin/${target}`]).then(() => true, () => false);
|
|
19369
|
+
if (landed) await deps.run("git", ["push", "origin", "--delete", name]).catch(() => void 0);
|
|
19370
|
+
}
|
|
19371
|
+
}
|
|
19297
19372
|
async function findAlignmentPr(deps, ctx, target, head) {
|
|
19298
19373
|
const out = clean2(await deps.run("gh", ["pr", "list", "--repo", ctx.repo, "--base", target, "--head", head, "--state", "all", "--json", "number,url,state"]));
|
|
19299
19374
|
if (!out) return void 0;
|
|
@@ -28647,8 +28722,14 @@ function renderPluginCachePlan(plan, applied) {
|
|
|
28647
28722
|
return lines2.join("\n");
|
|
28648
28723
|
}
|
|
28649
28724
|
|
|
28650
|
-
//
|
|
28725
|
+
// ../shared/github-quota-scope.ts
|
|
28651
28726
|
var import_node_crypto6 = require("node:crypto");
|
|
28727
|
+
function appQuotaScope(appId, installationId, origin = "https://api.github.com", resource = "graphql") {
|
|
28728
|
+
return `github-app-v1:${(0, import_node_crypto6.createHash)("sha256").update(JSON.stringify([new URL(origin).origin, appId.trim(), installationId.trim(), resource])).digest("hex")}`;
|
|
28729
|
+
}
|
|
28730
|
+
|
|
28731
|
+
// src/app-actor.ts
|
|
28732
|
+
var import_node_crypto7 = require("node:crypto");
|
|
28652
28733
|
var APP_ACTOR_ENV = "MMI_ACTOR";
|
|
28653
28734
|
var APP_VAULT_REPO = "mutmutco/MMI-Hub";
|
|
28654
28735
|
var APP_VAULT_KEYS = ["GITHUB_APP_ID", "GITHUB_APP_INSTALLATION_ID", "GITHUB_APP_PRIVATE_KEY"];
|
|
@@ -28699,10 +28780,14 @@ function mintAppJwt(appId, privateKeyPem, nowSec) {
|
|
|
28699
28780
|
exp: now + APP_JWT_TTL_S,
|
|
28700
28781
|
iss: appId
|
|
28701
28782
|
}));
|
|
28702
|
-
const signer = (0,
|
|
28783
|
+
const signer = (0, import_node_crypto7.createSign)("RSA-SHA256");
|
|
28703
28784
|
signer.update(`${header}.${payload}`);
|
|
28704
28785
|
return `${header}.${payload}.${signer.sign(privateKeyPem, "base64url")}`;
|
|
28705
28786
|
}
|
|
28787
|
+
var activeQuotaScope;
|
|
28788
|
+
function activeAppQuotaScope() {
|
|
28789
|
+
return activeQuotaScope;
|
|
28790
|
+
}
|
|
28706
28791
|
async function mintInstallationToken(deps) {
|
|
28707
28792
|
const [appId, installationId, privateKey] = await Promise.all(APP_VAULT_KEYS.map((k) => deps.fetchSecret(k)));
|
|
28708
28793
|
if (!appId || !installationId || !privateKey) {
|
|
@@ -28748,7 +28833,7 @@ async function mintInstallationToken(deps) {
|
|
|
28748
28833
|
if (!body.token || !body.expires_at) {
|
|
28749
28834
|
throw new AppActorError("app actor: mint response carried no token \u2014 automation fails closed");
|
|
28750
28835
|
}
|
|
28751
|
-
return { token: body.token, expiresAt: body.expires_at };
|
|
28836
|
+
return { token: body.token, expiresAt: body.expires_at, quotaScope: appQuotaScope(appId, installationId, process.env.GITHUB_API_URL) };
|
|
28752
28837
|
}
|
|
28753
28838
|
async function pollTokenOrUndefined(deps) {
|
|
28754
28839
|
try {
|
|
@@ -28760,6 +28845,7 @@ async function pollTokenOrUndefined(deps) {
|
|
|
28760
28845
|
var notedIneligibleCommand = false;
|
|
28761
28846
|
async function activateAppActor(commandPath3, env, mint, note = (line) => process.stderr.write(`${line}
|
|
28762
28847
|
`)) {
|
|
28848
|
+
activeQuotaScope = void 0;
|
|
28763
28849
|
const requested = (env[APP_ACTOR_ENV] ?? "").trim();
|
|
28764
28850
|
if (!requested) return "personal";
|
|
28765
28851
|
if (requested !== "app") {
|
|
@@ -28776,6 +28862,7 @@ async function activateAppActor(commandPath3, env, mint, note = (line) => proces
|
|
|
28776
28862
|
delete env.GITHUB_TOKEN;
|
|
28777
28863
|
const minted = await mint();
|
|
28778
28864
|
env.GH_TOKEN = minted.token;
|
|
28865
|
+
activeQuotaScope = minted.quotaScope;
|
|
28779
28866
|
return "app";
|
|
28780
28867
|
}
|
|
28781
28868
|
|
|
@@ -28991,6 +29078,7 @@ function registerBoardCommands(program3) {
|
|
|
28991
29078
|
if (!o.out) console.log(output);
|
|
28992
29079
|
else console.log(`Wrote board read to ${o.out} (UTF-8, ${writeUtf8Receipt(o.out, output)} bytes)`);
|
|
28993
29080
|
} catch (e) {
|
|
29081
|
+
if (o.json && e && typeof e === "object" && "quota" in e && e.quota) console.log(JSON.stringify(e.quota));
|
|
28994
29082
|
return failGraceful(`board read failed: ${withDiscoverMissDetail(e.message)}`);
|
|
28995
29083
|
}
|
|
28996
29084
|
}
|
|
@@ -31221,7 +31309,7 @@ function renderDeployPortDoctor(report) {
|
|
|
31221
31309
|
}
|
|
31222
31310
|
|
|
31223
31311
|
// src/bootstrap-drift.ts
|
|
31224
|
-
var
|
|
31312
|
+
var import_node_crypto8 = require("node:crypto");
|
|
31225
31313
|
function byteComparableSeeds(manifest, cls) {
|
|
31226
31314
|
return manifest.seeds.filter((s) => s.ownership === "org" && s.source === "self" && s.classes.includes(cls));
|
|
31227
31315
|
}
|
|
@@ -31241,7 +31329,7 @@ function compareSeedBytes(hubContent, repoContent) {
|
|
|
31241
31329
|
return normalize(hubContent) === normalize(repoContent) ? "match" : "drift";
|
|
31242
31330
|
}
|
|
31243
31331
|
function seedContentHash(content) {
|
|
31244
|
-
return (0,
|
|
31332
|
+
return (0, import_node_crypto8.createHash)("sha256").update(content.replace(/\r\n/g, "\n"), "utf8").digest("hex");
|
|
31245
31333
|
}
|
|
31246
31334
|
function auditRepoSeedDrift(repo, seeds, hubContents, repoReads) {
|
|
31247
31335
|
const byTarget = new Map(repoReads.map((r) => [r.target, r.content]));
|
|
@@ -31403,7 +31491,7 @@ function renderPropagationReport(plan) {
|
|
|
31403
31491
|
}
|
|
31404
31492
|
|
|
31405
31493
|
// src/bootstrap-propagation-identity.ts
|
|
31406
|
-
var
|
|
31494
|
+
var import_node_crypto9 = require("node:crypto");
|
|
31407
31495
|
var PROPAGATION_BRANCH_PREFIX = "seed-propagate-";
|
|
31408
31496
|
var TARGET_MARKER_NAME = "mmi-bootstrap-propagation-target";
|
|
31409
31497
|
function safeBranchPart(value, maxLength, fallback) {
|
|
@@ -31416,7 +31504,7 @@ function repoSlug2(repo) {
|
|
|
31416
31504
|
function propagationBranch(repo, target) {
|
|
31417
31505
|
const repoPart = safeBranchPart(repoSlug2(repo), 32, "repo");
|
|
31418
31506
|
const targetPart = safeBranchPart(target, 48, "target");
|
|
31419
|
-
const hash = (0,
|
|
31507
|
+
const hash = (0, import_node_crypto9.createHash)("sha256").update(repo.trim().toLowerCase()).update("\0").update(target).digest("hex").slice(0, 12);
|
|
31420
31508
|
return `${PROPAGATION_BRANCH_PREFIX}${repoPart}-${targetPart}-${hash}`;
|
|
31421
31509
|
}
|
|
31422
31510
|
function legacyPropagationBranch(repo) {
|
|
@@ -32572,7 +32660,7 @@ function renderBootstrapVerifyReport(report) {
|
|
|
32572
32660
|
}
|
|
32573
32661
|
|
|
32574
32662
|
// src/bootstrap-seed-delivery.ts
|
|
32575
|
-
var
|
|
32663
|
+
var import_node_crypto10 = require("node:crypto");
|
|
32576
32664
|
init_github_client();
|
|
32577
32665
|
var SHA = /^[a-f0-9]{40}$/;
|
|
32578
32666
|
async function readSeedFile(repo, target, ref, client) {
|
|
@@ -32605,7 +32693,7 @@ async function targetedSeedPlan(repo, slug, target, baseBranch, sourceSha, clien
|
|
|
32605
32693
|
}
|
|
32606
32694
|
const baseSha = base?.object?.sha;
|
|
32607
32695
|
if (!baseSha || !SHA.test(baseSha) || !SHA.test(sourceSha)) throw new Error("bootstrap apply: seed identity is unreadable");
|
|
32608
|
-
const identity = (0,
|
|
32696
|
+
const identity = (0, import_node_crypto10.createHash)("sha256").update(JSON.stringify([repo.toLowerCase(), target, baseSha, sourceSha])).digest("hex").slice(0, 24);
|
|
32609
32697
|
return { ...planSeedDelivery(rules, slug, baseBranch, `bootstrap-seed-${identity}`), baseSha, baseBranch };
|
|
32610
32698
|
}
|
|
32611
32699
|
async function verifyTargetedSeedBranch(repo, plan, target, client, allowMissing = false) {
|
|
@@ -34173,12 +34261,13 @@ async function stageStatus(member, stage, deps) {
|
|
|
34173
34261
|
}
|
|
34174
34262
|
}
|
|
34175
34263
|
}
|
|
34264
|
+
const lag = commit && head !== "unknown" ? commit.toLowerCase() !== head.toLowerCase() : null;
|
|
34176
34265
|
return {
|
|
34177
34266
|
stage,
|
|
34178
34267
|
head,
|
|
34179
34268
|
served,
|
|
34180
34269
|
source,
|
|
34181
|
-
lag:
|
|
34270
|
+
lag: source === "registry-pin" && lag === false ? null : lag,
|
|
34182
34271
|
...deployStatus ? { deployStatus } : {}
|
|
34183
34272
|
};
|
|
34184
34273
|
}
|
|
@@ -34208,7 +34297,7 @@ function formatSuiteStatus(report) {
|
|
|
34208
34297
|
// src/suite-train.ts
|
|
34209
34298
|
var import_node_fs29 = require("node:fs");
|
|
34210
34299
|
var import_node_path28 = require("node:path");
|
|
34211
|
-
var
|
|
34300
|
+
var import_node_crypto11 = require("node:crypto");
|
|
34212
34301
|
var SCHEMA = "mmi-suite-cut/v1";
|
|
34213
34302
|
var SHA2 = /^[a-f0-9]{40}$/i;
|
|
34214
34303
|
var RC_TAG = /^v\d+\.\d+\.\d+-rc\.\d+$/;
|
|
@@ -34243,7 +34332,7 @@ function readSuiteCut(path2) {
|
|
|
34243
34332
|
}
|
|
34244
34333
|
function writeSuiteCut(path2, record) {
|
|
34245
34334
|
(0, import_node_fs29.mkdirSync)((0, import_node_path28.dirname)(path2), { recursive: true });
|
|
34246
|
-
const temp = `${path2}.${(0,
|
|
34335
|
+
const temp = `${path2}.${(0, import_node_crypto11.randomBytes)(8).toString("hex")}.tmp`;
|
|
34247
34336
|
try {
|
|
34248
34337
|
(0, import_node_fs29.writeFileSync)(temp, JSON.stringify(record, null, 2), { flag: "wx" });
|
|
34249
34338
|
(0, import_node_fs29.renameSync)(temp, path2);
|
|
@@ -37866,7 +37955,7 @@ init_github_client();
|
|
|
37866
37955
|
|
|
37867
37956
|
// src/issue-commands.ts
|
|
37868
37957
|
var import_node_fs34 = require("node:fs");
|
|
37869
|
-
var
|
|
37958
|
+
var import_node_crypto12 = require("node:crypto");
|
|
37870
37959
|
init_cli_shared();
|
|
37871
37960
|
init_clean_exit();
|
|
37872
37961
|
init_error_codes();
|
|
@@ -38339,7 +38428,7 @@ function rowIdempotencyKey(batchKey, spec) {
|
|
|
38339
38428
|
const identity = `${spec.type}
|
|
38340
38429
|
${spec.title.trim()}
|
|
38341
38430
|
${spec.body ?? ""}`;
|
|
38342
|
-
const hash = (0,
|
|
38431
|
+
const hash = (0, import_node_crypto12.createHash)("sha256").update(identity).digest("hex").slice(0, 16);
|
|
38343
38432
|
return `${batchKey}:${hash}`;
|
|
38344
38433
|
}
|
|
38345
38434
|
var BATCH_SPEC_KEYS = /* @__PURE__ */ new Set(["type", "title", "body", "priority", "labels", "label", "parent", "repo", "surface"]);
|
|
@@ -42316,7 +42405,7 @@ ${list}`);
|
|
|
42316
42405
|
'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
42406
|
'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
42407
|
]);
|
|
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) => {
|
|
42408
|
+
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
42409
|
let parsed;
|
|
42321
42410
|
try {
|
|
42322
42411
|
parsed = parseIssueRef(ref, o.repo);
|
|
@@ -42329,6 +42418,13 @@ ${list}`);
|
|
|
42329
42418
|
const defaultPrFields = "number,title,state,url,isDraft,mergeable,mergedAt,mergeCommit,headRefName,baseRefName,author,labels";
|
|
42330
42419
|
const effective = resolvePrViewFields(o.json, defaultPrFields);
|
|
42331
42420
|
try {
|
|
42421
|
+
if (o.quotaContext) {
|
|
42422
|
+
if (o.comments || o.context || o.json) return fail("pr view: --quota-context cannot be combined with --comments, --context or --json");
|
|
42423
|
+
const data2 = await readPrQuotaContext(repo, n, activeAppQuotaScope(), defaultGitHubClient());
|
|
42424
|
+
console.log(JSON.stringify(data2));
|
|
42425
|
+
if (data2.status === "rate_limited") process.exitCode = 1;
|
|
42426
|
+
return;
|
|
42427
|
+
}
|
|
42332
42428
|
if (o.comments || o.context) {
|
|
42333
42429
|
const data2 = await runPrViewContext(queryDeps(), { number: n, repo, comments: Boolean(o.comments), context: Boolean(o.context), jsonFields: effective });
|
|
42334
42430
|
console.log(JSON.stringify(data2));
|
|
@@ -43244,6 +43340,10 @@ var NODE_VERIFY_TIMEOUT_MS = 5 * 6e4;
|
|
|
43244
43340
|
var NPM_TRAIN_TIMEOUT_MS = 6e4;
|
|
43245
43341
|
var NPM_FOLD_TIMEOUT_MS = NODE_PREPARE_TIMEOUT_MS;
|
|
43246
43342
|
var JERV_GATEWAY_DEPLOY_TIMEOUT_MS = 20 * 6e4;
|
|
43343
|
+
var GIT_COMMIT_TIMEOUT_MS = 6e4;
|
|
43344
|
+
function trainGitArgs(args) {
|
|
43345
|
+
return ["-c", "gc.auto=0", "-c", "maintenance.auto=false", ...args];
|
|
43346
|
+
}
|
|
43247
43347
|
async function announceSummaryRefusal(path2, isHub = false) {
|
|
43248
43348
|
let lines2;
|
|
43249
43349
|
try {
|
|
@@ -43283,10 +43383,10 @@ function trainApplyDeps() {
|
|
|
43283
43383
|
// #7027: every mutating train verb holds this checkout's train writer lock for its whole run.
|
|
43284
43384
|
trainWriter: (verb, cwd) => acquireTrainWriterLock(cwd, `mmi-cli devops ${verb}`),
|
|
43285
43385
|
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;
|
|
43386
|
+
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
43387
|
const watchToken = file === "gh" && args[0] === "run" && args[1] === "watch" ? await pollToken() : void 0;
|
|
43288
43388
|
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;
|
|
43389
|
+
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
43390
|
} catch (e) {
|
|
43291
43391
|
throw surfaceTrainSubprocessFailure(e, file, args);
|
|
43292
43392
|
}
|
|
@@ -48343,14 +48443,14 @@ vercel preview (${preview.branch}): ${d.state}${d.url ? ` \u2014 ${d.url}` : ""}
|
|
|
48343
48443
|
}
|
|
48344
48444
|
|
|
48345
48445
|
// src/tenant-artifact.ts
|
|
48346
|
-
var
|
|
48446
|
+
var import_node_crypto13 = require("node:crypto");
|
|
48347
48447
|
var import_node_fs48 = require("node:fs");
|
|
48348
48448
|
var import_promises10 = require("node:fs/promises");
|
|
48349
48449
|
var import_node_path45 = require("node:path");
|
|
48350
48450
|
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
48451
|
var MAX_BYTES = 5 * 1024 * 1024 * 1024;
|
|
48352
48452
|
async function sha256File(path2) {
|
|
48353
|
-
const hash = (0,
|
|
48453
|
+
const hash = (0, import_node_crypto13.createHash)("sha256");
|
|
48354
48454
|
for await (const chunk of (0, import_node_fs48.createReadStream)(path2)) hash.update(chunk);
|
|
48355
48455
|
return hash.digest("hex");
|
|
48356
48456
|
}
|
|
@@ -48580,13 +48680,13 @@ async function executeWaveLand(plan, deps) {
|
|
|
48580
48680
|
|
|
48581
48681
|
// src/dist-drift.ts
|
|
48582
48682
|
var import_node_child_process17 = require("node:child_process");
|
|
48583
|
-
var
|
|
48683
|
+
var import_node_crypto15 = require("node:crypto");
|
|
48584
48684
|
var import_node_fs50 = require("node:fs");
|
|
48585
48685
|
var import_node_os21 = require("node:os");
|
|
48586
48686
|
var import_node_path47 = require("node:path");
|
|
48587
48687
|
|
|
48588
48688
|
// ../scripts/distribution-digest.mjs
|
|
48589
|
-
var
|
|
48689
|
+
var import_node_crypto14 = require("node:crypto");
|
|
48590
48690
|
var import_node_fs49 = require("node:fs");
|
|
48591
48691
|
var import_node_path46 = require("node:path");
|
|
48592
48692
|
var slash = (value) => value.replaceAll("\\", "/");
|
|
@@ -48599,7 +48699,7 @@ function repoPath(root, declaredPath, label) {
|
|
|
48599
48699
|
return target;
|
|
48600
48700
|
}
|
|
48601
48701
|
function digestFiles(files) {
|
|
48602
|
-
const hash = (0,
|
|
48702
|
+
const hash = (0, import_node_crypto14.createHash)("sha256");
|
|
48603
48703
|
for (const file of [...files].sort((a, b) => a.relative.localeCompare(b.relative))) {
|
|
48604
48704
|
const content = file.stat.isSymbolicLink() ? Buffer.from((0, import_node_fs49.readlinkSync)(file.absolute), "utf8") : (0, import_node_fs49.readFileSync)(file.absolute);
|
|
48605
48705
|
hash.update(file.relative, "utf8");
|
|
@@ -48629,7 +48729,7 @@ var DIST_ARTIFACTS = [
|
|
|
48629
48729
|
];
|
|
48630
48730
|
var BOM_DIST_TREE_ID = "mmi-cli-dist";
|
|
48631
48731
|
var ABSENT = "absent";
|
|
48632
|
-
var sha256 = (bytes) => `sha256:${(0,
|
|
48732
|
+
var sha256 = (bytes) => `sha256:${(0, import_node_crypto15.createHash)("sha256").update(bytes).digest("hex")}`;
|
|
48633
48733
|
function artifactDrift(path2, committedBytes, rebuiltBytes) {
|
|
48634
48734
|
const committed = committedBytes ? sha256(committedBytes) : ABSENT;
|
|
48635
48735
|
const rebuiltExpected = rebuiltBytes ? sha256(rebuiltBytes) : ABSENT;
|
package/package.json
CHANGED