@mutmutco/cli 4.5.63 → 4.5.64
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 +404 -118
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -587,7 +587,147 @@ var init_github_client = __esm({
|
|
|
587
587
|
}
|
|
588
588
|
});
|
|
589
589
|
|
|
590
|
+
// src/github-quota.ts
|
|
591
|
+
function hubQuotaResetWaitMs(resetAt, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS, jitterMs = 0) {
|
|
592
|
+
if (typeof resetAt !== "string" || !resetAt) return void 0;
|
|
593
|
+
const resetMs = Date.parse(resetAt);
|
|
594
|
+
if (!Number.isFinite(resetMs)) return void 0;
|
|
595
|
+
const wait = rateLimitWaitMs(resetMs / 1e3, nowMs, capMs);
|
|
596
|
+
if (wait === void 0) return void 0;
|
|
597
|
+
return wait + Math.max(0, jitterMs);
|
|
598
|
+
}
|
|
599
|
+
function isRateLimitedError(e) {
|
|
600
|
+
return e instanceof RateLimitedError;
|
|
601
|
+
}
|
|
602
|
+
function isRateLimitText(text) {
|
|
603
|
+
return /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection|HTTP\/?\d*(?:\.\d)?\s+429\b|\b429\b.*rate limit/i.test(text) || /rate limit/i.test(text) && !/not rate.?limit/i.test(text);
|
|
604
|
+
}
|
|
605
|
+
function isRateLimitRefusal(e) {
|
|
606
|
+
if (e instanceof GitHubApiError && e.rateLimited) return true;
|
|
607
|
+
if (e instanceof GitHubApiError && e.status === 403) {
|
|
608
|
+
return /rate limit|abuse detection|secondary rate/i.test(e.message);
|
|
609
|
+
}
|
|
610
|
+
if (e instanceof RateLimitedError) return true;
|
|
611
|
+
const text = e instanceof Error ? `${e.message}
|
|
612
|
+
${e.stderr ?? ""}` : String(e ?? "");
|
|
613
|
+
return isRateLimitText(text);
|
|
614
|
+
}
|
|
615
|
+
function resetEpochFromError(e) {
|
|
616
|
+
if (e instanceof GitHubApiError && typeof e.rateLimitReset === "number") return e.rateLimitReset;
|
|
617
|
+
if (e instanceof RateLimitedError && typeof e.receipt.resetEpochSeconds === "number") {
|
|
618
|
+
return e.receipt.resetEpochSeconds;
|
|
619
|
+
}
|
|
620
|
+
return void 0;
|
|
621
|
+
}
|
|
622
|
+
function rateLimitWaitMs(resetEpochSeconds, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS) {
|
|
623
|
+
if (typeof resetEpochSeconds !== "number" || !Number.isFinite(resetEpochSeconds)) return void 0;
|
|
624
|
+
const wait = resetEpochSeconds * 1e3 - nowMs;
|
|
625
|
+
if (wait <= 0) return 0;
|
|
626
|
+
if (wait > capMs) return void 0;
|
|
627
|
+
return wait;
|
|
628
|
+
}
|
|
629
|
+
function rateLimitedReceipt(opts) {
|
|
630
|
+
const resetEpochSeconds = opts.resetEpochSeconds;
|
|
631
|
+
const resetAt = typeof resetEpochSeconds === "number" ? new Date(resetEpochSeconds * 1e3).toISOString() : void 0;
|
|
632
|
+
const note = rateLimitResetNote(resetEpochSeconds, opts.nowMs ?? Date.now());
|
|
633
|
+
const context = opts.context ?? "GitHub API";
|
|
634
|
+
const remaining = opts.remaining && (opts.remaining.graphql !== void 0 || opts.remaining.core !== void 0) ? opts.remaining : void 0;
|
|
635
|
+
return {
|
|
636
|
+
status: "rate_limited",
|
|
637
|
+
...resetEpochSeconds !== void 0 ? { resetEpochSeconds } : {},
|
|
638
|
+
...resetAt ? { resetAt } : {},
|
|
639
|
+
message: opts.message ?? `${context}: ${note}`,
|
|
640
|
+
retryCommand: opts.retryCommand,
|
|
641
|
+
...remaining ? { remaining } : {}
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
async function runWithRateLimitBackoff(operation, opts) {
|
|
645
|
+
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
646
|
+
const now = opts.now ?? Date.now;
|
|
647
|
+
const log = opts.log ?? ((message2) => console.warn(message2));
|
|
648
|
+
const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
|
|
649
|
+
const tryOnce = async () => {
|
|
650
|
+
try {
|
|
651
|
+
return await operation();
|
|
652
|
+
} catch (e) {
|
|
653
|
+
if (!isRateLimitRefusal(e) || !opts.fallback) throw e;
|
|
654
|
+
try {
|
|
655
|
+
return await opts.fallback();
|
|
656
|
+
} catch (e2) {
|
|
657
|
+
if (isRateLimitRefusal(e2)) throw e2;
|
|
658
|
+
throw e2;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
try {
|
|
663
|
+
return await tryOnce();
|
|
664
|
+
} catch (e) {
|
|
665
|
+
if (!isRateLimitRefusal(e)) throw e;
|
|
666
|
+
const reset = resetEpochFromError(e);
|
|
667
|
+
const waitMs = rateLimitWaitMs(reset, now(), capMs);
|
|
668
|
+
if (waitMs === void 0) {
|
|
669
|
+
throw new RateLimitedError(rateLimitedReceipt({
|
|
670
|
+
resetEpochSeconds: reset,
|
|
671
|
+
retryCommand: opts.retryCommand,
|
|
672
|
+
context: opts.context,
|
|
673
|
+
nowMs: now()
|
|
674
|
+
}));
|
|
675
|
+
}
|
|
676
|
+
if (waitMs > 0) {
|
|
677
|
+
log(`${opts.context}: rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for pool reset`);
|
|
678
|
+
await sleep2(waitMs);
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
return await tryOnce();
|
|
682
|
+
} catch (e2) {
|
|
683
|
+
if (!isRateLimitRefusal(e2)) throw e2;
|
|
684
|
+
throw new RateLimitedError(rateLimitedReceipt({
|
|
685
|
+
resetEpochSeconds: resetEpochFromError(e2) ?? reset,
|
|
686
|
+
retryCommand: opts.retryCommand,
|
|
687
|
+
context: opts.context,
|
|
688
|
+
nowMs: now()
|
|
689
|
+
}));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
var RATE_LIMIT_WAIT_CAP_MS, RateLimitedError;
|
|
694
|
+
var init_github_quota = __esm({
|
|
695
|
+
"src/github-quota.ts"() {
|
|
696
|
+
"use strict";
|
|
697
|
+
init_github_client();
|
|
698
|
+
RATE_LIMIT_WAIT_CAP_MS = 12e4;
|
|
699
|
+
RateLimitedError = class extends Error {
|
|
700
|
+
receipt;
|
|
701
|
+
constructor(receipt) {
|
|
702
|
+
super(receipt.message);
|
|
703
|
+
this.name = "RateLimitedError";
|
|
704
|
+
this.receipt = receipt;
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
});
|
|
709
|
+
|
|
590
710
|
// src/fetch-retry.ts
|
|
711
|
+
async function hubQuota503(res, opts) {
|
|
712
|
+
if (res.status !== 503) return "pass";
|
|
713
|
+
let body;
|
|
714
|
+
try {
|
|
715
|
+
body = await res.clone().json();
|
|
716
|
+
} catch {
|
|
717
|
+
return "pass";
|
|
718
|
+
}
|
|
719
|
+
const code = body?.code;
|
|
720
|
+
if (code !== "github-rate-limited") return "pass";
|
|
721
|
+
const q = opts.hubQuotaWait;
|
|
722
|
+
const resetAt = body?.resetAt;
|
|
723
|
+
const wait = hubQuotaResetWaitMs(
|
|
724
|
+
typeof resetAt === "string" ? resetAt : void 0,
|
|
725
|
+
q?.now?.() ?? Date.now(),
|
|
726
|
+
q?.capMs ?? RATE_LIMIT_WAIT_CAP_MS,
|
|
727
|
+
(q?.jitterMs ?? (() => Math.floor(Math.random() * HUB_QUOTA_JITTER_MAX_MS)))()
|
|
728
|
+
);
|
|
729
|
+
return wait === void 0 ? "fail-fast" : { waitMs: wait };
|
|
730
|
+
}
|
|
591
731
|
async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
592
732
|
const attempts = opts.attempts ?? 3;
|
|
593
733
|
const baseDelayMs = opts.baseDelayMs ?? 250;
|
|
@@ -599,9 +739,17 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
|
599
739
|
const attemptInit = opts.timeoutMs ? { ...init, signal: AbortSignal.timeout(opts.timeoutMs) } : init;
|
|
600
740
|
try {
|
|
601
741
|
const res = await fetchImpl(url, attemptInit);
|
|
602
|
-
if (!isLast
|
|
603
|
-
|
|
604
|
-
|
|
742
|
+
if (!isLast) {
|
|
743
|
+
const quota = await hubQuota503(res, opts);
|
|
744
|
+
if (typeof quota === "object") {
|
|
745
|
+
await sleep2(quota.waitMs);
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (quota === "fail-fast") return res;
|
|
749
|
+
if (retryOn(res)) {
|
|
750
|
+
await sleep2(baseDelayMs * 2 ** i);
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
605
753
|
}
|
|
606
754
|
return res;
|
|
607
755
|
} catch (e) {
|
|
@@ -612,9 +760,12 @@ async function fetchWithRetry(fetchImpl, url, init, opts = {}) {
|
|
|
612
760
|
}
|
|
613
761
|
throw lastErr;
|
|
614
762
|
}
|
|
763
|
+
var HUB_QUOTA_JITTER_MAX_MS;
|
|
615
764
|
var init_fetch_retry = __esm({
|
|
616
765
|
"src/fetch-retry.ts"() {
|
|
617
766
|
"use strict";
|
|
767
|
+
init_github_quota();
|
|
768
|
+
HUB_QUOTA_JITTER_MAX_MS = 5e3;
|
|
618
769
|
}
|
|
619
770
|
});
|
|
620
771
|
|
|
@@ -5568,114 +5719,12 @@ async function fetchHubBoardSnapshot(request, deps) {
|
|
|
5568
5719
|
return { state: "ok", snapshot: body };
|
|
5569
5720
|
}
|
|
5570
5721
|
|
|
5571
|
-
// src/
|
|
5572
|
-
|
|
5573
|
-
var RATE_LIMIT_WAIT_CAP_MS = 12e4;
|
|
5574
|
-
var RateLimitedError = class extends Error {
|
|
5575
|
-
receipt;
|
|
5576
|
-
constructor(receipt) {
|
|
5577
|
-
super(receipt.message);
|
|
5578
|
-
this.name = "RateLimitedError";
|
|
5579
|
-
this.receipt = receipt;
|
|
5580
|
-
}
|
|
5581
|
-
};
|
|
5582
|
-
function isRateLimitedError(e) {
|
|
5583
|
-
return e instanceof RateLimitedError;
|
|
5584
|
-
}
|
|
5585
|
-
function isRateLimitText(text) {
|
|
5586
|
-
return /API rate limit already exceeded|rate limit already exceeded|RATE_LIMITED|secondary rate|abuse detection|HTTP\/?\d*(?:\.\d)?\s+429\b|\b429\b.*rate limit/i.test(text) || /rate limit/i.test(text) && !/not rate.?limit/i.test(text);
|
|
5587
|
-
}
|
|
5588
|
-
function isRateLimitRefusal(e) {
|
|
5589
|
-
if (e instanceof GitHubApiError && e.rateLimited) return true;
|
|
5590
|
-
if (e instanceof GitHubApiError && e.status === 403) {
|
|
5591
|
-
return /rate limit|abuse detection|secondary rate/i.test(e.message);
|
|
5592
|
-
}
|
|
5593
|
-
if (e instanceof RateLimitedError) return true;
|
|
5594
|
-
const text = e instanceof Error ? `${e.message}
|
|
5595
|
-
${e.stderr ?? ""}` : String(e ?? "");
|
|
5596
|
-
return isRateLimitText(text);
|
|
5597
|
-
}
|
|
5598
|
-
function resetEpochFromError(e) {
|
|
5599
|
-
if (e instanceof GitHubApiError && typeof e.rateLimitReset === "number") return e.rateLimitReset;
|
|
5600
|
-
if (e instanceof RateLimitedError && typeof e.receipt.resetEpochSeconds === "number") {
|
|
5601
|
-
return e.receipt.resetEpochSeconds;
|
|
5602
|
-
}
|
|
5603
|
-
return void 0;
|
|
5604
|
-
}
|
|
5605
|
-
function rateLimitWaitMs(resetEpochSeconds, nowMs, capMs = RATE_LIMIT_WAIT_CAP_MS) {
|
|
5606
|
-
if (typeof resetEpochSeconds !== "number" || !Number.isFinite(resetEpochSeconds)) return void 0;
|
|
5607
|
-
const wait = resetEpochSeconds * 1e3 - nowMs;
|
|
5608
|
-
if (wait <= 0) return 0;
|
|
5609
|
-
if (wait > capMs) return void 0;
|
|
5610
|
-
return wait;
|
|
5611
|
-
}
|
|
5612
|
-
function rateLimitedReceipt(opts) {
|
|
5613
|
-
const resetEpochSeconds = opts.resetEpochSeconds;
|
|
5614
|
-
const resetAt = typeof resetEpochSeconds === "number" ? new Date(resetEpochSeconds * 1e3).toISOString() : void 0;
|
|
5615
|
-
const note = rateLimitResetNote(resetEpochSeconds, opts.nowMs ?? Date.now());
|
|
5616
|
-
const context = opts.context ?? "GitHub API";
|
|
5617
|
-
const remaining = opts.remaining && (opts.remaining.graphql !== void 0 || opts.remaining.core !== void 0) ? opts.remaining : void 0;
|
|
5618
|
-
return {
|
|
5619
|
-
status: "rate_limited",
|
|
5620
|
-
...resetEpochSeconds !== void 0 ? { resetEpochSeconds } : {},
|
|
5621
|
-
...resetAt ? { resetAt } : {},
|
|
5622
|
-
message: opts.message ?? `${context}: ${note}`,
|
|
5623
|
-
retryCommand: opts.retryCommand,
|
|
5624
|
-
...remaining ? { remaining } : {}
|
|
5625
|
-
};
|
|
5626
|
-
}
|
|
5627
|
-
async function runWithRateLimitBackoff(operation, opts) {
|
|
5628
|
-
const sleep2 = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
|
|
5629
|
-
const now = opts.now ?? Date.now;
|
|
5630
|
-
const log = opts.log ?? ((message2) => console.warn(message2));
|
|
5631
|
-
const capMs = opts.capMs ?? RATE_LIMIT_WAIT_CAP_MS;
|
|
5632
|
-
const tryOnce = async () => {
|
|
5633
|
-
try {
|
|
5634
|
-
return await operation();
|
|
5635
|
-
} catch (e) {
|
|
5636
|
-
if (!isRateLimitRefusal(e) || !opts.fallback) throw e;
|
|
5637
|
-
try {
|
|
5638
|
-
return await opts.fallback();
|
|
5639
|
-
} catch (e2) {
|
|
5640
|
-
if (isRateLimitRefusal(e2)) throw e2;
|
|
5641
|
-
throw e2;
|
|
5642
|
-
}
|
|
5643
|
-
}
|
|
5644
|
-
};
|
|
5645
|
-
try {
|
|
5646
|
-
return await tryOnce();
|
|
5647
|
-
} catch (e) {
|
|
5648
|
-
if (!isRateLimitRefusal(e)) throw e;
|
|
5649
|
-
const reset = resetEpochFromError(e);
|
|
5650
|
-
const waitMs = rateLimitWaitMs(reset, now(), capMs);
|
|
5651
|
-
if (waitMs === void 0) {
|
|
5652
|
-
throw new RateLimitedError(rateLimitedReceipt({
|
|
5653
|
-
resetEpochSeconds: reset,
|
|
5654
|
-
retryCommand: opts.retryCommand,
|
|
5655
|
-
context: opts.context,
|
|
5656
|
-
nowMs: now()
|
|
5657
|
-
}));
|
|
5658
|
-
}
|
|
5659
|
-
if (waitMs > 0) {
|
|
5660
|
-
log(`${opts.context}: rate-limited \u2014 waiting ${Math.round(waitMs / 1e3)}s for pool reset`);
|
|
5661
|
-
await sleep2(waitMs);
|
|
5662
|
-
}
|
|
5663
|
-
try {
|
|
5664
|
-
return await tryOnce();
|
|
5665
|
-
} catch (e2) {
|
|
5666
|
-
if (!isRateLimitRefusal(e2)) throw e2;
|
|
5667
|
-
throw new RateLimitedError(rateLimitedReceipt({
|
|
5668
|
-
resetEpochSeconds: resetEpochFromError(e2) ?? reset,
|
|
5669
|
-
retryCommand: opts.retryCommand,
|
|
5670
|
-
context: opts.context,
|
|
5671
|
-
nowMs: now()
|
|
5672
|
-
}));
|
|
5673
|
-
}
|
|
5674
|
-
}
|
|
5675
|
-
}
|
|
5722
|
+
// src/board-read.ts
|
|
5723
|
+
init_github_quota();
|
|
5676
5724
|
|
|
5677
5725
|
// src/claim-marker-client.ts
|
|
5678
5726
|
init_github_client();
|
|
5727
|
+
init_github_quota();
|
|
5679
5728
|
|
|
5680
5729
|
// src/pr-checks-rest.ts
|
|
5681
5730
|
init_cli_shared();
|
|
@@ -6519,6 +6568,7 @@ function withAppClaimMarkerRead(client, appClient) {
|
|
|
6519
6568
|
}
|
|
6520
6569
|
|
|
6521
6570
|
// src/board-dependency.ts
|
|
6571
|
+
init_github_quota();
|
|
6522
6572
|
var DEPENDS_ON_LINE = /^\s*[-*]?\s*\*\*Depends on:\*\*\s*(.+)$/im;
|
|
6523
6573
|
var ISSUE_REF = /([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)#(\d+)/g;
|
|
6524
6574
|
var DEPENDENCY_FETCH_CONCURRENCY = 8;
|
|
@@ -7209,6 +7259,9 @@ function jobNeeds(job) {
|
|
|
7209
7259
|
function unquote(s) {
|
|
7210
7260
|
return s.replace(/#.*$/, "").trim().replace(/^['"]|['"]$/g, "");
|
|
7211
7261
|
}
|
|
7262
|
+
function contextMatchesJobId(jobId, context) {
|
|
7263
|
+
return context === jobId || context.startsWith(`${jobId} / `);
|
|
7264
|
+
}
|
|
7212
7265
|
function unblockingGateJobs(workflows, required) {
|
|
7213
7266
|
const unblocking = /* @__PURE__ */ new Set();
|
|
7214
7267
|
for (const wf of workflows) {
|
|
@@ -7216,7 +7269,7 @@ function unblockingGateJobs(workflows, required) {
|
|
|
7216
7269
|
const jobs = parseWorkflowJobs(wf.body);
|
|
7217
7270
|
const needs = new Map(jobs.map((job) => [job.id, jobNeeds(job)]));
|
|
7218
7271
|
const covered = /* @__PURE__ */ new Set();
|
|
7219
|
-
const stack = jobs.map((job) => job.id).filter((id) => required.
|
|
7272
|
+
const stack = jobs.map((job) => job.id).filter((id) => [...required].some((context) => contextMatchesJobId(id, context)));
|
|
7220
7273
|
while (stack.length) {
|
|
7221
7274
|
const id = stack.pop();
|
|
7222
7275
|
if (covered.has(id)) continue;
|
|
@@ -9174,6 +9227,7 @@ function isPriorityFieldConfigured(cfg) {
|
|
|
9174
9227
|
// src/gh-create.ts
|
|
9175
9228
|
init_cli_shared();
|
|
9176
9229
|
init_github_client();
|
|
9230
|
+
init_github_quota();
|
|
9177
9231
|
var ISSUE_TYPES = ["bug", "feature", "task"];
|
|
9178
9232
|
var GH_MUTATION_TIMEOUT_MS = 12e4;
|
|
9179
9233
|
function timeoutKillNote(err, timeoutMs) {
|
|
@@ -13483,9 +13537,11 @@ function ghError(e) {
|
|
|
13483
13537
|
|
|
13484
13538
|
// src/board-claim-move.ts
|
|
13485
13539
|
init_github_client();
|
|
13540
|
+
init_github_quota();
|
|
13486
13541
|
|
|
13487
13542
|
// src/sub-issue.ts
|
|
13488
13543
|
init_error_codes();
|
|
13544
|
+
init_github_quota();
|
|
13489
13545
|
function buildResolveIdArgs(ref) {
|
|
13490
13546
|
const args = ["issue", "view", String(ref.number), "--json", "id", "--jq", ".id"];
|
|
13491
13547
|
if (ref.repo) args.push("--repo", ref.repo);
|
|
@@ -17172,10 +17228,10 @@ var rollout_plan_default = {
|
|
|
17172
17228
|
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)."
|
|
17173
17229
|
},
|
|
17174
17230
|
baseline: {
|
|
17175
|
-
version: "4.5.
|
|
17176
|
-
tag: "v4.5.
|
|
17177
|
-
commit: "
|
|
17178
|
-
npm: "@mutmutco/cli@4.5.
|
|
17231
|
+
version: "4.5.64",
|
|
17232
|
+
tag: "v4.5.64",
|
|
17233
|
+
commit: "f201a5b12b79",
|
|
17234
|
+
npm: "@mutmutco/cli@4.5.64"
|
|
17179
17235
|
},
|
|
17180
17236
|
exitCriterion: "fleet-n-of-n",
|
|
17181
17237
|
hubOnlyShortcut: "forbidden",
|
|
@@ -17192,14 +17248,14 @@ var rollout_plan_default = {
|
|
|
17192
17248
|
repo: "mutmutco/mmi-hub",
|
|
17193
17249
|
role: "canary",
|
|
17194
17250
|
schedule: "train",
|
|
17195
|
-
v3Target: "v4.5.
|
|
17251
|
+
v3Target: "v4.5.64"
|
|
17196
17252
|
}
|
|
17197
17253
|
],
|
|
17198
17254
|
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.",
|
|
17199
17255
|
rollback: {
|
|
17200
17256
|
independent: true,
|
|
17201
|
-
mechanism: "npm dist-tag latest -> 4.5.
|
|
17202
|
-
v3Target: "v4.5.
|
|
17257
|
+
mechanism: "npm dist-tag latest -> 4.5.64 and redeploy the Hub Lambda from tag v4.5.64 (f201a5b12b79); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
|
|
17258
|
+
v3Target: "v4.5.64 (@mutmutco/cli@4.5.64, tag commit f201a5b12b79 \u2014 last known-good release carrying the repo-index v4-only contract)"
|
|
17203
17259
|
}
|
|
17204
17260
|
},
|
|
17205
17261
|
{
|
|
@@ -27982,7 +28038,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
27982
28038
|
if (hasRequiredChecks) {
|
|
27983
28039
|
const gateFiles = new Set(gateWorkflowFiles(prWorkflowPaths));
|
|
27984
28040
|
const gateWorkflows = (await getBranchWorkflows() ?? []).filter((wf) => gateFiles.has(wf.path.slice(wf.path.lastIndexOf("/") + 1)));
|
|
27985
|
-
const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job) && !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.
|
|
28041
|
+
const unblocking = unblockingGateJobs(gateWorkflows, new Set(liveContexts)).filter((job) => !AGENT_PR_BOOKKEEPING_CONTEXTS.has(job) && !branchPlan?.pending.some((entry) => entry.branch === baseBranch && entry.contexts.some((context) => contextMatchesJobId(job, context))));
|
|
27986
28042
|
checks.push({
|
|
27987
28043
|
ok: unblocking.length === 0,
|
|
27988
28044
|
label: UNBLOCKING_GATE_JOBS_LABEL,
|
|
@@ -28067,7 +28123,7 @@ async function auditRepoCi(repo, callerDeps) {
|
|
|
28067
28123
|
const body = await fetchFileContent(deps, repo, baseBranch, path2);
|
|
28068
28124
|
if (body == null) continue;
|
|
28069
28125
|
if (isGateWorkflowPath(path2)) gateBodies.push({ path: path2, body });
|
|
28070
|
-
if (!/^\s*concurrency:/m.test(body) ||
|
|
28126
|
+
if (!/^\s*concurrency:/m.test(body) || !/^\s*cancel-in-progress:[ \t]*(?:true|\$\{\{\s*github\.event_name\s*==\s*(['"])pull_request\1\s*\}\})[ \t]*(?:#.*)?$/m.test(body)) {
|
|
28071
28127
|
if (NO_CANCEL_WAIVER.test(body)) waived.push(path2);
|
|
28072
28128
|
else missing.push(path2);
|
|
28073
28129
|
}
|
|
@@ -29683,6 +29739,7 @@ async function activateAppActor(commandPath3, env, mint, note = (line) => proces
|
|
|
29683
29739
|
init_cli_shared();
|
|
29684
29740
|
|
|
29685
29741
|
// src/attach-to-project.ts
|
|
29742
|
+
init_github_quota();
|
|
29686
29743
|
async function attachViaHubApp(cfg, contentNodeId) {
|
|
29687
29744
|
const { addIssueToProjectViaHub: addIssueToProjectViaHub2 } = await Promise.resolve().then(() => (init_board_write(), board_write_exports));
|
|
29688
29745
|
return addIssueToProjectViaHub2(cfg, contentNodeId);
|
|
@@ -29843,6 +29900,7 @@ async function waitBoardEvents(since, waitS, deps) {
|
|
|
29843
29900
|
}
|
|
29844
29901
|
|
|
29845
29902
|
// src/board-commands.ts
|
|
29903
|
+
init_github_quota();
|
|
29846
29904
|
function refuseRateLimited(e, json) {
|
|
29847
29905
|
if (!isRateLimitedError(e)) return false;
|
|
29848
29906
|
if (json) console.log(JSON.stringify(e.receipt));
|
|
@@ -35848,6 +35906,194 @@ Each member's live release must serve <data>/main/module.js at its main pin path
|
|
|
35848
35906
|
return record;
|
|
35849
35907
|
}
|
|
35850
35908
|
|
|
35909
|
+
// src/suite-rc-data.ts
|
|
35910
|
+
var RC_DATA_VAULT_REPO = "mutmutco/MM-Work";
|
|
35911
|
+
var RC_DATA_VERCEL_PROJECT = "prj_2Q1UcZ6tscIxHr4lPEhSD3jU1ZGm";
|
|
35912
|
+
var RC_DATA_VERCEL_TEAM = "team_ntTdWEQ896Eh20RT5Ri5XJDB";
|
|
35913
|
+
var RC_DATA_ALIAS = "rc.mutmut.work";
|
|
35914
|
+
var RC_DATA_GIT_BRANCH = "rc";
|
|
35915
|
+
var RC_DATA_BOUND_KEYS = [
|
|
35916
|
+
{ envKey: "DATABASE_URL", liveVaultKey: "main/DATABASE_URL", rcVaultKey: "rc/DATABASE_URL" },
|
|
35917
|
+
{ envKey: "MIGRATION_DATABASE_URL", liveVaultKey: "main/MIGRATION_DATABASE_URL", rcVaultKey: "rc/MIGRATION_DATABASE_URL" },
|
|
35918
|
+
{ envKey: "MMW_DATA_KEY", liveVaultKey: "main/MMW_DATA_KEY", rcVaultKey: "rc/MMW_DATA_KEY" },
|
|
35919
|
+
{ envKey: "GOOGLE_CLIENT_ID", liveVaultKey: "GOOGLE_CLIENT_ID", rcVaultKey: null },
|
|
35920
|
+
{ envKey: "GOOGLE_CLIENT_SECRET", liveVaultKey: "GOOGLE_CLIENT_SECRET", rcVaultKey: null }
|
|
35921
|
+
];
|
|
35922
|
+
var POLL_ATTEMPTS = 60;
|
|
35923
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
35924
|
+
function withTeam(path2) {
|
|
35925
|
+
const url = new URL(`https://api.vercel.com${path2}`);
|
|
35926
|
+
url.searchParams.set("teamId", RC_DATA_VERCEL_TEAM);
|
|
35927
|
+
return url.toString();
|
|
35928
|
+
}
|
|
35929
|
+
function envCollectionUrl2() {
|
|
35930
|
+
return withTeam(`/v10/projects/${encodeURIComponent(RC_DATA_VERCEL_PROJECT)}/env`);
|
|
35931
|
+
}
|
|
35932
|
+
function envItemUrl2(id) {
|
|
35933
|
+
return withTeam(`/v9/projects/${encodeURIComponent(RC_DATA_VERCEL_PROJECT)}/env/${encodeURIComponent(id)}`);
|
|
35934
|
+
}
|
|
35935
|
+
function aliasUrl() {
|
|
35936
|
+
return withTeam(`/v4/aliases/${RC_DATA_ALIAS}`);
|
|
35937
|
+
}
|
|
35938
|
+
function deploymentUrl(id) {
|
|
35939
|
+
return withTeam(`/v13/deployments/${encodeURIComponent(id)}`);
|
|
35940
|
+
}
|
|
35941
|
+
function redeployUrl() {
|
|
35942
|
+
const url = new URL("https://api.vercel.com/v13/deployments");
|
|
35943
|
+
url.searchParams.set("forceNew", "1");
|
|
35944
|
+
url.searchParams.set("teamId", RC_DATA_VERCEL_TEAM);
|
|
35945
|
+
return url.toString();
|
|
35946
|
+
}
|
|
35947
|
+
function isRcBranchEnv(row, key) {
|
|
35948
|
+
if (row.key !== key) return false;
|
|
35949
|
+
if (row.gitBranch !== RC_DATA_GIT_BRANCH) return false;
|
|
35950
|
+
const target = Array.isArray(row.target) ? row.target.filter((item) => typeof item === "string") : [];
|
|
35951
|
+
return target.includes("preview");
|
|
35952
|
+
}
|
|
35953
|
+
function envsOf2(body) {
|
|
35954
|
+
if (Array.isArray(body)) return body;
|
|
35955
|
+
if (body && typeof body === "object" && Array.isArray(body.envs)) {
|
|
35956
|
+
return body.envs;
|
|
35957
|
+
}
|
|
35958
|
+
return null;
|
|
35959
|
+
}
|
|
35960
|
+
function scrub(reason, secrets) {
|
|
35961
|
+
let out = reason.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
35962
|
+
for (const secret of secrets) {
|
|
35963
|
+
if (secret) out = out.split(secret).join("[redacted]");
|
|
35964
|
+
}
|
|
35965
|
+
return out || "unknown error";
|
|
35966
|
+
}
|
|
35967
|
+
async function readJson(deps, label, url, secrets, init) {
|
|
35968
|
+
const token = secrets[0];
|
|
35969
|
+
const headers = { ...init?.headers ?? {}, ...token ? { Authorization: `Bearer ${token}` } : {} };
|
|
35970
|
+
const method = init?.method ?? "GET";
|
|
35971
|
+
let res;
|
|
35972
|
+
try {
|
|
35973
|
+
res = await deps.fetch(url, { ...init, method, headers });
|
|
35974
|
+
} catch (e) {
|
|
35975
|
+
throw new Error(`${label} failed: ${scrub(e instanceof Error ? e.message : String(e), secrets)}`);
|
|
35976
|
+
}
|
|
35977
|
+
let raw = "";
|
|
35978
|
+
try {
|
|
35979
|
+
raw = await res.text();
|
|
35980
|
+
} catch (e) {
|
|
35981
|
+
throw new Error(`${label} failed: ${scrub(e instanceof Error ? e.message : String(e), secrets)}`);
|
|
35982
|
+
}
|
|
35983
|
+
if (!res.ok) throw new Error(`${label} failed: HTTP ${res.status}${raw ? `: ${scrub(raw, secrets)}` : ""}`);
|
|
35984
|
+
if (!raw.trim()) return {};
|
|
35985
|
+
try {
|
|
35986
|
+
return JSON.parse(raw);
|
|
35987
|
+
} catch {
|
|
35988
|
+
throw new Error(`${label} failed: response was not JSON`);
|
|
35989
|
+
}
|
|
35990
|
+
}
|
|
35991
|
+
function deploymentReadyState(body) {
|
|
35992
|
+
if (body && typeof body === "object") {
|
|
35993
|
+
const row = body;
|
|
35994
|
+
if (typeof row.status === "string") return row.status;
|
|
35995
|
+
if (typeof row.readyState === "string") return row.readyState;
|
|
35996
|
+
}
|
|
35997
|
+
return null;
|
|
35998
|
+
}
|
|
35999
|
+
function deploymentName(body) {
|
|
36000
|
+
if (body && typeof body === "object" && typeof body.name === "string") {
|
|
36001
|
+
return body.name;
|
|
36002
|
+
}
|
|
36003
|
+
return null;
|
|
36004
|
+
}
|
|
36005
|
+
function aliasDeploymentId(body) {
|
|
36006
|
+
if (body && typeof body === "object" && typeof body.deploymentId === "string") {
|
|
36007
|
+
return body.deploymentId;
|
|
36008
|
+
}
|
|
36009
|
+
return null;
|
|
36010
|
+
}
|
|
36011
|
+
async function runRcDataSwitch(deps, opts) {
|
|
36012
|
+
if (opts.to !== "live" && opts.to !== "rc") {
|
|
36013
|
+
throw new Error(`--to must be live or rc (got ${String(opts.to ?? "nothing")}); nothing was written`);
|
|
36014
|
+
}
|
|
36015
|
+
if (opts.apply && !opts.ownerGo) {
|
|
36016
|
+
throw new Error("--apply requires --owner-go (it touches the live data key); nothing was written");
|
|
36017
|
+
}
|
|
36018
|
+
const secrets = [];
|
|
36019
|
+
const token = await deps.readToken();
|
|
36020
|
+
secrets.push(token);
|
|
36021
|
+
if (!token) throw new Error("no _org/vercel/API_TOKEN; nothing was written");
|
|
36022
|
+
const listed = await readJson(deps, "GET envs", envCollectionUrl2(), secrets, { method: "GET" });
|
|
36023
|
+
const rows = envsOf2(listed);
|
|
36024
|
+
if (!rows) throw new Error("env list returned an unexpected shape; nothing was written");
|
|
36025
|
+
const plan = RC_DATA_BOUND_KEYS.map((bound) => {
|
|
36026
|
+
const source = opts.to === "live" ? bound.liveVaultKey : bound.rcVaultKey;
|
|
36027
|
+
const existing = rows.find((row) => isRcBranchEnv(row, bound.envKey) && typeof row.id === "string" && row.id.length > 0);
|
|
36028
|
+
const entryId = existing && typeof existing.id === "string" ? existing.id : null;
|
|
36029
|
+
if (source === null) return { action: existing ? "delete" : "noop", key: bound.envKey, entryId, source };
|
|
36030
|
+
return { action: entryId ? "patch" : "create", key: bound.envKey, entryId, source };
|
|
36031
|
+
});
|
|
36032
|
+
if (!opts.apply) {
|
|
36033
|
+
return { command: "suite rc-data", direction: opts.to, dryRun: true, plan, changed: [] };
|
|
36034
|
+
}
|
|
36035
|
+
const values = /* @__PURE__ */ new Map();
|
|
36036
|
+
for (const step of plan) {
|
|
36037
|
+
if (step.action !== "patch" && step.action !== "create") continue;
|
|
36038
|
+
const value = await deps.readVault(step.source);
|
|
36039
|
+
secrets.push(value);
|
|
36040
|
+
if (!value) throw new Error(`vault ${step.source} is missing or unreadable; nothing was written`);
|
|
36041
|
+
values.set(step.key, value);
|
|
36042
|
+
}
|
|
36043
|
+
const changed = [];
|
|
36044
|
+
for (const step of plan) {
|
|
36045
|
+
try {
|
|
36046
|
+
if (step.action === "patch") {
|
|
36047
|
+
await readJson(deps, `PATCH ${step.key}`, envItemUrl2(step.entryId), secrets, {
|
|
36048
|
+
method: "PATCH",
|
|
36049
|
+
headers: { "Content-Type": "application/json" },
|
|
36050
|
+
body: JSON.stringify({ value: values.get(step.key), type: "sensitive" })
|
|
36051
|
+
});
|
|
36052
|
+
changed.push(step.key);
|
|
36053
|
+
} else if (step.action === "create") {
|
|
36054
|
+
await readJson(deps, `POST ${step.key}`, envCollectionUrl2(), secrets, {
|
|
36055
|
+
method: "POST",
|
|
36056
|
+
headers: { "Content-Type": "application/json" },
|
|
36057
|
+
body: JSON.stringify({ key: step.key, value: values.get(step.key), type: "sensitive", target: ["preview"], gitBranch: RC_DATA_GIT_BRANCH })
|
|
36058
|
+
});
|
|
36059
|
+
changed.push(step.key);
|
|
36060
|
+
} else if (step.action === "delete") {
|
|
36061
|
+
await readJson(deps, `DELETE ${step.key}`, envItemUrl2(step.entryId), secrets, { method: "DELETE" });
|
|
36062
|
+
changed.push(step.key);
|
|
36063
|
+
}
|
|
36064
|
+
} catch (e) {
|
|
36065
|
+
const changedLabel = changed.length ? changed.join(", ") : "none";
|
|
36066
|
+
throw new Error(`${e.message}; changed: ${changedLabel}; no redeploy`);
|
|
36067
|
+
}
|
|
36068
|
+
}
|
|
36069
|
+
const aliasBefore = aliasDeploymentId(await readJson(deps, "GET alias", aliasUrl(), secrets, { method: "GET" }));
|
|
36070
|
+
if (!aliasBefore) throw new Error(`alias ${RC_DATA_ALIAS} has no deployment; envs changed: ${changed.join(", ")}; no redeploy`);
|
|
36071
|
+
const name = deploymentName(await readJson(deps, "GET deployment", deploymentUrl(aliasBefore), secrets, { method: "GET" }));
|
|
36072
|
+
if (!name) throw new Error(`deployment ${aliasBefore} has no name; envs changed: ${changed.join(", ")}; no redeploy`);
|
|
36073
|
+
const created = await readJson(deps, "POST redeploy", redeployUrl(), secrets, {
|
|
36074
|
+
method: "POST",
|
|
36075
|
+
headers: { "Content-Type": "application/json" },
|
|
36076
|
+
body: JSON.stringify({ name, deploymentId: aliasBefore, meta: { redeployReason: `MMI-Hub#7588 rc-data switch to ${opts.to}` } })
|
|
36077
|
+
});
|
|
36078
|
+
const newId = aliasDeploymentId({ deploymentId: created.id });
|
|
36079
|
+
if (!newId) throw new Error(`redeploy answered without an id; envs changed: ${changed.join(", ")}`);
|
|
36080
|
+
let state = null;
|
|
36081
|
+
for (let attempt = 0; attempt < POLL_ATTEMPTS; attempt += 1) {
|
|
36082
|
+
state = deploymentReadyState(await readJson(deps, "GET deployment", deploymentUrl(newId), secrets, { method: "GET" }));
|
|
36083
|
+
if (state === "READY") break;
|
|
36084
|
+
if (state === "ERROR" || state === "CANCELED" || state === "FAILED") {
|
|
36085
|
+
throw new Error(`redeploy ${newId} ended ${state}; envs changed: ${changed.join(", ")}`);
|
|
36086
|
+
}
|
|
36087
|
+
await deps.sleep(POLL_INTERVAL_MS);
|
|
36088
|
+
}
|
|
36089
|
+
if (state !== "READY") throw new Error(`redeploy ${newId} never reached READY (last: ${state ?? "unknown"}); envs changed: ${changed.join(", ")}`);
|
|
36090
|
+
const aliasAfter = aliasDeploymentId(await readJson(deps, "GET alias", aliasUrl(), secrets, { method: "GET" }));
|
|
36091
|
+
if (aliasAfter !== newId) {
|
|
36092
|
+
throw new Error(`alias ${RC_DATA_ALIAS} still points at ${aliasAfter ?? "nothing"} (expected ${newId}); envs changed: ${changed.join(", ")}`);
|
|
36093
|
+
}
|
|
36094
|
+
return { command: "suite rc-data", direction: opts.to, dryRun: false, plan, changed, deploymentId: newId, alias: RC_DATA_ALIAS };
|
|
36095
|
+
}
|
|
36096
|
+
|
|
35851
36097
|
// src/suite-train-io.ts
|
|
35852
36098
|
var import_node_fs43 = require("node:fs");
|
|
35853
36099
|
var import_node_path43 = require("node:path");
|
|
@@ -39437,6 +39683,7 @@ function applyChecklistCheck(body, query, checked) {
|
|
|
39437
39683
|
|
|
39438
39684
|
// src/command-register-collaboration.ts
|
|
39439
39685
|
init_github_client();
|
|
39686
|
+
init_github_quota();
|
|
39440
39687
|
|
|
39441
39688
|
// src/issue-commands.ts
|
|
39442
39689
|
var import_node_fs34 = require("node:fs");
|
|
@@ -40377,6 +40624,7 @@ var import_node_path35 = require("node:path");
|
|
|
40377
40624
|
|
|
40378
40625
|
// src/cross-repo-filing-issue.ts
|
|
40379
40626
|
init_github_client();
|
|
40627
|
+
init_github_quota();
|
|
40380
40628
|
function crossRepoFilingRetryCommand(prRepo, prNumber) {
|
|
40381
40629
|
return `mmi-cli devops pr merge ${prNumber} --repo ${prRepo}`;
|
|
40382
40630
|
}
|
|
@@ -40479,6 +40727,7 @@ ${pr.body ?? ""}`;
|
|
|
40479
40727
|
}
|
|
40480
40728
|
|
|
40481
40729
|
// src/post-merge-recon.ts
|
|
40730
|
+
init_github_quota();
|
|
40482
40731
|
var POST_MERGE_RECON_SCHEMA_VERSION = 1;
|
|
40483
40732
|
function postMergeReconRetryCommand(repo, pr) {
|
|
40484
40733
|
return crossRepoFilingRetryCommand(repo, Number(pr));
|
|
@@ -41281,6 +41530,7 @@ async function checkDocsIndexAtHead(opts, deps) {
|
|
|
41281
41530
|
|
|
41282
41531
|
// src/pr-create-claim-guard.ts
|
|
41283
41532
|
init_github_client();
|
|
41533
|
+
init_github_quota();
|
|
41284
41534
|
var CLAIM_GUARD_RATE_LIMIT_WAIT_CAP_MS = 3e4;
|
|
41285
41535
|
function withRateLimitRetry(client, seams = {}) {
|
|
41286
41536
|
const throttledPaths = /* @__PURE__ */ new Set();
|
|
@@ -46051,6 +46301,17 @@ function parseMemberSummaryFiles(entries) {
|
|
|
46051
46301
|
}
|
|
46052
46302
|
return memberSummaryFiles;
|
|
46053
46303
|
}
|
|
46304
|
+
function suiteRcDataProdDeps() {
|
|
46305
|
+
return {
|
|
46306
|
+
fetch: async (url, init) => {
|
|
46307
|
+
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(15e3) });
|
|
46308
|
+
return { ok: res.ok, status: res.status, text: () => res.text() };
|
|
46309
|
+
},
|
|
46310
|
+
readToken: readOrgVercelApiToken,
|
|
46311
|
+
readVault: async (vaultKey) => fetchSecretValue(makeSecretsDeps(await loadConfig()), vaultKey, { repo: RC_DATA_VAULT_REPO }),
|
|
46312
|
+
sleep: (ms) => new Promise((resolve7) => setTimeout(resolve7, ms))
|
|
46313
|
+
};
|
|
46314
|
+
}
|
|
46054
46315
|
function registerSuiteCommands(program3) {
|
|
46055
46316
|
const suite = program3.command("suite").description("MM-Work suite status, candidate cut and release (#7262/#7263)");
|
|
46056
46317
|
const leadRoot = async () => {
|
|
@@ -46123,6 +46384,31 @@ function registerSuiteCommands(program3) {
|
|
|
46123
46384
|
return fail(`suite release: ${e.message}`);
|
|
46124
46385
|
}
|
|
46125
46386
|
});
|
|
46387
|
+
suite.command("rc-data").description("switch the rc Preview envs between the live and rc data sets as one bound switch (#7588)").option("--to <direction>", "switch source: live or rc").option("--apply", "write the switch (default is a dry-run plan)").option("--owner-go", "explicit approval: touches the live data key").option("--json", "structured receipt").action(async (o) => {
|
|
46388
|
+
try {
|
|
46389
|
+
if (o.to !== "live" && o.to !== "rc") throw new Error(`--to must be live or rc (got ${o.to ?? "nothing"}); nothing was written`);
|
|
46390
|
+
const result = await runRcDataSwitch(suiteRcDataProdDeps(), { to: o.to, apply: o.apply === true, ownerGo: o.ownerGo === true });
|
|
46391
|
+
if (o.json) {
|
|
46392
|
+
console.log(JSON.stringify(result, null, 2));
|
|
46393
|
+
return;
|
|
46394
|
+
}
|
|
46395
|
+
console.log([
|
|
46396
|
+
`suite rc-data ${result.dryRun ? "dry-run" : "receipt"} (--to ${result.direction}):`,
|
|
46397
|
+
...result.plan.map((step) => {
|
|
46398
|
+
if (step.action === "patch") return ` - patch ${step.key} rc entry ${step.entryId} <- vault ${step.source}`;
|
|
46399
|
+
if (step.action === "create") return ` - create ${step.key} rc preview entry <- vault ${step.source}`;
|
|
46400
|
+
if (step.action === "delete") return ` - delete ${step.key} rc entry ${step.entryId} (generic Preview applies again)`;
|
|
46401
|
+
return ` - keep ${step.key} (no rc entry; generic Preview already applies)`;
|
|
46402
|
+
}),
|
|
46403
|
+
...result.dryRun ? [" rerun with --apply --owner-go to write, then redeploy rc.mutmut.work"] : [
|
|
46404
|
+
` - changed: ${result.changed.join(", ") || "none"}`,
|
|
46405
|
+
` - redeployed ${result.deploymentId}; alias ${result.alias} verified`
|
|
46406
|
+
]
|
|
46407
|
+
].join("\n"));
|
|
46408
|
+
} catch (e) {
|
|
46409
|
+
return fail(`suite rc-data: ${e.message}`);
|
|
46410
|
+
}
|
|
46411
|
+
});
|
|
46126
46412
|
suite.command("status").description("compare served versions with development, rc and main branch heads across MM-Work modules").option("--suite <name>", "suite name (mm-work)", "mm-work").option("--json", "structured output").action(async (o) => {
|
|
46127
46413
|
if (o.suite !== "mm-work") return fail("suite status: only mm-work is supported");
|
|
46128
46414
|
const github = defaultGitHubClient();
|
package/package.json
CHANGED