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
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
import { LocalStorageCacheRepository } from '../LocalStorageCacheRepository';
|
|
21
21
|
import typia from 'typia';
|
|
22
22
|
import { BaseGitHubRepository } from '../BaseGitHubRepository';
|
|
23
|
+
import { fetchGithubGraphql } from '../githubGraphqlClient';
|
|
23
24
|
import { normalizeFieldName } from '../utils';
|
|
24
25
|
import { LocalStorageRepository } from '../LocalStorageRepository';
|
|
25
26
|
import { Member } from '../../../domain/entities/Member';
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
|
|
36
37
|
export const FULL_ISSUE_FETCH_INTERVAL_MS = 60 * 60 * 1000;
|
|
37
38
|
export const INCREMENTAL_FETCH_SKEW_BUFFER_MS = 5 * 60 * 1000;
|
|
39
|
+
export const REQUIRED_CHECKS_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
38
40
|
|
|
39
41
|
export type CachedProjectIssues = {
|
|
40
42
|
lastFetchedAt: string;
|
|
@@ -56,69 +58,6 @@ type TimelineItem = {
|
|
|
56
58
|
mergeable?: string;
|
|
57
59
|
headRefName?: string;
|
|
58
60
|
baseRefName?: string;
|
|
59
|
-
baseRepository?: {
|
|
60
|
-
branchProtectionRules?: {
|
|
61
|
-
nodes: Array<{
|
|
62
|
-
pattern: string;
|
|
63
|
-
requiredStatusCheckContexts: string[];
|
|
64
|
-
}>;
|
|
65
|
-
};
|
|
66
|
-
defaultBranchRef?: {
|
|
67
|
-
name: string;
|
|
68
|
-
} | null;
|
|
69
|
-
rulesets?: {
|
|
70
|
-
nodes: Array<{
|
|
71
|
-
name: string;
|
|
72
|
-
enforcement: string;
|
|
73
|
-
conditions: {
|
|
74
|
-
refName: {
|
|
75
|
-
include: string[];
|
|
76
|
-
exclude: string[];
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
rules: {
|
|
80
|
-
nodes: Array<{
|
|
81
|
-
type: string;
|
|
82
|
-
parameters:
|
|
83
|
-
| {
|
|
84
|
-
requiredStatusChecks: Array<{
|
|
85
|
-
context: string;
|
|
86
|
-
}>;
|
|
87
|
-
}
|
|
88
|
-
| Record<string, never>;
|
|
89
|
-
}>;
|
|
90
|
-
};
|
|
91
|
-
}>;
|
|
92
|
-
};
|
|
93
|
-
};
|
|
94
|
-
commits?: {
|
|
95
|
-
nodes: Array<{
|
|
96
|
-
commit: {
|
|
97
|
-
statusCheckRollup?: {
|
|
98
|
-
contexts?: {
|
|
99
|
-
nodes: Array<
|
|
100
|
-
| {
|
|
101
|
-
__typename: 'CheckRun';
|
|
102
|
-
name: string;
|
|
103
|
-
conclusion: string | null;
|
|
104
|
-
databaseId: number;
|
|
105
|
-
}
|
|
106
|
-
| {
|
|
107
|
-
__typename: 'StatusContext';
|
|
108
|
-
context: string;
|
|
109
|
-
state: string;
|
|
110
|
-
}
|
|
111
|
-
>;
|
|
112
|
-
};
|
|
113
|
-
} | null;
|
|
114
|
-
};
|
|
115
|
-
}>;
|
|
116
|
-
};
|
|
117
|
-
reviewThreads?: {
|
|
118
|
-
nodes: Array<{
|
|
119
|
-
isResolved: boolean;
|
|
120
|
-
}>;
|
|
121
|
-
};
|
|
122
61
|
baseRef?: {
|
|
123
62
|
name: string;
|
|
124
63
|
} | null;
|
|
@@ -142,88 +81,101 @@ type IssueTimelineResponse = {
|
|
|
142
81
|
errors?: Array<{ message: string }>;
|
|
143
82
|
};
|
|
144
83
|
|
|
84
|
+
type CiContextNode =
|
|
85
|
+
| {
|
|
86
|
+
__typename: 'CheckRun';
|
|
87
|
+
name: string;
|
|
88
|
+
conclusion: string | null;
|
|
89
|
+
databaseId: number;
|
|
90
|
+
}
|
|
91
|
+
| {
|
|
92
|
+
__typename: 'StatusContext';
|
|
93
|
+
context: string;
|
|
94
|
+
state: string;
|
|
95
|
+
};
|
|
96
|
+
|
|
145
97
|
type PrStatusComputationData = {
|
|
146
98
|
isDraft?: boolean;
|
|
147
99
|
mergeable?: string;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}>;
|
|
154
|
-
};
|
|
155
|
-
defaultBranchRef?: {
|
|
156
|
-
name: string;
|
|
157
|
-
} | null;
|
|
158
|
-
rulesets?: {
|
|
159
|
-
nodes: Array<{
|
|
160
|
-
name: string;
|
|
161
|
-
enforcement: string;
|
|
162
|
-
conditions: {
|
|
163
|
-
refName: {
|
|
164
|
-
include: string[];
|
|
165
|
-
exclude: string[];
|
|
166
|
-
};
|
|
167
|
-
};
|
|
168
|
-
rules: {
|
|
169
|
-
nodes: Array<{
|
|
170
|
-
type: string;
|
|
171
|
-
parameters:
|
|
172
|
-
| {
|
|
173
|
-
requiredStatusChecks: Array<{
|
|
174
|
-
context: string;
|
|
175
|
-
}>;
|
|
176
|
-
}
|
|
177
|
-
| Record<string, never>;
|
|
178
|
-
}>;
|
|
179
|
-
};
|
|
180
|
-
}>;
|
|
181
|
-
};
|
|
182
|
-
};
|
|
183
|
-
commits?: {
|
|
184
|
-
nodes: Array<{
|
|
185
|
-
commit: {
|
|
186
|
-
statusCheckRollup?: {
|
|
187
|
-
contexts?: {
|
|
188
|
-
nodes: Array<
|
|
189
|
-
| {
|
|
190
|
-
__typename: 'CheckRun';
|
|
191
|
-
name: string;
|
|
192
|
-
conclusion: string | null;
|
|
193
|
-
databaseId: number;
|
|
194
|
-
}
|
|
195
|
-
| {
|
|
196
|
-
__typename: 'StatusContext';
|
|
197
|
-
context: string;
|
|
198
|
-
state: string;
|
|
199
|
-
}
|
|
200
|
-
>;
|
|
201
|
-
};
|
|
202
|
-
} | null;
|
|
203
|
-
};
|
|
204
|
-
}>;
|
|
205
|
-
};
|
|
206
|
-
reviewThreads?: {
|
|
207
|
-
nodes: Array<{
|
|
208
|
-
isResolved: boolean;
|
|
209
|
-
}>;
|
|
210
|
-
};
|
|
100
|
+
requiredCheckNames: string[];
|
|
101
|
+
ciContexts: CiContextNode[];
|
|
102
|
+
reviewThreads: Array<{
|
|
103
|
+
isResolved: boolean;
|
|
104
|
+
}>;
|
|
211
105
|
};
|
|
212
106
|
|
|
213
|
-
type
|
|
107
|
+
type SlimPullRequestResponse = {
|
|
214
108
|
data?: {
|
|
215
109
|
repository?: {
|
|
216
110
|
pullRequest?: {
|
|
217
111
|
url: string;
|
|
218
112
|
state: string;
|
|
113
|
+
isDraft?: boolean;
|
|
219
114
|
headRefName?: string;
|
|
220
115
|
baseRefName?: string;
|
|
221
|
-
|
|
222
|
-
|
|
116
|
+
mergeable?: string;
|
|
117
|
+
headRefOid?: string;
|
|
118
|
+
reviewThreads?: {
|
|
119
|
+
pageInfo: {
|
|
120
|
+
endCursor: string | null;
|
|
121
|
+
hasNextPage: boolean;
|
|
122
|
+
};
|
|
123
|
+
nodes: Array<{
|
|
124
|
+
isResolved: boolean;
|
|
125
|
+
}>;
|
|
126
|
+
};
|
|
127
|
+
} | null;
|
|
128
|
+
} | null;
|
|
223
129
|
};
|
|
224
130
|
errors?: Array<{ message: string }>;
|
|
225
131
|
};
|
|
226
132
|
|
|
133
|
+
type SlimPullRequest = {
|
|
134
|
+
url: string;
|
|
135
|
+
state: string;
|
|
136
|
+
isDraft?: boolean;
|
|
137
|
+
headRefName?: string;
|
|
138
|
+
baseRefName?: string;
|
|
139
|
+
mergeable?: string;
|
|
140
|
+
headRefOid?: string;
|
|
141
|
+
reviewThreads: Array<{
|
|
142
|
+
isResolved: boolean;
|
|
143
|
+
}>;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
type BranchRulesResponseItem = {
|
|
147
|
+
type: string;
|
|
148
|
+
parameters?: {
|
|
149
|
+
required_status_checks?: Array<{
|
|
150
|
+
context: string;
|
|
151
|
+
}>;
|
|
152
|
+
};
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
type BranchDetailResponse = {
|
|
156
|
+
protection?: {
|
|
157
|
+
required_status_checks?: {
|
|
158
|
+
contexts?: string[];
|
|
159
|
+
} | null;
|
|
160
|
+
} | null;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
type CheckRunsResponse = {
|
|
164
|
+
total_count: number;
|
|
165
|
+
check_runs: Array<{
|
|
166
|
+
id: number;
|
|
167
|
+
name: string;
|
|
168
|
+
conclusion: string | null;
|
|
169
|
+
}>;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
type CombinedStatusResponse = {
|
|
173
|
+
statuses: Array<{
|
|
174
|
+
context: string;
|
|
175
|
+
state: string;
|
|
176
|
+
}>;
|
|
177
|
+
};
|
|
178
|
+
|
|
227
179
|
type PullRequestMergeabilityResponse = {
|
|
228
180
|
data?: {
|
|
229
181
|
repository?: {
|
|
@@ -243,13 +195,36 @@ function isIssueTimelineResponse(
|
|
|
243
195
|
return true;
|
|
244
196
|
}
|
|
245
197
|
|
|
246
|
-
function
|
|
198
|
+
function isSlimPullRequestResponse(
|
|
247
199
|
value: unknown,
|
|
248
|
-
): value is
|
|
200
|
+
): value is SlimPullRequestResponse {
|
|
201
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function isBranchRulesResponse(
|
|
206
|
+
value: unknown,
|
|
207
|
+
): value is BranchRulesResponseItem[] {
|
|
208
|
+
return Array.isArray(value);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isBranchDetailResponse(value: unknown): value is BranchDetailResponse {
|
|
249
212
|
if (typeof value !== 'object' || value === null) return false;
|
|
250
213
|
return true;
|
|
251
214
|
}
|
|
252
215
|
|
|
216
|
+
function isCheckRunsResponse(value: unknown): value is CheckRunsResponse {
|
|
217
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
218
|
+
return 'check_runs' in value && Array.isArray(value.check_runs);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function isCombinedStatusResponse(
|
|
222
|
+
value: unknown,
|
|
223
|
+
): value is CombinedStatusResponse {
|
|
224
|
+
if (typeof value !== 'object' || value === null) return false;
|
|
225
|
+
return 'statuses' in value && Array.isArray(value.statuses);
|
|
226
|
+
}
|
|
227
|
+
|
|
253
228
|
function isPullRequestMergeabilityResponse(
|
|
254
229
|
value: unknown,
|
|
255
230
|
): value is PullRequestMergeabilityResponse {
|
|
@@ -427,58 +402,6 @@ function isPullRequestCommitsResponse(
|
|
|
427
402
|
return Array.isArray(value) && value.every(isPullRequestCommitsResponseItem);
|
|
428
403
|
}
|
|
429
404
|
|
|
430
|
-
const fnmatch = (pattern: string, str: string): boolean => {
|
|
431
|
-
let regexStr = '^';
|
|
432
|
-
let i = 0;
|
|
433
|
-
while (i < pattern.length) {
|
|
434
|
-
const c = pattern[i];
|
|
435
|
-
if (c === '*') {
|
|
436
|
-
if (pattern[i + 1] === '*') {
|
|
437
|
-
regexStr += '.*';
|
|
438
|
-
i += 2;
|
|
439
|
-
if (pattern[i] === '/') {
|
|
440
|
-
i++;
|
|
441
|
-
}
|
|
442
|
-
} else {
|
|
443
|
-
regexStr += '[^/]*';
|
|
444
|
-
i++;
|
|
445
|
-
}
|
|
446
|
-
} else if (c === '?') {
|
|
447
|
-
regexStr += '[^/]';
|
|
448
|
-
i++;
|
|
449
|
-
} else if (c === '[') {
|
|
450
|
-
let j = i + 1;
|
|
451
|
-
while (j < pattern.length && pattern[j] !== ']') {
|
|
452
|
-
j++;
|
|
453
|
-
}
|
|
454
|
-
if (j >= pattern.length) {
|
|
455
|
-
regexStr += '\\[';
|
|
456
|
-
i++;
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
const content = pattern.slice(i + 1, j);
|
|
460
|
-
if (content.length > 0 && (content[0] === '!' || content[0] === '^')) {
|
|
461
|
-
const body = content.slice(1).replace(/\\/g, '\\\\');
|
|
462
|
-
regexStr += '[^' + body + ']';
|
|
463
|
-
} else {
|
|
464
|
-
const escapedContent = content.replace(/\\/g, '\\\\');
|
|
465
|
-
regexStr += '[' + escapedContent + ']';
|
|
466
|
-
}
|
|
467
|
-
i = j + 1;
|
|
468
|
-
} else {
|
|
469
|
-
regexStr += c.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
470
|
-
i++;
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
regexStr += '$';
|
|
474
|
-
try {
|
|
475
|
-
const regex = new RegExp(regexStr);
|
|
476
|
-
return regex.test(str);
|
|
477
|
-
} catch {
|
|
478
|
-
return pattern === str;
|
|
479
|
-
}
|
|
480
|
-
};
|
|
481
|
-
|
|
482
405
|
export class ApiV3CheerioRestIssueRepository
|
|
483
406
|
extends BaseGitHubRepository
|
|
484
407
|
implements IssueRepository
|
|
@@ -999,73 +922,13 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
999
922
|
private computePrStatus = (
|
|
1000
923
|
prUrl: string,
|
|
1001
924
|
headRefName: string | undefined,
|
|
1002
|
-
baseRefName: string | undefined,
|
|
1003
925
|
data: PrStatusComputationData,
|
|
1004
926
|
): RelatedPullRequest => {
|
|
1005
927
|
const isConflicted = data.mergeable === 'CONFLICTING';
|
|
1006
|
-
const
|
|
1007
|
-
const
|
|
1008
|
-
const contexts = statusCheckRollup?.contexts?.nodes || [];
|
|
1009
|
-
|
|
1010
|
-
const branchProtectionRules =
|
|
1011
|
-
data.baseRepository?.branchProtectionRules?.nodes || [];
|
|
1012
|
-
const matchingRules = baseRefName
|
|
1013
|
-
? branchProtectionRules.filter(
|
|
1014
|
-
(rule) =>
|
|
1015
|
-
rule.pattern === baseRefName || fnmatch(rule.pattern, baseRefName),
|
|
1016
|
-
)
|
|
1017
|
-
: [];
|
|
1018
|
-
const requiredCheckNamesSet = new Set<string>();
|
|
1019
|
-
for (const rule of matchingRules) {
|
|
1020
|
-
for (const name of rule.requiredStatusCheckContexts) {
|
|
1021
|
-
requiredCheckNamesSet.add(name);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
928
|
+
const hasStatusCheckRollup = data.ciContexts.length > 0;
|
|
929
|
+
const contexts = data.ciContexts;
|
|
1024
930
|
|
|
1025
|
-
const
|
|
1026
|
-
const defaultBranchName = data.baseRepository?.defaultBranchRef?.name || '';
|
|
1027
|
-
for (const ruleset of rulesets) {
|
|
1028
|
-
if (ruleset.enforcement !== 'ACTIVE') continue;
|
|
1029
|
-
const refIncludes = ruleset.conditions.refName.include;
|
|
1030
|
-
const refExcludes = ruleset.conditions.refName.exclude;
|
|
1031
|
-
const matchesInclude =
|
|
1032
|
-
baseRefName !== undefined &&
|
|
1033
|
-
refIncludes.some((pattern) => {
|
|
1034
|
-
if (pattern === '~DEFAULT_BRANCH') {
|
|
1035
|
-
return baseRefName === defaultBranchName;
|
|
1036
|
-
}
|
|
1037
|
-
if (pattern === '~ALL') {
|
|
1038
|
-
return true;
|
|
1039
|
-
}
|
|
1040
|
-
const branchPattern = pattern.replace(/^refs\/heads\//, '');
|
|
1041
|
-
return (
|
|
1042
|
-
branchPattern === baseRefName || fnmatch(branchPattern, baseRefName)
|
|
1043
|
-
);
|
|
1044
|
-
});
|
|
1045
|
-
if (!matchesInclude) continue;
|
|
1046
|
-
const matchesExclude =
|
|
1047
|
-
baseRefName !== undefined &&
|
|
1048
|
-
refExcludes.some((pattern) => {
|
|
1049
|
-
if (pattern === '~DEFAULT_BRANCH') {
|
|
1050
|
-
return baseRefName === defaultBranchName;
|
|
1051
|
-
}
|
|
1052
|
-
const branchPattern = pattern.replace(/^refs\/heads\//, '');
|
|
1053
|
-
return (
|
|
1054
|
-
branchPattern === baseRefName || fnmatch(branchPattern, baseRefName)
|
|
1055
|
-
);
|
|
1056
|
-
});
|
|
1057
|
-
if (matchesExclude) continue;
|
|
1058
|
-
for (const rule of ruleset.rules.nodes) {
|
|
1059
|
-
if (rule.type !== 'REQUIRED_STATUS_CHECKS') continue;
|
|
1060
|
-
if ('requiredStatusChecks' in rule.parameters) {
|
|
1061
|
-
for (const check of rule.parameters.requiredStatusChecks) {
|
|
1062
|
-
requiredCheckNamesSet.add(check.context);
|
|
1063
|
-
}
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
const requiredCheckNames = Array.from(requiredCheckNamesSet);
|
|
931
|
+
const requiredCheckNames = data.requiredCheckNames;
|
|
1069
932
|
const seenContextNames = new Set<string>();
|
|
1070
933
|
for (const ctx of contexts) {
|
|
1071
934
|
if ('name' in ctx) {
|
|
@@ -1105,7 +968,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1105
968
|
'STALE',
|
|
1106
969
|
]);
|
|
1107
970
|
const isCiStateSuccess = (() => {
|
|
1108
|
-
if (!
|
|
971
|
+
if (!hasStatusCheckRollup) return false;
|
|
1109
972
|
const latestRuns = [...latestCheckRunByName.values()];
|
|
1110
973
|
const statusContexts = contexts.filter(
|
|
1111
974
|
(
|
|
@@ -1131,7 +994,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1131
994
|
})();
|
|
1132
995
|
const isPassedAllCiJob = isCiStateSuccess && allRequiredChecksPassed;
|
|
1133
996
|
|
|
1134
|
-
const reviewThreads = data.reviewThreads
|
|
997
|
+
const reviewThreads = data.reviewThreads;
|
|
1135
998
|
const isResolvedAllReviewComments =
|
|
1136
999
|
reviewThreads.length === 0 ||
|
|
1137
1000
|
reviewThreads.every((thread) => thread.isResolved);
|
|
@@ -1151,6 +1014,310 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1151
1014
|
};
|
|
1152
1015
|
};
|
|
1153
1016
|
|
|
1017
|
+
private readonly requiredCheckNamesCache = new Map<
|
|
1018
|
+
string,
|
|
1019
|
+
{ fetchedAtMs: number; names: string[] }
|
|
1020
|
+
>();
|
|
1021
|
+
|
|
1022
|
+
private getRequiredCheckNames = async (
|
|
1023
|
+
owner: string,
|
|
1024
|
+
repo: string,
|
|
1025
|
+
branch: string,
|
|
1026
|
+
): Promise<string[]> => {
|
|
1027
|
+
const cacheKey = `${owner}/${repo}/${branch}`;
|
|
1028
|
+
const nowMs = (await this.dateRepository.now()).getTime();
|
|
1029
|
+
const cached = this.requiredCheckNamesCache.get(cacheKey);
|
|
1030
|
+
if (cached && nowMs - cached.fetchedAtMs < REQUIRED_CHECKS_CACHE_TTL_MS) {
|
|
1031
|
+
return cached.names;
|
|
1032
|
+
}
|
|
1033
|
+
|
|
1034
|
+
const ownerSegment = encodeURIComponent(owner);
|
|
1035
|
+
const repoSegment = encodeURIComponent(repo);
|
|
1036
|
+
const branchSegment = encodeURIComponent(branch);
|
|
1037
|
+
const requiredCheckNamesSet = new Set<string>();
|
|
1038
|
+
|
|
1039
|
+
const rulesResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1040
|
+
fetch(
|
|
1041
|
+
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/rules/branches/${branchSegment}?per_page=100`,
|
|
1042
|
+
{
|
|
1043
|
+
method: 'GET',
|
|
1044
|
+
headers: {
|
|
1045
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1046
|
+
Accept: 'application/vnd.github+json',
|
|
1047
|
+
},
|
|
1048
|
+
},
|
|
1049
|
+
),
|
|
1050
|
+
);
|
|
1051
|
+
if (rulesResponse.ok) {
|
|
1052
|
+
const rulesBody: unknown = await rulesResponse.json();
|
|
1053
|
+
if (!isBranchRulesResponse(rulesBody)) {
|
|
1054
|
+
throw new Error(
|
|
1055
|
+
`Unexpected response shape when fetching branch rules: ${owner}/${repo}/${branch}`,
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
for (const rule of rulesBody) {
|
|
1059
|
+
if (rule.type !== 'required_status_checks') continue;
|
|
1060
|
+
for (const check of rule.parameters?.required_status_checks || []) {
|
|
1061
|
+
requiredCheckNamesSet.add(check.context);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
} else if (rulesResponse.status !== 404) {
|
|
1065
|
+
const reason = await this.formatGitHubErrorWithStatus(rulesResponse);
|
|
1066
|
+
throw new Error(
|
|
1067
|
+
`Failed to fetch branch rules for ${owner}/${repo}/${branch}: ${reason}`,
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
const branchResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1072
|
+
fetch(
|
|
1073
|
+
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/branches/${branchSegment}`,
|
|
1074
|
+
{
|
|
1075
|
+
method: 'GET',
|
|
1076
|
+
headers: {
|
|
1077
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1078
|
+
Accept: 'application/vnd.github+json',
|
|
1079
|
+
},
|
|
1080
|
+
},
|
|
1081
|
+
),
|
|
1082
|
+
);
|
|
1083
|
+
if (branchResponse.ok) {
|
|
1084
|
+
const branchBody: unknown = await branchResponse.json();
|
|
1085
|
+
if (!isBranchDetailResponse(branchBody)) {
|
|
1086
|
+
throw new Error(
|
|
1087
|
+
`Unexpected response shape when fetching branch detail: ${owner}/${repo}/${branch}`,
|
|
1088
|
+
);
|
|
1089
|
+
}
|
|
1090
|
+
for (const context of branchBody.protection?.required_status_checks
|
|
1091
|
+
?.contexts || []) {
|
|
1092
|
+
requiredCheckNamesSet.add(context);
|
|
1093
|
+
}
|
|
1094
|
+
} else if (branchResponse.status !== 404) {
|
|
1095
|
+
const reason = await this.formatGitHubErrorWithStatus(branchResponse);
|
|
1096
|
+
throw new Error(
|
|
1097
|
+
`Failed to fetch branch detail for ${owner}/${repo}/${branch}: ${reason}`,
|
|
1098
|
+
);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const names = Array.from(requiredCheckNamesSet);
|
|
1102
|
+
this.requiredCheckNamesCache.set(cacheKey, {
|
|
1103
|
+
fetchedAtMs: nowMs,
|
|
1104
|
+
names,
|
|
1105
|
+
});
|
|
1106
|
+
return names;
|
|
1107
|
+
};
|
|
1108
|
+
|
|
1109
|
+
private getCommitCiContexts = async (
|
|
1110
|
+
owner: string,
|
|
1111
|
+
repo: string,
|
|
1112
|
+
commitSha: string,
|
|
1113
|
+
): Promise<CiContextNode[]> => {
|
|
1114
|
+
const ownerSegment = encodeURIComponent(owner);
|
|
1115
|
+
const repoSegment = encodeURIComponent(repo);
|
|
1116
|
+
const shaSegment = encodeURIComponent(commitSha);
|
|
1117
|
+
const contexts: CiContextNode[] = [];
|
|
1118
|
+
|
|
1119
|
+
const perPage = 100;
|
|
1120
|
+
let page = 1;
|
|
1121
|
+
let hasMore = true;
|
|
1122
|
+
while (hasMore) {
|
|
1123
|
+
const checkRunsResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1124
|
+
fetch(
|
|
1125
|
+
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/check-runs?per_page=${perPage}&page=${page}`,
|
|
1126
|
+
{
|
|
1127
|
+
method: 'GET',
|
|
1128
|
+
headers: {
|
|
1129
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1130
|
+
Accept: 'application/vnd.github+json',
|
|
1131
|
+
},
|
|
1132
|
+
},
|
|
1133
|
+
),
|
|
1134
|
+
);
|
|
1135
|
+
if (!checkRunsResponse.ok) {
|
|
1136
|
+
const reason =
|
|
1137
|
+
await this.formatGitHubErrorWithStatus(checkRunsResponse);
|
|
1138
|
+
throw new Error(
|
|
1139
|
+
`Failed to fetch check runs for ${owner}/${repo}@${commitSha}: ${reason}`,
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
const checkRunsBody: unknown = await checkRunsResponse.json();
|
|
1143
|
+
if (!isCheckRunsResponse(checkRunsBody)) {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
`Unexpected response shape when fetching check runs: ${owner}/${repo}@${commitSha}`,
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
for (const checkRun of checkRunsBody.check_runs) {
|
|
1149
|
+
contexts.push({
|
|
1150
|
+
__typename: 'CheckRun',
|
|
1151
|
+
name: checkRun.name,
|
|
1152
|
+
conclusion: checkRun.conclusion
|
|
1153
|
+
? checkRun.conclusion.toUpperCase()
|
|
1154
|
+
: null,
|
|
1155
|
+
databaseId: checkRun.id,
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
if (
|
|
1159
|
+
checkRunsBody.check_runs.length < perPage ||
|
|
1160
|
+
page * perPage >= checkRunsBody.total_count
|
|
1161
|
+
) {
|
|
1162
|
+
hasMore = false;
|
|
1163
|
+
} else {
|
|
1164
|
+
page += 1;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
const combinedStatusResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1169
|
+
fetch(
|
|
1170
|
+
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/commits/${shaSegment}/status?per_page=100`,
|
|
1171
|
+
{
|
|
1172
|
+
method: 'GET',
|
|
1173
|
+
headers: {
|
|
1174
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1175
|
+
Accept: 'application/vnd.github+json',
|
|
1176
|
+
},
|
|
1177
|
+
},
|
|
1178
|
+
),
|
|
1179
|
+
);
|
|
1180
|
+
if (!combinedStatusResponse.ok) {
|
|
1181
|
+
const reason = await this.formatGitHubErrorWithStatus(
|
|
1182
|
+
combinedStatusResponse,
|
|
1183
|
+
);
|
|
1184
|
+
throw new Error(
|
|
1185
|
+
`Failed to fetch combined status for ${owner}/${repo}@${commitSha}: ${reason}`,
|
|
1186
|
+
);
|
|
1187
|
+
}
|
|
1188
|
+
const combinedStatusBody: unknown = await combinedStatusResponse.json();
|
|
1189
|
+
if (!isCombinedStatusResponse(combinedStatusBody)) {
|
|
1190
|
+
throw new Error(
|
|
1191
|
+
`Unexpected response shape when fetching combined status: ${owner}/${repo}@${commitSha}`,
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
for (const status of combinedStatusBody.statuses) {
|
|
1195
|
+
contexts.push({
|
|
1196
|
+
__typename: 'StatusContext',
|
|
1197
|
+
context: status.context,
|
|
1198
|
+
state: status.state.toUpperCase(),
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
return contexts;
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
private fetchSlimPullRequest = async (
|
|
1206
|
+
owner: string,
|
|
1207
|
+
repo: string,
|
|
1208
|
+
prNumber: number,
|
|
1209
|
+
): Promise<SlimPullRequest | null> => {
|
|
1210
|
+
const query = `
|
|
1211
|
+
query PullRequestSlimStatus($owner: String!, $repo: String!, $prNumber: Int!, $reviewThreadsAfter: String) {
|
|
1212
|
+
repository(owner: $owner, name: $repo) {
|
|
1213
|
+
pullRequest(number: $prNumber) {
|
|
1214
|
+
url
|
|
1215
|
+
state
|
|
1216
|
+
isDraft
|
|
1217
|
+
headRefName
|
|
1218
|
+
baseRefName
|
|
1219
|
+
mergeable
|
|
1220
|
+
headRefOid
|
|
1221
|
+
reviewThreads(first: 100, after: $reviewThreadsAfter) {
|
|
1222
|
+
pageInfo {
|
|
1223
|
+
endCursor
|
|
1224
|
+
hasNextPage
|
|
1225
|
+
}
|
|
1226
|
+
nodes {
|
|
1227
|
+
isResolved
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
`;
|
|
1234
|
+
|
|
1235
|
+
let slimPullRequest: SlimPullRequest | null = null;
|
|
1236
|
+
let reviewThreadsAfter: string | null = null;
|
|
1237
|
+
let hasNextPage = true;
|
|
1238
|
+
|
|
1239
|
+
while (hasNextPage) {
|
|
1240
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1241
|
+
fetchGithubGraphql({
|
|
1242
|
+
ghToken: this.ghToken,
|
|
1243
|
+
query,
|
|
1244
|
+
variables: { owner, repo, prNumber, reviewThreadsAfter },
|
|
1245
|
+
}),
|
|
1246
|
+
);
|
|
1247
|
+
|
|
1248
|
+
if (!response.ok) {
|
|
1249
|
+
throw new Error(
|
|
1250
|
+
`Failed to fetch pull request from GitHub GraphQL API: HTTP ${response.status}`,
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const responseData: unknown = await response.json();
|
|
1255
|
+
if (!isSlimPullRequestResponse(responseData)) {
|
|
1256
|
+
throw new Error('Unexpected response shape when fetching pull request');
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (responseData.errors && responseData.errors.length > 0) {
|
|
1260
|
+
throw new Error(
|
|
1261
|
+
`GraphQL errors: ${JSON.stringify(responseData.errors)}`,
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
const pr = responseData.data?.repository?.pullRequest;
|
|
1266
|
+
if (!pr) {
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
if (!slimPullRequest) {
|
|
1271
|
+
slimPullRequest = {
|
|
1272
|
+
url: pr.url,
|
|
1273
|
+
state: pr.state,
|
|
1274
|
+
isDraft: pr.isDraft,
|
|
1275
|
+
headRefName: pr.headRefName,
|
|
1276
|
+
baseRefName: pr.baseRefName,
|
|
1277
|
+
mergeable: pr.mergeable,
|
|
1278
|
+
headRefOid: pr.headRefOid,
|
|
1279
|
+
reviewThreads: [],
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
for (const thread of pr.reviewThreads?.nodes || []) {
|
|
1283
|
+
slimPullRequest.reviewThreads.push({ isResolved: thread.isResolved });
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
hasNextPage = pr.reviewThreads?.pageInfo.hasNextPage === true;
|
|
1287
|
+
reviewThreadsAfter = pr.reviewThreads?.pageInfo.endCursor ?? null;
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
return slimPullRequest;
|
|
1291
|
+
};
|
|
1292
|
+
|
|
1293
|
+
private buildRelatedPullRequestFromSlim = async (
|
|
1294
|
+
owner: string,
|
|
1295
|
+
repo: string,
|
|
1296
|
+
slimPullRequest: SlimPullRequest,
|
|
1297
|
+
): Promise<RelatedPullRequest> => {
|
|
1298
|
+
const requiredCheckNames = slimPullRequest.baseRefName
|
|
1299
|
+
? await this.getRequiredCheckNames(
|
|
1300
|
+
owner,
|
|
1301
|
+
repo,
|
|
1302
|
+
slimPullRequest.baseRefName,
|
|
1303
|
+
)
|
|
1304
|
+
: [];
|
|
1305
|
+
const ciContexts = slimPullRequest.headRefOid
|
|
1306
|
+
? await this.getCommitCiContexts(owner, repo, slimPullRequest.headRefOid)
|
|
1307
|
+
: [];
|
|
1308
|
+
return this.computePrStatus(
|
|
1309
|
+
slimPullRequest.url,
|
|
1310
|
+
slimPullRequest.headRefName,
|
|
1311
|
+
{
|
|
1312
|
+
isDraft: slimPullRequest.isDraft,
|
|
1313
|
+
mergeable: slimPullRequest.mergeable,
|
|
1314
|
+
requiredCheckNames,
|
|
1315
|
+
ciContexts,
|
|
1316
|
+
reviewThreads: slimPullRequest.reviewThreads,
|
|
1317
|
+
},
|
|
1318
|
+
);
|
|
1319
|
+
};
|
|
1320
|
+
|
|
1154
1321
|
private resolveMergeabilityWithRetry = async (
|
|
1155
1322
|
owner: string,
|
|
1156
1323
|
repo: string,
|
|
@@ -1160,7 +1327,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1160
1327
|
mergeStateStatus: string | null;
|
|
1161
1328
|
} | null> => {
|
|
1162
1329
|
const query = `
|
|
1163
|
-
query($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
1330
|
+
query PullRequestMergeability($owner: String!, $repo: String!, $prNumber: Int!) {
|
|
1164
1331
|
repository(owner: $owner, name: $repo) {
|
|
1165
1332
|
pullRequest(number: $prNumber) {
|
|
1166
1333
|
mergeable
|
|
@@ -1183,16 +1350,10 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1183
1350
|
}
|
|
1184
1351
|
|
|
1185
1352
|
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
'Content-Type': 'application/json',
|
|
1191
|
-
},
|
|
1192
|
-
body: JSON.stringify({
|
|
1193
|
-
query,
|
|
1194
|
-
variables: { owner, repo, prNumber },
|
|
1195
|
-
}),
|
|
1353
|
+
fetchGithubGraphql({
|
|
1354
|
+
ghToken: this.ghToken,
|
|
1355
|
+
query,
|
|
1356
|
+
variables: { owner, repo, prNumber },
|
|
1196
1357
|
}),
|
|
1197
1358
|
);
|
|
1198
1359
|
|
|
@@ -1244,7 +1405,7 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1244
1405
|
}
|
|
1245
1406
|
|
|
1246
1407
|
const query = `
|
|
1247
|
-
query($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
|
|
1408
|
+
query IssueRelatedOpenPullRequests($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
|
|
1248
1409
|
repository(owner: $owner, name: $repo) {
|
|
1249
1410
|
issue(number: $issueNumber) {
|
|
1250
1411
|
timelineItems(first: 100, after: $after, itemTypes: [CROSS_REFERENCED_EVENT]) {
|
|
@@ -1267,68 +1428,6 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1267
1428
|
mergeable
|
|
1268
1429
|
headRefName
|
|
1269
1430
|
baseRefName
|
|
1270
|
-
baseRepository {
|
|
1271
|
-
branchProtectionRules(first: 100) {
|
|
1272
|
-
nodes {
|
|
1273
|
-
pattern
|
|
1274
|
-
requiredStatusCheckContexts
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
defaultBranchRef {
|
|
1278
|
-
name
|
|
1279
|
-
}
|
|
1280
|
-
rulesets(first: 100) {
|
|
1281
|
-
nodes {
|
|
1282
|
-
name
|
|
1283
|
-
enforcement
|
|
1284
|
-
conditions {
|
|
1285
|
-
refName {
|
|
1286
|
-
include
|
|
1287
|
-
exclude
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
rules(first: 100) {
|
|
1291
|
-
nodes {
|
|
1292
|
-
type
|
|
1293
|
-
parameters {
|
|
1294
|
-
... on RequiredStatusChecksParameters {
|
|
1295
|
-
requiredStatusChecks {
|
|
1296
|
-
context
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
commits(last: 1) {
|
|
1306
|
-
nodes {
|
|
1307
|
-
commit {
|
|
1308
|
-
statusCheckRollup {
|
|
1309
|
-
contexts(first: 100) {
|
|
1310
|
-
nodes {
|
|
1311
|
-
__typename
|
|
1312
|
-
... on CheckRun {
|
|
1313
|
-
databaseId
|
|
1314
|
-
name
|
|
1315
|
-
conclusion
|
|
1316
|
-
}
|
|
1317
|
-
... on StatusContext {
|
|
1318
|
-
context
|
|
1319
|
-
state
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
reviewThreads(first: 100) {
|
|
1328
|
-
nodes {
|
|
1329
|
-
isResolved
|
|
1330
|
-
}
|
|
1331
|
-
}
|
|
1332
1431
|
baseRef {
|
|
1333
1432
|
name
|
|
1334
1433
|
}
|
|
@@ -1348,16 +1447,10 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1348
1447
|
|
|
1349
1448
|
while (hasNextPage) {
|
|
1350
1449
|
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
'Content-Type': 'application/json',
|
|
1356
|
-
},
|
|
1357
|
-
body: JSON.stringify({
|
|
1358
|
-
query,
|
|
1359
|
-
variables: { owner, repo, issueNumber, after },
|
|
1360
|
-
}),
|
|
1450
|
+
fetchGithubGraphql({
|
|
1451
|
+
ghToken: this.ghToken,
|
|
1452
|
+
query,
|
|
1453
|
+
variables: { owner, repo, issueNumber, after },
|
|
1361
1454
|
}),
|
|
1362
1455
|
);
|
|
1363
1456
|
|
|
@@ -1389,16 +1482,9 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1389
1482
|
|
|
1390
1483
|
const pr = item.source;
|
|
1391
1484
|
const prUrl = pr.url || '';
|
|
1392
|
-
const baseRefName = pr.baseRefName ?? pr.baseRef?.name;
|
|
1393
|
-
const prStatus = this.computePrStatus(
|
|
1394
|
-
prUrl,
|
|
1395
|
-
pr.headRefName,
|
|
1396
|
-
baseRefName,
|
|
1397
|
-
pr,
|
|
1398
|
-
);
|
|
1399
1485
|
|
|
1400
|
-
let isConflicted =
|
|
1401
|
-
let mergeable =
|
|
1486
|
+
let isConflicted = pr.mergeable === 'CONFLICTING';
|
|
1487
|
+
let mergeable: string | null = pr.mergeable ?? null;
|
|
1402
1488
|
if (
|
|
1403
1489
|
pr.number !== undefined &&
|
|
1404
1490
|
(pr.mergeable === undefined ||
|
|
@@ -1437,6 +1523,38 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1437
1523
|
}
|
|
1438
1524
|
}
|
|
1439
1525
|
|
|
1526
|
+
if (pr.number === undefined) continue;
|
|
1527
|
+
|
|
1528
|
+
let prStatus: RelatedPullRequest;
|
|
1529
|
+
try {
|
|
1530
|
+
const slimPullRequest = await this.fetchSlimPullRequest(
|
|
1531
|
+
owner,
|
|
1532
|
+
repo,
|
|
1533
|
+
pr.number,
|
|
1534
|
+
);
|
|
1535
|
+
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
1536
|
+
console.info(
|
|
1537
|
+
`ApiV3CheerioRestIssueRepository: pull request is no longer open, excluding it from related open PRs. prUrl: ${prUrl}`,
|
|
1538
|
+
);
|
|
1539
|
+
continue;
|
|
1540
|
+
}
|
|
1541
|
+
const baseRefName =
|
|
1542
|
+
slimPullRequest.baseRefName ?? pr.baseRefName ?? pr.baseRef?.name;
|
|
1543
|
+
prStatus = await this.buildRelatedPullRequestFromSlim(owner, repo, {
|
|
1544
|
+
...slimPullRequest,
|
|
1545
|
+
url: slimPullRequest.url || prUrl,
|
|
1546
|
+
headRefName: slimPullRequest.headRefName ?? pr.headRefName,
|
|
1547
|
+
baseRefName,
|
|
1548
|
+
});
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
const errorMessage =
|
|
1551
|
+
error instanceof Error ? error.message : String(error);
|
|
1552
|
+
console.warn(
|
|
1553
|
+
`ApiV3CheerioRestIssueRepository: fetching pull request status failed, skipping PR for this cycle. prUrl: ${prUrl} error: ${errorMessage}`,
|
|
1554
|
+
);
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1440
1558
|
relatedPRsMap.set(prUrl, {
|
|
1441
1559
|
...prStatus,
|
|
1442
1560
|
isConflicted,
|
|
@@ -1487,118 +1605,16 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1487
1605
|
}
|
|
1488
1606
|
const { owner, repo, issueNumber: prNumber } = parsedUrl;
|
|
1489
1607
|
|
|
1490
|
-
const
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
url
|
|
1495
|
-
state
|
|
1496
|
-
isDraft
|
|
1497
|
-
headRefName
|
|
1498
|
-
baseRefName
|
|
1499
|
-
mergeable
|
|
1500
|
-
baseRepository {
|
|
1501
|
-
branchProtectionRules(first: 100) {
|
|
1502
|
-
nodes {
|
|
1503
|
-
pattern
|
|
1504
|
-
requiredStatusCheckContexts
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
defaultBranchRef {
|
|
1508
|
-
name
|
|
1509
|
-
}
|
|
1510
|
-
rulesets(first: 100) {
|
|
1511
|
-
nodes {
|
|
1512
|
-
name
|
|
1513
|
-
enforcement
|
|
1514
|
-
conditions {
|
|
1515
|
-
refName {
|
|
1516
|
-
include
|
|
1517
|
-
exclude
|
|
1518
|
-
}
|
|
1519
|
-
}
|
|
1520
|
-
rules(first: 100) {
|
|
1521
|
-
nodes {
|
|
1522
|
-
type
|
|
1523
|
-
parameters {
|
|
1524
|
-
... on RequiredStatusChecksParameters {
|
|
1525
|
-
requiredStatusChecks {
|
|
1526
|
-
context
|
|
1527
|
-
}
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1532
|
-
}
|
|
1533
|
-
}
|
|
1534
|
-
}
|
|
1535
|
-
commits(last: 1) {
|
|
1536
|
-
nodes {
|
|
1537
|
-
commit {
|
|
1538
|
-
statusCheckRollup {
|
|
1539
|
-
contexts(first: 100) {
|
|
1540
|
-
nodes {
|
|
1541
|
-
__typename
|
|
1542
|
-
... on CheckRun {
|
|
1543
|
-
databaseId
|
|
1544
|
-
name
|
|
1545
|
-
conclusion
|
|
1546
|
-
}
|
|
1547
|
-
... on StatusContext {
|
|
1548
|
-
context
|
|
1549
|
-
state
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
reviewThreads(first: 100) {
|
|
1558
|
-
nodes {
|
|
1559
|
-
isResolved
|
|
1560
|
-
}
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
`;
|
|
1566
|
-
|
|
1567
|
-
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1568
|
-
fetch('https://api.github.com/graphql', {
|
|
1569
|
-
method: 'POST',
|
|
1570
|
-
headers: {
|
|
1571
|
-
Authorization: `Bearer ${this.ghToken}`,
|
|
1572
|
-
'Content-Type': 'application/json',
|
|
1573
|
-
},
|
|
1574
|
-
body: JSON.stringify({
|
|
1575
|
-
query,
|
|
1576
|
-
variables: { owner, repo, prNumber },
|
|
1577
|
-
}),
|
|
1578
|
-
}),
|
|
1608
|
+
const slimPullRequest = await this.fetchSlimPullRequest(
|
|
1609
|
+
owner,
|
|
1610
|
+
repo,
|
|
1611
|
+
prNumber,
|
|
1579
1612
|
);
|
|
1580
|
-
|
|
1581
|
-
if (!response.ok) {
|
|
1582
|
-
throw new Error(
|
|
1583
|
-
`Failed to fetch pull request from GitHub GraphQL API: HTTP ${response.status}`,
|
|
1584
|
-
);
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
const responseData: unknown = await response.json();
|
|
1588
|
-
if (!isDirectPullRequestResponse(responseData)) {
|
|
1589
|
-
throw new Error('Unexpected response shape when fetching pull request');
|
|
1590
|
-
}
|
|
1591
|
-
|
|
1592
|
-
if (responseData.errors && responseData.errors.length > 0) {
|
|
1593
|
-
throw new Error(`GraphQL errors: ${JSON.stringify(responseData.errors)}`);
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
const pr = responseData.data?.repository?.pullRequest;
|
|
1597
|
-
if (!pr || pr.state !== 'OPEN') {
|
|
1613
|
+
if (!slimPullRequest || slimPullRequest.state !== 'OPEN') {
|
|
1598
1614
|
return null;
|
|
1599
1615
|
}
|
|
1600
1616
|
|
|
1601
|
-
return this.
|
|
1617
|
+
return this.buildRelatedPullRequestFromSlim(owner, repo, slimPullRequest);
|
|
1602
1618
|
};
|
|
1603
1619
|
|
|
1604
1620
|
closePullRequest = async (prUrl: string): Promise<void> => {
|