github-issue-tower-defence-management 1.123.3 → 1.125.0
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/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/cli/projectConfig.js +6 -11
- package/bin/adapter/entry-points/cli/projectConfig.js.map +1 -1
- package/bin/adapter/repositories/GraphqlProjectRepository.js +21 -36
- package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
- package/bin/adapter/repositories/githubGraphqlClient.js +99 -0
- package/bin/adapter/repositories/githubGraphqlClient.js.map +1 -0
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +278 -304
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +79 -142
- package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/projectConfig.ts +6 -11
- package/src/adapter/repositories/GraphqlProjectRepository.ts +81 -77
- package/src/adapter/repositories/githubGraphqlClient.test.ts +251 -0
- package/src/adapter/repositories/githubGraphqlClient.ts +122 -0
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +663 -328
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +465 -449
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +62 -0
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +208 -237
- package/types/adapter/entry-points/cli/projectConfig.d.ts.map +1 -1
- package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
- package/types/adapter/repositories/githubGraphqlClient.d.ts +21 -0
- package/types/adapter/repositories/githubGraphqlClient.d.ts.map +1 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +6 -0
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts +2 -0
- package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
|
@@ -3,23 +3,43 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ApiV3CheerioRestIssueRepository = exports.INCREMENTAL_FETCH_SKEW_BUFFER_MS = exports.FULL_ISSUE_FETCH_INTERVAL_MS = void 0;
|
|
6
|
+
exports.ApiV3CheerioRestIssueRepository = exports.REQUIRED_CHECKS_CACHE_TTL_MS = exports.INCREMENTAL_FETCH_SKEW_BUFFER_MS = exports.FULL_ISSUE_FETCH_INTERVAL_MS = void 0;
|
|
7
7
|
const typia_1 = __importDefault(require("typia"));
|
|
8
8
|
const BaseGitHubRepository_1 = require("../BaseGitHubRepository");
|
|
9
|
+
const githubGraphqlClient_1 = require("../githubGraphqlClient");
|
|
9
10
|
const utils_1 = require("../utils");
|
|
10
11
|
const githubRateLimitRetry_1 = require("./githubRateLimitRetry");
|
|
11
12
|
exports.FULL_ISSUE_FETCH_INTERVAL_MS = 60 * 60 * 1000;
|
|
12
13
|
exports.INCREMENTAL_FETCH_SKEW_BUFFER_MS = 5 * 60 * 1000;
|
|
14
|
+
exports.REQUIRED_CHECKS_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
13
15
|
function isIssueTimelineResponse(value) {
|
|
14
16
|
if (typeof value !== 'object' || value === null)
|
|
15
17
|
return false;
|
|
16
18
|
return true;
|
|
17
19
|
}
|
|
18
|
-
function
|
|
20
|
+
function isSlimPullRequestResponse(value) {
|
|
19
21
|
if (typeof value !== 'object' || value === null)
|
|
20
22
|
return false;
|
|
21
23
|
return true;
|
|
22
24
|
}
|
|
25
|
+
function isBranchRulesResponse(value) {
|
|
26
|
+
return Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
function isBranchDetailResponse(value) {
|
|
29
|
+
if (typeof value !== 'object' || value === null)
|
|
30
|
+
return false;
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
function isCheckRunsResponse(value) {
|
|
34
|
+
if (typeof value !== 'object' || value === null)
|
|
35
|
+
return false;
|
|
36
|
+
return 'check_runs' in value && Array.isArray(value.check_runs);
|
|
37
|
+
}
|
|
38
|
+
function isCombinedStatusResponse(value) {
|
|
39
|
+
if (typeof value !== 'object' || value === null)
|
|
40
|
+
return false;
|
|
41
|
+
return 'statuses' in value && Array.isArray(value.statuses);
|
|
42
|
+
}
|
|
23
43
|
function isPullRequestMergeabilityResponse(value) {
|
|
24
44
|
if (typeof value !== 'object' || value === null)
|
|
25
45
|
return false;
|
|
@@ -106,63 +126,6 @@ function isPullRequestCommitsResponseItem(value) {
|
|
|
106
126
|
function isPullRequestCommitsResponse(value) {
|
|
107
127
|
return Array.isArray(value) && value.every(isPullRequestCommitsResponseItem);
|
|
108
128
|
}
|
|
109
|
-
const fnmatch = (pattern, str) => {
|
|
110
|
-
let regexStr = '^';
|
|
111
|
-
let i = 0;
|
|
112
|
-
while (i < pattern.length) {
|
|
113
|
-
const c = pattern[i];
|
|
114
|
-
if (c === '*') {
|
|
115
|
-
if (pattern[i + 1] === '*') {
|
|
116
|
-
regexStr += '.*';
|
|
117
|
-
i += 2;
|
|
118
|
-
if (pattern[i] === '/') {
|
|
119
|
-
i++;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
regexStr += '[^/]*';
|
|
124
|
-
i++;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
else if (c === '?') {
|
|
128
|
-
regexStr += '[^/]';
|
|
129
|
-
i++;
|
|
130
|
-
}
|
|
131
|
-
else if (c === '[') {
|
|
132
|
-
let j = i + 1;
|
|
133
|
-
while (j < pattern.length && pattern[j] !== ']') {
|
|
134
|
-
j++;
|
|
135
|
-
}
|
|
136
|
-
if (j >= pattern.length) {
|
|
137
|
-
regexStr += '\\[';
|
|
138
|
-
i++;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
const content = pattern.slice(i + 1, j);
|
|
142
|
-
if (content.length > 0 && (content[0] === '!' || content[0] === '^')) {
|
|
143
|
-
const body = content.slice(1).replace(/\\/g, '\\\\');
|
|
144
|
-
regexStr += '[^' + body + ']';
|
|
145
|
-
}
|
|
146
|
-
else {
|
|
147
|
-
const escapedContent = content.replace(/\\/g, '\\\\');
|
|
148
|
-
regexStr += '[' + escapedContent + ']';
|
|
149
|
-
}
|
|
150
|
-
i = j + 1;
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
regexStr += c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
154
|
-
i++;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
regexStr += '$';
|
|
158
|
-
try {
|
|
159
|
-
const regex = new RegExp(regexStr);
|
|
160
|
-
return regex.test(str);
|
|
161
|
-
}
|
|
162
|
-
catch {
|
|
163
|
-
return pattern === str;
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
129
|
class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubRepository {
|
|
167
130
|
constructor(apiV3IssueRepository, restIssueRepository, graphqlProjectItemRepository, localStorageCacheRepository, projectRepository, dateRepository, localStorageRepository, ghToken = process.env.GH_TOKEN || 'dummy', sleep = githubRateLimitRetry_1.realSleep) {
|
|
168
131
|
super(localStorageRepository, ghToken);
|
|
@@ -448,62 +411,11 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
448
411
|
isPr: urlMatch[3] === 'pull',
|
|
449
412
|
};
|
|
450
413
|
};
|
|
451
|
-
this.computePrStatus = (prUrl, headRefName,
|
|
414
|
+
this.computePrStatus = (prUrl, headRefName, data) => {
|
|
452
415
|
const isConflicted = data.mergeable === 'CONFLICTING';
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
const
|
|
456
|
-
const branchProtectionRules = data.baseRepository?.branchProtectionRules?.nodes || [];
|
|
457
|
-
const matchingRules = baseRefName
|
|
458
|
-
? branchProtectionRules.filter((rule) => rule.pattern === baseRefName || fnmatch(rule.pattern, baseRefName))
|
|
459
|
-
: [];
|
|
460
|
-
const requiredCheckNamesSet = new Set();
|
|
461
|
-
for (const rule of matchingRules) {
|
|
462
|
-
for (const name of rule.requiredStatusCheckContexts) {
|
|
463
|
-
requiredCheckNamesSet.add(name);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
const rulesets = data.baseRepository?.rulesets?.nodes || [];
|
|
467
|
-
const defaultBranchName = data.baseRepository?.defaultBranchRef?.name || '';
|
|
468
|
-
for (const ruleset of rulesets) {
|
|
469
|
-
if (ruleset.enforcement !== 'ACTIVE')
|
|
470
|
-
continue;
|
|
471
|
-
const refIncludes = ruleset.conditions.refName.include;
|
|
472
|
-
const refExcludes = ruleset.conditions.refName.exclude;
|
|
473
|
-
const matchesInclude = baseRefName !== undefined &&
|
|
474
|
-
refIncludes.some((pattern) => {
|
|
475
|
-
if (pattern === '~DEFAULT_BRANCH') {
|
|
476
|
-
return baseRefName === defaultBranchName;
|
|
477
|
-
}
|
|
478
|
-
if (pattern === '~ALL') {
|
|
479
|
-
return true;
|
|
480
|
-
}
|
|
481
|
-
const branchPattern = pattern.replace(/^refs\/heads\//, '');
|
|
482
|
-
return (branchPattern === baseRefName || fnmatch(branchPattern, baseRefName));
|
|
483
|
-
});
|
|
484
|
-
if (!matchesInclude)
|
|
485
|
-
continue;
|
|
486
|
-
const matchesExclude = baseRefName !== undefined &&
|
|
487
|
-
refExcludes.some((pattern) => {
|
|
488
|
-
if (pattern === '~DEFAULT_BRANCH') {
|
|
489
|
-
return baseRefName === defaultBranchName;
|
|
490
|
-
}
|
|
491
|
-
const branchPattern = pattern.replace(/^refs\/heads\//, '');
|
|
492
|
-
return (branchPattern === baseRefName || fnmatch(branchPattern, baseRefName));
|
|
493
|
-
});
|
|
494
|
-
if (matchesExclude)
|
|
495
|
-
continue;
|
|
496
|
-
for (const rule of ruleset.rules.nodes) {
|
|
497
|
-
if (rule.type !== 'REQUIRED_STATUS_CHECKS')
|
|
498
|
-
continue;
|
|
499
|
-
if ('requiredStatusChecks' in rule.parameters) {
|
|
500
|
-
for (const check of rule.parameters.requiredStatusChecks) {
|
|
501
|
-
requiredCheckNamesSet.add(check.context);
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
const requiredCheckNames = Array.from(requiredCheckNamesSet);
|
|
416
|
+
const hasStatusCheckRollup = data.ciContexts.length > 0;
|
|
417
|
+
const contexts = data.ciContexts;
|
|
418
|
+
const requiredCheckNames = data.requiredCheckNames;
|
|
507
419
|
const seenContextNames = new Set();
|
|
508
420
|
for (const ctx of contexts) {
|
|
509
421
|
if ('name' in ctx) {
|
|
@@ -536,7 +448,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
536
448
|
'STALE',
|
|
537
449
|
]);
|
|
538
450
|
const isCiStateSuccess = (() => {
|
|
539
|
-
if (!
|
|
451
|
+
if (!hasStatusCheckRollup)
|
|
540
452
|
return false;
|
|
541
453
|
const latestRuns = [...latestCheckRunByName.values()];
|
|
542
454
|
const statusContexts = contexts.filter((ctx) => ctx.__typename === 'StatusContext');
|
|
@@ -549,7 +461,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
549
461
|
return !hasPending;
|
|
550
462
|
})();
|
|
551
463
|
const isPassedAllCiJob = isCiStateSuccess && allRequiredChecksPassed;
|
|
552
|
-
const reviewThreads = data.reviewThreads
|
|
464
|
+
const reviewThreads = data.reviewThreads;
|
|
553
465
|
const isResolvedAllReviewComments = reviewThreads.length === 0 ||
|
|
554
466
|
reviewThreads.every((thread) => thread.isResolved);
|
|
555
467
|
return {
|
|
@@ -566,9 +478,222 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
566
478
|
missingRequiredCheckNames,
|
|
567
479
|
};
|
|
568
480
|
};
|
|
481
|
+
this.requiredCheckNamesCache = new Map();
|
|
482
|
+
this.getRequiredCheckNames = async (owner, repo, branch) => {
|
|
483
|
+
const cacheKey = `${owner}/${repo}/${branch}`;
|
|
484
|
+
const nowMs = (await this.dateRepository.now()).getTime();
|
|
485
|
+
const cached = this.requiredCheckNamesCache.get(cacheKey);
|
|
486
|
+
if (cached && nowMs - cached.fetchedAtMs < exports.REQUIRED_CHECKS_CACHE_TTL_MS) {
|
|
487
|
+
return cached.names;
|
|
488
|
+
}
|
|
489
|
+
const ownerSegment = encodeURIComponent(owner);
|
|
490
|
+
const repoSegment = encodeURIComponent(repo);
|
|
491
|
+
const branchSegment = encodeURIComponent(branch);
|
|
492
|
+
const requiredCheckNamesSet = new Set();
|
|
493
|
+
const rulesResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/rules/branches/${branchSegment}?per_page=100`, {
|
|
494
|
+
method: 'GET',
|
|
495
|
+
headers: {
|
|
496
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
497
|
+
Accept: 'application/vnd.github+json',
|
|
498
|
+
},
|
|
499
|
+
}));
|
|
500
|
+
if (rulesResponse.ok) {
|
|
501
|
+
const rulesBody = await rulesResponse.json();
|
|
502
|
+
if (!isBranchRulesResponse(rulesBody)) {
|
|
503
|
+
throw new Error(`Unexpected response shape when fetching branch rules: ${owner}/${repo}/${branch}`);
|
|
504
|
+
}
|
|
505
|
+
for (const rule of rulesBody) {
|
|
506
|
+
if (rule.type !== 'required_status_checks')
|
|
507
|
+
continue;
|
|
508
|
+
for (const check of rule.parameters?.required_status_checks || []) {
|
|
509
|
+
requiredCheckNamesSet.add(check.context);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else if (rulesResponse.status !== 404) {
|
|
514
|
+
const reason = await this.formatGitHubErrorWithStatus(rulesResponse);
|
|
515
|
+
throw new Error(`Failed to fetch branch rules for ${owner}/${repo}/${branch}: ${reason}`);
|
|
516
|
+
}
|
|
517
|
+
const branchResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/branches/${branchSegment}`, {
|
|
518
|
+
method: 'GET',
|
|
519
|
+
headers: {
|
|
520
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
521
|
+
Accept: 'application/vnd.github+json',
|
|
522
|
+
},
|
|
523
|
+
}));
|
|
524
|
+
if (branchResponse.ok) {
|
|
525
|
+
const branchBody = await branchResponse.json();
|
|
526
|
+
if (!isBranchDetailResponse(branchBody)) {
|
|
527
|
+
throw new Error(`Unexpected response shape when fetching branch detail: ${owner}/${repo}/${branch}`);
|
|
528
|
+
}
|
|
529
|
+
for (const context of branchBody.protection?.required_status_checks
|
|
530
|
+
?.contexts || []) {
|
|
531
|
+
requiredCheckNamesSet.add(context);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
else if (branchResponse.status !== 404) {
|
|
535
|
+
const reason = await this.formatGitHubErrorWithStatus(branchResponse);
|
|
536
|
+
throw new Error(`Failed to fetch branch detail for ${owner}/${repo}/${branch}: ${reason}`);
|
|
537
|
+
}
|
|
538
|
+
const names = Array.from(requiredCheckNamesSet);
|
|
539
|
+
this.requiredCheckNamesCache.set(cacheKey, {
|
|
540
|
+
fetchedAtMs: nowMs,
|
|
541
|
+
names,
|
|
542
|
+
});
|
|
543
|
+
return names;
|
|
544
|
+
};
|
|
545
|
+
this.getCommitCiContexts = async (owner, repo, commitSha) => {
|
|
546
|
+
const ownerSegment = encodeURIComponent(owner);
|
|
547
|
+
const repoSegment = encodeURIComponent(repo);
|
|
548
|
+
const shaSegment = encodeURIComponent(commitSha);
|
|
549
|
+
const contexts = [];
|
|
550
|
+
const perPage = 100;
|
|
551
|
+
let page = 1;
|
|
552
|
+
let hasMore = true;
|
|
553
|
+
while (hasMore) {
|
|
554
|
+
const checkRunsResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=${perPage}&page=${page}`, {
|
|
555
|
+
method: 'GET',
|
|
556
|
+
headers: {
|
|
557
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
558
|
+
Accept: 'application/vnd.github+json',
|
|
559
|
+
},
|
|
560
|
+
}));
|
|
561
|
+
if (!checkRunsResponse.ok) {
|
|
562
|
+
const reason = await this.formatGitHubErrorWithStatus(checkRunsResponse);
|
|
563
|
+
throw new Error(`Failed to fetch check runs for ${owner}/${repo}@${commitSha}: ${reason}`);
|
|
564
|
+
}
|
|
565
|
+
const checkRunsBody = await checkRunsResponse.json();
|
|
566
|
+
if (!isCheckRunsResponse(checkRunsBody)) {
|
|
567
|
+
throw new Error(`Unexpected response shape when fetching check runs: ${owner}/${repo}@${commitSha}`);
|
|
568
|
+
}
|
|
569
|
+
for (const checkRun of checkRunsBody.check_runs) {
|
|
570
|
+
contexts.push({
|
|
571
|
+
__typename: 'CheckRun',
|
|
572
|
+
name: checkRun.name,
|
|
573
|
+
conclusion: checkRun.conclusion
|
|
574
|
+
? checkRun.conclusion.toUpperCase()
|
|
575
|
+
: null,
|
|
576
|
+
databaseId: checkRun.id,
|
|
577
|
+
});
|
|
578
|
+
}
|
|
579
|
+
if (checkRunsBody.check_runs.length < perPage ||
|
|
580
|
+
page * perPage >= checkRunsBody.total_count) {
|
|
581
|
+
hasMore = false;
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
page += 1;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const combinedStatusResponse = await this.fetchWithRateLimitRetry(() => fetch(`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/status?per_page=100`, {
|
|
588
|
+
method: 'GET',
|
|
589
|
+
headers: {
|
|
590
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
591
|
+
Accept: 'application/vnd.github+json',
|
|
592
|
+
},
|
|
593
|
+
}));
|
|
594
|
+
if (!combinedStatusResponse.ok) {
|
|
595
|
+
const reason = await this.formatGitHubErrorWithStatus(combinedStatusResponse);
|
|
596
|
+
throw new Error(`Failed to fetch combined status for ${owner}/${repo}@${commitSha}: ${reason}`);
|
|
597
|
+
}
|
|
598
|
+
const combinedStatusBody = await combinedStatusResponse.json();
|
|
599
|
+
if (!isCombinedStatusResponse(combinedStatusBody)) {
|
|
600
|
+
throw new Error(`Unexpected response shape when fetching combined status: ${owner}/${repo}@${commitSha}`);
|
|
601
|
+
}
|
|
602
|
+
for (const status of combinedStatusBody.statuses) {
|
|
603
|
+
contexts.push({
|
|
604
|
+
__typename: 'StatusContext',
|
|
605
|
+
context: status.context,
|
|
606
|
+
state: status.state.toUpperCase(),
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
return contexts;
|
|
610
|
+
};
|
|
611
|
+
this.fetchSlimPullRequest = async (owner, repo, prNumber) => {
|
|
612
|
+
const query = `
|
|
613
|
+
query PullRequestSlimStatus($owner: String!, $repo: String!, $prNumber: Int!, $reviewThreadsAfter: String) {
|
|
614
|
+
repository(owner: $owner, name: $repo) {
|
|
615
|
+
pullRequest(number: $prNumber) {
|
|
616
|
+
url
|
|
617
|
+
state
|
|
618
|
+
isDraft
|
|
619
|
+
headRefName
|
|
620
|
+
baseRefName
|
|
621
|
+
mergeable
|
|
622
|
+
headRefOid
|
|
623
|
+
reviewThreads(first: 100, after: $reviewThreadsAfter) {
|
|
624
|
+
pageInfo {
|
|
625
|
+
endCursor
|
|
626
|
+
hasNextPage
|
|
627
|
+
}
|
|
628
|
+
nodes {
|
|
629
|
+
isResolved
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
`;
|
|
636
|
+
let slimPullRequest = null;
|
|
637
|
+
let reviewThreadsAfter = null;
|
|
638
|
+
let hasNextPage = true;
|
|
639
|
+
while (hasNextPage) {
|
|
640
|
+
const response = await this.fetchWithRateLimitRetry(() => (0, githubGraphqlClient_1.fetchGithubGraphql)({
|
|
641
|
+
ghToken: this.ghToken,
|
|
642
|
+
query,
|
|
643
|
+
variables: { owner, repo, prNumber, reviewThreadsAfter },
|
|
644
|
+
}));
|
|
645
|
+
if (!response.ok) {
|
|
646
|
+
throw new Error(`Failed to fetch pull request from GitHub GraphQL API: HTTP ${response.status}`);
|
|
647
|
+
}
|
|
648
|
+
const responseData = await response.json();
|
|
649
|
+
if (!isSlimPullRequestResponse(responseData)) {
|
|
650
|
+
throw new Error('Unexpected response shape when fetching pull request');
|
|
651
|
+
}
|
|
652
|
+
if (responseData.errors && responseData.errors.length > 0) {
|
|
653
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(responseData.errors)}`);
|
|
654
|
+
}
|
|
655
|
+
const pr = responseData.data?.repository?.pullRequest;
|
|
656
|
+
if (!pr) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
if (!slimPullRequest) {
|
|
660
|
+
slimPullRequest = {
|
|
661
|
+
url: pr.url,
|
|
662
|
+
state: pr.state,
|
|
663
|
+
isDraft: pr.isDraft,
|
|
664
|
+
headRefName: pr.headRefName,
|
|
665
|
+
baseRefName: pr.baseRefName,
|
|
666
|
+
mergeable: pr.mergeable,
|
|
667
|
+
headRefOid: pr.headRefOid,
|
|
668
|
+
reviewThreads: [],
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
for (const thread of pr.reviewThreads?.nodes || []) {
|
|
672
|
+
slimPullRequest.reviewThreads.push({ isResolved: thread.isResolved });
|
|
673
|
+
}
|
|
674
|
+
hasNextPage = pr.reviewThreads?.pageInfo.hasNextPage === true;
|
|
675
|
+
reviewThreadsAfter = pr.reviewThreads?.pageInfo.endCursor ?? null;
|
|
676
|
+
}
|
|
677
|
+
return slimPullRequest;
|
|
678
|
+
};
|
|
679
|
+
this.buildRelatedPullRequestFromSlim = async (owner, repo, slimPullRequest) => {
|
|
680
|
+
const requiredCheckNames = slimPullRequest.baseRefName
|
|
681
|
+
? await this.getRequiredCheckNames(owner, repo, slimPullRequest.baseRefName)
|
|
682
|
+
: [];
|
|
683
|
+
const ciContexts = slimPullRequest.headRefOid
|
|
684
|
+
? await this.getCommitCiContexts(owner, repo, slimPullRequest.headRefOid)
|
|
685
|
+
: [];
|
|
686
|
+
return this.computePrStatus(slimPullRequest.url, slimPullRequest.headRefName, {
|
|
687
|
+
isDraft: slimPullRequest.isDraft,
|
|
688
|
+
mergeable: slimPullRequest.mergeable,
|
|
689
|
+
requiredCheckNames,
|
|
690
|
+
ciContexts,
|
|
691
|
+
reviewThreads: slimPullRequest.reviewThreads,
|
|
692
|
+
});
|
|
693
|
+
};
|
|
569
694
|
this.resolveMergeabilityWithRetry = async (owner, repo, prNumber) => {
|
|
570
695
|
const query = `
|
|
571
|
-
query($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
696
|
+
query PullRequestMergeability($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
572
697
|
repository(owner: $owner, name: $repo) {
|
|
573
698
|
pullRequest(number: $prNumber) {
|
|
574
699
|
mergeable
|
|
@@ -584,16 +709,10 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
584
709
|
if (attempt > 0) {
|
|
585
710
|
await this.sleep(retryDelayMilliseconds);
|
|
586
711
|
}
|
|
587
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
'Content-Type': 'application/json',
|
|
592
|
-
},
|
|
593
|
-
body: JSON.stringify({
|
|
594
|
-
query,
|
|
595
|
-
variables: { owner, repo, prNumber },
|
|
596
|
-
}),
|
|
712
|
+
const response = await this.fetchWithRateLimitRetry(() => (0, githubGraphqlClient_1.fetchGithubGraphql)({
|
|
713
|
+
ghToken: this.ghToken,
|
|
714
|
+
query,
|
|
715
|
+
variables: { owner, repo, prNumber },
|
|
597
716
|
}));
|
|
598
717
|
if (!response.ok) {
|
|
599
718
|
throw new Error(`Failed to fetch pull request mergeability from GitHub GraphQL API: HTTP ${response.status}`);
|
|
@@ -625,7 +744,7 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
625
744
|
throw new Error('findRelatedOpenPRs only supports issue URLs, not pull request URLs');
|
|
626
745
|
}
|
|
627
746
|
const query = `
|
|
628
|
-
query($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
|
|
747
|
+
query IssueRelatedOpenPullRequests($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
|
|
629
748
|
repository(owner: $owner, name: $repo) {
|
|
630
749
|
issue(number: $issueNumber) {
|
|
631
750
|
timelineItems(first: 100, after: $after, itemTypes: [CROSS_REFERENCED_EVENT]) {
|
|
@@ -648,68 +767,6 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
648
767
|
mergeable
|
|
649
768
|
headRefName
|
|
650
769
|
baseRefName
|
|
651
|
-
baseRepository {
|
|
652
|
-
branchProtectionRules(first: 100) {
|
|
653
|
-
nodes {
|
|
654
|
-
pattern
|
|
655
|
-
requiredStatusCheckContexts
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
defaultBranchRef {
|
|
659
|
-
name
|
|
660
|
-
}
|
|
661
|
-
rulesets(first: 100) {
|
|
662
|
-
nodes {
|
|
663
|
-
name
|
|
664
|
-
enforcement
|
|
665
|
-
conditions {
|
|
666
|
-
refName {
|
|
667
|
-
include
|
|
668
|
-
exclude
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
rules(first: 100) {
|
|
672
|
-
nodes {
|
|
673
|
-
type
|
|
674
|
-
parameters {
|
|
675
|
-
... on RequiredStatusChecksParameters {
|
|
676
|
-
requiredStatusChecks {
|
|
677
|
-
context
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
commits(last: 1) {
|
|
687
|
-
nodes {
|
|
688
|
-
commit {
|
|
689
|
-
statusCheckRollup {
|
|
690
|
-
contexts(first: 100) {
|
|
691
|
-
nodes {
|
|
692
|
-
__typename
|
|
693
|
-
... on CheckRun {
|
|
694
|
-
databaseId
|
|
695
|
-
name
|
|
696
|
-
conclusion
|
|
697
|
-
}
|
|
698
|
-
... on StatusContext {
|
|
699
|
-
context
|
|
700
|
-
state
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
reviewThreads(first: 100) {
|
|
709
|
-
nodes {
|
|
710
|
-
isResolved
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
770
|
baseRef {
|
|
714
771
|
name
|
|
715
772
|
}
|
|
@@ -726,16 +783,10 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
726
783
|
let after = null;
|
|
727
784
|
let hasNextPage = true;
|
|
728
785
|
while (hasNextPage) {
|
|
729
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
'Content-Type': 'application/json',
|
|
734
|
-
},
|
|
735
|
-
body: JSON.stringify({
|
|
736
|
-
query,
|
|
737
|
-
variables: { owner, repo, issueNumber, after },
|
|
738
|
-
}),
|
|
786
|
+
const response = await this.fetchWithRateLimitRetry(() => (0, githubGraphqlClient_1.fetchGithubGraphql)({
|
|
787
|
+
ghToken: this.ghToken,
|
|
788
|
+
query,
|
|
789
|
+
variables: { owner, repo, issueNumber, after },
|
|
739
790
|
}));
|
|
740
791
|
if (!response.ok) {
|
|
741
792
|
throw new Error(`Failed to fetch issue timeline from GitHub GraphQL API: HTTP ${response.status}`);
|
|
@@ -759,10 +810,8 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
759
810
|
continue;
|
|
760
811
|
const pr = item.source;
|
|
761
812
|
const prUrl = pr.url || '';
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
let isConflicted = prStatus.isConflicted;
|
|
765
|
-
let mergeable = prStatus.mergeable;
|
|
813
|
+
let isConflicted = pr.mergeable === 'CONFLICTING';
|
|
814
|
+
let mergeable = pr.mergeable ?? null;
|
|
766
815
|
if (pr.number !== undefined &&
|
|
767
816
|
(pr.mergeable === undefined ||
|
|
768
817
|
pr.mergeable === null ||
|
|
@@ -788,6 +837,28 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
788
837
|
resolved.mergeStateStatus === 'DIRTY';
|
|
789
838
|
}
|
|
790
839
|
}
|
|
840
|
+
if (pr.number === undefined)
|
|
841
|
+
continue;
|
|
842
|
+
let prStatus;
|
|
843
|
+
try {
|
|
844
|
+
const slimPullRequest = await this.fetchSlimPullRequest(owner, repo, pr.number);
|
|
845
|
+
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
846
|
+
console.info(`ApiV3CheerioRestIssueRepository: pull request is no longer open, excluding it from related open PRs. prUrl: ${prUrl}`);
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
const baseRefName = slimPullRequest.baseRefName ?? pr.baseRefName ?? pr.baseRef?.name;
|
|
850
|
+
prStatus = await this.buildRelatedPullRequestFromSlim(owner, repo, {
|
|
851
|
+
...slimPullRequest,
|
|
852
|
+
url: slimPullRequest.url || prUrl,
|
|
853
|
+
headRefName: slimPullRequest.headRefName ?? pr.headRefName,
|
|
854
|
+
baseRefName,
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
859
|
+
console.warn(`ApiV3CheerioRestIssueRepository: fetching pull request status failed, skipping PR for this cycle. prUrl: ${prUrl} error: ${errorMessage}`);
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
791
862
|
relatedPRsMap.set(prUrl, {
|
|
792
863
|
...prStatus,
|
|
793
864
|
isConflicted,
|
|
@@ -829,108 +900,11 @@ class ApiV3CheerioRestIssueRepository extends BaseGitHubRepository_1.BaseGitHubR
|
|
|
829
900
|
return null;
|
|
830
901
|
}
|
|
831
902
|
const { owner, repo, issueNumber: prNumber } = parsedUrl;
|
|
832
|
-
const
|
|
833
|
-
|
|
834
|
-
repository(owner: $owner, name: $repo) {
|
|
835
|
-
pullRequest(number: $prNumber) {
|
|
836
|
-
url
|
|
837
|
-
state
|
|
838
|
-
isDraft
|
|
839
|
-
headRefName
|
|
840
|
-
baseRefName
|
|
841
|
-
mergeable
|
|
842
|
-
baseRepository {
|
|
843
|
-
branchProtectionRules(first: 100) {
|
|
844
|
-
nodes {
|
|
845
|
-
pattern
|
|
846
|
-
requiredStatusCheckContexts
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
defaultBranchRef {
|
|
850
|
-
name
|
|
851
|
-
}
|
|
852
|
-
rulesets(first: 100) {
|
|
853
|
-
nodes {
|
|
854
|
-
name
|
|
855
|
-
enforcement
|
|
856
|
-
conditions {
|
|
857
|
-
refName {
|
|
858
|
-
include
|
|
859
|
-
exclude
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
rules(first: 100) {
|
|
863
|
-
nodes {
|
|
864
|
-
type
|
|
865
|
-
parameters {
|
|
866
|
-
... on RequiredStatusChecksParameters {
|
|
867
|
-
requiredStatusChecks {
|
|
868
|
-
context
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
commits(last: 1) {
|
|
878
|
-
nodes {
|
|
879
|
-
commit {
|
|
880
|
-
statusCheckRollup {
|
|
881
|
-
contexts(first: 100) {
|
|
882
|
-
nodes {
|
|
883
|
-
__typename
|
|
884
|
-
... on CheckRun {
|
|
885
|
-
databaseId
|
|
886
|
-
name
|
|
887
|
-
conclusion
|
|
888
|
-
}
|
|
889
|
-
... on StatusContext {
|
|
890
|
-
context
|
|
891
|
-
state
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
}
|
|
899
|
-
reviewThreads(first: 100) {
|
|
900
|
-
nodes {
|
|
901
|
-
isResolved
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
`;
|
|
908
|
-
const response = await this.fetchWithRateLimitRetry(() => fetch('https://api.github.com/graphql', {
|
|
909
|
-
method: 'POST',
|
|
910
|
-
headers: {
|
|
911
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
912
|
-
'Content-Type': 'application/json',
|
|
913
|
-
},
|
|
914
|
-
body: JSON.stringify({
|
|
915
|
-
query,
|
|
916
|
-
variables: { owner, repo, prNumber },
|
|
917
|
-
}),
|
|
918
|
-
}));
|
|
919
|
-
if (!response.ok) {
|
|
920
|
-
throw new Error(`Failed to fetch pull request from GitHub GraphQL API: HTTP ${response.status}`);
|
|
921
|
-
}
|
|
922
|
-
const responseData = await response.json();
|
|
923
|
-
if (!isDirectPullRequestResponse(responseData)) {
|
|
924
|
-
throw new Error('Unexpected response shape when fetching pull request');
|
|
925
|
-
}
|
|
926
|
-
if (responseData.errors && responseData.errors.length > 0) {
|
|
927
|
-
throw new Error(`GraphQL errors: ${JSON.stringify(responseData.errors)}`);
|
|
928
|
-
}
|
|
929
|
-
const pr = responseData.data?.repository?.pullRequest;
|
|
930
|
-
if (!pr || pr.state !== 'OPEN') {
|
|
903
|
+
const slimPullRequest = await this.fetchSlimPullRequest(owner, repo, prNumber);
|
|
904
|
+
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
931
905
|
return null;
|
|
932
906
|
}
|
|
933
|
-
return this.
|
|
907
|
+
return this.buildRelatedPullRequestFromSlim(owner, repo, slimPullRequest);
|
|
934
908
|
};
|
|
935
909
|
this.closePullRequest = async (prUrl) => {
|
|
936
910
|
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|