@remogram/provider-github-api 0.1.0-beta.0 → 0.1.0-beta.10
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/branch-protection-internal.js +13 -0
- package/index.js +739 -32
- package/package.json +4 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @type {((ctx: object, opts: { branchRef: string }) => Promise<object>) | null} */
|
|
2
|
+
let branchProtectionImpl = null;
|
|
3
|
+
|
|
4
|
+
export function setBranchProtectionImpl(fn) {
|
|
5
|
+
branchProtectionImpl = fn;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function resolveBranchProtection(ctx, opts) {
|
|
9
|
+
if (typeof branchProtectionImpl !== 'function') {
|
|
10
|
+
throw new Error('branch protection impl not registered');
|
|
11
|
+
}
|
|
12
|
+
return branchProtectionImpl(ctx, opts);
|
|
13
|
+
}
|
package/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fetchJson,
|
|
3
|
+
fetchJsonWithMeta,
|
|
4
|
+
parseLinkHeader,
|
|
5
|
+
isTrustedPaginationUrl,
|
|
3
6
|
sanitizeField,
|
|
4
7
|
sanitizeUrl,
|
|
5
8
|
assertGitRef,
|
|
@@ -7,9 +10,58 @@ import {
|
|
|
7
10
|
gitRevParse,
|
|
8
11
|
gitCurrentBranch,
|
|
9
12
|
gitAheadBehind,
|
|
13
|
+
refsInventory,
|
|
14
|
+
crInventory,
|
|
15
|
+
buildMergePlanFromProviderFacts,
|
|
10
16
|
ERROR_CODES,
|
|
11
17
|
forgeError,
|
|
18
|
+
LIVE_REACHABILITY_TIMEOUT_MS,
|
|
19
|
+
forgeIngestCapabilityFacts,
|
|
20
|
+
forgeWriteFieldCapabilityFacts,
|
|
21
|
+
checkPaginationCapabilityFacts,
|
|
22
|
+
openPullListCapabilityFacts,
|
|
23
|
+
DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
24
|
+
MAX_CHECK_STATUS_PAGES,
|
|
25
|
+
DEFAULT_OPEN_PULL_LIST_PAGE_SIZE,
|
|
26
|
+
fetchWithIngestPageBackoff,
|
|
27
|
+
paginateOffsetListPages,
|
|
28
|
+
fetchPageWithIngestBackoff,
|
|
29
|
+
withPerPageParam,
|
|
30
|
+
apiProviderCommands,
|
|
31
|
+
normalizeCrInventorySort,
|
|
32
|
+
DEFAULT_CR_INVENTORY_SLICE_SORT,
|
|
33
|
+
isCrInventoryFastPathEligible,
|
|
34
|
+
validateFastPathPageLength,
|
|
35
|
+
isNumberSortFastPathEligible,
|
|
36
|
+
isNumberSortFullCollectRequired,
|
|
37
|
+
resolvePaginatedEntryCount,
|
|
38
|
+
resolveListTruncatedWithTrustedTotal,
|
|
39
|
+
orderOpenPullNumbers,
|
|
40
|
+
buildOpenPullListMeta,
|
|
41
|
+
githubOpenPullSortQuery,
|
|
42
|
+
buildProviderIdentityFromGitHubUser,
|
|
43
|
+
buildBranchProtectionFromGitHubProtection,
|
|
44
|
+
buildPrChecksBody,
|
|
45
|
+
buildCrFilesBody,
|
|
46
|
+
buildCrFilesFromGiteaFiles,
|
|
47
|
+
buildCrCommentsBody,
|
|
48
|
+
buildCrCommentsFromGiteaComments,
|
|
49
|
+
parseSinceObservedAt,
|
|
50
|
+
buildForgeChangesFromGiteaPulls,
|
|
51
|
+
buildChecksConclusionObservedEvent,
|
|
52
|
+
appendForgeChangeEvents,
|
|
53
|
+
buildCommitStatusSetBody,
|
|
54
|
+
idempotencyPacketFields,
|
|
55
|
+
parseStatusSetArgs,
|
|
56
|
+
normalizeStatusSetState,
|
|
57
|
+
statusSetIdempotencyScanCapabilityFacts,
|
|
58
|
+
MAX_OPEN_PULL_IDEMPOTENCY_PAGES,
|
|
59
|
+
assertWriteCommandConfigured,
|
|
12
60
|
} from '@remogram/core';
|
|
61
|
+
import {
|
|
62
|
+
resolveBranchProtection,
|
|
63
|
+
setBranchProtectionImpl,
|
|
64
|
+
} from './branch-protection-internal.js';
|
|
13
65
|
|
|
14
66
|
const PUBLIC_GITHUB_HOST = 'github.com';
|
|
15
67
|
const PUBLIC_GITHUB_API = 'https://api.github.com';
|
|
@@ -36,13 +88,27 @@ query RemogramPrView($owner: String!, $repo: String!, $number: Int!) {
|
|
|
36
88
|
const AUTH_CAPABILITIES = [
|
|
37
89
|
'repo_status',
|
|
38
90
|
'ref_compare',
|
|
91
|
+
'ref_inventory',
|
|
92
|
+
'cr_inventory',
|
|
39
93
|
'pr_status',
|
|
40
94
|
'pr_checks',
|
|
41
95
|
'merge_plan',
|
|
42
96
|
'sync_plan',
|
|
97
|
+
'status_set',
|
|
98
|
+
'whoami',
|
|
99
|
+
'branch_protection',
|
|
100
|
+
'cr_files',
|
|
101
|
+
'cr_comments',
|
|
102
|
+
'forge_changes',
|
|
43
103
|
];
|
|
44
104
|
|
|
45
|
-
const STRUCTURED_COMMANDS =
|
|
105
|
+
const STRUCTURED_COMMANDS = apiProviderCommands({
|
|
106
|
+
branchProtectionImplemented: true,
|
|
107
|
+
crFilesImplemented: true,
|
|
108
|
+
crCommentsImplemented: true,
|
|
109
|
+
forgeChangesImplemented: true,
|
|
110
|
+
statusSetImplemented: true,
|
|
111
|
+
});
|
|
46
112
|
|
|
47
113
|
export function githubToken() {
|
|
48
114
|
if (process.env.GITHUB_TOKEN) return { token: process.env.GITHUB_TOKEN, env: 'GITHUB_TOKEN' };
|
|
@@ -149,6 +215,124 @@ export async function githubFetch(config, parsed, path, options = {}) {
|
|
|
149
215
|
});
|
|
150
216
|
}
|
|
151
217
|
|
|
218
|
+
export async function apiReachability(ctx) {
|
|
219
|
+
if (!githubToken()) {
|
|
220
|
+
throw Object.assign(new Error('GitHub token not set'), {
|
|
221
|
+
forgeError: forgeError(
|
|
222
|
+
ERROR_CODES.UNAUTHENTICATED_PROVIDER,
|
|
223
|
+
'GITHUB_TOKEN or GH_TOKEN not set',
|
|
224
|
+
),
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const { token } = requireToken();
|
|
228
|
+
const url = `${apiBase(ctx.config, ctx.parsed)}${repoApiPath(ctx.config)}`;
|
|
229
|
+
await fetchJson(
|
|
230
|
+
url,
|
|
231
|
+
{ headers: authHeaders(token) },
|
|
232
|
+
LIVE_REACHABILITY_TIMEOUT_MS,
|
|
233
|
+
);
|
|
234
|
+
return { repo_accessible: true };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const MAX_CHECK_PAGES = MAX_CHECK_STATUS_PAGES;
|
|
238
|
+
|
|
239
|
+
export function resolveGitHubLinkNextPage({ trustedOrigin, currentUrl, linkHeader, pageIndex, maxPages }) {
|
|
240
|
+
const nextRaw = parseLinkHeader(linkHeader).next ?? null;
|
|
241
|
+
if (!nextRaw) {
|
|
242
|
+
return { nextUrl: null, truncated: false };
|
|
243
|
+
}
|
|
244
|
+
if (!isTrustedPaginationUrl(trustedOrigin, nextRaw, currentUrl)) {
|
|
245
|
+
return { nextUrl: null, truncated: true };
|
|
246
|
+
}
|
|
247
|
+
if (pageIndex === maxPages - 1) {
|
|
248
|
+
return { nextUrl: null, truncated: true };
|
|
249
|
+
}
|
|
250
|
+
return { nextUrl: new URL(nextRaw, currentUrl).href, truncated: false };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function paginateGitHubLinkPages({
|
|
254
|
+
trustedOrigin,
|
|
255
|
+
startUrl,
|
|
256
|
+
token,
|
|
257
|
+
initialLimit = DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
258
|
+
mapPageItems,
|
|
259
|
+
seededFirstPage = null,
|
|
260
|
+
}) {
|
|
261
|
+
const all = [];
|
|
262
|
+
let truncated = false;
|
|
263
|
+
let walkedCount = 0;
|
|
264
|
+
let url = startUrl;
|
|
265
|
+
let activeLimit = initialLimit;
|
|
266
|
+
let pageIndex = 0;
|
|
267
|
+
|
|
268
|
+
if (seededFirstPage) {
|
|
269
|
+
const { items, linkHeader, currentUrl, usedLimit } = seededFirstPage;
|
|
270
|
+
activeLimit = usedLimit;
|
|
271
|
+
const mapped = mapPageItems(items);
|
|
272
|
+
walkedCount += mapped.length;
|
|
273
|
+
all.push(...mapped);
|
|
274
|
+
const linkPage = resolveGitHubLinkNextPage({
|
|
275
|
+
trustedOrigin,
|
|
276
|
+
currentUrl,
|
|
277
|
+
linkHeader,
|
|
278
|
+
pageIndex: 0,
|
|
279
|
+
maxPages: MAX_CHECK_PAGES,
|
|
280
|
+
});
|
|
281
|
+
if (linkPage.truncated) {
|
|
282
|
+
truncated = true;
|
|
283
|
+
}
|
|
284
|
+
url = linkPage.nextUrl ? withPerPageParam(linkPage.nextUrl, activeLimit) : null;
|
|
285
|
+
pageIndex = 1;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
for (let page = pageIndex; page < MAX_CHECK_PAGES && url; page += 1) {
|
|
289
|
+
const currentUrl = url;
|
|
290
|
+
let usedLimit = activeLimit;
|
|
291
|
+
const { body, headers } = await fetchWithIngestPageBackoff(
|
|
292
|
+
(attemptUrl) =>
|
|
293
|
+
fetchJsonWithMeta(attemptUrl, {
|
|
294
|
+
headers: authHeaders(token),
|
|
295
|
+
}),
|
|
296
|
+
(limit) => {
|
|
297
|
+
usedLimit = limit;
|
|
298
|
+
return withPerPageParam(currentUrl, limit);
|
|
299
|
+
},
|
|
300
|
+
activeLimit,
|
|
301
|
+
);
|
|
302
|
+
activeLimit = usedLimit;
|
|
303
|
+
const mapped = mapPageItems(body);
|
|
304
|
+
walkedCount += mapped.length;
|
|
305
|
+
all.push(...mapped);
|
|
306
|
+
const linkHeader = headers?.get?.('link') ?? headers?.get?.('Link') ?? null;
|
|
307
|
+
const linkPage = resolveGitHubLinkNextPage({
|
|
308
|
+
trustedOrigin,
|
|
309
|
+
currentUrl,
|
|
310
|
+
linkHeader,
|
|
311
|
+
pageIndex: page,
|
|
312
|
+
maxPages: MAX_CHECK_PAGES,
|
|
313
|
+
});
|
|
314
|
+
if (linkPage.truncated) {
|
|
315
|
+
truncated = true;
|
|
316
|
+
}
|
|
317
|
+
url = linkPage.nextUrl ? withPerPageParam(linkPage.nextUrl, activeLimit) : null;
|
|
318
|
+
}
|
|
319
|
+
return { items: all, truncated, walked_count: walkedCount };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export async function githubFetchPaginated(config, parsed, path, slice) {
|
|
323
|
+
const base = apiBase(config, parsed);
|
|
324
|
+
const trustedOrigin = new URL(base).origin;
|
|
325
|
+
const { token } = requireToken();
|
|
326
|
+
const pageQuery = `${path.includes('?') ? '&' : '?'}per_page=${DEFAULT_CHECK_STATUS_PAGE_SIZE}`;
|
|
327
|
+
const startUrl = `${base}${path}${pageQuery}`;
|
|
328
|
+
return paginateGitHubLinkPages({
|
|
329
|
+
trustedOrigin,
|
|
330
|
+
startUrl,
|
|
331
|
+
token,
|
|
332
|
+
mapPageItems: (body) => slice(body),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
152
336
|
export function graphqlEndpoint(config, parsed = {}) {
|
|
153
337
|
const remoteHost = (parsed.host || configuredHost(config) || '').toLowerCase();
|
|
154
338
|
if (remoteHost === PUBLIC_GITHUB_HOST) {
|
|
@@ -241,21 +425,33 @@ export async function repoStatus(ctx) {
|
|
|
241
425
|
};
|
|
242
426
|
}
|
|
243
427
|
|
|
244
|
-
export function providerCapabilities() {
|
|
428
|
+
export function providerCapabilities(ctx = {}) {
|
|
429
|
+
const check_sources = ['commit_statuses', 'check_runs'];
|
|
245
430
|
return {
|
|
246
431
|
commands: STRUCTURED_COMMANDS,
|
|
247
432
|
auth_envs: ['GITHUB_TOKEN', 'GH_TOKEN'],
|
|
248
|
-
check_sources
|
|
433
|
+
check_sources,
|
|
249
434
|
mergeability_confidence: 'derived',
|
|
250
435
|
host_binding: 'verified_remote_host',
|
|
251
|
-
pagination: '
|
|
252
|
-
write_support:
|
|
436
|
+
pagination: 'supported',
|
|
437
|
+
write_support: true,
|
|
438
|
+
write_commands: ['status_set'],
|
|
439
|
+
...forgeIngestCapabilityFacts(),
|
|
440
|
+
...forgeWriteFieldCapabilityFacts(ctx.writeFieldPolicy),
|
|
441
|
+
...statusSetIdempotencyScanCapabilityFacts(),
|
|
442
|
+
...checkPaginationCapabilityFacts({
|
|
443
|
+
strategy: 'link_header',
|
|
444
|
+
pageSizeParam: 'per_page',
|
|
445
|
+
sourceCount: check_sources.length,
|
|
446
|
+
}),
|
|
447
|
+
...openPullListCapabilityFacts({
|
|
448
|
+
totalCountSource: 'search_api',
|
|
449
|
+
}),
|
|
253
450
|
};
|
|
254
451
|
}
|
|
255
452
|
|
|
256
453
|
export async function refsCompare(ctx, baseRef, headRef) {
|
|
257
454
|
apiBase(ctx.config, ctx.parsed);
|
|
258
|
-
requireToken();
|
|
259
455
|
assertGitRef(baseRef, 'base');
|
|
260
456
|
assertGitRef(headRef, 'head');
|
|
261
457
|
const baseSha = gitRevParse(ctx.cwd, baseRef);
|
|
@@ -267,10 +463,10 @@ export async function refsCompare(ctx, baseRef, headRef) {
|
|
|
267
463
|
}
|
|
268
464
|
const counts = gitAheadBehind(ctx.cwd, baseSha, headSha);
|
|
269
465
|
return {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
466
|
+
compare_base_ref: sanitizeField(baseRef),
|
|
467
|
+
compare_base_sha: baseSha,
|
|
468
|
+
compare_head_ref: sanitizeField(headRef),
|
|
469
|
+
compare_head_sha: headSha,
|
|
274
470
|
...counts,
|
|
275
471
|
};
|
|
276
472
|
}
|
|
@@ -292,10 +488,10 @@ export async function prView(ctx, opts) {
|
|
|
292
488
|
url: sanitizeUrl(pr.html_url ?? pr.url),
|
|
293
489
|
title: sanitizeField(pr.title),
|
|
294
490
|
state: sanitizeField(pr.state),
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
491
|
+
forge_target_branch_ref: sanitizeField(pr.base?.ref),
|
|
492
|
+
forge_target_sha: sanitizeField(pr.base?.sha),
|
|
493
|
+
forge_source_branch_ref: sanitizeField(pr.head?.ref),
|
|
494
|
+
forge_source_sha: sanitizeField(pr.head?.sha),
|
|
299
495
|
mergeability: mergeability(pr),
|
|
300
496
|
};
|
|
301
497
|
}
|
|
@@ -341,6 +537,7 @@ export async function prChecks(ctx, opts) {
|
|
|
341
537
|
apiBase(ctx.config, ctx.parsed);
|
|
342
538
|
requireToken();
|
|
343
539
|
let sha;
|
|
540
|
+
let requiredContexts = [];
|
|
344
541
|
if (opts.ref) {
|
|
345
542
|
assertGitRef(opts.ref, 'ref');
|
|
346
543
|
sha = gitRevParse(ctx.cwd, opts.ref);
|
|
@@ -352,6 +549,11 @@ export async function prChecks(ctx, opts) {
|
|
|
352
549
|
} else {
|
|
353
550
|
const pr = await fetchPullGraphql(ctx.config, ctx.parsed, opts.number);
|
|
354
551
|
sha = pr.head?.sha;
|
|
552
|
+
const targetBranch = pr.base?.ref;
|
|
553
|
+
if (targetBranch) {
|
|
554
|
+
const protection = await resolveBranchProtection(ctx, { branchRef: targetBranch });
|
|
555
|
+
requiredContexts = protection.required_status_contexts ?? [];
|
|
556
|
+
}
|
|
355
557
|
}
|
|
356
558
|
if (!sha) {
|
|
357
559
|
throw Object.assign(new Error('No SHA'), {
|
|
@@ -359,41 +561,401 @@ export async function prChecks(ctx, opts) {
|
|
|
359
561
|
});
|
|
360
562
|
}
|
|
361
563
|
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
564
|
+
const statusPath = repoApiPath(ctx.config, 'commits', sha, 'statuses');
|
|
565
|
+
const checkRunsPath = repoApiPath(ctx.config, 'commits', sha, 'check-runs');
|
|
566
|
+
const [statusResult, checkRunResult] = await Promise.all([
|
|
567
|
+
githubFetchPaginated(ctx.config, ctx.parsed, statusPath, (body) =>
|
|
568
|
+
Array.isArray(body) ? body : [],
|
|
569
|
+
),
|
|
570
|
+
githubFetchPaginated(ctx.config, ctx.parsed, checkRunsPath, (body) => body?.check_runs ?? []),
|
|
365
571
|
]);
|
|
366
|
-
const
|
|
572
|
+
const statusRecords = statusResult.items;
|
|
573
|
+
const checkRunRecords = checkRunResult.items;
|
|
574
|
+
const mappedStatuses = statusRecords.map((s) => ({
|
|
367
575
|
context: sanitizeField(s.context),
|
|
368
576
|
state: normalizeCommitStatusState(s.state),
|
|
369
577
|
description: sanitizeField(s.description),
|
|
578
|
+
...(s.target_url ? { target_url: sanitizeField(s.target_url) } : {}),
|
|
579
|
+
sha,
|
|
580
|
+
source: 'commit_status',
|
|
370
581
|
}));
|
|
371
|
-
const mappedCheckRuns =
|
|
582
|
+
const mappedCheckRuns = checkRunRecords.map((run) => ({
|
|
372
583
|
context: sanitizeField(run.name),
|
|
373
584
|
state: normalizeCheckRunState(run),
|
|
374
585
|
description: sanitizeField(checkRunDescription(run)),
|
|
586
|
+
...(run.details_url ? { target_url: sanitizeField(run.details_url) } : {}),
|
|
587
|
+
sha: sanitizeField(run.head_sha) || sha,
|
|
588
|
+
source: 'check_run',
|
|
375
589
|
}));
|
|
376
590
|
const mapped = [...mappedStatuses, ...mappedCheckRuns];
|
|
377
|
-
|
|
591
|
+
const checks_truncated = statusResult.truncated || checkRunResult.truncated;
|
|
592
|
+
return buildPrChecksBody({
|
|
593
|
+
forge_source_sha: sha,
|
|
594
|
+
check_conclusion: summarizeChecks(mapped),
|
|
595
|
+
checks_truncated,
|
|
596
|
+
statuses: mapped,
|
|
597
|
+
required_contexts: requiredContexts,
|
|
598
|
+
});
|
|
378
599
|
}
|
|
379
600
|
|
|
380
601
|
export async function mergePlan(ctx, opts) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
602
|
+
return buildMergePlanFromProviderFacts(ctx, opts, { prView, prChecks, crFiles });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
export async function crFiles(ctx, { number }) {
|
|
606
|
+
if (number == null) {
|
|
607
|
+
throw Object.assign(new Error('--number required'), {
|
|
608
|
+
forgeError: forgeError(ERROR_CODES.INVALID_ARGS, 'Provide --number for PR changed paths'),
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
612
|
+
const trustedOrigin = new URL(base).origin;
|
|
613
|
+
const { token } = requireToken();
|
|
614
|
+
const startUrl = `${base}${repoApiPath(ctx.config, 'pulls', number, 'files')}?per_page=${DEFAULT_CHECK_STATUS_PAGE_SIZE}`;
|
|
615
|
+
const { items, truncated, walked_count } = await paginateGitHubLinkPages({
|
|
616
|
+
trustedOrigin,
|
|
617
|
+
startUrl,
|
|
618
|
+
token,
|
|
619
|
+
mapPageItems: (body) => (Array.isArray(body) ? body : []),
|
|
620
|
+
});
|
|
621
|
+
const body = buildCrFilesFromGiteaFiles(number, items);
|
|
622
|
+
if (truncated) {
|
|
623
|
+
return buildCrFilesBody({
|
|
624
|
+
pr_number: body.pr_number,
|
|
625
|
+
changed_paths: body.changed_paths,
|
|
626
|
+
paths_truncated: true,
|
|
627
|
+
path_count: walked_count,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
return body;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
export async function crComments(ctx, { number }) {
|
|
634
|
+
if (number == null) {
|
|
635
|
+
throw Object.assign(new Error('--number required'), {
|
|
636
|
+
forgeError: forgeError(ERROR_CODES.INVALID_ARGS, 'Provide --number for PR review comments'),
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
640
|
+
const trustedOrigin = new URL(base).origin;
|
|
641
|
+
const { token } = requireToken();
|
|
642
|
+
const startUrl = `${base}${repoApiPath(ctx.config, 'pulls', number, 'comments')}?per_page=${DEFAULT_CHECK_STATUS_PAGE_SIZE}`;
|
|
643
|
+
const { items, truncated, walked_count } = await paginateGitHubLinkPages({
|
|
644
|
+
trustedOrigin,
|
|
645
|
+
startUrl,
|
|
646
|
+
token,
|
|
647
|
+
mapPageItems: (body) => (Array.isArray(body) ? body : []),
|
|
648
|
+
});
|
|
649
|
+
const body = buildCrCommentsFromGiteaComments(number, items);
|
|
650
|
+
if (truncated) {
|
|
651
|
+
return buildCrCommentsBody({
|
|
652
|
+
pr_number: body.pr_number,
|
|
653
|
+
comments: body.comments,
|
|
654
|
+
comments_truncated: true,
|
|
655
|
+
comment_count: walked_count,
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
return body;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
export async function forgeChanges(ctx, { since }) {
|
|
662
|
+
requireToken();
|
|
663
|
+
const sinceIso = parseSinceObservedAt(since);
|
|
664
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
665
|
+
const trustedOrigin = new URL(base).origin;
|
|
666
|
+
const { token } = requireToken();
|
|
667
|
+
const startUrl = `${base}${repoApiPath(ctx.config, 'pulls')}?state=all&sort=updated&direction=desc&per_page=${DEFAULT_CHECK_STATUS_PAGE_SIZE}`;
|
|
668
|
+
const { items, truncated } = await paginateGitHubLinkPages({
|
|
669
|
+
trustedOrigin,
|
|
670
|
+
startUrl,
|
|
671
|
+
token,
|
|
672
|
+
mapPageItems: (body) => (Array.isArray(body) ? body : []),
|
|
673
|
+
});
|
|
674
|
+
let body = buildForgeChangesFromGiteaPulls(sinceIso, items, { listTruncated: truncated });
|
|
675
|
+
const checkNumbers = new Set();
|
|
676
|
+
for (const event of body.events) {
|
|
677
|
+
if (event.kind === 'pr_opened' || event.kind === 'head_sha_moved') {
|
|
678
|
+
checkNumbers.add(event.pr_number);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const checkEvents = [];
|
|
682
|
+
for (const number of checkNumbers) {
|
|
683
|
+
const checks = await prChecks(ctx, { number });
|
|
684
|
+
checkEvents.push(buildChecksConclusionObservedEvent(number, checks));
|
|
685
|
+
}
|
|
686
|
+
if (checkEvents.length > 0) {
|
|
687
|
+
body = appendForgeChangeEvents(body, checkEvents, { listTruncated: truncated });
|
|
688
|
+
}
|
|
689
|
+
return body;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const GITHUB_OPEN_PULL_COMPLIANT_MAX =
|
|
693
|
+
DEFAULT_OPEN_PULL_LIST_PAGE_SIZE * MAX_CHECK_STATUS_PAGES;
|
|
694
|
+
const GITHUB_PAGE_SIZE = 100;
|
|
695
|
+
|
|
696
|
+
function githubOpenPullsListPath(config, sliceSort) {
|
|
697
|
+
const params = new URLSearchParams({ state: 'open', ...githubOpenPullSortQuery(sliceSort) });
|
|
698
|
+
return `${repoApiPath(config, 'pulls')}?${params.toString()}`;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function fetchGitHubOpenPullSearchTotal(ctx) {
|
|
702
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
703
|
+
const { token } = requireToken();
|
|
704
|
+
const owner = encodeURIComponent(ctx.config.owner);
|
|
705
|
+
const repo = encodeURIComponent(ctx.config.repo);
|
|
706
|
+
const searchPath = `/search/issues?q=repo:${owner}/${repo}+is:pr+state:open&per_page=1`;
|
|
707
|
+
try {
|
|
708
|
+
const { body } = await fetchJsonWithMeta(`${base}${searchPath}`, {
|
|
709
|
+
headers: authHeaders(token),
|
|
710
|
+
});
|
|
711
|
+
if (!body || body.incomplete_results === true) return null;
|
|
712
|
+
const total = Number(body.total_count);
|
|
713
|
+
const maxTrusted = GITHUB_OPEN_PULL_COMPLIANT_MAX * 2;
|
|
714
|
+
if (!Number.isInteger(total) || total <= 0 || total > maxTrusted) return null;
|
|
715
|
+
return total;
|
|
716
|
+
} catch {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
async function probeGitHubOpenPullPageOne(ctx, retainMax, sliceSort) {
|
|
722
|
+
const totalCount = await fetchGitHubOpenPullSearchTotal(ctx);
|
|
723
|
+
if (totalCount == null) return null;
|
|
724
|
+
|
|
725
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
726
|
+
const { token } = requireToken();
|
|
727
|
+
const requestLimit = Math.min(retainMax, GITHUB_PAGE_SIZE);
|
|
728
|
+
const listPath = githubOpenPullsListPath(ctx.config, sliceSort);
|
|
729
|
+
const listUrl = withPerPageParam(`${base}${listPath}`, requestLimit);
|
|
730
|
+
try {
|
|
731
|
+
const { body, headers } = await fetchJsonWithMeta(listUrl, {
|
|
732
|
+
headers: authHeaders(token),
|
|
733
|
+
});
|
|
734
|
+
if (!Array.isArray(body)) return null;
|
|
735
|
+
const listTruncated = totalCount > GITHUB_OPEN_PULL_COMPLIANT_MAX;
|
|
736
|
+
const linkHeader = headers?.get?.('link') ?? headers?.get?.('Link') ?? null;
|
|
737
|
+
return { body, totalCount, listTruncated, requestLimit, listUrl, linkHeader };
|
|
738
|
+
} catch {
|
|
739
|
+
return null;
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
function buildGitHubOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, listTruncated) {
|
|
744
|
+
let numbers = orderOpenPullNumbers(body, (pr) => pr?.number, sliceSort);
|
|
745
|
+
if (numbers.length > retainMax) numbers = numbers.slice(0, retainMax);
|
|
746
|
+
return buildOpenPullListMeta({
|
|
747
|
+
totalCount,
|
|
748
|
+
numbers,
|
|
749
|
+
listTruncated,
|
|
750
|
+
sliceSort,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
function githubProbePaginationOpts(probe) {
|
|
755
|
+
const { body, totalCount, requestLimit, listUrl, linkHeader } = probe;
|
|
389
756
|
return {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
checks_conclusion: checks.check_conclusion,
|
|
393
|
-
blockers,
|
|
757
|
+
trustedTotalCount: totalCount,
|
|
758
|
+
seededFirstPage: { items: body, usedLimit: requestLimit, listUrl, linkHeader },
|
|
394
759
|
};
|
|
395
760
|
}
|
|
396
761
|
|
|
762
|
+
async function paginateGitHubOpenPullList(ctx, opts, sliceSort, paginationOpts = {}) {
|
|
763
|
+
const {
|
|
764
|
+
trustedTotalCount = null,
|
|
765
|
+
seededFirstPage = null,
|
|
766
|
+
numberSortFullCollect = false,
|
|
767
|
+
} = paginationOpts;
|
|
768
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
769
|
+
const { token } = requireToken();
|
|
770
|
+
const listLimit =
|
|
771
|
+
opts.limit != null && Number.isInteger(Number(opts.limit)) && Number(opts.limit) > 0
|
|
772
|
+
? Number(opts.limit)
|
|
773
|
+
: null;
|
|
774
|
+
const all = [];
|
|
775
|
+
let listTruncated = false;
|
|
776
|
+
let walkedCount = 0;
|
|
777
|
+
const listPath = githubOpenPullsListPath(ctx.config, sliceSort);
|
|
778
|
+
const retainMax =
|
|
779
|
+
listLimit == null &&
|
|
780
|
+
opts.retain_max != null &&
|
|
781
|
+
Number.isInteger(Number(opts.retain_max)) &&
|
|
782
|
+
Number(opts.retain_max) > 0
|
|
783
|
+
? Number(opts.retain_max)
|
|
784
|
+
: null;
|
|
785
|
+
|
|
786
|
+
if (listLimit == null && numberSortFullCollect) {
|
|
787
|
+
if (seededFirstPage?.linkHeader) {
|
|
788
|
+
const trustedOrigin = new URL(base).origin;
|
|
789
|
+
const startUrl = seededFirstPage.listUrl ?? `${base}${listPath}`;
|
|
790
|
+
const linkSeed = {
|
|
791
|
+
items: seededFirstPage.items,
|
|
792
|
+
linkHeader: seededFirstPage.linkHeader,
|
|
793
|
+
currentUrl: startUrl,
|
|
794
|
+
usedLimit: seededFirstPage.usedLimit,
|
|
795
|
+
};
|
|
796
|
+
const {
|
|
797
|
+
items: linkItems,
|
|
798
|
+
truncated: linkTruncated,
|
|
799
|
+
walked_count: linkWalkedCount,
|
|
800
|
+
} = await paginateGitHubLinkPages({
|
|
801
|
+
trustedOrigin,
|
|
802
|
+
startUrl,
|
|
803
|
+
token,
|
|
804
|
+
initialLimit: seededFirstPage.usedLimit ?? DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
805
|
+
mapPageItems: (body) => (Array.isArray(body) ? body : []),
|
|
806
|
+
seededFirstPage: linkSeed,
|
|
807
|
+
});
|
|
808
|
+
all.push(...linkItems);
|
|
809
|
+
listTruncated = linkTruncated;
|
|
810
|
+
walkedCount = linkWalkedCount;
|
|
811
|
+
} else {
|
|
812
|
+
const {
|
|
813
|
+
items: offsetItems,
|
|
814
|
+
list_truncated: offsetTruncated,
|
|
815
|
+
walked_count: offsetWalkedCount,
|
|
816
|
+
} = await paginateOffsetListPages({
|
|
817
|
+
pageSize: GITHUB_PAGE_SIZE,
|
|
818
|
+
retainMax: null,
|
|
819
|
+
trustedEntryCount: trustedTotalCount,
|
|
820
|
+
seededFirstPage: seededFirstPage
|
|
821
|
+
? { items: seededFirstPage.items, usedLimit: seededFirstPage.usedLimit }
|
|
822
|
+
: null,
|
|
823
|
+
fetchPage: async ({ page, limit }) => {
|
|
824
|
+
const pageUrl = `${base}${listPath}&page=${page}`;
|
|
825
|
+
const { body } = await fetchJsonWithMeta(withPerPageParam(pageUrl, limit), {
|
|
826
|
+
headers: authHeaders(token),
|
|
827
|
+
});
|
|
828
|
+
return Array.isArray(body) ? body : [];
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
all.push(...offsetItems);
|
|
832
|
+
listTruncated = offsetTruncated;
|
|
833
|
+
walkedCount = offsetWalkedCount;
|
|
834
|
+
}
|
|
835
|
+
} else if (listLimit == null) {
|
|
836
|
+
const trustedOrigin = new URL(base).origin;
|
|
837
|
+
const startUrl = `${base}${listPath}`;
|
|
838
|
+
const linkSeed = seededFirstPage
|
|
839
|
+
? {
|
|
840
|
+
items: seededFirstPage.items,
|
|
841
|
+
linkHeader: seededFirstPage.linkHeader,
|
|
842
|
+
currentUrl: seededFirstPage.listUrl,
|
|
843
|
+
usedLimit: seededFirstPage.usedLimit,
|
|
844
|
+
}
|
|
845
|
+
: null;
|
|
846
|
+
const {
|
|
847
|
+
items: linkItems,
|
|
848
|
+
truncated: linkTruncated,
|
|
849
|
+
walked_count: linkWalkedCount,
|
|
850
|
+
} = await paginateGitHubLinkPages({
|
|
851
|
+
trustedOrigin,
|
|
852
|
+
startUrl,
|
|
853
|
+
token,
|
|
854
|
+
initialLimit: seededFirstPage?.usedLimit ?? DEFAULT_CHECK_STATUS_PAGE_SIZE,
|
|
855
|
+
mapPageItems: (body) => (Array.isArray(body) ? body : []),
|
|
856
|
+
seededFirstPage: linkSeed,
|
|
857
|
+
});
|
|
858
|
+
all.push(...linkItems);
|
|
859
|
+
listTruncated = linkTruncated;
|
|
860
|
+
walkedCount = linkWalkedCount;
|
|
861
|
+
} else {
|
|
862
|
+
const {
|
|
863
|
+
items: limitItems,
|
|
864
|
+
list_truncated: limitTruncated,
|
|
865
|
+
walked_count: limitWalkedCount,
|
|
866
|
+
} = await paginateOffsetListPages({
|
|
867
|
+
pageSize: GITHUB_PAGE_SIZE,
|
|
868
|
+
listLimit,
|
|
869
|
+
maxPagesTruncatesWithLimit: true,
|
|
870
|
+
trustedEntryCount: trustedTotalCount,
|
|
871
|
+
seededFirstPage: seededFirstPage
|
|
872
|
+
? { items: seededFirstPage.items, usedLimit: seededFirstPage.usedLimit }
|
|
873
|
+
: null,
|
|
874
|
+
fetchPage: async ({ page, limit }) => {
|
|
875
|
+
const pageUrl = `${base}${listPath}&page=${page}`;
|
|
876
|
+
const { body } = await fetchJsonWithMeta(withPerPageParam(pageUrl, limit), {
|
|
877
|
+
headers: authHeaders(token),
|
|
878
|
+
});
|
|
879
|
+
return Array.isArray(body) ? body : [];
|
|
880
|
+
},
|
|
881
|
+
});
|
|
882
|
+
all.push(...limitItems);
|
|
883
|
+
listTruncated = limitTruncated;
|
|
884
|
+
walkedCount = limitWalkedCount;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
let numbers = orderOpenPullNumbers(all, (pr) => pr?.number, sliceSort);
|
|
888
|
+
const outputCap = listLimit ?? retainMax;
|
|
889
|
+
if (outputCap != null && numbers.length > outputCap) {
|
|
890
|
+
numbers = numbers.slice(0, outputCap);
|
|
891
|
+
}
|
|
892
|
+
const entryCount =
|
|
893
|
+
trustedTotalCount != null
|
|
894
|
+
? resolvePaginatedEntryCount(trustedTotalCount, walkedCount)
|
|
895
|
+
: undefined;
|
|
896
|
+
return {
|
|
897
|
+
numbers,
|
|
898
|
+
list_truncated: resolveListTruncatedWithTrustedTotal({
|
|
899
|
+
listTruncated,
|
|
900
|
+
trustedTotalCount,
|
|
901
|
+
walkedCount,
|
|
902
|
+
fullCollect: numberSortFullCollect,
|
|
903
|
+
}),
|
|
904
|
+
slice_sort: sliceSort,
|
|
905
|
+
...(entryCount != null ? { entry_count: entryCount } : {}),
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
export async function listOpenPullsWithMeta(ctx, opts = {}) {
|
|
910
|
+
apiBase(ctx.config, ctx.parsed);
|
|
911
|
+
requireToken();
|
|
912
|
+
const sliceSort = normalizeCrInventorySort(opts.sort ?? DEFAULT_CR_INVENTORY_SLICE_SORT);
|
|
913
|
+
if (!isCrInventoryFastPathEligible(opts)) {
|
|
914
|
+
return paginateGitHubOpenPullList(ctx, opts, sliceSort);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const retainMax = Number(opts.retain_max);
|
|
918
|
+
const probe = await probeGitHubOpenPullPageOne(ctx, retainMax, sliceSort);
|
|
919
|
+
if (!probe) {
|
|
920
|
+
return paginateGitHubOpenPullList(ctx, opts, sliceSort);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const { body, totalCount, listTruncated, requestLimit } = probe;
|
|
924
|
+
|
|
925
|
+
if (listTruncated) {
|
|
926
|
+
if (body.length === 0) {
|
|
927
|
+
return paginateGitHubOpenPullList(ctx, opts, sliceSort, githubProbePaginationOpts(probe));
|
|
928
|
+
}
|
|
929
|
+
return buildGitHubOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, true);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
if (
|
|
933
|
+
isNumberSortFastPathEligible(totalCount, retainMax, sliceSort) &&
|
|
934
|
+
validateFastPathPageLength(totalCount, requestLimit, body.length)
|
|
935
|
+
) {
|
|
936
|
+
return buildGitHubOpenPullMetaFromPage(body, retainMax, sliceSort, totalCount, false);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
return paginateGitHubOpenPullList(
|
|
940
|
+
ctx,
|
|
941
|
+
opts,
|
|
942
|
+
sliceSort,
|
|
943
|
+
{
|
|
944
|
+
...githubProbePaginationOpts(probe),
|
|
945
|
+
numberSortFullCollect: isNumberSortFullCollectRequired(totalCount, retainMax, sliceSort),
|
|
946
|
+
},
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
export async function listOpenPulls(ctx, opts = {}) {
|
|
951
|
+
const meta = await listOpenPullsWithMeta(ctx, opts);
|
|
952
|
+
return meta.numbers;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
export async function crInventorySlice(ctx, opts = {}) {
|
|
956
|
+
return crInventory(ctx, { listOpenPulls, listOpenPullsWithMeta, prView, prChecks }, opts);
|
|
957
|
+
}
|
|
958
|
+
|
|
397
959
|
export async function syncPlan(ctx, remoteName = 'origin') {
|
|
398
960
|
assertGitRemote(remoteName, 'remote');
|
|
399
961
|
apiBase(ctx.config, ctx.parsed);
|
|
@@ -422,13 +984,158 @@ export async function syncPlan(ctx, remoteName = 'origin') {
|
|
|
422
984
|
};
|
|
423
985
|
}
|
|
424
986
|
|
|
987
|
+
export async function whoami(ctx) {
|
|
988
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
989
|
+
const { token } = requireToken();
|
|
990
|
+
const url = `${base}/user`;
|
|
991
|
+
const { body, headers } = await fetchJsonWithMeta(url, {
|
|
992
|
+
headers: authHeaders(token),
|
|
993
|
+
});
|
|
994
|
+
const scopeHeader =
|
|
995
|
+
headers?.get?.('x-oauth-scopes') ?? headers?.get?.('X-OAuth-Scopes') ?? null;
|
|
996
|
+
return buildProviderIdentityFromGitHubUser(body, scopeHeader);
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
function githubStatusRecordOrder(a, b) {
|
|
1000
|
+
const aUpdated = Date.parse(a?.updated_at ?? '') || 0;
|
|
1001
|
+
const bUpdated = Date.parse(b?.updated_at ?? '') || 0;
|
|
1002
|
+
if (aUpdated !== bUpdated) return aUpdated - bUpdated;
|
|
1003
|
+
const aId = Number(a.id) || 0;
|
|
1004
|
+
const bId = Number(b.id) || 0;
|
|
1005
|
+
return aId - bId;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
function statusSetIdempotencyScanIncompleteError(pagesScanned, pageSizeUsed) {
|
|
1009
|
+
return forgeError(
|
|
1010
|
+
ERROR_CODES.IDEMPOTENCY_SCAN_INCOMPLETE,
|
|
1011
|
+
'Cannot prove no commit status exists for sha+context within scan limit; retry or set manually',
|
|
1012
|
+
null,
|
|
1013
|
+
{
|
|
1014
|
+
idempotency_scan: {
|
|
1015
|
+
pages: pagesScanned,
|
|
1016
|
+
max_pages: MAX_OPEN_PULL_IDEMPOTENCY_PAGES,
|
|
1017
|
+
page_size: pageSizeUsed,
|
|
1018
|
+
},
|
|
1019
|
+
},
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
/** Paginated commit-status scan for idempotent status set; fail-closed when scan cap prevents proof of absence. */
|
|
1024
|
+
export async function findCommitStatusByContext(ctx, sha, context) {
|
|
1025
|
+
const { token } = requireToken();
|
|
1026
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
1027
|
+
const trustedOrigin = new URL(base).origin;
|
|
1028
|
+
const path = repoApiPath(ctx.config, 'commits', sha, 'statuses');
|
|
1029
|
+
const pageQuery = `${path.includes('?') ? '&' : '?'}per_page=${DEFAULT_CHECK_STATUS_PAGE_SIZE}`;
|
|
1030
|
+
let url = `${base}${path}${pageQuery}`;
|
|
1031
|
+
let bestMatch = null;
|
|
1032
|
+
const activeLimit = DEFAULT_CHECK_STATUS_PAGE_SIZE;
|
|
1033
|
+
|
|
1034
|
+
for (let page = 1; page <= MAX_OPEN_PULL_IDEMPOTENCY_PAGES; page += 1) {
|
|
1035
|
+
const { body, headers } = await fetchJsonWithMeta(url, {
|
|
1036
|
+
headers: authHeaders(token),
|
|
1037
|
+
});
|
|
1038
|
+
const items = Array.isArray(body) ? body : [];
|
|
1039
|
+
for (const record of items) {
|
|
1040
|
+
if (record?.context !== context) continue;
|
|
1041
|
+
if (!bestMatch || githubStatusRecordOrder(record, bestMatch) > 0) {
|
|
1042
|
+
bestMatch = record;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const linkPage = resolveGitHubLinkNextPage({
|
|
1046
|
+
trustedOrigin,
|
|
1047
|
+
currentUrl: url,
|
|
1048
|
+
linkHeader: headers?.get?.('link') ?? headers?.get?.('Link'),
|
|
1049
|
+
pageIndex: page - 1,
|
|
1050
|
+
maxPages: MAX_OPEN_PULL_IDEMPOTENCY_PAGES,
|
|
1051
|
+
});
|
|
1052
|
+
if (items.length < activeLimit) {
|
|
1053
|
+
return bestMatch;
|
|
1054
|
+
}
|
|
1055
|
+
if (page === MAX_OPEN_PULL_IDEMPOTENCY_PAGES) {
|
|
1056
|
+
throw Object.assign(new Error('Commit status idempotency scan incomplete'), {
|
|
1057
|
+
forgeError: statusSetIdempotencyScanIncompleteError(page, activeLimit),
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
if (!linkPage.nextUrl) {
|
|
1061
|
+
return bestMatch;
|
|
1062
|
+
}
|
|
1063
|
+
url = withPerPageParam(linkPage.nextUrl, activeLimit);
|
|
1064
|
+
}
|
|
1065
|
+
return bestMatch;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
export async function statusSet(ctx, args) {
|
|
1069
|
+
assertWriteCommandConfigured(ctx.writePolicy ?? ctx.config, 'status_set');
|
|
1070
|
+
const { idempotencyFingerprint = null, ...rest } = args;
|
|
1071
|
+
const parsed = parseStatusSetArgs(rest);
|
|
1072
|
+
const existing = await findCommitStatusByContext(ctx, parsed.sha, parsed.context);
|
|
1073
|
+
if (existing) {
|
|
1074
|
+
const existingState = normalizeStatusSetState(existing.state ?? existing.status);
|
|
1075
|
+
if (existingState === parsed.state) {
|
|
1076
|
+
return buildCommitStatusSetBody(existing, parsed, {
|
|
1077
|
+
reusedExisting: true,
|
|
1078
|
+
idempotencyFields: idempotencyFingerprint
|
|
1079
|
+
? idempotencyPacketFields(idempotencyFingerprint, { reusedExisting: true })
|
|
1080
|
+
: null,
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
const payload = {
|
|
1085
|
+
state: parsed.state,
|
|
1086
|
+
context: parsed.context,
|
|
1087
|
+
};
|
|
1088
|
+
if (parsed.description != null) payload.description = parsed.description;
|
|
1089
|
+
if (parsed.target_url != null) payload.target_url = parsed.target_url;
|
|
1090
|
+
const response = await githubFetch(ctx.config, ctx.parsed, repoApiPath(ctx.config, 'statuses', parsed.sha), {
|
|
1091
|
+
method: 'POST',
|
|
1092
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1093
|
+
body: JSON.stringify(payload),
|
|
1094
|
+
});
|
|
1095
|
+
return buildCommitStatusSetBody(response, parsed, {
|
|
1096
|
+
idempotencyFields: idempotencyFingerprint
|
|
1097
|
+
? idempotencyPacketFields(idempotencyFingerprint, { reusedExisting: false })
|
|
1098
|
+
: null,
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
export async function branchProtection(ctx, { branchRef }) {
|
|
1103
|
+
assertGitRef(branchRef, '--branch-ref');
|
|
1104
|
+
const base = apiBase(ctx.config, ctx.parsed);
|
|
1105
|
+
const { token } = requireToken();
|
|
1106
|
+
const url = `${base}${repoApiPath(ctx.config, 'branches', branchRef, 'protection')}`;
|
|
1107
|
+
try {
|
|
1108
|
+
const { body } = await fetchJsonWithMeta(url, {
|
|
1109
|
+
headers: authHeaders(token),
|
|
1110
|
+
});
|
|
1111
|
+
return buildBranchProtectionFromGitHubProtection(branchRef, body);
|
|
1112
|
+
} catch (err) {
|
|
1113
|
+
if (err?.status === 404) {
|
|
1114
|
+
return buildBranchProtectionFromGitHubProtection(branchRef, null);
|
|
1115
|
+
}
|
|
1116
|
+
throw err;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
425
1120
|
export const provider = {
|
|
426
1121
|
id: 'github-api',
|
|
427
1122
|
providerCapabilities,
|
|
1123
|
+
apiReachability,
|
|
428
1124
|
repoStatus,
|
|
429
1125
|
refsCompare,
|
|
1126
|
+
refsInventory,
|
|
1127
|
+
listOpenPulls,
|
|
1128
|
+
crInventory: crInventorySlice,
|
|
430
1129
|
prView,
|
|
431
1130
|
prChecks,
|
|
432
1131
|
mergePlan,
|
|
433
1132
|
syncPlan,
|
|
1133
|
+
whoami,
|
|
1134
|
+
branchProtection,
|
|
1135
|
+
crFiles,
|
|
1136
|
+
crComments,
|
|
1137
|
+
forgeChanges,
|
|
1138
|
+
statusSet,
|
|
434
1139
|
};
|
|
1140
|
+
|
|
1141
|
+
setBranchProtectionImpl(branchProtection);
|
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.10",
|
|
4
4
|
"description": "GitHub API provider for remogram",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
"*.js"
|
|
17
17
|
],
|
|
18
18
|
"exports": {
|
|
19
|
-
".": "./index.js"
|
|
19
|
+
".": "./index.js",
|
|
20
|
+
"./branch-protection-internal.js": "./branch-protection-internal.js"
|
|
20
21
|
},
|
|
21
22
|
"engines": {
|
|
22
23
|
"node": ">=20"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@remogram/core": "0.1.0-beta.
|
|
26
|
+
"@remogram/core": "0.1.0-beta.10"
|
|
26
27
|
}
|
|
27
28
|
}
|