@gh-symphony/cli 0.1.4 → 0.2.2
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/README.md +87 -0
- package/dist/{chunk-HT3FAJAO.js → chunk-27UZ6KX2.js} +273 -10
- package/dist/{chunk-EWTMSDCE.js → chunk-3SKN5L3I.js} +260 -18
- package/dist/{chunk-WOVNN5NW.js → chunk-4ICDSQCJ.js} +1 -0
- package/dist/{chunk-DW63WPRE.js → chunk-6PFFGP7S.js} +18 -3
- package/dist/{chunk-Z3NZOPLZ.js → chunk-BOM2BYZQ.js} +43 -0
- package/dist/{chunk-E7OCBNB2.js → chunk-FAU72YC2.js} +1 -1
- package/dist/{chunk-RHLUIMBN.js → chunk-PLBG7TZA.js} +306 -30
- package/dist/{chunk-6I753NYO.js → chunk-RZ3WO7OV.js} +1 -1
- package/dist/{repo-IH6UWE4H.js → chunk-X4QSP3AX.js} +5443 -6207
- package/dist/{config-cmd-2ADPUYWA.js → config-cmd-AOZVS6GU.js} +1 -1
- package/dist/{doctor-I32MANQ4.js → doctor-GDZSGJIT.js} +602 -26
- package/dist/index.js +12 -8
- package/dist/repo-SWEUWY4H.js +2693 -0
- package/dist/{setup-UJC2WYHQ.js → setup-XNOSJ3RX.js} +35 -32
- package/dist/{upgrade-XYHCUGHT.js → upgrade-ZWUAJLHK.js} +2 -2
- package/dist/{version-B2AYYGLM.js → version-PLQK6X2P.js} +1 -1
- package/dist/worker-entry.js +77 -9
- package/dist/{workflow-WSXHMO5B.js → workflow-2ERPNGRB.js} +7 -6
- package/package.json +3 -3
- package/dist/chunk-YIARPBOR.js +0 -1648
package/dist/chunk-YIARPBOR.js
DELETED
|
@@ -1,1648 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_WORKFLOW_LIFECYCLE
|
|
4
|
-
} from "./chunk-EWTMSDCE.js";
|
|
5
|
-
import {
|
|
6
|
-
loadGlobalConfig,
|
|
7
|
-
loadProjectConfig
|
|
8
|
-
} from "./chunk-WOVNN5NW.js";
|
|
9
|
-
|
|
10
|
-
// ../tracker-github/src/adapter.ts
|
|
11
|
-
import { createHash } from "crypto";
|
|
12
|
-
var DEFAULT_API_URL = "https://api.github.com/graphql";
|
|
13
|
-
var DEFAULT_PAGE_SIZE = 25;
|
|
14
|
-
var DEFAULT_NETWORK_TIMEOUT_MS = 3e4;
|
|
15
|
-
var RATE_LIMIT_THRESHOLD = 100;
|
|
16
|
-
var MAX_RATE_LIMIT_WAIT_MS = 6e4;
|
|
17
|
-
var GitHubTrackerError = class extends Error {
|
|
18
|
-
};
|
|
19
|
-
var GitHubTrackerHttpError = class extends GitHubTrackerError {
|
|
20
|
-
constructor(message, status, details) {
|
|
21
|
-
super(message);
|
|
22
|
-
this.status = status;
|
|
23
|
-
this.details = details;
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
var GitHubTrackerQueryError = class extends GitHubTrackerError {
|
|
27
|
-
};
|
|
28
|
-
var cachedGitHubGraphQLRateLimits = /* @__PURE__ */ new Map();
|
|
29
|
-
function normalizeProjectItem(projectId, item, lifecycle = DEFAULT_WORKFLOW_LIFECYCLE, priority = {}, rateLimits = null) {
|
|
30
|
-
if (item.content?.__typename !== "Issue" && item.content?.__typename !== "PullRequest") {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
const fieldValues = extractFieldValues(item.fieldValues?.nodes ?? []);
|
|
34
|
-
const state = fieldValues[lifecycle.stateFieldName] ?? "Unknown";
|
|
35
|
-
if (item.content.__typename === "PullRequest") {
|
|
36
|
-
return normalizePullRequestProjectItem(
|
|
37
|
-
projectId,
|
|
38
|
-
item,
|
|
39
|
-
item.content,
|
|
40
|
-
fieldValues,
|
|
41
|
-
state,
|
|
42
|
-
priority,
|
|
43
|
-
rateLimits
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
const repository = item.content.repository;
|
|
47
|
-
const blockedBy = (item.content.blockedBy?.nodes ?? []).flatMap(
|
|
48
|
-
(node) => node ? [
|
|
49
|
-
{
|
|
50
|
-
id: node.id,
|
|
51
|
-
identifier: `${node.repository.owner.login}/${node.repository.name}#${node.number}`,
|
|
52
|
-
state: normalizeBlockerState(node.state, lifecycle)
|
|
53
|
-
}
|
|
54
|
-
] : []
|
|
55
|
-
);
|
|
56
|
-
const issueUpdatedAtMs = parseTimestampMs(item.content.updatedAt);
|
|
57
|
-
const itemUpdatedAtMs = parseTimestampMs(item.updatedAt);
|
|
58
|
-
const trackedUpdatedAt = itemUpdatedAtMs !== null && (issueUpdatedAtMs === null || itemUpdatedAtMs > issueUpdatedAtMs) ? item.updatedAt : item.content.updatedAt ?? item.updatedAt;
|
|
59
|
-
const linkedPullRequests = normalizePullRequestNodes(
|
|
60
|
-
item.content.closedByPullRequestsReferences?.nodes ?? []
|
|
61
|
-
);
|
|
62
|
-
const linkedPullRequestsTruncated = item.content.closedByPullRequestsReferences?.pageInfo?.hasNextPage ?? false;
|
|
63
|
-
return {
|
|
64
|
-
id: item.content.id,
|
|
65
|
-
identifier: `${repository.owner.login}/${repository.name}#${item.content.number}`,
|
|
66
|
-
number: item.content.number,
|
|
67
|
-
title: item.content.title,
|
|
68
|
-
description: item.content.body,
|
|
69
|
-
priority: resolvePriority(item, priority),
|
|
70
|
-
state,
|
|
71
|
-
branchName: null,
|
|
72
|
-
url: item.content.url,
|
|
73
|
-
labels: normalizeLabelNames(item.content.labels?.nodes ?? []),
|
|
74
|
-
blockedBy,
|
|
75
|
-
createdAt: item.content.createdAt,
|
|
76
|
-
updatedAt: trackedUpdatedAt,
|
|
77
|
-
repository: {
|
|
78
|
-
owner: repository.owner.login,
|
|
79
|
-
name: repository.name,
|
|
80
|
-
url: repository.url,
|
|
81
|
-
cloneUrl: deriveCloneUrl(repository.url)
|
|
82
|
-
},
|
|
83
|
-
tracker: {
|
|
84
|
-
adapter: "github-project",
|
|
85
|
-
bindingId: projectId,
|
|
86
|
-
itemId: item.id
|
|
87
|
-
},
|
|
88
|
-
metadata: withIssueMetadata(
|
|
89
|
-
fieldValues,
|
|
90
|
-
linkedPullRequests,
|
|
91
|
-
linkedPullRequestsTruncated
|
|
92
|
-
),
|
|
93
|
-
rateLimits
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
function normalizePullRequestProjectItem(projectId, item, content, fieldValues, state, priority, rateLimits) {
|
|
97
|
-
const pullRequest = normalizePullRequestNode(content);
|
|
98
|
-
const itemUpdatedAtMs = parseTimestampMs(item.updatedAt);
|
|
99
|
-
const pullRequestUpdatedAtMs = parseTimestampMs(content.updatedAt);
|
|
100
|
-
const trackedUpdatedAt = itemUpdatedAtMs !== null && (pullRequestUpdatedAtMs === null || itemUpdatedAtMs > pullRequestUpdatedAtMs) ? item.updatedAt : content.updatedAt ?? item.updatedAt;
|
|
101
|
-
return {
|
|
102
|
-
id: content.id,
|
|
103
|
-
identifier: pullRequest.identifier,
|
|
104
|
-
number: content.number,
|
|
105
|
-
title: content.title,
|
|
106
|
-
description: content.body,
|
|
107
|
-
priority: resolvePriority(item, priority),
|
|
108
|
-
state,
|
|
109
|
-
branchName: content.headRefName,
|
|
110
|
-
url: content.url,
|
|
111
|
-
labels: pullRequest.labels,
|
|
112
|
-
blockedBy: [],
|
|
113
|
-
createdAt: content.createdAt,
|
|
114
|
-
updatedAt: trackedUpdatedAt,
|
|
115
|
-
repository: pullRequest.repository,
|
|
116
|
-
tracker: {
|
|
117
|
-
adapter: "github-project",
|
|
118
|
-
bindingId: projectId,
|
|
119
|
-
itemId: item.id
|
|
120
|
-
},
|
|
121
|
-
metadata: withGitHubMetadata(fieldValues, {
|
|
122
|
-
contentType: "PullRequest",
|
|
123
|
-
pullRequest,
|
|
124
|
-
linkedPullRequests: []
|
|
125
|
-
}),
|
|
126
|
-
rateLimits
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
async function fetchProjectIssues(config, fetchImpl = fetch) {
|
|
130
|
-
const issues = [];
|
|
131
|
-
let cursor = null;
|
|
132
|
-
const priorityOptionIds = config.priorityFieldName ? await fetchPriorityOptionOrder(
|
|
133
|
-
config,
|
|
134
|
-
config.priorityFieldName,
|
|
135
|
-
fetchImpl
|
|
136
|
-
) : void 0;
|
|
137
|
-
const currentUserLogin = config.assignedOnly ? await fetchCurrentUserLogin(config, fetchImpl) : null;
|
|
138
|
-
let excludedCount = 0;
|
|
139
|
-
let latestRateLimits = null;
|
|
140
|
-
do {
|
|
141
|
-
const pageResult = await fetchProjectItemsPage(config, cursor, fetchImpl);
|
|
142
|
-
const page = pageResult.page;
|
|
143
|
-
latestRateLimits = pageResult.rateLimits ?? latestRateLimits;
|
|
144
|
-
const pageIssues = (page.nodes ?? []).flatMap((item) => {
|
|
145
|
-
if (!item) {
|
|
146
|
-
return [];
|
|
147
|
-
}
|
|
148
|
-
const normalized = normalizeProjectItem(
|
|
149
|
-
config.projectId,
|
|
150
|
-
item,
|
|
151
|
-
config.lifecycle,
|
|
152
|
-
{
|
|
153
|
-
fieldName: config.priorityFieldName,
|
|
154
|
-
optionIds: priorityOptionIds
|
|
155
|
-
},
|
|
156
|
-
latestRateLimits
|
|
157
|
-
);
|
|
158
|
-
if (!normalized) {
|
|
159
|
-
return [];
|
|
160
|
-
}
|
|
161
|
-
if (currentUserLogin && !isIssueAssignedToLogin(item, currentUserLogin)) {
|
|
162
|
-
excludedCount += 1;
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
return [normalized];
|
|
166
|
-
});
|
|
167
|
-
issues.push(...pageIssues);
|
|
168
|
-
cursor = page.pageInfo.hasNextPage ? page.pageInfo.endCursor : null;
|
|
169
|
-
} while (cursor);
|
|
170
|
-
if (currentUserLogin) {
|
|
171
|
-
emitAssignedOnlyFilterEvent({
|
|
172
|
-
projectId: config.projectId,
|
|
173
|
-
currentUserLogin,
|
|
174
|
-
includedCount: issues.length,
|
|
175
|
-
excludedCount
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
if (latestRateLimits) {
|
|
179
|
-
for (const issue of issues) {
|
|
180
|
-
issue.rateLimits = latestRateLimits;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return issues;
|
|
184
|
-
}
|
|
185
|
-
async function fetchIssueStatesByIds(config, issueIds, fetchImpl = fetch) {
|
|
186
|
-
if (issueIds.length === 0) {
|
|
187
|
-
return [];
|
|
188
|
-
}
|
|
189
|
-
const issues = [];
|
|
190
|
-
for (const issueIdBatch of chunkValues([...new Set(issueIds)], 100)) {
|
|
191
|
-
const result = await executeGraphQLQueryWithMetadata(
|
|
192
|
-
config,
|
|
193
|
-
ISSUE_STATES_BY_IDS_QUERY,
|
|
194
|
-
{
|
|
195
|
-
issueIds: issueIdBatch
|
|
196
|
-
},
|
|
197
|
-
fetchImpl
|
|
198
|
-
);
|
|
199
|
-
const data = result.data;
|
|
200
|
-
const rateLimits = result.rateLimits;
|
|
201
|
-
for (const node of data.nodes ?? []) {
|
|
202
|
-
const projectItem = await resolveIssueProjectItemForStateLookup(
|
|
203
|
-
config,
|
|
204
|
-
node,
|
|
205
|
-
fetchImpl
|
|
206
|
-
);
|
|
207
|
-
const normalized = normalizeIssueStateLookupNode(
|
|
208
|
-
config.projectId,
|
|
209
|
-
node,
|
|
210
|
-
projectItem,
|
|
211
|
-
config.lifecycle,
|
|
212
|
-
rateLimits
|
|
213
|
-
);
|
|
214
|
-
if (normalized) {
|
|
215
|
-
issues.push(normalized);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return issues;
|
|
220
|
-
}
|
|
221
|
-
async function fetchProjectIssueByRepositoryAndNumber(config, repository, issueNumber, fetchImpl = fetch) {
|
|
222
|
-
const priorityOptionIds = config.priorityFieldName ? await fetchPriorityOptionOrder(
|
|
223
|
-
config,
|
|
224
|
-
config.priorityFieldName,
|
|
225
|
-
fetchImpl
|
|
226
|
-
) : void 0;
|
|
227
|
-
const result = await executeGraphQLQueryWithMetadata(
|
|
228
|
-
config,
|
|
229
|
-
REPOSITORY_ISSUE_QUERY,
|
|
230
|
-
{
|
|
231
|
-
owner: repository.owner,
|
|
232
|
-
name: repository.name,
|
|
233
|
-
issueNumber
|
|
234
|
-
},
|
|
235
|
-
fetchImpl
|
|
236
|
-
);
|
|
237
|
-
const issue = result.data.repository?.issue ?? null;
|
|
238
|
-
if (!issue) {
|
|
239
|
-
return null;
|
|
240
|
-
}
|
|
241
|
-
const projectItem = await resolveIssueProjectItemForStateLookup(
|
|
242
|
-
config,
|
|
243
|
-
issue,
|
|
244
|
-
fetchImpl
|
|
245
|
-
);
|
|
246
|
-
if (!projectItem) {
|
|
247
|
-
return null;
|
|
248
|
-
}
|
|
249
|
-
return normalizeRepositoryIssueLookup(
|
|
250
|
-
config.projectId,
|
|
251
|
-
issue,
|
|
252
|
-
projectItem,
|
|
253
|
-
config.lifecycle,
|
|
254
|
-
{
|
|
255
|
-
fieldName: config.priorityFieldName,
|
|
256
|
-
optionIds: priorityOptionIds
|
|
257
|
-
},
|
|
258
|
-
result.rateLimits
|
|
259
|
-
);
|
|
260
|
-
}
|
|
261
|
-
async function fetchProjectItemsPage(config, cursor, fetchImpl) {
|
|
262
|
-
const result = await executeGraphQLQueryWithMetadata(
|
|
263
|
-
config,
|
|
264
|
-
PROJECT_ITEMS_QUERY,
|
|
265
|
-
{
|
|
266
|
-
projectId: config.projectId,
|
|
267
|
-
cursor,
|
|
268
|
-
pageSize: config.pageSize ?? DEFAULT_PAGE_SIZE
|
|
269
|
-
},
|
|
270
|
-
fetchImpl
|
|
271
|
-
);
|
|
272
|
-
const data = result.data;
|
|
273
|
-
const items = data.node?.items;
|
|
274
|
-
if (!items) {
|
|
275
|
-
throw new GitHubTrackerQueryError(
|
|
276
|
-
"GitHub GraphQL response did not include project items."
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
return {
|
|
280
|
-
page: items,
|
|
281
|
-
rateLimits: result.rateLimits
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
var fetchGithubProjectIssues = fetchProjectIssues;
|
|
285
|
-
var fetchGithubIssueStatesByIds = fetchIssueStatesByIds;
|
|
286
|
-
var fetchGithubProjectIssueByRepositoryAndNumber = fetchProjectIssueByRepositoryAndNumber;
|
|
287
|
-
var upsertGithubIssueComment = upsertIssueComment;
|
|
288
|
-
async function upsertIssueComment(config, issueId, input, fetchImpl = fetch) {
|
|
289
|
-
const existingComment = await findIssueCommentByMarker(
|
|
290
|
-
config,
|
|
291
|
-
issueId,
|
|
292
|
-
input.marker,
|
|
293
|
-
fetchImpl
|
|
294
|
-
);
|
|
295
|
-
if (!existingComment) {
|
|
296
|
-
await executeGraphQLQuery(
|
|
297
|
-
config,
|
|
298
|
-
ADD_ISSUE_COMMENT_MUTATION,
|
|
299
|
-
{
|
|
300
|
-
subjectId: issueId,
|
|
301
|
-
body: input.body
|
|
302
|
-
},
|
|
303
|
-
fetchImpl
|
|
304
|
-
);
|
|
305
|
-
return "created";
|
|
306
|
-
}
|
|
307
|
-
if (existingComment.body === input.body) {
|
|
308
|
-
return "unchanged";
|
|
309
|
-
}
|
|
310
|
-
await executeGraphQLQuery(
|
|
311
|
-
config,
|
|
312
|
-
UPDATE_ISSUE_COMMENT_MUTATION,
|
|
313
|
-
{
|
|
314
|
-
commentId: existingComment.id,
|
|
315
|
-
body: input.body
|
|
316
|
-
},
|
|
317
|
-
fetchImpl
|
|
318
|
-
);
|
|
319
|
-
return "updated";
|
|
320
|
-
}
|
|
321
|
-
async function findIssueCommentByMarker(config, issueId, marker, fetchImpl) {
|
|
322
|
-
let cursor = null;
|
|
323
|
-
while (true) {
|
|
324
|
-
const data = await executeGraphQLQuery(
|
|
325
|
-
config,
|
|
326
|
-
ISSUE_COMMENTS_BY_ID_QUERY,
|
|
327
|
-
{
|
|
328
|
-
issueId,
|
|
329
|
-
cursor
|
|
330
|
-
},
|
|
331
|
-
fetchImpl
|
|
332
|
-
);
|
|
333
|
-
if (data.node?.__typename !== "Issue") {
|
|
334
|
-
throw new GitHubTrackerQueryError(
|
|
335
|
-
"GitHub GraphQL response did not include issue comments."
|
|
336
|
-
);
|
|
337
|
-
}
|
|
338
|
-
const issueNode = data.node;
|
|
339
|
-
const match = issueNode.comments.nodes?.find(
|
|
340
|
-
(comment) => comment?.body.includes(marker)
|
|
341
|
-
) ?? null;
|
|
342
|
-
if (match) {
|
|
343
|
-
return match;
|
|
344
|
-
}
|
|
345
|
-
if (!issueNode.comments.pageInfo.hasNextPage) {
|
|
346
|
-
return null;
|
|
347
|
-
}
|
|
348
|
-
cursor = issueNode.comments.pageInfo.endCursor;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
async function fetchCurrentUserLogin(config, fetchImpl) {
|
|
352
|
-
const response = await fetchImpl(resolveRestUserApiUrl(config.apiUrl), {
|
|
353
|
-
method: "GET",
|
|
354
|
-
headers: {
|
|
355
|
-
authorization: `Bearer ${config.token}`,
|
|
356
|
-
"user-agent": "gh-symphony",
|
|
357
|
-
accept: "application/vnd.github+json"
|
|
358
|
-
},
|
|
359
|
-
signal: buildRequestSignal(config.timeoutMs)
|
|
360
|
-
});
|
|
361
|
-
if (!response.ok) {
|
|
362
|
-
const details = await response.text();
|
|
363
|
-
throw new GitHubTrackerHttpError(
|
|
364
|
-
`GitHub REST request failed with status ${response.status}`,
|
|
365
|
-
response.status,
|
|
366
|
-
details
|
|
367
|
-
);
|
|
368
|
-
}
|
|
369
|
-
const payload = await response.json();
|
|
370
|
-
if (!payload.login) {
|
|
371
|
-
throw new GitHubTrackerQueryError(
|
|
372
|
-
"GitHub REST response did not include the authenticated user login."
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
return payload.login;
|
|
376
|
-
}
|
|
377
|
-
function isIssueAssignedToLogin(item, login) {
|
|
378
|
-
if (item.content?.__typename !== "Issue" && item.content?.__typename !== "PullRequest") {
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
return (item.content.assignees?.nodes ?? []).some(
|
|
382
|
-
(assignee) => assignee?.login === login
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
function emitAssignedOnlyFilterEvent(input) {
|
|
386
|
-
console.info(
|
|
387
|
-
JSON.stringify({
|
|
388
|
-
event: "tracker-assigned-only-filtered",
|
|
389
|
-
projectId: input.projectId,
|
|
390
|
-
currentUserLogin: input.currentUserLogin,
|
|
391
|
-
includedCount: input.includedCount,
|
|
392
|
-
excludedCount: input.excludedCount
|
|
393
|
-
})
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
function extractFieldValues(nodes) {
|
|
397
|
-
return nodes.reduce((values, node) => {
|
|
398
|
-
const fieldName = node?.field?.name;
|
|
399
|
-
if (!fieldName) {
|
|
400
|
-
return values;
|
|
401
|
-
}
|
|
402
|
-
if (node.__typename === "ProjectV2ItemFieldSingleSelectValue" && node.name) {
|
|
403
|
-
values[fieldName] = node.name;
|
|
404
|
-
}
|
|
405
|
-
if (node.__typename === "ProjectV2ItemFieldTextValue" && node.text) {
|
|
406
|
-
values[fieldName] = node.text;
|
|
407
|
-
}
|
|
408
|
-
return values;
|
|
409
|
-
}, {});
|
|
410
|
-
}
|
|
411
|
-
function normalizeIssueStateLookupNode(projectId, issue, projectItem, lifecycle = DEFAULT_WORKFLOW_LIFECYCLE, rateLimits = null) {
|
|
412
|
-
if (issue?.__typename !== "Issue" && issue?.__typename !== "PullRequest") {
|
|
413
|
-
return null;
|
|
414
|
-
}
|
|
415
|
-
if (!projectItem) {
|
|
416
|
-
return null;
|
|
417
|
-
}
|
|
418
|
-
const fieldValues = extractFieldValues(projectItem.fieldValues?.nodes ?? []);
|
|
419
|
-
const state = fieldValues[lifecycle.stateFieldName] ?? "Unknown";
|
|
420
|
-
const repository = issue.repository;
|
|
421
|
-
const identifier = `${repository.owner.login}/${repository.name}#${issue.number}`;
|
|
422
|
-
const url = issue.url ?? `${repository.url}/${issue.__typename === "PullRequest" ? "pull" : "issues"}/${issue.number}`;
|
|
423
|
-
return {
|
|
424
|
-
id: issue.id,
|
|
425
|
-
identifier,
|
|
426
|
-
number: issue.number,
|
|
427
|
-
title: identifier,
|
|
428
|
-
description: null,
|
|
429
|
-
priority: null,
|
|
430
|
-
state,
|
|
431
|
-
branchName: issue.__typename === "PullRequest" ? issue.headRefName ?? null : null,
|
|
432
|
-
url,
|
|
433
|
-
labels: [],
|
|
434
|
-
blockedBy: [],
|
|
435
|
-
createdAt: null,
|
|
436
|
-
updatedAt: projectItem.updatedAt ?? issue.updatedAt,
|
|
437
|
-
repository: {
|
|
438
|
-
owner: repository.owner.login,
|
|
439
|
-
name: repository.name,
|
|
440
|
-
url: repository.url,
|
|
441
|
-
cloneUrl: deriveCloneUrl(repository.url)
|
|
442
|
-
},
|
|
443
|
-
tracker: {
|
|
444
|
-
adapter: "github-project",
|
|
445
|
-
bindingId: projectId,
|
|
446
|
-
itemId: projectItem.id
|
|
447
|
-
},
|
|
448
|
-
metadata: issue.__typename === "PullRequest" ? withGitHubMetadata(fieldValues, { contentType: "PullRequest" }) : fieldValues,
|
|
449
|
-
rateLimits
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
function normalizeRepositoryIssueLookup(projectId, issue, projectItem, lifecycle = DEFAULT_WORKFLOW_LIFECYCLE, priority = {}, rateLimits = null) {
|
|
453
|
-
if (!issue || !projectItem) {
|
|
454
|
-
return null;
|
|
455
|
-
}
|
|
456
|
-
return normalizeProjectItem(
|
|
457
|
-
projectId,
|
|
458
|
-
{
|
|
459
|
-
id: projectItem.id,
|
|
460
|
-
updatedAt: projectItem.updatedAt,
|
|
461
|
-
fieldValues: projectItem.fieldValues,
|
|
462
|
-
content: issue
|
|
463
|
-
},
|
|
464
|
-
lifecycle,
|
|
465
|
-
priority,
|
|
466
|
-
rateLimits
|
|
467
|
-
);
|
|
468
|
-
}
|
|
469
|
-
function normalizePullRequestNodes(nodes) {
|
|
470
|
-
return nodes.flatMap(
|
|
471
|
-
(node) => node ? [normalizePullRequestNode(node)] : []
|
|
472
|
-
);
|
|
473
|
-
}
|
|
474
|
-
function normalizePullRequestNode(node) {
|
|
475
|
-
const repository = normalizeRepositoryRef(node.repository);
|
|
476
|
-
return {
|
|
477
|
-
id: node.id,
|
|
478
|
-
number: node.number,
|
|
479
|
-
identifier: `${repository.owner}/${repository.name}#${node.number}`,
|
|
480
|
-
title: node.title,
|
|
481
|
-
body: node.body,
|
|
482
|
-
url: node.url,
|
|
483
|
-
state: node.state,
|
|
484
|
-
isDraft: node.isDraft,
|
|
485
|
-
merged: node.merged,
|
|
486
|
-
headRefName: node.headRefName,
|
|
487
|
-
baseRefName: node.baseRefName,
|
|
488
|
-
headRepository: node.headRepository ? normalizeRepositoryRef(node.headRepository) : null,
|
|
489
|
-
repository,
|
|
490
|
-
labels: normalizeLabelNames(node.labels?.nodes ?? []),
|
|
491
|
-
assignees: normalizeAssigneeLogins(node.assignees?.nodes ?? []),
|
|
492
|
-
createdAt: node.createdAt,
|
|
493
|
-
updatedAt: node.updatedAt
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
function normalizeRepositoryRef(repository) {
|
|
497
|
-
return {
|
|
498
|
-
owner: repository.owner.login,
|
|
499
|
-
name: repository.name,
|
|
500
|
-
url: repository.url,
|
|
501
|
-
cloneUrl: deriveCloneUrl(repository.url)
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
function normalizeLabelNames(nodes) {
|
|
505
|
-
return nodes.flatMap((label) => label?.name ? [label.name.toLowerCase()] : []).sort();
|
|
506
|
-
}
|
|
507
|
-
function normalizeAssigneeLogins(nodes) {
|
|
508
|
-
return nodes.flatMap((assignee) => assignee?.login ? [assignee.login] : []);
|
|
509
|
-
}
|
|
510
|
-
function withGitHubMetadata(fieldValues, metadata) {
|
|
511
|
-
return {
|
|
512
|
-
...fieldValues,
|
|
513
|
-
...metadata
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
function withIssueMetadata(fieldValues, linkedPullRequests, linkedPullRequestsTruncated = false) {
|
|
517
|
-
if (linkedPullRequests.length === 0 && !linkedPullRequestsTruncated) {
|
|
518
|
-
return fieldValues;
|
|
519
|
-
}
|
|
520
|
-
return withGitHubMetadata(fieldValues, {
|
|
521
|
-
linkedPullRequests,
|
|
522
|
-
linkedPullRequestsTruncated
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
async function resolveIssueProjectItemForStateLookup(config, issue, fetchImpl) {
|
|
526
|
-
if (issue?.__typename !== "Issue" && issue?.__typename !== "PullRequest") {
|
|
527
|
-
return null;
|
|
528
|
-
}
|
|
529
|
-
let connection = issue.projectItems;
|
|
530
|
-
let projectItem = findProjectItemByProjectId(
|
|
531
|
-
connection?.nodes ?? [],
|
|
532
|
-
config.projectId
|
|
533
|
-
);
|
|
534
|
-
let cursor = connection?.pageInfo.endCursor ?? null;
|
|
535
|
-
while (!projectItem && connection?.pageInfo.hasNextPage) {
|
|
536
|
-
const nextPage = await fetchIssueProjectItemsPage(
|
|
537
|
-
config,
|
|
538
|
-
issue.id,
|
|
539
|
-
cursor,
|
|
540
|
-
fetchImpl
|
|
541
|
-
);
|
|
542
|
-
projectItem = findProjectItemByProjectId(
|
|
543
|
-
nextPage.nodes ?? [],
|
|
544
|
-
config.projectId
|
|
545
|
-
);
|
|
546
|
-
connection = nextPage;
|
|
547
|
-
cursor = nextPage.pageInfo.endCursor;
|
|
548
|
-
}
|
|
549
|
-
return projectItem;
|
|
550
|
-
}
|
|
551
|
-
async function fetchIssueProjectItemsPage(config, issueId, cursor, fetchImpl) {
|
|
552
|
-
const result = await executeGraphQLQueryWithMetadata(
|
|
553
|
-
config,
|
|
554
|
-
ISSUE_PROJECT_ITEMS_PAGE_QUERY,
|
|
555
|
-
{
|
|
556
|
-
issueId,
|
|
557
|
-
cursor
|
|
558
|
-
},
|
|
559
|
-
fetchImpl
|
|
560
|
-
);
|
|
561
|
-
const data = result.data;
|
|
562
|
-
const issue = data.node;
|
|
563
|
-
if (issue?.__typename !== "Issue" && issue?.__typename !== "PullRequest" || !issue.projectItems) {
|
|
564
|
-
throw new GitHubTrackerQueryError(
|
|
565
|
-
"GitHub GraphQL response did not include issue project items."
|
|
566
|
-
);
|
|
567
|
-
}
|
|
568
|
-
return issue.projectItems;
|
|
569
|
-
}
|
|
570
|
-
function findProjectItemByProjectId(nodes, projectId) {
|
|
571
|
-
return nodes.find((item) => item?.project?.id === projectId) ?? null;
|
|
572
|
-
}
|
|
573
|
-
function resolvePriority(item, priority) {
|
|
574
|
-
if (!priority.fieldName || !priority.optionIds) {
|
|
575
|
-
return null;
|
|
576
|
-
}
|
|
577
|
-
for (const node of item.fieldValues?.nodes ?? []) {
|
|
578
|
-
if (node?.__typename === "ProjectV2ItemFieldSingleSelectValue" && node.field?.name === priority.fieldName && node.optionId) {
|
|
579
|
-
return priority.optionIds[node.optionId] ?? null;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
function extractPriorityOptionOrder(fields, priorityFieldName) {
|
|
585
|
-
for (const field of fields) {
|
|
586
|
-
if (isSingleSelectProjectField(field) && field.name === priorityFieldName) {
|
|
587
|
-
let nextPriority = 0;
|
|
588
|
-
const optionEntries = (field.options ?? []).flatMap((option) => {
|
|
589
|
-
if (!option?.id) {
|
|
590
|
-
return [];
|
|
591
|
-
}
|
|
592
|
-
const entry = [option.id, nextPriority];
|
|
593
|
-
nextPriority += 1;
|
|
594
|
-
return [entry];
|
|
595
|
-
});
|
|
596
|
-
return Object.fromEntries(optionEntries);
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
return void 0;
|
|
600
|
-
}
|
|
601
|
-
async function fetchPriorityOptionOrder(config, priorityFieldName, fetchImpl) {
|
|
602
|
-
const data = await executeGraphQLQuery(
|
|
603
|
-
config,
|
|
604
|
-
PROJECT_FIELDS_QUERY,
|
|
605
|
-
{ projectId: config.projectId },
|
|
606
|
-
fetchImpl
|
|
607
|
-
);
|
|
608
|
-
return extractPriorityOptionOrder(
|
|
609
|
-
data.node?.fields?.nodes ?? [],
|
|
610
|
-
priorityFieldName
|
|
611
|
-
);
|
|
612
|
-
}
|
|
613
|
-
function isSingleSelectProjectField(field) {
|
|
614
|
-
return field?.__typename === "ProjectV2SingleSelectField";
|
|
615
|
-
}
|
|
616
|
-
function deriveCloneUrl(repositoryUrl) {
|
|
617
|
-
if (repositoryUrl.startsWith("file://") || repositoryUrl.endsWith(".git")) {
|
|
618
|
-
return repositoryUrl;
|
|
619
|
-
}
|
|
620
|
-
return `${repositoryUrl}.git`;
|
|
621
|
-
}
|
|
622
|
-
function normalizeBlockerState(state, lifecycle) {
|
|
623
|
-
if (!state) {
|
|
624
|
-
return null;
|
|
625
|
-
}
|
|
626
|
-
const normalized = state.trim().toLowerCase();
|
|
627
|
-
if (normalized === "closed") {
|
|
628
|
-
return lifecycle.terminalStates[0] ?? state;
|
|
629
|
-
}
|
|
630
|
-
if (normalized === "open") {
|
|
631
|
-
return null;
|
|
632
|
-
}
|
|
633
|
-
return state;
|
|
634
|
-
}
|
|
635
|
-
function resolveRestUserApiUrl(apiUrl) {
|
|
636
|
-
const parsed = new URL(apiUrl ?? DEFAULT_API_URL);
|
|
637
|
-
const pathSegments = parsed.pathname.split("/").filter(Boolean);
|
|
638
|
-
if (pathSegments.at(-1) === "graphql") {
|
|
639
|
-
pathSegments.pop();
|
|
640
|
-
}
|
|
641
|
-
parsed.pathname = `/${pathSegments.join("/")}/user`.replace(/\/{2,}/g, "/");
|
|
642
|
-
parsed.search = "";
|
|
643
|
-
parsed.hash = "";
|
|
644
|
-
return parsed.toString();
|
|
645
|
-
}
|
|
646
|
-
function chunkValues(values, size) {
|
|
647
|
-
const chunks = [];
|
|
648
|
-
for (let index = 0; index < values.length; index += size) {
|
|
649
|
-
chunks.push(values.slice(index, index + size));
|
|
650
|
-
}
|
|
651
|
-
return chunks;
|
|
652
|
-
}
|
|
653
|
-
function buildRequestSignal(timeoutMs) {
|
|
654
|
-
return AbortSignal.timeout(resolveNetworkTimeoutMs(timeoutMs));
|
|
655
|
-
}
|
|
656
|
-
function resolveNetworkTimeoutMs(timeoutMs) {
|
|
657
|
-
if (timeoutMs !== void 0 && Number.isInteger(timeoutMs) && timeoutMs > 0) {
|
|
658
|
-
return timeoutMs;
|
|
659
|
-
}
|
|
660
|
-
return DEFAULT_NETWORK_TIMEOUT_MS;
|
|
661
|
-
}
|
|
662
|
-
async function executeGraphQLQuery(config, query, variables, fetchImpl) {
|
|
663
|
-
const result = await executeGraphQLQueryWithMetadata(
|
|
664
|
-
config,
|
|
665
|
-
query,
|
|
666
|
-
variables,
|
|
667
|
-
fetchImpl
|
|
668
|
-
);
|
|
669
|
-
return result.data;
|
|
670
|
-
}
|
|
671
|
-
async function executeGraphQLQueryWithMetadata(config, query, variables, fetchImpl) {
|
|
672
|
-
const tokenFingerprint = fingerprintToken(config.token);
|
|
673
|
-
await guardGraphQLRateLimit(tokenFingerprint);
|
|
674
|
-
const response = await fetchImpl(config.apiUrl ?? DEFAULT_API_URL, {
|
|
675
|
-
method: "POST",
|
|
676
|
-
headers: {
|
|
677
|
-
"content-type": "application/json",
|
|
678
|
-
authorization: `Bearer ${config.token}`
|
|
679
|
-
},
|
|
680
|
-
body: JSON.stringify({
|
|
681
|
-
query,
|
|
682
|
-
variables
|
|
683
|
-
}),
|
|
684
|
-
signal: buildRequestSignal(config.timeoutMs)
|
|
685
|
-
});
|
|
686
|
-
if (!response.ok) {
|
|
687
|
-
const details = await response.text();
|
|
688
|
-
throw new GitHubTrackerHttpError(
|
|
689
|
-
`GitHub GraphQL request failed with status ${response.status}`,
|
|
690
|
-
response.status,
|
|
691
|
-
details
|
|
692
|
-
);
|
|
693
|
-
}
|
|
694
|
-
const payload = await response.json();
|
|
695
|
-
if (payload.errors?.length) {
|
|
696
|
-
throw new GitHubTrackerQueryError(
|
|
697
|
-
payload.errors.map((error) => error.message).join("; ")
|
|
698
|
-
);
|
|
699
|
-
}
|
|
700
|
-
if (!payload.data) {
|
|
701
|
-
throw new GitHubTrackerQueryError(
|
|
702
|
-
"GitHub GraphQL response did not include data."
|
|
703
|
-
);
|
|
704
|
-
}
|
|
705
|
-
const data = payload.data;
|
|
706
|
-
const rateLimits = extractGitHubRateLimits(response.headers);
|
|
707
|
-
cachedGitHubGraphQLRateLimits.set(tokenFingerprint, rateLimits);
|
|
708
|
-
return {
|
|
709
|
-
data,
|
|
710
|
-
rateLimits
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
async function guardGraphQLRateLimit(tokenFingerprint) {
|
|
714
|
-
const rateLimit = cachedGitHubGraphQLRateLimits.get(tokenFingerprint) ?? null;
|
|
715
|
-
if (!rateLimit) {
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
const remaining = rateLimit.remaining;
|
|
719
|
-
if (remaining === null || remaining > RATE_LIMIT_THRESHOLD) {
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
const resetAtMs = parseTimestampMs(rateLimit.resetAt);
|
|
723
|
-
if (resetAtMs === null) {
|
|
724
|
-
throw new GitHubTrackerError("Rate limit near exhaustion");
|
|
725
|
-
}
|
|
726
|
-
const waitMs = Math.max(0, resetAtMs - Date.now());
|
|
727
|
-
if (waitMs > MAX_RATE_LIMIT_WAIT_MS) {
|
|
728
|
-
throw new GitHubTrackerError("Rate limit near exhaustion");
|
|
729
|
-
}
|
|
730
|
-
cachedGitHubGraphQLRateLimits.delete(tokenFingerprint);
|
|
731
|
-
if (waitMs > 0) {
|
|
732
|
-
await sleep(waitMs);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
function fingerprintToken(token) {
|
|
736
|
-
return createHash("sha256").update(token).digest("hex");
|
|
737
|
-
}
|
|
738
|
-
function extractGitHubRateLimits(headers) {
|
|
739
|
-
if (!headers || typeof headers.get !== "function") {
|
|
740
|
-
return null;
|
|
741
|
-
}
|
|
742
|
-
const limit = parseIntegerHeader(headers.get("x-ratelimit-limit"));
|
|
743
|
-
const remaining = parseIntegerHeader(headers.get("x-ratelimit-remaining"));
|
|
744
|
-
const used = parseIntegerHeader(headers.get("x-ratelimit-used"));
|
|
745
|
-
const reset = parseIntegerHeader(headers.get("x-ratelimit-reset"));
|
|
746
|
-
const resource = headers.get("x-ratelimit-resource");
|
|
747
|
-
if (limit === null && remaining === null && used === null && reset === null && resource === null) {
|
|
748
|
-
return null;
|
|
749
|
-
}
|
|
750
|
-
return {
|
|
751
|
-
source: "github",
|
|
752
|
-
limit,
|
|
753
|
-
remaining,
|
|
754
|
-
used,
|
|
755
|
-
reset,
|
|
756
|
-
resetAt: reset === null ? null : new Date(reset * 1e3).toISOString(),
|
|
757
|
-
resource
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
function parseIntegerHeader(value) {
|
|
761
|
-
if (value === null) {
|
|
762
|
-
return null;
|
|
763
|
-
}
|
|
764
|
-
const parsed = Number.parseInt(value, 10);
|
|
765
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
766
|
-
}
|
|
767
|
-
function parseTimestampMs(value) {
|
|
768
|
-
if (!value) {
|
|
769
|
-
return null;
|
|
770
|
-
}
|
|
771
|
-
const timestampMs = Date.parse(value);
|
|
772
|
-
return Number.isFinite(timestampMs) ? timestampMs : null;
|
|
773
|
-
}
|
|
774
|
-
function sleep(ms) {
|
|
775
|
-
return new Promise((resolve) => {
|
|
776
|
-
setTimeout(resolve, ms);
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
var PROJECT_ITEMS_QUERY = `
|
|
780
|
-
query ProjectItems($projectId: ID!, $cursor: String, $pageSize: Int!) {
|
|
781
|
-
node(id: $projectId) {
|
|
782
|
-
__typename
|
|
783
|
-
... on ProjectV2 {
|
|
784
|
-
items(first: $pageSize, after: $cursor) {
|
|
785
|
-
nodes {
|
|
786
|
-
id
|
|
787
|
-
updatedAt
|
|
788
|
-
fieldValues(first: 20) {
|
|
789
|
-
nodes {
|
|
790
|
-
__typename
|
|
791
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
792
|
-
name
|
|
793
|
-
optionId
|
|
794
|
-
field {
|
|
795
|
-
... on ProjectV2SingleSelectField {
|
|
796
|
-
name
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
... on ProjectV2ItemFieldTextValue {
|
|
801
|
-
text
|
|
802
|
-
field {
|
|
803
|
-
... on ProjectV2FieldCommon {
|
|
804
|
-
name
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
content {
|
|
811
|
-
__typename
|
|
812
|
-
... on Issue {
|
|
813
|
-
id
|
|
814
|
-
number
|
|
815
|
-
title
|
|
816
|
-
body
|
|
817
|
-
url
|
|
818
|
-
createdAt
|
|
819
|
-
updatedAt
|
|
820
|
-
labels(first: 20) {
|
|
821
|
-
nodes {
|
|
822
|
-
name
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
assignees(first: 20) {
|
|
826
|
-
nodes {
|
|
827
|
-
login
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
repository {
|
|
831
|
-
name
|
|
832
|
-
url
|
|
833
|
-
owner {
|
|
834
|
-
login
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
blockedBy(first: 100) {
|
|
838
|
-
nodes {
|
|
839
|
-
id
|
|
840
|
-
number
|
|
841
|
-
state
|
|
842
|
-
repository {
|
|
843
|
-
name
|
|
844
|
-
owner {
|
|
845
|
-
login
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
}
|
|
850
|
-
closedByPullRequestsReferences(first: 20) {
|
|
851
|
-
nodes {
|
|
852
|
-
...PullRequestMetadata
|
|
853
|
-
}
|
|
854
|
-
pageInfo {
|
|
855
|
-
hasNextPage
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
... on PullRequest {
|
|
860
|
-
...PullRequestMetadata
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
pageInfo {
|
|
865
|
-
endCursor
|
|
866
|
-
hasNextPage
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
fragment PullRequestMetadata on PullRequest {
|
|
874
|
-
id
|
|
875
|
-
number
|
|
876
|
-
title
|
|
877
|
-
body
|
|
878
|
-
url
|
|
879
|
-
state
|
|
880
|
-
isDraft
|
|
881
|
-
merged
|
|
882
|
-
headRefName
|
|
883
|
-
baseRefName
|
|
884
|
-
headRepository {
|
|
885
|
-
name
|
|
886
|
-
url
|
|
887
|
-
owner {
|
|
888
|
-
login
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
repository {
|
|
892
|
-
name
|
|
893
|
-
url
|
|
894
|
-
owner {
|
|
895
|
-
login
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
labels(first: 20) {
|
|
899
|
-
nodes {
|
|
900
|
-
name
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
assignees(first: 20) {
|
|
904
|
-
nodes {
|
|
905
|
-
login
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
createdAt
|
|
909
|
-
updatedAt
|
|
910
|
-
}
|
|
911
|
-
`;
|
|
912
|
-
var PROJECT_FIELDS_QUERY = `
|
|
913
|
-
query ProjectFields($projectId: ID!) {
|
|
914
|
-
node(id: $projectId) {
|
|
915
|
-
__typename
|
|
916
|
-
... on ProjectV2 {
|
|
917
|
-
fields(first: 100) {
|
|
918
|
-
nodes {
|
|
919
|
-
__typename
|
|
920
|
-
... on ProjectV2SingleSelectField {
|
|
921
|
-
name
|
|
922
|
-
options {
|
|
923
|
-
id
|
|
924
|
-
name
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
}
|
|
932
|
-
`;
|
|
933
|
-
var ISSUE_STATES_BY_IDS_QUERY = `
|
|
934
|
-
query IssueStatesByIds($issueIds: [ID!]!) {
|
|
935
|
-
nodes(ids: $issueIds) {
|
|
936
|
-
__typename
|
|
937
|
-
... on Issue {
|
|
938
|
-
id
|
|
939
|
-
number
|
|
940
|
-
url
|
|
941
|
-
updatedAt
|
|
942
|
-
repository {
|
|
943
|
-
name
|
|
944
|
-
url
|
|
945
|
-
owner {
|
|
946
|
-
login
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
projectItems(first: 100, includeArchived: false) {
|
|
950
|
-
nodes {
|
|
951
|
-
id
|
|
952
|
-
updatedAt
|
|
953
|
-
project {
|
|
954
|
-
id
|
|
955
|
-
}
|
|
956
|
-
fieldValues(first: 20) {
|
|
957
|
-
nodes {
|
|
958
|
-
__typename
|
|
959
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
960
|
-
name
|
|
961
|
-
optionId
|
|
962
|
-
field {
|
|
963
|
-
... on ProjectV2SingleSelectField {
|
|
964
|
-
name
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
... on ProjectV2ItemFieldTextValue {
|
|
969
|
-
text
|
|
970
|
-
field {
|
|
971
|
-
... on ProjectV2FieldCommon {
|
|
972
|
-
name
|
|
973
|
-
}
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
pageInfo {
|
|
980
|
-
endCursor
|
|
981
|
-
hasNextPage
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
... on PullRequest {
|
|
986
|
-
id
|
|
987
|
-
number
|
|
988
|
-
url
|
|
989
|
-
updatedAt
|
|
990
|
-
headRefName
|
|
991
|
-
repository {
|
|
992
|
-
name
|
|
993
|
-
url
|
|
994
|
-
owner {
|
|
995
|
-
login
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
projectItems(first: 100, includeArchived: false) {
|
|
999
|
-
nodes {
|
|
1000
|
-
id
|
|
1001
|
-
updatedAt
|
|
1002
|
-
project {
|
|
1003
|
-
id
|
|
1004
|
-
}
|
|
1005
|
-
fieldValues(first: 20) {
|
|
1006
|
-
nodes {
|
|
1007
|
-
__typename
|
|
1008
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1009
|
-
name
|
|
1010
|
-
optionId
|
|
1011
|
-
field {
|
|
1012
|
-
... on ProjectV2SingleSelectField {
|
|
1013
|
-
name
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
}
|
|
1017
|
-
... on ProjectV2ItemFieldTextValue {
|
|
1018
|
-
text
|
|
1019
|
-
field {
|
|
1020
|
-
... on ProjectV2FieldCommon {
|
|
1021
|
-
name
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
}
|
|
1028
|
-
pageInfo {
|
|
1029
|
-
endCursor
|
|
1030
|
-
hasNextPage
|
|
1031
|
-
}
|
|
1032
|
-
}
|
|
1033
|
-
}
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
`;
|
|
1037
|
-
var ISSUE_PROJECT_ITEMS_PAGE_QUERY = `
|
|
1038
|
-
query IssueProjectItemsPage($issueId: ID!, $cursor: String) {
|
|
1039
|
-
node(id: $issueId) {
|
|
1040
|
-
__typename
|
|
1041
|
-
... on Issue {
|
|
1042
|
-
id
|
|
1043
|
-
number
|
|
1044
|
-
url
|
|
1045
|
-
updatedAt
|
|
1046
|
-
repository {
|
|
1047
|
-
name
|
|
1048
|
-
url
|
|
1049
|
-
owner {
|
|
1050
|
-
login
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
projectItems(first: 100, after: $cursor, includeArchived: false) {
|
|
1054
|
-
nodes {
|
|
1055
|
-
id
|
|
1056
|
-
updatedAt
|
|
1057
|
-
project {
|
|
1058
|
-
id
|
|
1059
|
-
}
|
|
1060
|
-
fieldValues(first: 20) {
|
|
1061
|
-
nodes {
|
|
1062
|
-
__typename
|
|
1063
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1064
|
-
name
|
|
1065
|
-
optionId
|
|
1066
|
-
field {
|
|
1067
|
-
... on ProjectV2SingleSelectField {
|
|
1068
|
-
name
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
... on ProjectV2ItemFieldTextValue {
|
|
1073
|
-
text
|
|
1074
|
-
field {
|
|
1075
|
-
... on ProjectV2FieldCommon {
|
|
1076
|
-
name
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
pageInfo {
|
|
1084
|
-
endCursor
|
|
1085
|
-
hasNextPage
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1089
|
-
... on PullRequest {
|
|
1090
|
-
id
|
|
1091
|
-
number
|
|
1092
|
-
url
|
|
1093
|
-
updatedAt
|
|
1094
|
-
headRefName
|
|
1095
|
-
repository {
|
|
1096
|
-
name
|
|
1097
|
-
url
|
|
1098
|
-
owner {
|
|
1099
|
-
login
|
|
1100
|
-
}
|
|
1101
|
-
}
|
|
1102
|
-
projectItems(first: 100, after: $cursor, includeArchived: false) {
|
|
1103
|
-
nodes {
|
|
1104
|
-
id
|
|
1105
|
-
updatedAt
|
|
1106
|
-
project {
|
|
1107
|
-
id
|
|
1108
|
-
}
|
|
1109
|
-
fieldValues(first: 20) {
|
|
1110
|
-
nodes {
|
|
1111
|
-
__typename
|
|
1112
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1113
|
-
name
|
|
1114
|
-
optionId
|
|
1115
|
-
field {
|
|
1116
|
-
... on ProjectV2SingleSelectField {
|
|
1117
|
-
name
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
... on ProjectV2ItemFieldTextValue {
|
|
1122
|
-
text
|
|
1123
|
-
field {
|
|
1124
|
-
... on ProjectV2FieldCommon {
|
|
1125
|
-
name
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
pageInfo {
|
|
1133
|
-
endCursor
|
|
1134
|
-
hasNextPage
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
`;
|
|
1141
|
-
var ISSUE_COMMENTS_BY_ID_QUERY = `
|
|
1142
|
-
query IssueCommentsById($issueId: ID!, $cursor: String) {
|
|
1143
|
-
node(id: $issueId) {
|
|
1144
|
-
__typename
|
|
1145
|
-
... on Issue {
|
|
1146
|
-
comments(first: 100, after: $cursor) {
|
|
1147
|
-
nodes {
|
|
1148
|
-
id
|
|
1149
|
-
body
|
|
1150
|
-
}
|
|
1151
|
-
pageInfo {
|
|
1152
|
-
endCursor
|
|
1153
|
-
hasNextPage
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
`;
|
|
1160
|
-
var ADD_ISSUE_COMMENT_MUTATION = `
|
|
1161
|
-
mutation AddIssueComment($subjectId: ID!, $body: String!) {
|
|
1162
|
-
addComment(input: { subjectId: $subjectId, body: $body }) {
|
|
1163
|
-
commentEdge {
|
|
1164
|
-
node {
|
|
1165
|
-
id
|
|
1166
|
-
body
|
|
1167
|
-
}
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
`;
|
|
1172
|
-
var UPDATE_ISSUE_COMMENT_MUTATION = `
|
|
1173
|
-
mutation UpdateIssueComment($commentId: ID!, $body: String!) {
|
|
1174
|
-
updateIssueComment(input: { id: $commentId, body: $body }) {
|
|
1175
|
-
issueComment {
|
|
1176
|
-
id
|
|
1177
|
-
body
|
|
1178
|
-
}
|
|
1179
|
-
}
|
|
1180
|
-
}
|
|
1181
|
-
`;
|
|
1182
|
-
var REPOSITORY_ISSUE_QUERY = `
|
|
1183
|
-
query RepositoryIssue(
|
|
1184
|
-
$owner: String!
|
|
1185
|
-
$name: String!
|
|
1186
|
-
$issueNumber: Int!
|
|
1187
|
-
) {
|
|
1188
|
-
repository(owner: $owner, name: $name) {
|
|
1189
|
-
issue(number: $issueNumber) {
|
|
1190
|
-
__typename
|
|
1191
|
-
id
|
|
1192
|
-
number
|
|
1193
|
-
title
|
|
1194
|
-
body
|
|
1195
|
-
url
|
|
1196
|
-
createdAt
|
|
1197
|
-
updatedAt
|
|
1198
|
-
labels(first: 20) {
|
|
1199
|
-
nodes {
|
|
1200
|
-
name
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
assignees(first: 20) {
|
|
1204
|
-
nodes {
|
|
1205
|
-
login
|
|
1206
|
-
}
|
|
1207
|
-
}
|
|
1208
|
-
repository {
|
|
1209
|
-
name
|
|
1210
|
-
url
|
|
1211
|
-
owner {
|
|
1212
|
-
login
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
blockedBy(first: 100) {
|
|
1216
|
-
nodes {
|
|
1217
|
-
id
|
|
1218
|
-
number
|
|
1219
|
-
state
|
|
1220
|
-
repository {
|
|
1221
|
-
name
|
|
1222
|
-
owner {
|
|
1223
|
-
login
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
|
-
closedByPullRequestsReferences(first: 20) {
|
|
1229
|
-
nodes {
|
|
1230
|
-
...RepositoryIssuePullRequestMetadata
|
|
1231
|
-
}
|
|
1232
|
-
pageInfo {
|
|
1233
|
-
hasNextPage
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
projectItems(first: 20, includeArchived: false) {
|
|
1237
|
-
nodes {
|
|
1238
|
-
id
|
|
1239
|
-
updatedAt
|
|
1240
|
-
project {
|
|
1241
|
-
id
|
|
1242
|
-
}
|
|
1243
|
-
fieldValues(first: 20) {
|
|
1244
|
-
nodes {
|
|
1245
|
-
__typename
|
|
1246
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1247
|
-
name
|
|
1248
|
-
optionId
|
|
1249
|
-
field {
|
|
1250
|
-
... on ProjectV2SingleSelectField {
|
|
1251
|
-
name
|
|
1252
|
-
}
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
... on ProjectV2ItemFieldTextValue {
|
|
1256
|
-
text
|
|
1257
|
-
field {
|
|
1258
|
-
... on ProjectV2FieldCommon {
|
|
1259
|
-
name
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
}
|
|
1266
|
-
pageInfo {
|
|
1267
|
-
endCursor
|
|
1268
|
-
hasNextPage
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
fragment RepositoryIssuePullRequestMetadata on PullRequest {
|
|
1276
|
-
id
|
|
1277
|
-
number
|
|
1278
|
-
title
|
|
1279
|
-
body
|
|
1280
|
-
url
|
|
1281
|
-
state
|
|
1282
|
-
isDraft
|
|
1283
|
-
merged
|
|
1284
|
-
headRefName
|
|
1285
|
-
baseRefName
|
|
1286
|
-
headRepository {
|
|
1287
|
-
name
|
|
1288
|
-
url
|
|
1289
|
-
owner {
|
|
1290
|
-
login
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
repository {
|
|
1294
|
-
name
|
|
1295
|
-
url
|
|
1296
|
-
owner {
|
|
1297
|
-
login
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
labels(first: 20) {
|
|
1301
|
-
nodes {
|
|
1302
|
-
name
|
|
1303
|
-
}
|
|
1304
|
-
}
|
|
1305
|
-
assignees(first: 20) {
|
|
1306
|
-
nodes {
|
|
1307
|
-
login
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
createdAt
|
|
1311
|
-
updatedAt
|
|
1312
|
-
}
|
|
1313
|
-
`;
|
|
1314
|
-
|
|
1315
|
-
// ../tracker-github/src/orchestrator-adapter.ts
|
|
1316
|
-
import { createHash as createHash2 } from "crypto";
|
|
1317
|
-
var githubProjectTrackerAdapter = {
|
|
1318
|
-
async listIssues(project, dependencies = {}) {
|
|
1319
|
-
return listProjectIssues(project, dependencies);
|
|
1320
|
-
},
|
|
1321
|
-
async listIssuesByStates(project, states, dependencies = {}) {
|
|
1322
|
-
if (states.length === 0) {
|
|
1323
|
-
return [];
|
|
1324
|
-
}
|
|
1325
|
-
const issues = await listProjectIssues(project, dependencies);
|
|
1326
|
-
const normalizedStates = new Set(
|
|
1327
|
-
states.map((state) => state.trim().toLowerCase())
|
|
1328
|
-
);
|
|
1329
|
-
return issues.filter(
|
|
1330
|
-
(issue) => normalizedStates.has(issue.state.trim().toLowerCase())
|
|
1331
|
-
);
|
|
1332
|
-
},
|
|
1333
|
-
async fetchIssueStatesByIds(project, issueIds, dependencies = {}) {
|
|
1334
|
-
if (issueIds.length === 0) {
|
|
1335
|
-
return [];
|
|
1336
|
-
}
|
|
1337
|
-
return fetchProjectIssueStatesByIds(project, issueIds, dependencies);
|
|
1338
|
-
},
|
|
1339
|
-
buildWorkerEnvironment(project) {
|
|
1340
|
-
return {
|
|
1341
|
-
GITHUB_PROJECT_ID: requireTrackerSetting(project.tracker, "projectId")
|
|
1342
|
-
};
|
|
1343
|
-
},
|
|
1344
|
-
reviveIssue(project, run) {
|
|
1345
|
-
return {
|
|
1346
|
-
id: run.issueId,
|
|
1347
|
-
identifier: run.issueIdentifier,
|
|
1348
|
-
number: parseIssueNumber(run.issueIdentifier),
|
|
1349
|
-
title: run.issueTitle ?? run.issueIdentifier,
|
|
1350
|
-
description: null,
|
|
1351
|
-
priority: null,
|
|
1352
|
-
state: run.issueState,
|
|
1353
|
-
branchName: null,
|
|
1354
|
-
url: null,
|
|
1355
|
-
labels: [],
|
|
1356
|
-
blockedBy: [],
|
|
1357
|
-
createdAt: null,
|
|
1358
|
-
updatedAt: null,
|
|
1359
|
-
repository: run.repository,
|
|
1360
|
-
tracker: {
|
|
1361
|
-
adapter: "github-project",
|
|
1362
|
-
bindingId: project.tracker.bindingId,
|
|
1363
|
-
itemId: run.issueId
|
|
1364
|
-
},
|
|
1365
|
-
metadata: {}
|
|
1366
|
-
};
|
|
1367
|
-
},
|
|
1368
|
-
async upsertIssueComment(project, issue, input, dependencies = {}) {
|
|
1369
|
-
const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
|
|
1370
|
-
return upsertGithubIssueComment(
|
|
1371
|
-
trackerConfig,
|
|
1372
|
-
issue.id,
|
|
1373
|
-
input,
|
|
1374
|
-
dependencies.fetchImpl
|
|
1375
|
-
);
|
|
1376
|
-
}
|
|
1377
|
-
};
|
|
1378
|
-
async function findGithubProjectIssue(project, identifier, dependencies = {}) {
|
|
1379
|
-
const parsed = parseIssueIdentifier(identifier);
|
|
1380
|
-
if (!parsed) {
|
|
1381
|
-
return null;
|
|
1382
|
-
}
|
|
1383
|
-
const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
|
|
1384
|
-
return fetchGithubProjectIssueByRepositoryAndNumber(
|
|
1385
|
-
trackerConfig,
|
|
1386
|
-
{ owner: parsed.owner, name: parsed.name },
|
|
1387
|
-
parsed.number,
|
|
1388
|
-
dependencies.fetchImpl
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
|
-
async function listProjectIssues(project, dependencies = {}) {
|
|
1392
|
-
const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
|
|
1393
|
-
const loadProjectIssues = () => fetchGithubProjectIssues(trackerConfig, dependencies.fetchImpl);
|
|
1394
|
-
return dependencies.projectItemsCache?.getOrLoad(
|
|
1395
|
-
buildProjectItemsCacheKey(trackerConfig, dependencies),
|
|
1396
|
-
loadProjectIssues
|
|
1397
|
-
) ?? loadProjectIssues();
|
|
1398
|
-
}
|
|
1399
|
-
async function fetchProjectIssueStatesByIds(project, issueIds, dependencies = {}) {
|
|
1400
|
-
const trackerConfig = resolveGitHubTrackerConfig(project, dependencies);
|
|
1401
|
-
return fetchGithubIssueStatesByIds(
|
|
1402
|
-
trackerConfig,
|
|
1403
|
-
[...issueIds],
|
|
1404
|
-
dependencies.fetchImpl
|
|
1405
|
-
);
|
|
1406
|
-
}
|
|
1407
|
-
function resolveGitHubTrackerConfig(project, dependencies = {}) {
|
|
1408
|
-
const token = dependencies.token ?? process.env.GITHUB_GRAPHQL_TOKEN;
|
|
1409
|
-
if (!token) {
|
|
1410
|
-
throw new Error(
|
|
1411
|
-
"GITHUB_GRAPHQL_TOKEN environment variable is required. Run 'gh auth token' or set the variable."
|
|
1412
|
-
);
|
|
1413
|
-
}
|
|
1414
|
-
const githubProjectId = requireTrackerSetting(project.tracker, "projectId");
|
|
1415
|
-
return {
|
|
1416
|
-
projectId: githubProjectId,
|
|
1417
|
-
token,
|
|
1418
|
-
apiUrl: project.tracker.apiUrl,
|
|
1419
|
-
assignedOnly: readBooleanTrackerSetting(project.tracker, "assignedOnly"),
|
|
1420
|
-
priorityFieldName: readOptionalStringTrackerSetting(
|
|
1421
|
-
project.tracker,
|
|
1422
|
-
"priorityFieldName"
|
|
1423
|
-
),
|
|
1424
|
-
timeoutMs: readNumberTrackerSetting(project.tracker, "timeoutMs")
|
|
1425
|
-
};
|
|
1426
|
-
}
|
|
1427
|
-
function buildProjectItemsCacheKey(config, _dependencies) {
|
|
1428
|
-
return JSON.stringify({
|
|
1429
|
-
adapter: "github-project",
|
|
1430
|
-
apiUrl: config.apiUrl,
|
|
1431
|
-
assignedOnly: config.assignedOnly ?? false,
|
|
1432
|
-
priorityFieldName: config.priorityFieldName ?? null,
|
|
1433
|
-
projectId: config.projectId,
|
|
1434
|
-
timeoutMs: config.timeoutMs,
|
|
1435
|
-
tokenFingerprint: hashToken(config.token)
|
|
1436
|
-
});
|
|
1437
|
-
}
|
|
1438
|
-
function hashToken(token) {
|
|
1439
|
-
if (!token) {
|
|
1440
|
-
return null;
|
|
1441
|
-
}
|
|
1442
|
-
return createHash2("sha256").update(token).digest("hex");
|
|
1443
|
-
}
|
|
1444
|
-
var trackerAdapters = {
|
|
1445
|
-
"github-project": githubProjectTrackerAdapter
|
|
1446
|
-
};
|
|
1447
|
-
function resolveTrackerAdapter(tracker) {
|
|
1448
|
-
const adapter = trackerAdapters[tracker.adapter];
|
|
1449
|
-
if (!adapter) {
|
|
1450
|
-
throw new Error(`Unsupported tracker adapter: ${tracker.adapter}`);
|
|
1451
|
-
}
|
|
1452
|
-
return adapter;
|
|
1453
|
-
}
|
|
1454
|
-
function requireTrackerSetting(tracker, key) {
|
|
1455
|
-
const value = tracker.settings?.[key];
|
|
1456
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
1457
|
-
throw new Error(
|
|
1458
|
-
`Tracker adapter "${tracker.adapter}" requires the "${key}" setting.`
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
return value;
|
|
1462
|
-
}
|
|
1463
|
-
function readBooleanTrackerSetting(tracker, key) {
|
|
1464
|
-
const value = tracker.settings?.[key];
|
|
1465
|
-
return value === true || value === "true";
|
|
1466
|
-
}
|
|
1467
|
-
function readNumberTrackerSetting(tracker, key) {
|
|
1468
|
-
const value = tracker.settings?.[key];
|
|
1469
|
-
if (value === void 0) {
|
|
1470
|
-
return void 0;
|
|
1471
|
-
}
|
|
1472
|
-
if (typeof value === "number" && Number.isInteger(value) && value > 0) {
|
|
1473
|
-
return value;
|
|
1474
|
-
}
|
|
1475
|
-
if (typeof value === "string") {
|
|
1476
|
-
const parsed = Number(value);
|
|
1477
|
-
if (Number.isInteger(parsed) && parsed > 0) {
|
|
1478
|
-
return parsed;
|
|
1479
|
-
}
|
|
1480
|
-
}
|
|
1481
|
-
throw new Error(
|
|
1482
|
-
`Tracker adapter "${tracker.adapter}" requires the "${key}" setting to be a positive integer when provided.`
|
|
1483
|
-
);
|
|
1484
|
-
}
|
|
1485
|
-
function readOptionalStringTrackerSetting(tracker, key) {
|
|
1486
|
-
const value = tracker.settings?.[key];
|
|
1487
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
1488
|
-
}
|
|
1489
|
-
function parseIssueNumber(identifier) {
|
|
1490
|
-
const match = identifier.match(/#(\d+)$/);
|
|
1491
|
-
return match ? Number.parseInt(match[1] ?? "0", 10) : 0;
|
|
1492
|
-
}
|
|
1493
|
-
function parseIssueIdentifier(identifier) {
|
|
1494
|
-
const match = identifier.match(/^([^/\s#]+)\/([^/\s#]+)#(\d+)$/);
|
|
1495
|
-
if (!match) {
|
|
1496
|
-
return null;
|
|
1497
|
-
}
|
|
1498
|
-
return {
|
|
1499
|
-
owner: match[1],
|
|
1500
|
-
name: match[2],
|
|
1501
|
-
number: Number.parseInt(match[3], 10)
|
|
1502
|
-
};
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
// src/project-selection.ts
|
|
1506
|
-
import * as p from "@clack/prompts";
|
|
1507
|
-
function isInteractiveTerminal() {
|
|
1508
|
-
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
1509
|
-
}
|
|
1510
|
-
function explicitProjectRequiredMessage() {
|
|
1511
|
-
return "Multiple repository runtime configs are present. Run 'gh-symphony repo init' from the target repository to refresh the cwd runtime.\n";
|
|
1512
|
-
}
|
|
1513
|
-
async function inspectManagedProjectSelection(input) {
|
|
1514
|
-
if (input.requestedProjectId) {
|
|
1515
|
-
const projectConfig = await loadProjectConfig(
|
|
1516
|
-
input.configDir,
|
|
1517
|
-
input.requestedProjectId
|
|
1518
|
-
);
|
|
1519
|
-
if (!projectConfig) {
|
|
1520
|
-
return {
|
|
1521
|
-
kind: "requested_project_missing",
|
|
1522
|
-
projectId: input.requestedProjectId,
|
|
1523
|
-
message: `Project "${input.requestedProjectId}" is not configured. Run 'gh-symphony repo init' from the target repository.`
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
1526
|
-
return {
|
|
1527
|
-
kind: "resolved",
|
|
1528
|
-
projectId: input.requestedProjectId,
|
|
1529
|
-
projectConfig
|
|
1530
|
-
};
|
|
1531
|
-
}
|
|
1532
|
-
const global = await loadGlobalConfig(input.configDir);
|
|
1533
|
-
if (!global) {
|
|
1534
|
-
return {
|
|
1535
|
-
kind: "missing_global_config",
|
|
1536
|
-
message: "No repository runtime config found. Run 'gh-symphony repo init' first."
|
|
1537
|
-
};
|
|
1538
|
-
}
|
|
1539
|
-
const projectIds = global.projects ?? [];
|
|
1540
|
-
if (projectIds.length === 0) {
|
|
1541
|
-
return {
|
|
1542
|
-
kind: "no_projects",
|
|
1543
|
-
message: "No repository runtime config is configured. Run 'gh-symphony repo init' first."
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
|
-
if (projectIds.length > 1 && !isInteractiveTerminal()) {
|
|
1547
|
-
return {
|
|
1548
|
-
kind: "multiple_projects_require_selection",
|
|
1549
|
-
message: explicitProjectRequiredMessage().trimEnd()
|
|
1550
|
-
};
|
|
1551
|
-
}
|
|
1552
|
-
if (global.activeProject) {
|
|
1553
|
-
const projectConfig = await loadProjectConfig(
|
|
1554
|
-
input.configDir,
|
|
1555
|
-
global.activeProject
|
|
1556
|
-
);
|
|
1557
|
-
if (!projectConfig) {
|
|
1558
|
-
return {
|
|
1559
|
-
kind: "active_project_missing",
|
|
1560
|
-
projectId: global.activeProject,
|
|
1561
|
-
message: `Active project "${global.activeProject}" is configured in config.json but its project config is missing. Re-run 'gh-symphony repo init'.`
|
|
1562
|
-
};
|
|
1563
|
-
}
|
|
1564
|
-
return {
|
|
1565
|
-
kind: "resolved",
|
|
1566
|
-
projectId: global.activeProject,
|
|
1567
|
-
projectConfig
|
|
1568
|
-
};
|
|
1569
|
-
}
|
|
1570
|
-
if (projectIds.length === 1) {
|
|
1571
|
-
const projectId = projectIds[0];
|
|
1572
|
-
const projectConfig = await loadProjectConfig(input.configDir, projectId);
|
|
1573
|
-
if (!projectConfig) {
|
|
1574
|
-
return {
|
|
1575
|
-
kind: "configured_project_missing",
|
|
1576
|
-
projectId,
|
|
1577
|
-
message: `Configured project "${projectId}" is missing its project config file. Re-run 'gh-symphony repo init'.`
|
|
1578
|
-
};
|
|
1579
|
-
}
|
|
1580
|
-
return {
|
|
1581
|
-
kind: "resolved",
|
|
1582
|
-
projectId,
|
|
1583
|
-
projectConfig
|
|
1584
|
-
};
|
|
1585
|
-
}
|
|
1586
|
-
return {
|
|
1587
|
-
kind: "multiple_projects_require_selection",
|
|
1588
|
-
message: "Multiple repository runtime configs are present and no active project is set. Re-run 'gh-symphony repo init' from the target repository."
|
|
1589
|
-
};
|
|
1590
|
-
}
|
|
1591
|
-
async function resolveManagedProjectConfig(input) {
|
|
1592
|
-
if (input.requestedProjectId) {
|
|
1593
|
-
return loadProjectConfig(input.configDir, input.requestedProjectId);
|
|
1594
|
-
}
|
|
1595
|
-
const global = await loadGlobalConfig(input.configDir);
|
|
1596
|
-
const projectIds = global?.projects ?? [];
|
|
1597
|
-
if (projectIds.length === 0) {
|
|
1598
|
-
return null;
|
|
1599
|
-
}
|
|
1600
|
-
if (projectIds.length === 1) {
|
|
1601
|
-
return loadProjectConfig(input.configDir, projectIds[0]);
|
|
1602
|
-
}
|
|
1603
|
-
if (!isInteractiveTerminal()) {
|
|
1604
|
-
process.stderr.write(explicitProjectRequiredMessage());
|
|
1605
|
-
process.exitCode = 1;
|
|
1606
|
-
return null;
|
|
1607
|
-
}
|
|
1608
|
-
const projects = await Promise.all(
|
|
1609
|
-
projectIds.map(async (projectId) => ({
|
|
1610
|
-
projectId,
|
|
1611
|
-
config: await loadProjectConfig(input.configDir, projectId)
|
|
1612
|
-
}))
|
|
1613
|
-
);
|
|
1614
|
-
const selected = await p.select({
|
|
1615
|
-
message: "Select a project:",
|
|
1616
|
-
options: projects.map(({ projectId, config }) => ({
|
|
1617
|
-
value: projectId,
|
|
1618
|
-
label: config?.displayName ?? config?.slug ?? projectId,
|
|
1619
|
-
hint: projectId === global?.activeProject ? "current" : config && config.displayName && config.displayName !== projectId ? projectId : void 0
|
|
1620
|
-
})),
|
|
1621
|
-
maxItems: 10
|
|
1622
|
-
});
|
|
1623
|
-
if (p.isCancel(selected)) {
|
|
1624
|
-
p.cancel("Cancelled.");
|
|
1625
|
-
process.exitCode = 130;
|
|
1626
|
-
return null;
|
|
1627
|
-
}
|
|
1628
|
-
return loadProjectConfig(input.configDir, selected);
|
|
1629
|
-
}
|
|
1630
|
-
function handleMissingManagedProjectConfig() {
|
|
1631
|
-
if (process.exitCode) {
|
|
1632
|
-
return;
|
|
1633
|
-
}
|
|
1634
|
-
process.stderr.write(
|
|
1635
|
-
"No repository runtime config found. Run 'gh-symphony repo init' first.\n"
|
|
1636
|
-
);
|
|
1637
|
-
process.exitCode = 1;
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
export {
|
|
1641
|
-
fetchGithubProjectIssues,
|
|
1642
|
-
fetchGithubProjectIssueByRepositoryAndNumber,
|
|
1643
|
-
findGithubProjectIssue,
|
|
1644
|
-
resolveTrackerAdapter,
|
|
1645
|
-
inspectManagedProjectSelection,
|
|
1646
|
-
resolveManagedProjectConfig,
|
|
1647
|
-
handleMissingManagedProjectConfig
|
|
1648
|
-
};
|