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