github-issue-tower-defence-management 1.49.1 → 1.49.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [1.49.2](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.49.1...v1.49.2) (2026-05-21)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **core:** resolve lint errors in getCommentsFromIssue REST implementation ([e876cdf](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/e876cdfd5830063e0bbed922913c3ddc4a7eff35))
7
+
1
8
  ## [1.49.1](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.49.0...v1.49.1) (2026-05-21)
2
9
 
3
10
 
@@ -1,8 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GitHubIssueCommentRepository = void 0;
4
- function isIssueCommentsResponse(value) {
5
- if (typeof value !== 'object' || value === null)
4
+ function isRestCommentPayloadArray(value) {
5
+ if (!Array.isArray(value))
6
6
  return false;
7
7
  return true;
8
8
  }
@@ -33,72 +33,35 @@ class GitHubIssueCommentRepository {
33
33
  };
34
34
  }
35
35
  async getCommentsFromIssue(issue) {
36
- const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(issue);
37
- const entityType = isPr ? 'pullRequest' : 'issue';
38
- const query = `
39
- query($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
40
- repository(owner: $owner, name: $repo) {
41
- ${entityType}(number: $issueNumber) {
42
- comments(first: 100, after: $after) {
43
- pageInfo {
44
- endCursor
45
- hasNextPage
46
- }
47
- nodes {
48
- author {
49
- login
50
- }
51
- body
52
- createdAt
53
- }
54
- }
55
- }
56
- }
57
- }
58
- `;
36
+ const { owner, repo, issueNumber } = this.parseIssueUrl(issue);
59
37
  const comments = [];
60
- let after = null;
38
+ let page = 1;
61
39
  let hasNextPage = true;
62
40
  while (hasNextPage) {
63
- const response = await fetch('https://api.github.com/graphql', {
64
- method: 'POST',
41
+ const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments?per_page=100&page=${page}`;
42
+ const response = await fetch(url, {
65
43
  headers: {
66
44
  Authorization: `Bearer ${this.token}`,
67
- 'Content-Type': 'application/json',
45
+ Accept: 'application/vnd.github+json',
68
46
  },
69
- body: JSON.stringify({
70
- query,
71
- variables: {
72
- owner,
73
- repo,
74
- issueNumber,
75
- after,
76
- },
77
- }),
78
47
  });
79
48
  if (!response.ok) {
80
- throw new Error(`Failed to fetch comments from GitHub GraphQL API: ${response.status} ${response.statusText}`);
49
+ throw new Error(`Failed to fetch comments from GitHub REST API: ${response.status} ${response.statusText}`);
81
50
  }
82
51
  const responseData = await response.json();
83
- if (!isIssueCommentsResponse(responseData)) {
84
- throw new Error('Unexpected response shape when fetching comments from GitHub GraphQL API');
85
- }
86
- const issueData = isPr
87
- ? responseData.data?.repository?.pullRequest
88
- : responseData.data?.repository?.issue;
89
- if (!issueData) {
90
- throw new Error(`${isPr ? 'Pull request' : 'Issue'} not found when fetching comments from GitHub GraphQL API`);
52
+ if (!isRestCommentPayloadArray(responseData)) {
53
+ throw new Error('Unexpected response shape when fetching comments from GitHub REST API');
91
54
  }
92
- const commentNodes = issueData.comments.nodes;
93
- for (const node of commentNodes) {
55
+ for (const payload of responseData) {
94
56
  comments.push({
95
- author: node.author?.login || '',
96
- content: node.body,
97
- createdAt: new Date(node.createdAt),
57
+ author: payload.user?.login ?? '',
58
+ content: payload.body,
59
+ createdAt: new Date(payload.created_at),
98
60
  });
99
61
  }
100
- hasNextPage = issueData.comments.pageInfo.hasNextPage;
101
- after = issueData.comments.pageInfo.endCursor;
62
+ const linkHeader = response.headers.get('Link') ?? '';
63
+ hasNextPage = linkHeader.includes('rel="next"');
64
+ page++;
102
65
  }
103
66
  return comments;
104
67
  }
@@ -1 +1 @@
1
- {"version":3,"file":"GitHubIssueCommentRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubIssueCommentRepository.ts"],"names":[],"mappings":";;;AAyDA,SAAS,uBAAuB,CAC9B,KAAc;IAEd,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,uBAAuB,CAC9B,KAAc;IAEd,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAa,4BAA4B;IACvC,YAA6B,KAAa;QAAb,UAAK,GAAL,KAAK,CAAQ;IAAG,CAAC;IAEtC,aAAa,CAAC,KAAY;QAMhC,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,KAAK,CAC9B,qDAAqD,CACtD,CAAC;QACF,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,6BAA6B,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,OAAO;YACL,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC;YAClB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;YACjB,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;YACtC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,MAAM;SAC7B,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,KAAY;QACrC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAErE,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC;QAClD,MAAM,KAAK,GAAG;;;YAGN,UAAU;;;;;;;;;;;;;;;;;KAiBjB,CAAC;QAEF,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,IAAI,KAAK,GAAkB,IAAI,CAAC;QAChC,IAAI,WAAW,GAAG,IAAI,CAAC;QAEvB,OAAO,WAAW,EAAE,CAAC;YACnB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gCAAgC,EAAE;gBAC7D,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;oBACrC,cAAc,EAAE,kBAAkB;iBACnC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK;oBACL,SAAS,EAAE;wBACT,KAAK;wBACL,IAAI;wBACJ,WAAW;wBACX,KAAK;qBACN;iBACF,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;gBACjB,MAAM,IAAI,KAAK,CACb,qDAAqD,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAC9F,CAAC;YACJ,CAAC;YAED,MAAM,YAAY,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACpD,IAAI,CAAC,uBAAuB,CAAC,YAAY,CAAC,EAAE,CAAC;gBAC3C,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAC;YACJ,CAAC;YAED,MAAM,SAAS,GAAG,IAAI;gBACpB,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW;gBAC5C,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC;YACzC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,2DAA2D,CAC9F,CAAC;YACJ,CAAC;YAED,MAAM,YAAY,GAAG,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC;YAC9C,KAAK,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;gBAChC,QAAQ,CAAC,IAAI,CAAC;oBACZ,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,EAAE;oBAChC,OAAO,EAAE,IAAI,CAAC,IAAI;oBAClB,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC;iBACpC,CAAC,CAAC;YACL,CAAC;YAED,WAAW,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC;YACtD,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC;QAChD,CAAC;QAED,OAAO,QAAQ,CAAC;IAClB,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,KAAY;QACvC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAErE,MAAM,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC;QAClD,MAAM,KAAK,GAAG;;;YAGN,UAAU;;;;;KAKjB,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gCAAgC,EAAE;YAC7D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;gBACrC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK;gBACL,SAAS,EAAE;oBACT,KAAK;oBACL,IAAI;oBACJ,WAAW;iBACZ;aACF,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,qDAAqD,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAC9F,CAAC;QACJ,CAAC;QAED,MAAM,YAAY,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,0EAA0E,CAC3E,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,IAAI;YAClB,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE;YAChD,CAAC,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,2DAA2D,CAC9F,CAAC;QACJ,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,KAAY,EAAE,cAAsB;QACtD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAEjD,MAAM,QAAQ,GAAG;;;;;;;;;;;;;KAahB,CAAC;QAEF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,gCAAgC,EAAE;YAC7D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE;gBACrC,cAAc,EAAE,kBAAkB;aACnC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,KAAK,EAAE,QAAQ;gBACf,SAAS,EAAE;oBACT,OAAO;oBACP,IAAI,EAAE,cAAc;iBACrB;aACF,CAAC;SACH,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CACb,oDAAoD,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAC7F,CAAC;QACJ,CAAC;QAED,MAAM,YAAY,GAAY,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACpD,IAAI,CAAC,uBAAuB,CAAC,YAAY,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;QACvE,CAAC;QAED,IAAI,YAAY,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,IAAI,KAAK,CACb,yCAAyC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,CAC/E,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAtND,oEAsNC"}
1
+ {"version":3,"file":"GitHubIssueCommentRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubIssueCommentRepository.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.49.1",
3
+ "version": "1.49.2",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -0,0 +1,173 @@
1
+ import { GitHubIssueCommentRepository } from './GitHubIssueCommentRepository';
2
+ import { Issue } from '../../domain/entities/Issue';
3
+
4
+ const buildIssue = (url: string): Issue => ({
5
+ url,
6
+ nameWithOwner: 'HiromiShikata/test-repo',
7
+ number: 123,
8
+ title: 'Test Issue',
9
+ state: 'OPEN',
10
+ status: null,
11
+ story: null,
12
+ nextActionDate: null,
13
+ nextActionHour: null,
14
+ estimationMinutes: null,
15
+ dependedIssueUrls: [],
16
+ completionDate50PercentConfidence: null,
17
+ assignees: [],
18
+ labels: [],
19
+ org: 'HiromiShikata',
20
+ repo: 'test-repo',
21
+ body: '',
22
+ itemId: 'item-1',
23
+ isPr: false,
24
+ isInProgress: false,
25
+ isClosed: false,
26
+ createdAt: new Date('2024-01-01T00:00:00Z'),
27
+ author: 'testuser',
28
+ });
29
+
30
+ const TEST_URL = 'https://github.com/HiromiShikata/test-repo/issues/123';
31
+ const EXPECTED_REST_URL =
32
+ 'https://api.github.com/repos/HiromiShikata/test-repo/issues/123/comments?per_page=100&page=1';
33
+
34
+ describe('GitHubIssueCommentRepository', () => {
35
+ let repository: GitHubIssueCommentRepository;
36
+
37
+ beforeEach(() => {
38
+ jest.restoreAllMocks();
39
+ repository = new GitHubIssueCommentRepository('test-token');
40
+ });
41
+
42
+ describe('getCommentsFromIssue', () => {
43
+ it('fetches single page with correct REST endpoint URL and headers, and maps comments correctly', async () => {
44
+ const commentPayloads = [
45
+ {
46
+ user: { login: 'testuser' },
47
+ body: 'Comment body',
48
+ created_at: '2024-01-01T00:00:00Z',
49
+ },
50
+ ];
51
+ const fetchSpy = jest
52
+ .spyOn(global, 'fetch')
53
+ .mockResolvedValue(
54
+ new Response(JSON.stringify(commentPayloads), { status: 200 }),
55
+ );
56
+
57
+ const result = await repository.getCommentsFromIssue(
58
+ buildIssue(TEST_URL),
59
+ );
60
+
61
+ expect(fetchSpy).toHaveBeenCalledTimes(1);
62
+ expect(fetchSpy).toHaveBeenCalledWith(EXPECTED_REST_URL, {
63
+ headers: {
64
+ Authorization: 'Bearer test-token',
65
+ Accept: 'application/vnd.github+json',
66
+ },
67
+ });
68
+ expect(result).toEqual([
69
+ {
70
+ author: 'testuser',
71
+ content: 'Comment body',
72
+ createdAt: new Date('2024-01-01T00:00:00Z'),
73
+ },
74
+ ]);
75
+ });
76
+
77
+ it('fetches two pages when first response contains rel="next" Link header', async () => {
78
+ const page1Payloads = [
79
+ {
80
+ user: { login: 'user1' },
81
+ body: 'Page 1 comment',
82
+ created_at: '2024-01-01T00:00:00Z',
83
+ },
84
+ ];
85
+ const page2Payloads = [
86
+ {
87
+ user: { login: 'user2' },
88
+ body: 'Page 2 comment',
89
+ created_at: '2024-01-02T00:00:00Z',
90
+ },
91
+ ];
92
+
93
+ const fetchSpy = jest
94
+ .spyOn(global, 'fetch')
95
+ .mockResolvedValueOnce(
96
+ new Response(JSON.stringify(page1Payloads), {
97
+ status: 200,
98
+ headers: {
99
+ Link: '<https://api.github.com/repos/HiromiShikata/test-repo/issues/123/comments?per_page=100&page=2>; rel="next"',
100
+ },
101
+ }),
102
+ )
103
+ .mockResolvedValueOnce(
104
+ new Response(JSON.stringify(page2Payloads), { status: 200 }),
105
+ );
106
+
107
+ const result = await repository.getCommentsFromIssue(
108
+ buildIssue(TEST_URL),
109
+ );
110
+
111
+ expect(fetchSpy).toHaveBeenCalledTimes(2);
112
+ expect(fetchSpy).toHaveBeenNthCalledWith(
113
+ 1,
114
+ 'https://api.github.com/repos/HiromiShikata/test-repo/issues/123/comments?per_page=100&page=1',
115
+ expect.anything(),
116
+ );
117
+ expect(fetchSpy).toHaveBeenNthCalledWith(
118
+ 2,
119
+ 'https://api.github.com/repos/HiromiShikata/test-repo/issues/123/comments?per_page=100&page=2',
120
+ expect.anything(),
121
+ );
122
+ expect(result).toEqual([
123
+ {
124
+ author: 'user1',
125
+ content: 'Page 1 comment',
126
+ createdAt: new Date('2024-01-01T00:00:00Z'),
127
+ },
128
+ {
129
+ author: 'user2',
130
+ content: 'Page 2 comment',
131
+ createdAt: new Date('2024-01-02T00:00:00Z'),
132
+ },
133
+ ]);
134
+ });
135
+
136
+ it('maps author to empty string when user field is null (ghost user)', async () => {
137
+ const commentPayloads = [
138
+ {
139
+ user: null,
140
+ body: 'Ghost comment',
141
+ created_at: '2024-01-01T00:00:00Z',
142
+ },
143
+ ];
144
+ jest
145
+ .spyOn(global, 'fetch')
146
+ .mockResolvedValue(
147
+ new Response(JSON.stringify(commentPayloads), { status: 200 }),
148
+ );
149
+
150
+ const result = await repository.getCommentsFromIssue(
151
+ buildIssue(TEST_URL),
152
+ );
153
+
154
+ expect(result[0].author).toBe('');
155
+ });
156
+
157
+ it('throws an error including status and statusText when response is non-2xx', async () => {
158
+ jest.spyOn(global, 'fetch').mockResolvedValue(
159
+ new Response('Not Found', {
160
+ status: 404,
161
+ statusText: 'Not Found',
162
+ }),
163
+ );
164
+
165
+ await expect(
166
+ repository.getCommentsFromIssue(buildIssue(TEST_URL)),
167
+ ).rejects.toThrow('404');
168
+ await expect(
169
+ repository.getCommentsFromIssue(buildIssue(TEST_URL)),
170
+ ).rejects.toThrow('Not Found');
171
+ });
172
+ });
173
+ });
@@ -2,31 +2,10 @@ import { IssueCommentRepository } from '../../domain/usecases/adapter-interfaces
2
2
  import { Issue } from '../../domain/entities/Issue';
3
3
  import { Comment } from '../../domain/entities/Comment';
4
4
 
5
- type CommentNode = {
6
- author: {
7
- login: string;
8
- } | null;
5
+ type RestCommentPayload = {
6
+ user: { login: string } | null;
9
7
  body: string;
10
- createdAt: string;
11
- };
12
-
13
- type CommentsData = {
14
- comments: {
15
- pageInfo: {
16
- endCursor: string;
17
- hasNextPage: boolean;
18
- };
19
- nodes: CommentNode[];
20
- };
21
- };
22
-
23
- type IssueCommentsResponse = {
24
- data?: {
25
- repository?: {
26
- issue?: CommentsData;
27
- pullRequest?: CommentsData;
28
- };
29
- };
8
+ created_at: string;
30
9
  };
31
10
 
32
11
  type CreateCommentResponse = {
@@ -55,10 +34,10 @@ type IssueIdResponse = {
55
34
  };
56
35
  };
57
36
 
58
- function isIssueCommentsResponse(
37
+ function isRestCommentPayloadArray(
59
38
  value: unknown,
60
- ): value is IssueCommentsResponse {
61
- if (typeof value !== 'object' || value === null) return false;
39
+ ): value is RestCommentPayload[] {
40
+ if (!Array.isArray(value)) return false;
62
41
  return true;
63
42
  }
64
43
 
@@ -98,86 +77,46 @@ export class GitHubIssueCommentRepository implements IssueCommentRepository {
98
77
  }
99
78
 
100
79
  async getCommentsFromIssue(issue: Issue): Promise<Comment[]> {
101
- const { owner, repo, issueNumber, isPr } = this.parseIssueUrl(issue);
102
-
103
- const entityType = isPr ? 'pullRequest' : 'issue';
104
- const query = `
105
- query($owner: String!, $repo: String!, $issueNumber: Int!, $after: String) {
106
- repository(owner: $owner, name: $repo) {
107
- ${entityType}(number: $issueNumber) {
108
- comments(first: 100, after: $after) {
109
- pageInfo {
110
- endCursor
111
- hasNextPage
112
- }
113
- nodes {
114
- author {
115
- login
116
- }
117
- body
118
- createdAt
119
- }
120
- }
121
- }
122
- }
123
- }
124
- `;
80
+ const { owner, repo, issueNumber } = this.parseIssueUrl(issue);
125
81
 
126
82
  const comments: Comment[] = [];
127
- let after: string | null = null;
83
+ let page = 1;
128
84
  let hasNextPage = true;
129
85
 
130
86
  while (hasNextPage) {
131
- const response = await fetch('https://api.github.com/graphql', {
132
- method: 'POST',
87
+ const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments?per_page=100&page=${page}`;
88
+ const response = await fetch(url, {
133
89
  headers: {
134
90
  Authorization: `Bearer ${this.token}`,
135
- 'Content-Type': 'application/json',
91
+ Accept: 'application/vnd.github+json',
136
92
  },
137
- body: JSON.stringify({
138
- query,
139
- variables: {
140
- owner,
141
- repo,
142
- issueNumber,
143
- after,
144
- },
145
- }),
146
93
  });
147
94
 
148
95
  if (!response.ok) {
149
96
  throw new Error(
150
- `Failed to fetch comments from GitHub GraphQL API: ${response.status} ${response.statusText}`,
97
+ `Failed to fetch comments from GitHub REST API: ${response.status} ${response.statusText}`,
151
98
  );
152
99
  }
153
100
 
154
101
  const responseData: unknown = await response.json();
155
- if (!isIssueCommentsResponse(responseData)) {
156
- throw new Error(
157
- 'Unexpected response shape when fetching comments from GitHub GraphQL API',
158
- );
159
- }
160
-
161
- const issueData = isPr
162
- ? responseData.data?.repository?.pullRequest
163
- : responseData.data?.repository?.issue;
164
- if (!issueData) {
102
+ if (!isRestCommentPayloadArray(responseData)) {
165
103
  throw new Error(
166
- `${isPr ? 'Pull request' : 'Issue'} not found when fetching comments from GitHub GraphQL API`,
104
+ 'Unexpected response shape when fetching comments from GitHub REST API',
167
105
  );
168
106
  }
169
107
 
170
- const commentNodes = issueData.comments.nodes;
171
- for (const node of commentNodes) {
108
+ for (const payload of responseData) {
172
109
  comments.push({
173
- author: node.author?.login || '',
174
- content: node.body,
175
- createdAt: new Date(node.createdAt),
110
+ author: payload.user?.login ?? '',
111
+ content: payload.body,
112
+ createdAt: new Date(payload.created_at),
176
113
  });
177
114
  }
178
115
 
179
- hasNextPage = issueData.comments.pageInfo.hasNextPage;
180
- after = issueData.comments.pageInfo.endCursor;
116
+ const linkHeader = response.headers.get('Link') ?? '';
117
+ hasNextPage = linkHeader.includes('rel="next"');
118
+
119
+ page++;
181
120
  }
182
121
 
183
122
  return comments;
@@ -1 +1 @@
1
- {"version":3,"file":"GitHubIssueCommentRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubIssueCommentRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AA0ExD,qBAAa,4BAA6B,YAAW,sBAAsB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAE1C,OAAO,CAAC,aAAa;IAoBf,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;YAsF9C,cAAc;IAuDtB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkDzE"}
1
+ {"version":3,"file":"GitHubIssueCommentRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubIssueCommentRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iEAAiE,CAAC;AACzG,OAAO,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAqDxD,qBAAa,4BAA6B,YAAW,sBAAsB;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAE1C,OAAO,CAAC,aAAa;IAoBf,oBAAoB,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;YA8C9C,cAAc;IAuDtB,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkDzE"}