github-issue-tower-defence-management 1.116.1 → 1.116.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/commit-lint.yml +1 -1
- package/.github/workflows/configs/commitlint.config.cjs +32 -0
- package/.github/workflows/umino-project.yml +9 -3
- package/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js +18 -6
- package/bin/adapter/repositories/ProcClaudeLiveSessionRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +101 -60
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/githubRateLimitRetry.js +77 -0
- package/bin/adapter/repositories/issue/githubRateLimitRetry.js.map +1 -0
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js +6 -6
- package/bin/domain/usecases/LiveSessionOauthTokenSelectUseCase.js.map +1 -1
- package/bin/domain/usecases/OauthTokenSelectUseCase.js +1 -1
- package/bin/domain/usecases/OauthTokenSelectUseCase.js.map +1 -1
- package/package.json +1 -1
- package/renovate.json +1 -0
- package/src/adapter/entry-points/handlers/LiveSessionOauthTokenSelectHandler.test.ts +9 -9
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.test.ts +69 -7
- package/src/adapter/repositories/ProcClaudeLiveSessionRepository.ts +19 -8
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.test.ts +237 -1
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +274 -209
- package/src/adapter/repositories/issue/githubRateLimitRetry.test.ts +167 -0
- package/src/adapter/repositories/issue/githubRateLimitRetry.ts +105 -0
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.test.ts +25 -3
- package/src/domain/usecases/LiveSessionOauthTokenSelectUseCase.ts +7 -7
- package/src/domain/usecases/OauthTokenSelectUseCase.test.ts +13 -4
- package/src/domain/usecases/OauthTokenSelectUseCase.ts +1 -1
- package/src/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.ts +1 -1
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts +1 -0
- package/types/adapter/repositories/ProcClaudeLiveSessionRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +6 -2
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts +10 -0
- package/types/adapter/repositories/issue/githubRateLimitRetry.d.ts.map +1 -0
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts +1 -1
- package/types/domain/usecases/OauthTokenSelectUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts +1 -1
- package/types/domain/usecases/adapter-interfaces/ClaudeLiveSessionRepository.d.ts.map +1 -1
|
@@ -22,6 +22,13 @@ import { BaseGitHubRepository } from '../BaseGitHubRepository';
|
|
|
22
22
|
import { normalizeFieldName } from '../utils';
|
|
23
23
|
import { LocalStorageRepository } from '../LocalStorageRepository';
|
|
24
24
|
import { Member } from '../../../domain/entities/Member';
|
|
25
|
+
import {
|
|
26
|
+
Sleep,
|
|
27
|
+
realSleep,
|
|
28
|
+
fetchWithGitHubRateLimitRetry,
|
|
29
|
+
computeRateLimitResetIso,
|
|
30
|
+
hasRateLimitSignals,
|
|
31
|
+
} from './githubRateLimitRetry';
|
|
25
32
|
|
|
26
33
|
type TimelineItem = {
|
|
27
34
|
__typename: string;
|
|
@@ -471,10 +478,15 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
471
478
|
>,
|
|
472
479
|
readonly localStorageRepository: LocalStorageRepository,
|
|
473
480
|
readonly ghToken: string = process.env.GH_TOKEN || 'dummy',
|
|
481
|
+
readonly sleep: Sleep = realSleep,
|
|
474
482
|
) {
|
|
475
483
|
super(localStorageRepository, ghToken);
|
|
476
484
|
}
|
|
477
485
|
|
|
486
|
+
private fetchWithRateLimitRetry = (
|
|
487
|
+
request: () => Promise<Response>,
|
|
488
|
+
): Promise<Response> => fetchWithGitHubRateLimitRetry(request, this.sleep);
|
|
489
|
+
|
|
478
490
|
updateStatus: (
|
|
479
491
|
project: Project,
|
|
480
492
|
issue: Issue,
|
|
@@ -1106,17 +1118,19 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1106
1118
|
let hasNextPage = true;
|
|
1107
1119
|
|
|
1108
1120
|
while (hasNextPage) {
|
|
1109
|
-
const response = await
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1121
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1122
|
+
fetch('https://api.github.com/graphql', {
|
|
1123
|
+
method: 'POST',
|
|
1124
|
+
headers: {
|
|
1125
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1126
|
+
'Content-Type': 'application/json',
|
|
1127
|
+
},
|
|
1128
|
+
body: JSON.stringify({
|
|
1129
|
+
query,
|
|
1130
|
+
variables: { owner, repo, issueNumber, after },
|
|
1131
|
+
}),
|
|
1118
1132
|
}),
|
|
1119
|
-
|
|
1133
|
+
);
|
|
1120
1134
|
|
|
1121
1135
|
if (!response.ok) {
|
|
1122
1136
|
throw new Error(
|
|
@@ -1285,17 +1299,19 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1285
1299
|
}
|
|
1286
1300
|
`;
|
|
1287
1301
|
|
|
1288
|
-
const response = await
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1302
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1303
|
+
fetch('https://api.github.com/graphql', {
|
|
1304
|
+
method: 'POST',
|
|
1305
|
+
headers: {
|
|
1306
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1307
|
+
'Content-Type': 'application/json',
|
|
1308
|
+
},
|
|
1309
|
+
body: JSON.stringify({
|
|
1310
|
+
query,
|
|
1311
|
+
variables: { owner, repo, prNumber },
|
|
1312
|
+
}),
|
|
1297
1313
|
}),
|
|
1298
|
-
|
|
1314
|
+
);
|
|
1299
1315
|
|
|
1300
1316
|
if (!response.ok) {
|
|
1301
1317
|
throw new Error(
|
|
@@ -1322,19 +1338,22 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1322
1338
|
|
|
1323
1339
|
closePullRequest = async (prUrl: string): Promise<void> => {
|
|
1324
1340
|
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
1325
|
-
const response = await
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1341
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1342
|
+
fetch(
|
|
1343
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
|
|
1344
|
+
{
|
|
1345
|
+
method: 'PATCH',
|
|
1346
|
+
headers: {
|
|
1347
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1348
|
+
'Content-Type': 'application/json',
|
|
1349
|
+
},
|
|
1350
|
+
body: JSON.stringify({ state: 'closed' }),
|
|
1332
1351
|
},
|
|
1333
|
-
|
|
1334
|
-
},
|
|
1352
|
+
),
|
|
1335
1353
|
);
|
|
1336
1354
|
if (!response.ok) {
|
|
1337
|
-
|
|
1355
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1356
|
+
throw new Error(`Failed to close PR ${prUrl}: ${reason}`);
|
|
1338
1357
|
}
|
|
1339
1358
|
};
|
|
1340
1359
|
|
|
@@ -1345,21 +1364,22 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1345
1364
|
const { owner, repo, issueNumber } = this.parseIssueUrl(issueUrl);
|
|
1346
1365
|
const ownerSegment = encodeURIComponent(owner);
|
|
1347
1366
|
const repoSegment = encodeURIComponent(repo);
|
|
1348
|
-
const response = await
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1367
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1368
|
+
fetch(
|
|
1369
|
+
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/issues/${issueNumber}`,
|
|
1370
|
+
{
|
|
1371
|
+
method: 'PATCH',
|
|
1372
|
+
headers: {
|
|
1373
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1374
|
+
'Content-Type': 'application/json',
|
|
1375
|
+
},
|
|
1376
|
+
body: JSON.stringify({ state: 'closed', state_reason: stateReason }),
|
|
1355
1377
|
},
|
|
1356
|
-
|
|
1357
|
-
},
|
|
1378
|
+
),
|
|
1358
1379
|
);
|
|
1359
1380
|
if (!response.ok) {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
);
|
|
1381
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1382
|
+
throw new Error(`Failed to close issue ${issueUrl}: ${reason}`);
|
|
1363
1383
|
}
|
|
1364
1384
|
};
|
|
1365
1385
|
|
|
@@ -1370,19 +1390,22 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1370
1390
|
let page = 1;
|
|
1371
1391
|
let hasMore = true;
|
|
1372
1392
|
while (hasMore) {
|
|
1373
|
-
const response = await
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1393
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1394
|
+
fetch(
|
|
1395
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`,
|
|
1396
|
+
{
|
|
1397
|
+
method: 'GET',
|
|
1398
|
+
headers: {
|
|
1399
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1400
|
+
Accept: 'application/vnd.github+json',
|
|
1401
|
+
},
|
|
1380
1402
|
},
|
|
1381
|
-
|
|
1403
|
+
),
|
|
1382
1404
|
);
|
|
1383
1405
|
if (!response.ok) {
|
|
1406
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1384
1407
|
throw new Error(
|
|
1385
|
-
`Failed to fetch changed files for PR ${prUrl}:
|
|
1408
|
+
`Failed to fetch changed files for PR ${prUrl}: ${reason}`,
|
|
1386
1409
|
);
|
|
1387
1410
|
}
|
|
1388
1411
|
const body: unknown = await response.json();
|
|
@@ -1405,20 +1428,23 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1405
1428
|
|
|
1406
1429
|
approvePullRequest = async (prUrl: string): Promise<void> => {
|
|
1407
1430
|
const { owner, repo, issueNumber: prNumber } = this.parseIssueUrl(prUrl);
|
|
1408
|
-
const response = await
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1431
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1432
|
+
fetch(
|
|
1433
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`,
|
|
1434
|
+
{
|
|
1435
|
+
method: 'POST',
|
|
1436
|
+
headers: {
|
|
1437
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1438
|
+
'Content-Type': 'application/json',
|
|
1439
|
+
Accept: 'application/vnd.github+json',
|
|
1440
|
+
},
|
|
1441
|
+
body: JSON.stringify({ event: 'APPROVE' }),
|
|
1416
1442
|
},
|
|
1417
|
-
|
|
1418
|
-
},
|
|
1443
|
+
),
|
|
1419
1444
|
);
|
|
1420
1445
|
if (!response.ok) {
|
|
1421
|
-
|
|
1446
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1447
|
+
throw new Error(`Failed to approve PR ${prUrl}: ${reason}`);
|
|
1422
1448
|
}
|
|
1423
1449
|
};
|
|
1424
1450
|
|
|
@@ -1442,22 +1468,23 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1442
1468
|
},
|
|
1443
1469
|
],
|
|
1444
1470
|
};
|
|
1445
|
-
const response = await
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1471
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1472
|
+
fetch(
|
|
1473
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/reviews`,
|
|
1474
|
+
{
|
|
1475
|
+
method: 'POST',
|
|
1476
|
+
headers: {
|
|
1477
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1478
|
+
'Content-Type': 'application/json',
|
|
1479
|
+
Accept: 'application/vnd.github+json',
|
|
1480
|
+
},
|
|
1481
|
+
body: JSON.stringify(reviewBody),
|
|
1453
1482
|
},
|
|
1454
|
-
|
|
1455
|
-
},
|
|
1483
|
+
),
|
|
1456
1484
|
);
|
|
1457
1485
|
if (!response.ok) {
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
);
|
|
1486
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1487
|
+
throw new Error(`Failed to request changes on PR ${prUrl}: ${reason}`);
|
|
1461
1488
|
}
|
|
1462
1489
|
};
|
|
1463
1490
|
|
|
@@ -1469,20 +1496,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1469
1496
|
): Promise<string> => {
|
|
1470
1497
|
const ownerSegment = encodeURIComponent(owner);
|
|
1471
1498
|
const repoSegment = encodeURIComponent(repo);
|
|
1472
|
-
const response = await
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1499
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1500
|
+
fetch(
|
|
1501
|
+
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}`,
|
|
1502
|
+
{
|
|
1503
|
+
method: 'GET',
|
|
1504
|
+
headers: {
|
|
1505
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1506
|
+
Accept: 'application/vnd.github+json',
|
|
1507
|
+
},
|
|
1479
1508
|
},
|
|
1480
|
-
|
|
1509
|
+
),
|
|
1481
1510
|
);
|
|
1482
1511
|
if (!response.ok) {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
);
|
|
1512
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1513
|
+
throw new Error(`Failed to fetch head commit for PR ${prUrl}: ${reason}`);
|
|
1486
1514
|
}
|
|
1487
1515
|
const body: unknown = await response.json();
|
|
1488
1516
|
if (
|
|
@@ -1513,52 +1541,57 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1513
1541
|
);
|
|
1514
1542
|
const ownerSegment = encodeURIComponent(owner);
|
|
1515
1543
|
const repoSegment = encodeURIComponent(repo);
|
|
1516
|
-
const response = await
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1544
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1545
|
+
fetch(
|
|
1546
|
+
`https://api.github.com/repos/${ownerSegment}/${repoSegment}/pulls/${prNumber}/comments`,
|
|
1547
|
+
{
|
|
1548
|
+
method: 'POST',
|
|
1549
|
+
headers: {
|
|
1550
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1551
|
+
'Content-Type': 'application/json',
|
|
1552
|
+
Accept: 'application/vnd.github+json',
|
|
1553
|
+
},
|
|
1554
|
+
body: JSON.stringify({
|
|
1555
|
+
body: commentBody,
|
|
1556
|
+
commit_id: commitId,
|
|
1557
|
+
path,
|
|
1558
|
+
line,
|
|
1559
|
+
side,
|
|
1560
|
+
}),
|
|
1524
1561
|
},
|
|
1525
|
-
|
|
1526
|
-
body: commentBody,
|
|
1527
|
-
commit_id: commitId,
|
|
1528
|
-
path,
|
|
1529
|
-
line,
|
|
1530
|
-
side,
|
|
1531
|
-
}),
|
|
1532
|
-
},
|
|
1562
|
+
),
|
|
1533
1563
|
);
|
|
1534
1564
|
if (!response.ok) {
|
|
1535
|
-
const reason = await this.
|
|
1565
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1536
1566
|
throw new Error(
|
|
1537
1567
|
`Failed to create review comment on PR ${prUrl}: ${reason}`,
|
|
1538
1568
|
);
|
|
1539
1569
|
}
|
|
1540
1570
|
};
|
|
1541
1571
|
|
|
1542
|
-
private
|
|
1572
|
+
private readGitHubErrorReason = async (
|
|
1543
1573
|
response: Response,
|
|
1544
|
-
): Promise<string> => {
|
|
1545
|
-
const fallback = `HTTP ${response.status}`;
|
|
1574
|
+
): Promise<string | null> => {
|
|
1546
1575
|
let parsed: unknown;
|
|
1547
1576
|
try {
|
|
1548
1577
|
parsed = await response.json();
|
|
1549
1578
|
} catch {
|
|
1550
|
-
return
|
|
1579
|
+
return null;
|
|
1551
1580
|
}
|
|
1552
1581
|
if (!isRecord(parsed) || typeof parsed.message !== 'string') {
|
|
1553
|
-
return
|
|
1582
|
+
return null;
|
|
1554
1583
|
}
|
|
1555
1584
|
if (Array.isArray(parsed.errors) && parsed.errors.length > 0) {
|
|
1556
1585
|
const details = parsed.errors
|
|
1557
|
-
.map((error) =>
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1586
|
+
.map((error) => {
|
|
1587
|
+
if (typeof error === 'string') {
|
|
1588
|
+
return error;
|
|
1589
|
+
}
|
|
1590
|
+
if (isRecord(error) && typeof error.message === 'string') {
|
|
1591
|
+
return error.message;
|
|
1592
|
+
}
|
|
1593
|
+
return '';
|
|
1594
|
+
})
|
|
1562
1595
|
.filter((detail) => detail.length > 0)
|
|
1563
1596
|
.join('; ');
|
|
1564
1597
|
if (details.length > 0) {
|
|
@@ -1568,23 +1601,47 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1568
1601
|
return parsed.message;
|
|
1569
1602
|
};
|
|
1570
1603
|
|
|
1604
|
+
private formatGitHubErrorWithStatus = async (
|
|
1605
|
+
response: Response,
|
|
1606
|
+
): Promise<string> => {
|
|
1607
|
+
const status = `HTTP ${response.status}`;
|
|
1608
|
+
const bodyText = await response.clone().text();
|
|
1609
|
+
const reason = await this.readGitHubErrorReason(response);
|
|
1610
|
+
if (hasRateLimitSignals(response.status, response.headers, bodyText)) {
|
|
1611
|
+
const resetIso = computeRateLimitResetIso(response.headers);
|
|
1612
|
+
const resetSuffix = resetIso === null ? '' : ` (resets at ${resetIso})`;
|
|
1613
|
+
return `${status} GitHub rate limit exceeded, please retry shortly${resetSuffix}`;
|
|
1614
|
+
}
|
|
1615
|
+
if (response.status === 403) {
|
|
1616
|
+
const permissionSuffix = reason === null ? '' : ` ${reason}`;
|
|
1617
|
+
return `${status} permission denied, the token cannot perform this operation${permissionSuffix}`;
|
|
1618
|
+
}
|
|
1619
|
+
if (reason === null) {
|
|
1620
|
+
return status;
|
|
1621
|
+
}
|
|
1622
|
+
return `${status} ${reason}`;
|
|
1623
|
+
};
|
|
1624
|
+
|
|
1571
1625
|
deletePullRequestBranch = async (
|
|
1572
1626
|
prUrl: string,
|
|
1573
1627
|
branchName: string,
|
|
1574
1628
|
): Promise<void> => {
|
|
1575
1629
|
const { owner, repo } = this.parseIssueUrl(prUrl);
|
|
1576
|
-
const response = await
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1630
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1631
|
+
fetch(
|
|
1632
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/git/refs/heads/${encodeURIComponent(branchName)}`,
|
|
1633
|
+
{
|
|
1634
|
+
method: 'DELETE',
|
|
1635
|
+
headers: {
|
|
1636
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1637
|
+
},
|
|
1582
1638
|
},
|
|
1583
|
-
|
|
1639
|
+
),
|
|
1584
1640
|
);
|
|
1585
1641
|
if (!response.ok && response.status !== 422) {
|
|
1642
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1586
1643
|
throw new Error(
|
|
1587
|
-
`Failed to delete branch ${branchName} for PR ${prUrl}:
|
|
1644
|
+
`Failed to delete branch ${branchName} for PR ${prUrl}: ${reason}`,
|
|
1588
1645
|
);
|
|
1589
1646
|
}
|
|
1590
1647
|
};
|
|
@@ -1598,20 +1655,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1598
1655
|
|
|
1599
1656
|
getIssueOrPullRequestBody = async (url: string): Promise<string> => {
|
|
1600
1657
|
const { owner, repo, issueNumber } = this.parseIssueUrl(url);
|
|
1601
|
-
const response = await
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1658
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1659
|
+
fetch(
|
|
1660
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
|
|
1661
|
+
{
|
|
1662
|
+
method: 'GET',
|
|
1663
|
+
headers: {
|
|
1664
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1665
|
+
Accept: 'application/vnd.github+json',
|
|
1666
|
+
},
|
|
1608
1667
|
},
|
|
1609
|
-
|
|
1668
|
+
),
|
|
1610
1669
|
);
|
|
1611
1670
|
if (!response.ok) {
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
);
|
|
1671
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1672
|
+
throw new Error(`Failed to fetch body for ${url}: ${reason}`);
|
|
1615
1673
|
}
|
|
1616
1674
|
const body: unknown = await response.json();
|
|
1617
1675
|
if (!isIssueOrPullRequestBodyResponse(body)) {
|
|
@@ -1631,20 +1689,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1631
1689
|
let page = 1;
|
|
1632
1690
|
let hasMore = true;
|
|
1633
1691
|
while (hasMore) {
|
|
1634
|
-
const response = await
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1692
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1693
|
+
fetch(
|
|
1694
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments?per_page=${perPage}&page=${page}`,
|
|
1695
|
+
{
|
|
1696
|
+
method: 'GET',
|
|
1697
|
+
headers: {
|
|
1698
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1699
|
+
Accept: 'application/vnd.github+json',
|
|
1700
|
+
},
|
|
1641
1701
|
},
|
|
1642
|
-
|
|
1702
|
+
),
|
|
1643
1703
|
);
|
|
1644
1704
|
if (!response.ok) {
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
);
|
|
1705
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1706
|
+
throw new Error(`Failed to fetch comments for ${url}: ${reason}`);
|
|
1648
1707
|
}
|
|
1649
1708
|
const body: unknown = await response.json();
|
|
1650
1709
|
if (!isIssueCommentsResponse(body)) {
|
|
@@ -1680,20 +1739,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1680
1739
|
if (!isPr) {
|
|
1681
1740
|
return null;
|
|
1682
1741
|
}
|
|
1683
|
-
const detailResponse = await
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1742
|
+
const detailResponse = await this.fetchWithRateLimitRetry(() =>
|
|
1743
|
+
fetch(
|
|
1744
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
|
|
1745
|
+
{
|
|
1746
|
+
method: 'GET',
|
|
1747
|
+
headers: {
|
|
1748
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1749
|
+
Accept: 'application/vnd.github+json',
|
|
1750
|
+
},
|
|
1690
1751
|
},
|
|
1691
|
-
|
|
1752
|
+
),
|
|
1692
1753
|
);
|
|
1693
1754
|
if (!detailResponse.ok) {
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
);
|
|
1755
|
+
const reason = await this.formatGitHubErrorWithStatus(detailResponse);
|
|
1756
|
+
throw new Error(`Failed to fetch detail for PR ${prUrl}: ${reason}`);
|
|
1697
1757
|
}
|
|
1698
1758
|
const detailBody: unknown = await detailResponse.json();
|
|
1699
1759
|
if (!isPullRequestDetailResponse(detailBody)) {
|
|
@@ -1733,20 +1793,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1733
1793
|
let page = 1;
|
|
1734
1794
|
let hasMore = true;
|
|
1735
1795
|
while (hasMore) {
|
|
1736
|
-
const response = await
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1796
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1797
|
+
fetch(
|
|
1798
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/files?per_page=${perPage}&page=${page}`,
|
|
1799
|
+
{
|
|
1800
|
+
method: 'GET',
|
|
1801
|
+
headers: {
|
|
1802
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1803
|
+
Accept: 'application/vnd.github+json',
|
|
1804
|
+
},
|
|
1743
1805
|
},
|
|
1744
|
-
|
|
1806
|
+
),
|
|
1745
1807
|
);
|
|
1746
1808
|
if (!response.ok) {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
);
|
|
1809
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1810
|
+
throw new Error(`Failed to fetch files for PR ${prUrl}: ${reason}`);
|
|
1750
1811
|
}
|
|
1751
1812
|
const body: unknown = await response.json();
|
|
1752
1813
|
if (!isPullRequestDetailFilesResponse(body)) {
|
|
@@ -1789,20 +1850,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1789
1850
|
let page = 1;
|
|
1790
1851
|
let hasMore = true;
|
|
1791
1852
|
while (hasMore) {
|
|
1792
|
-
const response = await
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1853
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1854
|
+
fetch(
|
|
1855
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}/commits?per_page=${perPage}&page=${page}`,
|
|
1856
|
+
{
|
|
1857
|
+
method: 'GET',
|
|
1858
|
+
headers: {
|
|
1859
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1860
|
+
Accept: 'application/vnd.github+json',
|
|
1861
|
+
},
|
|
1799
1862
|
},
|
|
1800
|
-
|
|
1863
|
+
),
|
|
1801
1864
|
);
|
|
1802
1865
|
if (!response.ok) {
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
);
|
|
1866
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1867
|
+
throw new Error(`Failed to fetch commits for PR ${prUrl}: ${reason}`);
|
|
1806
1868
|
}
|
|
1807
1869
|
const body: unknown = await response.json();
|
|
1808
1870
|
if (!isPullRequestCommitsResponse(body)) {
|
|
@@ -1832,20 +1894,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1832
1894
|
): Promise<{ state: string; merged: boolean; isPullRequest: boolean }> => {
|
|
1833
1895
|
const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(url);
|
|
1834
1896
|
if (isPr) {
|
|
1835
|
-
const response = await
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1897
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1898
|
+
fetch(
|
|
1899
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${issueNumber}`,
|
|
1900
|
+
{
|
|
1901
|
+
method: 'GET',
|
|
1902
|
+
headers: {
|
|
1903
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1904
|
+
Accept: 'application/vnd.github+json',
|
|
1905
|
+
},
|
|
1842
1906
|
},
|
|
1843
|
-
|
|
1907
|
+
),
|
|
1844
1908
|
);
|
|
1845
1909
|
if (!response.ok) {
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
);
|
|
1910
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1911
|
+
throw new Error(`Failed to fetch state for ${url}: ${reason}`);
|
|
1849
1912
|
}
|
|
1850
1913
|
const body: unknown = await response.json();
|
|
1851
1914
|
if (!isPullRequestDetailResponse(body)) {
|
|
@@ -1855,20 +1918,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1855
1918
|
}
|
|
1856
1919
|
return { state: body.state, merged: body.merged, isPullRequest: true };
|
|
1857
1920
|
}
|
|
1858
|
-
const response = await
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1921
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1922
|
+
fetch(
|
|
1923
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}`,
|
|
1924
|
+
{
|
|
1925
|
+
method: 'GET',
|
|
1926
|
+
headers: {
|
|
1927
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1928
|
+
Accept: 'application/vnd.github+json',
|
|
1929
|
+
},
|
|
1865
1930
|
},
|
|
1866
|
-
|
|
1931
|
+
),
|
|
1867
1932
|
);
|
|
1868
1933
|
if (!response.ok) {
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
);
|
|
1934
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1935
|
+
throw new Error(`Failed to fetch state for ${url}: ${reason}`);
|
|
1872
1936
|
}
|
|
1873
1937
|
const body: unknown = await response.json();
|
|
1874
1938
|
if (!isIssueOrPullRequestStateResponse(body)) {
|
|
@@ -1897,20 +1961,21 @@ export class ApiV3CheerioRestIssueRepository
|
|
|
1897
1961
|
if (!isPr) {
|
|
1898
1962
|
return null;
|
|
1899
1963
|
}
|
|
1900
|
-
const response = await
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1964
|
+
const response = await this.fetchWithRateLimitRetry(() =>
|
|
1965
|
+
fetch(
|
|
1966
|
+
`https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls/${prNumber}`,
|
|
1967
|
+
{
|
|
1968
|
+
method: 'GET',
|
|
1969
|
+
headers: {
|
|
1970
|
+
Authorization: `Bearer ${this.ghToken}`,
|
|
1971
|
+
Accept: 'application/vnd.github+json',
|
|
1972
|
+
},
|
|
1907
1973
|
},
|
|
1908
|
-
|
|
1974
|
+
),
|
|
1909
1975
|
);
|
|
1910
1976
|
if (!response.ok) {
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
);
|
|
1977
|
+
const reason = await this.formatGitHubErrorWithStatus(response);
|
|
1978
|
+
throw new Error(`Failed to fetch summary for PR ${prUrl}: ${reason}`);
|
|
1914
1979
|
}
|
|
1915
1980
|
const body: unknown = await response.json();
|
|
1916
1981
|
if (!isPullRequestDetailResponse(body)) {
|