@remogram/provider-github-api 0.1.0-beta.3 → 0.1.0-beta.4
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/index.js +242 -27
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -12,18 +12,31 @@ import {
|
|
|
12
12
|
gitAheadBehind,
|
|
13
13
|
refsInventory,
|
|
14
14
|
crInventory,
|
|
15
|
-
|
|
15
|
+
buildMergePlanBodyFromFacts,
|
|
16
16
|
ERROR_CODES,
|
|
17
17
|
forgeError,
|
|
18
18
|
forgeIngestCapabilityFacts,
|
|
19
19
|
checkPaginationCapabilityFacts,
|
|
20
|
+
openPullListCapabilityFacts,
|
|
20
21
|
DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
21
22
|
MAX_CHECK_STATUS_PAGES,
|
|
23
|
+
DEFAULT_OPEN_PULL_LIST_PAGE_SIZE,
|
|
22
24
|
fetchWithIngestPageBackoff,
|
|
23
25
|
paginateOffsetListPages,
|
|
24
26
|
fetchPageWithIngestBackoff,
|
|
25
27
|
withPerPageParam,
|
|
26
28
|
apiProviderCommands,
|
|
29
|
+
normalizeCrInventorySort,
|
|
30
|
+
DEFAULT_CR_INVENTORY_SLICE_SORT,
|
|
31
|
+
isCrInventoryFastPathEligible,
|
|
32
|
+
validateFastPathPageLength,
|
|
33
|
+
isNumberSortFastPathEligible,
|
|
34
|
+
isNumberSortFullCollectRequired,
|
|
35
|
+
resolvePaginatedEntryCount,
|
|
36
|
+
resolveListTruncatedWithTrustedTotal,
|
|
37
|
+
orderOpenPullNumbers,
|
|
38
|
+
buildOpenPullListMeta,
|
|
39
|
+
githubOpenPullSortQuery,
|
|
27
40
|
} from '@remogram/core';
|
|
28
41
|
|
|
29
42
|
const PUBLIC_GITHUB_HOST = 'github.com';
|
|
@@ -188,12 +201,36 @@ async function paginateGitHubLinkPages({
|
|
|
188
201
|
token,
|
|
189
202
|
initialLimit = DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
190
203
|
mapPageItems,
|
|
204
|
+
seededFirstPage = null,
|
|
191
205
|
}) {
|
|
192
206
|
const all = [];
|
|
193
207
|
let truncated = false;
|
|
208
|
+
let walkedCount = 0;
|
|
194
209
|
let url = startUrl;
|
|
195
210
|
let activeLimit = initialLimit;
|
|
196
|
-
|
|
211
|
+
let pageIndex = 0;
|
|
212
|
+
|
|
213
|
+
if (seededFirstPage) {
|
|
214
|
+
const { items, linkHeader, currentUrl, usedLimit } = seededFirstPage;
|
|
215
|
+
activeLimit = usedLimit;
|
|
216
|
+
const mapped = mapPageItems(items);
|
|
217
|
+
walkedCount += mapped.length;
|
|
218
|
+
all.push(...mapped);
|
|
219
|
+
const linkPage = resolveGitHubLinkNextPage({
|
|
220
|
+
trustedOrigin,
|
|
221
|
+
currentUrl,
|
|
222
|
+
linkHeader,
|
|
223
|
+
pageIndex: 0,
|
|
224
|
+
maxPages: MAX_CHECK_PAGES,
|
|
225
|
+
});
|
|
226
|
+
if (linkPage.truncated) {
|
|
227
|
+
truncated = true;
|
|
228
|
+
}
|
|
229
|
+
url = linkPage.nextUrl ? withPerPageParam(linkPage.nextUrl, activeLimit) : null;
|
|
230
|
+
pageIndex = 1;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
for (let page = pageIndex; page < MAX_CHECK_PAGES && url; page += 1) {
|
|
197
234
|
const currentUrl = url;
|
|
198
235
|
let usedLimit = activeLimit;
|
|
199
236
|
const { body, headers } = await fetchWithIngestPageBackoff(
|
|
@@ -208,7 +245,9 @@ async function paginateGitHubLinkPages({
|
|
|
208
245
|
activeLimit,
|
|
209
246
|
);
|
|
210
247
|
activeLimit = usedLimit;
|
|
211
|
-
|
|
248
|
+
const mapped = mapPageItems(body);
|
|
249
|
+
walkedCount += mapped.length;
|
|
250
|
+
all.push(...mapped);
|
|
212
251
|
const linkHeader = headers?.get?.('link') ?? headers?.get?.('Link') ?? null;
|
|
213
252
|
const linkPage = resolveGitHubLinkNextPage({
|
|
214
253
|
trustedOrigin,
|
|
@@ -222,7 +261,7 @@ async function paginateGitHubLinkPages({
|
|
|
222
261
|
}
|
|
223
262
|
url = linkPage.nextUrl ? withPerPageParam(linkPage.nextUrl, activeLimit) : null;
|
|
224
263
|
}
|
|
225
|
-
return { items: all, truncated };
|
|
264
|
+
return { items: all, truncated, walked_count: walkedCount };
|
|
226
265
|
}
|
|
227
266
|
|
|
228
267
|
export async function githubFetchPaginated(config, parsed, path, slice) {
|
|
@@ -347,6 +386,9 @@ export function providerCapabilities() {
|
|
|
347
386
|
pageSizeParam: 'per_page',
|
|
348
387
|
sourceCount: check_sources.length,
|
|
349
388
|
}),
|
|
389
|
+
...openPullListCapabilityFacts({
|
|
390
|
+
totalCountSource: 'search_api',
|
|
391
|
+
}),
|
|
350
392
|
};
|
|
351
393
|
}
|
|
352
394
|
|
|
@@ -488,48 +530,167 @@ export async function prChecks(ctx, opts) {
|
|
|
488
530
|
export async function mergePlan(ctx, opts) {
|
|
489
531
|
const view = await prView(ctx, opts);
|
|
490
532
|
const checks = await prChecks(ctx, { number: view.pr_number });
|
|
491
|
-
|
|
533
|
+
return buildMergePlanBodyFromFacts(ctx, view, checks, opts);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const GITHUB_OPEN_PULL_COMPLIANT_MAX =
|
|
537
|
+
DEFAULT_OPEN_PULL_LIST_PAGE_SIZE * MAX_CHECK_STATUS_PAGES;
|
|
538
|
+
const GITHUB_PAGE_SIZE = 100;
|
|
539
|
+
|
|
540
|
+
function githubOpenPullsListPath(config, sliceSort) {
|
|
541
|
+
const params = new URLSearchParams({ state: 'open', ...githubOpenPullSortQuery(sliceSort) });
|
|
542
|
+
return `${repoApiPath(config, 'pulls')}?${params.toString()}`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
async function fetchGitHubOpenPullSearchTotal(ctx) {
|
|
546
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
547
|
+
const { token } = requireToken();
|
|
548
|
+
const owner = encodeURIComponent(ctx.config.owner);
|
|
549
|
+
const repo = encodeURIComponent(ctx.config.repo);
|
|
550
|
+
const searchPath = `/search/issues?q=repo:${owner}/${repo}+is:pr+state:open&per_page=1`;
|
|
551
|
+
try {
|
|
552
|
+
const { body } = await fetchJsonWithMeta(`${base}${searchPath}`, {
|
|
553
|
+
headers: authHeaders(token),
|
|
554
|
+
});
|
|
555
|
+
if (!body || body.incomplete_results === true) return null;
|
|
556
|
+
const total = Number(body.total_count);
|
|
557
|
+
const maxTrusted = GITHUB_OPEN_PULL_COMPLIANT_MAX * 2;
|
|
558
|
+
if (!Number.isInteger(total) || total <= 0 || total > maxTrusted) return null;
|
|
559
|
+
return total;
|
|
560
|
+
} catch {
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
async function probeGitHubOpenPullPageOne(ctx, retainMax, sliceSort) {
|
|
566
|
+
const totalCount = await fetchGitHubOpenPullSearchTotal(ctx);
|
|
567
|
+
if (totalCount == null) return null;
|
|
568
|
+
|
|
569
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
570
|
+
const { token } = requireToken();
|
|
571
|
+
const requestLimit = Math.min(retainMax, GITHUB_PAGE_SIZE);
|
|
572
|
+
const listPath = githubOpenPullsListPath(ctx.config, sliceSort);
|
|
573
|
+
const listUrl = withPerPageParam(`${base}${listPath}`, requestLimit);
|
|
574
|
+
try {
|
|
575
|
+
const { body, headers } = await fetchJsonWithMeta(listUrl, {
|
|
576
|
+
headers: authHeaders(token),
|
|
577
|
+
});
|
|
578
|
+
if (!Array.isArray(body)) return null;
|
|
579
|
+
const listTruncated = totalCount > GITHUB_OPEN_PULL_COMPLIANT_MAX;
|
|
580
|
+
const linkHeader = headers?.get?.('link') ?? headers?.get?.('Link') ?? null;
|
|
581
|
+
return { body, totalCount, listTruncated, requestLimit, listUrl, linkHeader };
|
|
582
|
+
} catch {
|
|
583
|
+
return null;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function buildGitHubOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, listTruncated) {
|
|
588
|
+
let numbers = orderOpenPullNumbers(body, (pr) => pr?.number, sliceSort);
|
|
589
|
+
if (numbers.length > retainMax) numbers = numbers.slice(0, retainMax);
|
|
590
|
+
return buildOpenPullListMeta({
|
|
591
|
+
totalCount,
|
|
592
|
+
numbers,
|
|
593
|
+
listTruncated,
|
|
594
|
+
sliceSort,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function githubProbePaginationOpts(probe) {
|
|
599
|
+
const { body, totalCount, requestLimit, listUrl, linkHeader } = probe;
|
|
492
600
|
return {
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
checks_conclusion: checks.check_conclusion,
|
|
496
|
-
blockers,
|
|
601
|
+
trustedTotalCount: totalCount,
|
|
602
|
+
seededFirstPage: { items: body, usedLimit: requestLimit, listUrl, linkHeader },
|
|
497
603
|
};
|
|
498
604
|
}
|
|
499
605
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
606
|
+
async function paginateGitHubOpenPullList(ctx, opts, sliceSort, paginationOpts = {}) {
|
|
607
|
+
const {
|
|
608
|
+
trustedTotalCount = null,
|
|
609
|
+
seededFirstPage = null,
|
|
610
|
+
numberSortFullCollect = false,
|
|
611
|
+
} = paginationOpts;
|
|
503
612
|
const base = apiBase(ctx.config, ctx.parsed);
|
|
504
613
|
const { token } = requireToken();
|
|
505
614
|
const listLimit =
|
|
506
615
|
opts.limit != null && Number.isInteger(Number(opts.limit)) && Number(opts.limit) > 0
|
|
507
616
|
? Number(opts.limit)
|
|
508
617
|
: null;
|
|
509
|
-
const GITHUB_PAGE_SIZE = 100;
|
|
510
618
|
const all = [];
|
|
511
619
|
let listTruncated = false;
|
|
620
|
+
let walkedCount = 0;
|
|
621
|
+
const listPath = githubOpenPullsListPath(ctx.config, sliceSort);
|
|
622
|
+
const retainMax =
|
|
623
|
+
listLimit == null &&
|
|
624
|
+
opts.retain_max != null &&
|
|
625
|
+
Number.isInteger(Number(opts.retain_max)) &&
|
|
626
|
+
Number(opts.retain_max) > 0
|
|
627
|
+
? Number(opts.retain_max)
|
|
628
|
+
: null;
|
|
512
629
|
|
|
513
|
-
if (listLimit == null) {
|
|
630
|
+
if (listLimit == null && numberSortFullCollect) {
|
|
631
|
+
const {
|
|
632
|
+
items: offsetItems,
|
|
633
|
+
list_truncated: offsetTruncated,
|
|
634
|
+
walked_count: offsetWalkedCount,
|
|
635
|
+
} = await paginateOffsetListPages({
|
|
636
|
+
pageSize: GITHUB_PAGE_SIZE,
|
|
637
|
+
retainMax: null,
|
|
638
|
+
trustedEntryCount: trustedTotalCount,
|
|
639
|
+
seededFirstPage: seededFirstPage
|
|
640
|
+
? { items: seededFirstPage.items, usedLimit: seededFirstPage.usedLimit }
|
|
641
|
+
: null,
|
|
642
|
+
fetchPage: async ({ page, limit }) => {
|
|
643
|
+
const pageUrl = `${base}${listPath}&page=${page}`;
|
|
644
|
+
const { body } = await fetchJsonWithMeta(withPerPageParam(pageUrl, limit), {
|
|
645
|
+
headers: authHeaders(token),
|
|
646
|
+
});
|
|
647
|
+
return Array.isArray(body) ? body : [];
|
|
648
|
+
},
|
|
649
|
+
});
|
|
650
|
+
all.push(...offsetItems);
|
|
651
|
+
listTruncated = offsetTruncated;
|
|
652
|
+
walkedCount = offsetWalkedCount;
|
|
653
|
+
} else if (listLimit == null) {
|
|
514
654
|
const trustedOrigin = new URL(base).origin;
|
|
515
|
-
const startUrl = `${base}${
|
|
516
|
-
const
|
|
655
|
+
const startUrl = `${base}${listPath}`;
|
|
656
|
+
const linkSeed = seededFirstPage
|
|
657
|
+
? {
|
|
658
|
+
items: seededFirstPage.items,
|
|
659
|
+
linkHeader: seededFirstPage.linkHeader,
|
|
660
|
+
currentUrl: seededFirstPage.listUrl,
|
|
661
|
+
usedLimit: seededFirstPage.usedLimit,
|
|
662
|
+
}
|
|
663
|
+
: null;
|
|
664
|
+
const {
|
|
665
|
+
items: linkItems,
|
|
666
|
+
truncated: linkTruncated,
|
|
667
|
+
walked_count: linkWalkedCount,
|
|
668
|
+
} = await paginateGitHubLinkPages({
|
|
517
669
|
trustedOrigin,
|
|
518
670
|
startUrl,
|
|
519
671
|
token,
|
|
672
|
+
initialLimit: seededFirstPage?.usedLimit ?? DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
520
673
|
mapPageItems: (body) => (Array.isArray(body) ? body : []),
|
|
674
|
+
seededFirstPage: linkSeed,
|
|
521
675
|
});
|
|
522
676
|
all.push(...linkItems);
|
|
523
677
|
listTruncated = linkTruncated;
|
|
678
|
+
walkedCount = linkWalkedCount;
|
|
524
679
|
} else {
|
|
525
|
-
const {
|
|
680
|
+
const {
|
|
681
|
+
items: limitItems,
|
|
682
|
+
list_truncated: limitTruncated,
|
|
683
|
+
walked_count: limitWalkedCount,
|
|
684
|
+
} = await paginateOffsetListPages({
|
|
526
685
|
pageSize: GITHUB_PAGE_SIZE,
|
|
527
686
|
listLimit,
|
|
528
|
-
// GitHub/GitLab use fixed pageSize with optional listLimit; mark truncated at maxPages.
|
|
529
|
-
// Gitea passes pageSize=min(listLimit, cap) so the limit branch often exits in one page.
|
|
530
687
|
maxPagesTruncatesWithLimit: true,
|
|
688
|
+
trustedEntryCount: trustedTotalCount,
|
|
689
|
+
seededFirstPage: seededFirstPage
|
|
690
|
+
? { items: seededFirstPage.items, usedLimit: seededFirstPage.usedLimit }
|
|
691
|
+
: null,
|
|
531
692
|
fetchPage: async ({ page, limit }) => {
|
|
532
|
-
const pageUrl = `${base}${
|
|
693
|
+
const pageUrl = `${base}${listPath}&page=${page}`;
|
|
533
694
|
const { body } = await fetchJsonWithMeta(withPerPageParam(pageUrl, limit), {
|
|
534
695
|
headers: authHeaders(token),
|
|
535
696
|
});
|
|
@@ -538,16 +699,70 @@ export async function listOpenPullsWithMeta(ctx, opts = {}) {
|
|
|
538
699
|
});
|
|
539
700
|
all.push(...limitItems);
|
|
540
701
|
listTruncated = limitTruncated;
|
|
702
|
+
walkedCount = limitWalkedCount;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
let numbers = orderOpenPullNumbers(all, (pr) => pr?.number, sliceSort);
|
|
706
|
+
const outputCap = listLimit ?? retainMax;
|
|
707
|
+
if (outputCap != null && numbers.length > outputCap) {
|
|
708
|
+
numbers = numbers.slice(0, outputCap);
|
|
709
|
+
}
|
|
710
|
+
const entryCount =
|
|
711
|
+
trustedTotalCount != null
|
|
712
|
+
? resolvePaginatedEntryCount(trustedTotalCount, walkedCount)
|
|
713
|
+
: undefined;
|
|
714
|
+
return {
|
|
715
|
+
numbers,
|
|
716
|
+
list_truncated: resolveListTruncatedWithTrustedTotal({
|
|
717
|
+
listTruncated,
|
|
718
|
+
trustedTotalCount,
|
|
719
|
+
walkedCount,
|
|
720
|
+
fullCollect: numberSortFullCollect,
|
|
721
|
+
}),
|
|
722
|
+
slice_sort: sliceSort,
|
|
723
|
+
...(entryCount != null ? { entry_count: entryCount } : {}),
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export async function listOpenPullsWithMeta(ctx, opts = {}) {
|
|
728
|
+
apiBase(ctx.config, ctx.parsed);
|
|
729
|
+
requireToken();
|
|
730
|
+
const sliceSort = normalizeCrInventorySort(opts.sort ?? DEFAULT_CR_INVENTORY_SLICE_SORT);
|
|
731
|
+
if (!isCrInventoryFastPathEligible(opts)) {
|
|
732
|
+
return paginateGitHubOpenPullList(ctx, opts, sliceSort);
|
|
541
733
|
}
|
|
542
734
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
if (listLimit != null && numbers.length > listLimit) {
|
|
548
|
-
numbers = numbers.slice(0, listLimit);
|
|
735
|
+
const retainMax = Number(opts.retain_max);
|
|
736
|
+
const probe = await probeGitHubOpenPullPageOne(ctx, retainMax, sliceSort);
|
|
737
|
+
if (!probe) {
|
|
738
|
+
return paginateGitHubOpenPullList(ctx, opts, sliceSort);
|
|
549
739
|
}
|
|
550
|
-
|
|
740
|
+
|
|
741
|
+
const { body, totalCount, listTruncated, requestLimit } = probe;
|
|
742
|
+
|
|
743
|
+
if (listTruncated) {
|
|
744
|
+
if (body.length === 0) {
|
|
745
|
+
return paginateGitHubOpenPullList(ctx, opts, sliceSort, githubProbePaginationOpts(probe));
|
|
746
|
+
}
|
|
747
|
+
return buildGitHubOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, true);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (
|
|
751
|
+
isNumberSortFastPathEligible(totalCount, retainMax, sliceSort) &&
|
|
752
|
+
validateFastPathPageLength(totalCount, requestLimit, body.length)
|
|
753
|
+
) {
|
|
754
|
+
return buildGitHubOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, false);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
return paginateGitHubOpenPullList(
|
|
758
|
+
ctx,
|
|
759
|
+
opts,
|
|
760
|
+
sliceSort,
|
|
761
|
+
{
|
|
762
|
+
...githubProbePaginationOpts(probe),
|
|
763
|
+
numberSortFullCollect: isNumberSortFullCollectRequired(totalCount, retainMax, sliceSort),
|
|
764
|
+
},
|
|
765
|
+
);
|
|
551
766
|
}
|
|
552
767
|
|
|
553
768
|
export async function listOpenPulls(ctx, opts = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remogram/provider-github-api",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "GitHub API provider for remogram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,6 +22,6 @@
|
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@remogram/core": "0.1.0-beta.
|
|
25
|
+
"@remogram/core": "0.1.0-beta.4"
|
|
26
26
|
}
|
|
27
27
|
}
|