github-issue-tower-defence-management 1.35.2 → 1.36.1

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 (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +1 -27
  3. package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
  4. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +1 -27
  5. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  6. package/bin/adapter/repositories/BaseGitHubRepository.js +3 -7
  7. package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
  8. package/bin/adapter/repositories/CheerioProjectRepository.js +11 -11
  9. package/bin/adapter/repositories/CheerioProjectRepository.js.map +1 -1
  10. package/bin/adapter/repositories/FetchWebhookRepository.js +5 -1
  11. package/bin/adapter/repositories/FetchWebhookRepository.js.map +1 -1
  12. package/bin/adapter/repositories/GraphqlProjectRepository.js +14 -16
  13. package/bin/adapter/repositories/GraphqlProjectRepository.js.map +1 -1
  14. package/bin/adapter/repositories/KySlackRepository.js +212 -0
  15. package/bin/adapter/repositories/KySlackRepository.js.map +1 -0
  16. package/bin/adapter/repositories/issue/ApiV3IssueRepository.js +11 -13
  17. package/bin/adapter/repositories/issue/ApiV3IssueRepository.js.map +1 -1
  18. package/bin/adapter/repositories/issue/CheerioIssueRepository.js +2 -3
  19. package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +1 -1
  20. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js +47 -63
  21. package/bin/adapter/repositories/issue/GraphqlProjectItemRepository.js.map +1 -1
  22. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +12 -8
  23. package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +1 -1
  24. package/bin/adapter/repositories/issue/RestIssueRepository.js +36 -69
  25. package/bin/adapter/repositories/issue/RestIssueRepository.js.map +1 -1
  26. package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js +9 -1
  27. package/bin/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.js.map +1 -1
  28. package/jest.config.js +2 -1
  29. package/package.json +2 -3
  30. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +0 -78
  31. package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +1 -33
  32. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +0 -78
  33. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +1 -33
  34. package/src/adapter/repositories/BaseGitHubRepository.test.ts +43 -33
  35. package/src/adapter/repositories/BaseGitHubRepository.ts +4 -8
  36. package/src/adapter/repositories/CheerioProjectRepository.ts +19 -23
  37. package/src/adapter/repositories/FetchWebhookRepository.ts +2 -1
  38. package/src/adapter/repositories/GraphqlProjectRepository.ts +63 -69
  39. package/src/adapter/repositories/{AxiosSlackRepository.test.ts → KySlackRepository.test.ts} +4 -4
  40. package/src/adapter/repositories/KySlackRepository.ts +297 -0
  41. package/src/adapter/repositories/issue/ApiV3IssueRepository.ts +31 -33
  42. package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +3 -3
  43. package/src/adapter/repositories/issue/CheerioIssueRepository.ts +2 -3
  44. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +30 -22
  45. package/src/adapter/repositories/issue/GraphqlProjectItemRepository.ts +178 -191
  46. package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +12 -8
  47. package/src/adapter/repositories/issue/RestIssueRepository.ts +51 -91
  48. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.test.ts +20 -0
  49. package/src/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.ts +10 -1
  50. package/types/adapter/entry-points/function/getStoryObjectMap.d.ts +1 -1
  51. package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts +1 -1
  52. package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
  53. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts +1 -1
  54. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  55. package/types/adapter/repositories/BaseGitHubRepository.d.ts +1 -1
  56. package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
  57. package/types/adapter/repositories/CheerioProjectRepository.d.ts.map +1 -1
  58. package/types/adapter/repositories/FetchWebhookRepository.d.ts.map +1 -1
  59. package/types/adapter/repositories/GraphqlProjectRepository.d.ts.map +1 -1
  60. package/types/adapter/repositories/{AxiosSlackRepository.d.ts → KySlackRepository.d.ts} +3 -2
  61. package/types/adapter/repositories/KySlackRepository.d.ts.map +1 -0
  62. package/types/adapter/repositories/issue/ApiV3IssueRepository.d.ts.map +1 -1
  63. package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +1 -1
  64. package/types/adapter/repositories/issue/GraphqlProjectItemRepository.d.ts.map +1 -1
  65. package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +1 -1
  66. package/types/adapter/repositories/issue/RestIssueRepository.d.ts.map +1 -1
  67. package/types/domain/usecases/AssignNoAssigneeIssueToManagerUseCase.d.ts.map +1 -1
  68. package/types/index.d.ts +1 -1
  69. package/bin/adapter/repositories/AxiosSlackRepository.js +0 -188
  70. package/bin/adapter/repositories/AxiosSlackRepository.js.map +0 -1
  71. package/src/adapter/repositories/AxiosSlackRepository.ts +0 -218
  72. package/types/adapter/repositories/AxiosSlackRepository.d.ts.map +0 -1
@@ -1,4 +1,4 @@
1
- import axios from 'axios';
1
+ import ky from 'ky';
2
2
  import { BaseGitHubRepository } from './BaseGitHubRepository';
3
3
  import { ProjectRepository } from '../../domain/usecases/adapter-interfaces/ProjectRepository';
4
4
  import { FieldOption, Project } from '../../domain/entities/Project';
@@ -46,34 +46,33 @@ export class GraphqlProjectRepository
46
46
  },
