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 +7 -0
- package/bin/adapter/repositories/GitHubIssueCommentRepository.js +17 -54
- package/bin/adapter/repositories/GitHubIssueCommentRepository.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/repositories/GitHubIssueCommentRepository.test.ts +173 -0
- package/src/adapter/repositories/GitHubIssueCommentRepository.ts +22 -83
- package/types/adapter/repositories/GitHubIssueCommentRepository.d.ts.map +1 -1
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
|
|
5
|
-
if (
|
|
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
|
|
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
|
|
38
|
+
let page = 1;
|
|
61
39
|
let hasNextPage = true;
|
|
62
40
|
while (hasNextPage) {
|
|
63
|
-
const
|
|
64
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
84
|
-
throw new Error('Unexpected response shape when fetching comments from GitHub
|
|
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
|
|
93
|
-
for (const node of commentNodes) {
|
|
55
|
+
for (const payload of responseData) {
|
|
94
56
|
comments.push({
|
|
95
|
-
author:
|
|
96
|
-
content:
|
|
97
|
-
createdAt: new Date(
|
|
57
|
+
author: payload.user?.login ?? '',
|
|
58
|
+
content: payload.body,
|
|
59
|
+
createdAt: new Date(payload.created_at),
|
|
98
60
|
});
|
|
99
61
|
}
|
|
100
|
-
|
|
101
|
-
|
|
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":";;;
|
|
1
|
+
{"version":3,"file":"GitHubIssueCommentRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubIssueCommentRepository.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -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
|
|
6
|
-
|
|
7
|
-
login: string;
|
|
8
|
-
} | null;
|
|
5
|
+
type RestCommentPayload = {
|
|
6
|
+
user: { login: string } | null;
|
|
9
7
|
body: string;
|
|
10
|
-
|
|
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
|
|
37
|
+
function isRestCommentPayloadArray(
|
|
59
38
|
value: unknown,
|
|
60
|
-
): value is
|
|
61
|
-
if (
|
|
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
|
|
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
|
|
83
|
+
let page = 1;
|
|
128
84
|
let hasNextPage = true;
|
|
129
85
|
|
|
130
86
|
while (hasNextPage) {
|
|
131
|
-
const
|
|
132
|
-
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
104
|
+
'Unexpected response shape when fetching comments from GitHub REST API',
|
|
167
105
|
);
|
|
168
106
|
}
|
|
169
107
|
|
|
170
|
-
const
|
|
171
|
-
for (const node of commentNodes) {
|
|
108
|
+
for (const payload of responseData) {
|
|
172
109
|
comments.push({
|
|
173
|
-
author:
|
|
174
|
-
content:
|
|
175
|
-
createdAt: new Date(
|
|
110
|
+
author: payload.user?.login ?? '',
|
|
111
|
+
content: payload.body,
|
|
112
|
+
createdAt: new Date(payload.created_at),
|
|
176
113
|
});
|
|
177
114
|
}
|
|
178
115
|
|
|
179
|
-
|
|
180
|
-
|
|
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;
|
|
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"}
|