47
47
  };
48
48
 
49
- const response = await axios<{
50
- data: {
51
- organization: {
52
- projectV2: {
53
- id: string;
54
- databaseId: number;
49
+ const response = await ky
50
+ .post('https://api.github.com/graphql', {
51
+ json: graphqlQuery,
52
+ headers: {
53
+ Authorization: `Bearer ${this.ghToken}`,
54
+ },
55
+ })
56
+ .json<{
57
+ data: {
58
+ organization: {
59
+ projectV2: {
60
+ id: string;
61
+ databaseId: number;
62
+ };
55
63
  };
56
- };
57
- user: {
58
- projectV2: {
59
- id: string;
60
- databaseId: number;
64
+ user: {
65
+ projectV2: {
66
+ id: string;
67
+ databaseId: number;
68
+ };
61
69
  };
62
70
  };
63
- };
64
- }>({
65
- url: 'https://api.github.com/graphql',
66
- method: 'post',
67
- headers: {
68
- Authorization: `Bearer ${this.ghToken}`,
69
- 'Content-Type': 'application/json',
70
- },
71
- data: JSON.stringify(graphqlQuery),
72
- });
71
+ }>();
73
72
 
74
73
  const projectId =
75
- response.data.data.organization?.projectV2?.id ||
76
- response.data.data.user?.projectV2?.id;
74
+ response.data.organization?.projectV2?.id ||
75
+ response.data.user?.projectV2?.id;
77
76
  if (!projectId) {
78
77
  throw new Error('projectId is not found');
79
78
  }
@@ -142,56 +141,51 @@ export class GraphqlProjectRepository
142
141
  const variables = {
143
142
  projectId: projectId,
144
143
  };
145
- const response = await axios.post<{
146
- data: {
147
- node: {
148
- id: string;
149
- databaseId: number;
150
- title: string;
151
- shortDescription: string;
152
- public: boolean;
153
- closed: boolean;
154
- createdAt: string;
155
- updatedAt: string;
156
- number: number;
157
- url: string;
158
- fields: {
159
- nodes: {
160
- id: string;
161
- databaseId: number;
162
- name: string;
163
- dataType: string;
164
- configuration: {
165
- iterations: {
166
- startDate: string;
167
- duration: string;
168
- title: string;
169
- }[];
170
- };
171
- options: {
144
+ const response = await ky
145
+ .post('https://api.github.com/graphql', {
146
+ json: { query, variables },
147
+ headers: {
148
+ Authorization: `Bearer ${this.ghToken}`,
149
+ },
150
+ })
151
+ .json<{
152
+ data: {
153
+ node: {
154
+ id: string;
155
+ databaseId: number;
156
+ title: string;
157
+ shortDescription: string;
158
+ public: boolean;
159
+ closed: boolean;
160
+ createdAt: string;
161
+ updatedAt: string;
162
+ number: number;
163
+ url: string;
164
+ fields: {
165
+ nodes: {
172
166
  id: string;
167
+ databaseId: number;
173
168
  name: string;
174
- description: string;
175
- color: string;
169
+ dataType: string;
170
+ configuration: {
171
+ iterations: {
172
+ startDate: string;
173
+ duration: string;
174
+ title: string;
175
+ }[];
176
+ };
177
+ options: {
178
+ id: string;
179
+ name: string;
180
+ description: string;
181
+ color: string;
182
+ }[];
176
183
  }[];
177
- }[];
184
+ };
178
185
  };
179
186
  };
180
- };
181
- }>(
182
- 'https://api.github.com/graphql',
183
- {
184
- query,
185
- variables,
186
- },
187
- {
188
- headers: {
189
- Authorization: `Bearer ${this.ghToken}`,
190
- 'Content-Type': 'application/json',
191
- },
192
- },
193
- );
194
- const project = response.data.data.node;
187
+ }>();
188
+ const project = response.data.node;
195
189
  if (!project) {
196
190
  return null;
197
191
  }
@@ -1,5 +1,5 @@
1
1
  import dotenv from 'dotenv';
2
- import { AxiosSlackRepository } from './AxiosSlackRepository';
2
+ import { KySlackRepository } from './KySlackRepository';
3
3
  import fs from 'fs';
4
4
  import https from 'https';
5
5
  import path from 'path';
@@ -16,13 +16,13 @@ if (!SLACK_USER_TOKEN) {
16
16
  throw new Error('SLACK_USER_TOKEN is required');
17
17
  }
18
18
 
19
- describe('AxiosSlackRepository Integration Tests', () => {
19
+ describe('KySlackRepository Integration Tests', () => {
20
20
  jest.setTimeout(60 * 1000);
21
21
  jest.retryTimes(3, { logErrorsBeforeRetry: true });
22
- let slackRepository: AxiosSlackRepository;
22
+ let slackRepository: KySlackRepository;
23
23
 
24
24
  beforeAll(() => {
25
- slackRepository = new AxiosSlackRepository(SLACK_USER_TOKEN);
25
+ slackRepository = new KySlackRepository(SLACK_USER_TOKEN);
26
26
  });
27
27
  beforeEach(async () => {
28
28
  await new Promise((resolve) => setTimeout(resolve, 2000));
@@ -0,0 +1,297 @@
1
+ import ky from 'ky';
2
+ import fs from 'fs';
3
+ import { SlackRepository } from '../../domain/usecases/adapter-interfaces/SlackRepository';
4
+
5
+ type ConversationsListResponse = {
6
+ ok: boolean;
7
+ channels: { id: string; name: string }[];
8
+ };
9
+ type UsersListResponse = {
10
+ ok: boolean;
11
+ members: { id: string; name: string }[];
12
+ };
13
+ type PostMessageResponse = {
14
+ ok: boolean;
15
+ ts: string;
16
+ };
17
+ type UploadUrlResponse = {
18
+ ok: boolean;
19
+ upload_url: string;
20
+ file_id: string;
21
+ };
22
+ type CompleteUploadResponse = {
23
+ ok: boolean;
24
+ };
25
+ type FileInfoResponse = {
26
+ ok: boolean;
27
+ file: { url_private: string };
28
+ };
29
+
30
+ const isRecord = (data: unknown): data is Record<string, unknown> => {
31
+ return typeof data === 'object' && data !== null;
32
+ };
33
+
34
+ const isConversationsListResponse = (
35
+ data: unknown,
36
+ ): data is ConversationsListResponse => {
37
+ return (
38
+ isRecord(data) &&
39
+ typeof data['ok'] === 'boolean' &&
40
+ Array.isArray(data['channels'])
41
+ );
42
+ };
43
+
44
+ const isUsersListResponse = (data: unknown): data is UsersListResponse => {
45
+ return (
46
+ isRecord(data) &&
47
+ typeof data['ok'] === 'boolean' &&
48
+ Array.isArray(data['members'])
49
+ );
50
+ };
51
+
52
+ const isPostMessageResponse = (data: unknown): data is PostMessageResponse => {
53
+ return isRecord(data) && typeof data['ok'] === 'boolean';
54
+ };
55
+
56
+ const isUploadUrlResponse = (data: unknown): data is UploadUrlResponse => {
57
+ return (
58
+ isRecord(data) &&
59
+ typeof data['ok'] === 'boolean' &&
60
+ 'upload_url' in data &&
61
+ 'file_id' in data
62
+ );
63
+ };
64
+
65
+ const isCompleteUploadResponse = (
66
+ data: unknown,
67
+ ): data is CompleteUploadResponse => {
68
+ return isRecord(data) && typeof data['ok'] === 'boolean';
69
+ };
70
+
71
+ const isFileInfoResponse = (data: unknown): data is FileInfoResponse => {
72
+ return isRecord(data) && typeof data['ok'] === 'boolean' && 'file' in data;
73
+ };
74
+
75
+ export class KySlackRepository implements SlackRepository {
76
+ private readonly client: typeof ky;
77
+ private readonly authHeader: string;
78
+ private readonly baseUrl = 'https://slack.com/api';
79
+
80
+ constructor(userToken: string) {
81
+ if (!userToken.startsWith('xoxp-')) {
82
+ throw new Error('Invalid user token. It should start with xoxp-');
83
+ }
84
+ this.authHeader = `Bearer ${userToken}`;
85
+ this.client = ky.extend({
86
+ retry: {
87
+ limit: 3,
88
+ methods: ['get', 'post'],
89
+ statusCodes: [429, 500, 502, 503, 504],
90
+ },
91
+ });
92
+ }
93
+
94
+ async postMessageToChannel(
95
+ message: string,
96
+ channelName: string,
97
+ ): Promise<{
98
+ threadTs: string;
99
+ }> {
100
+ const data = await this.client
101
+ .get(`${this.baseUrl}/conversations.list`, {
102
+ headers: { Authorization: this.authHeader },
103
+ })
104
+ .json<ConversationsListResponse>();
105
+
106
+ if (!isConversationsListResponse(data)) {
107
+ throw new Error(`Invalid response: ${JSON.stringify(data)}`);
108
+ }
109
+
110
+ const channel = data.channels.find((c) => c.name === channelName);
111
+ if (!channel) throw new Error(`Channel ${channelName} not found`);
112
+
113
+ const res = await this.client
114
+ .post(`${this.baseUrl}/chat.postMessage`, {
115
+ json: { channel: channel.id, text: message },
116
+ headers: { Authorization: this.authHeader },
117
+ })
118
+ .json<PostMessageResponse>();
119
+
120
+ if (!isPostMessageResponse(res)) {
121
+ throw new Error(`Invalid response: ${JSON.stringify(res)}`);
122
+ }
123
+ if (!res.ok) {
124
+ throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
125
+ }
126
+ return { threadTs: res.ts };
127
+ }
128
+
129
+ async postMessageToChannelThread(
130
+ message: string,
131
+ channelName: string,
132
+ threadTs: string,
133
+ ): Promise<void> {
134
+ const data = await this.client
135
+ .get(`${this.baseUrl}/conversations.list`, {
136
+ headers: { Authorization: this.authHeader },
137
+ })
138
+ .json<ConversationsListResponse>();
139
+
140
+ if (!isConversationsListResponse(data)) {
141
+ throw new Error(`Invalid response: ${JSON.stringify(data)}`);
142
+ }
143
+
144
+ const channel = data.channels.find((c) => c.name === channelName);
145
+ if (!channel) throw new Error(`Channel ${channelName} not found`);
146
+
147
+ const res = await this.client
148
+ .post(`${this.baseUrl}/chat.postMessage`, {
149
+ json: { channel: channel.id, text: message, thread_ts: threadTs },
150
+ headers: { Authorization: this.authHeader },
151
+ })
152
+ .json<PostMessageResponse>();
153
+
154
+ if (!isPostMessageResponse(res)) {
155
+ throw new Error(`Invalid response: ${JSON.stringify(res)}`);
156
+ }
157
+ if (!res.ok) {
158
+ throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
159
+ }
160
+ }
161
+
162
+ async postMessageToDirectMessage(
163
+ message: string,
164
+ userName: string,
165
+ ): Promise<void> {
166
+ const data = await this.client
167
+ .get(`${this.baseUrl}/users.list`, {
168
+ headers: { Authorization: this.authHeader },
169
+ })
170
+ .json<UsersListResponse>();
171
+
172
+ if (!isUsersListResponse(data)) {
173
+ throw new Error(`Invalid response: ${JSON.stringify(data)}`);
174
+ }
175
+
176
+ const user = data.members.find((u) => u.name === userName);
177
+ if (!user) throw new Error(`User ${userName} not found`);
178
+
179
+ const res = await this.client
180
+ .post(`${this.baseUrl}/chat.postMessage`, {
181
+ json: { channel: user.id, text: message },
182
+ headers: { Authorization: this.authHeader },
183
+ })
184
+ .json<PostMessageResponse>();
185
+
186
+ if (!isPostMessageResponse(res)) {
187
+ throw new Error(`Invalid response: ${JSON.stringify(res)}`);
188
+ }
189
+ if (!res.ok) {
190
+ throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
191
+ }
192
+ }
193
+
194
+ async postMessageToChannelWithImage(
195
+ message: string,
196
+ channelName: string,
197
+ imageFilePath: string,
198
+ ): Promise<void> {
199
+ const data = await this.client
200
+ .get(`${this.baseUrl}/conversations.list`, {
201
+ headers: { Authorization: this.authHeader },
202
+ })
203
+ .json<ConversationsListResponse>();
204
+
205
+ if (!isConversationsListResponse(data)) {
206
+ throw new Error(`Invalid response: ${JSON.stringify(data)}`);
207
+ }
208
+
209
+ const channel = data.channels.find((c) => c.name === channelName);
210
+ if (!channel) throw new Error(`Channel ${channelName} not found`);
211
+
212
+ const fileStats = fs.statSync(imageFilePath);
213
+ const fileName = imageFilePath.split('/').pop();
214
+
215
+ const formBody = new URLSearchParams({
216
+ filename: fileName ?? '',
217
+ length: String(fileStats.size),
218
+ });
219
+
220
+ const uploadUrlRes = await this.client
221
+ .post(`${this.baseUrl}/files.getUploadURLExternal`, {
222
+ body: formBody,
223
+ headers: {
224
+ Authorization: this.authHeader,
225
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
226
+ },
227
+ })
228
+ .json<UploadUrlResponse>();
229
+
230
+ if (!isUploadUrlResponse(uploadUrlRes)) {
231
+ throw new Error(`Invalid response: ${JSON.stringify(uploadUrlRes)}`);
232
+ }
233
+ if (!uploadUrlRes.ok) {
234
+ throw new Error(
235
+ `Failed to get upload URL: ${JSON.stringify(uploadUrlRes)}`,
236
+ );
237
+ }
238
+ await new Promise((resolve) => setTimeout(resolve, 1000));
239
+
240
+ const fileContent = fs.readFileSync(imageFilePath);
241
+ await ky.post(uploadUrlRes.upload_url, {
242
+ body: new Uint8Array(fileContent),
243
+ headers: { 'Content-Type': 'application/octet-stream' },
244
+ });
245
+ await new Promise((resolve) => setTimeout(resolve, 1000));
246
+
247
+ const completeRes = await this.client
248
+ .post(`${this.baseUrl}/files.completeUploadExternal`, {
249
+ json: { files: [{ id: uploadUrlRes.file_id }] },
250
+ headers: { Authorization: this.authHeader },
251
+ })
252
+ .json<CompleteUploadResponse>();
253
+
254
+ if (!isCompleteUploadResponse(completeRes)) {
255
+ throw new Error(`Invalid response: ${JSON.stringify(completeRes)}`);
256
+ }
257
+ if (!completeRes.ok) {
258
+ throw new Error(
259
+ `Failed to complete upload: ${JSON.stringify(completeRes)}`,
260
+ );
261
+ }
262
+
263
+ const fileInfo = await this.client
264
+ .get(`${this.baseUrl}/files.info?file=${uploadUrlRes.file_id}`, {
265
+ headers: { Authorization: this.authHeader },
266
+ })
267
+ .json<FileInfoResponse>();
268
+
269
+ if (!isFileInfoResponse(fileInfo)) {
270
+ throw new Error(`Invalid response: ${JSON.stringify(fileInfo)}`);
271
+ }
272
+ if (!fileInfo.ok) {
273
+ throw new Error(`Failed to get file info: ${JSON.stringify(fileInfo)}`);
274
+ }
275
+
276
+ const imageUrl = fileInfo.file.url_private;
277
+ await new Promise((resolve) => setTimeout(resolve, 2000));
278
+
279
+ const res = await this.client
280
+ .post(`${this.baseUrl}/chat.postMessage`, {
281
+ json: {
282
+ channel: channel.id,
283
+ text: message,
284
+ blocks: [{ type: 'image', image_url: imageUrl, alt_text: fileName }],
285
+ },
286
+ headers: { Authorization: this.authHeader },
287
+ })
288
+ .json<PostMessageResponse>();
289
+
290
+ if (!isPostMessageResponse(res)) {
291
+ throw new Error(`Invalid response: ${JSON.stringify(res)}`);
292
+ }
293
+ if (!res.ok) {
294
+ throw new Error(`Failed to post message: ${JSON.stringify(res)}`);
295
+ }
296
+ }
297
+ }
@@ -1,4 +1,4 @@
1
- import axios from 'axios';
1
+ import ky from 'ky';
2
2
  import { BaseGitHubRepository } from '../BaseGitHubRepository';
3
3
 
4
4
  export class ApiV3IssueRepository extends BaseGitHubRepository {
@@ -35,22 +35,21 @@ export class ApiV3IssueRepository extends BaseGitHubRepository {
35
35
  url += `&assignee=${query.assignee}`;
36
36
  }
37
37
 
38
- const response = await axios.get<{
39
- items: {
40
- html_url: string;
41
- title: string;
42
- number: string;
43
- }[];
44
- }>(url, {
45
- headers: {
46
- Authorization: `token ${this.ghToken}`,
47
- Accept: 'application/vnd.github.v3+json',
48
- },
49
- });
50
- if (response.status !== 200) {
51
- throw new Error(`Failed to search issue: ${response.status}`);
52
- }
53
- return response.data.items.map((item) => ({
38
+ const response = await ky
39
+ .get(url, {
40
+ headers: {
41
+ Authorization: `token ${this.ghToken}`,
42
+ Accept: 'application/vnd.github.v3+json',
43
+ },
44
+ })
45
+ .json<{
46
+ items: {
47
+ html_url: string;
48
+ title: string;
49
+ number: string;
50
+ }[];
51
+ }>();
52
+ return response.items.map((item) => ({
54
53
  url: item.html_url,
55
54
  title: item.title,
56
55
  number: item.number,
@@ -66,22 +65,21 @@ export class ApiV3IssueRepository extends BaseGitHubRepository {
66
65
  }[]
67
66
  > => {
68
67
  const url = `https://api.github.com/search/issues?q=${query}`;
69
- const response = await axios.get<{
70
- items: {
71
- html_url: string;
72
- title: string;
73
- number: string;
74
- }[];
75
- }>(url, {
76
- headers: {
77
- Authorization: `token ${this.ghToken}`,
78
- Accept: 'application/vnd.github.v3+json',
79
- },
80
- });
81
- if (response.status !== 200) {
82
- throw new Error(`Failed to search issue: ${response.status}`);
83
- }
84
- return response.data.items.map((item) => ({
68
+ const response = await ky
69
+ .get(url, {
70
+ headers: {
71
+ Authorization: `token ${this.ghToken}`,
72
+ Accept: 'application/vnd.github.v3+json',
73
+ },
74
+ })
75
+ .json<{
76
+ items: {
77
+ html_url: string;
78
+ title: string;
79
+ number: string;
80
+ }[];
81
+ }>();
82
+ return response.items.map((item) => ({
85
83
  url: item.html_url,
86
84
  title: item.title,
87
85
  number: item.number,
@@ -1,6 +1,6 @@
1
1
  import { CheerioIssueRepository } from './CheerioIssueRepository';
2
2
  import * as cheerio from 'cheerio';
3
- import axios from 'axios';
3
+ import ky from 'ky';
4
4
  import { InternalGraphqlIssueRepository } from './InternalGraphqlIssueRepository';
5
5
  import dotenv from 'dotenv';
6
6
  import { LocalStorageRepository } from '../LocalStorageRepository';
@@ -186,8 +186,8 @@ describe('CheerioIssueRepository', () => {
186
186
  describe('getStatusTimelineEvents', () => {
187
187
  it('should return status timeline events', async () => {
188
188
  const headers = await repository.createHeader();
189
- const content = await axios.get<string>(issueUrl, { headers });
190
- const $ = cheerio.load(content.data);
189
+ const html = await ky.get(issueUrl, { headers }).text();
190
+ const $ = cheerio.load(html);
191
191
  const statusTimeline = await repository.getStatusTimelineEvents($);
192
192
  expect(statusTimeline).toEqual([]);
193
193
  });
@@ -1,4 +1,4 @@
1
- import axios from 'axios';
1
+ import ky from 'ky';
2
2
  import * as cheerio from 'cheerio';
3
3
  import { BaseGitHubRepository } from '../BaseGitHubRepository';
4
4
  import { InternalGraphqlIssueRepository } from './InternalGraphqlIssueRepository';
@@ -44,8 +44,7 @@ export class CheerioIssueRepository extends BaseGitHubRepository {
44
44
  }
45
45
  getIssue = async (issueUrl: string): Promise<Issue> => {
46
46
  const headers = await this.createHeader();
47
- const content = await axios.get<string>(issueUrl, { headers });
48
- const html = content.data;
47
+ const html = await ky.get(issueUrl, { headers }).text();
49
48
  const $ = cheerio.load(html);
50
49
  if (html.includes('react-app.embeddedData')) {
51
50
  const issue =