npm-cli-gh-issue-preparator 1.0.3 → 1.0.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.
@@ -24,6 +24,8 @@ jobs:
24
24
 
25
25
  - name: Run tests
26
26
  run: npm run test
27
+ env:
28
+ GH_TOKEN: ${{ secrets.GH_TOKEN }}
27
29
 
28
30
  - name: Upload test results
29
31
  uses: actions/upload-artifact@v6
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [1.0.4](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.0.3...v1.0.4) (2026-01-10)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * **ci:** add GH_TOKEN to test workflow and fix test expectations ([86dbac0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/commit/86dbac0bd66e0806531a26338b225ddec22c7826))
7
+
1
8
  ## [1.0.3](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.0.2...v1.0.3) (2025-12-14)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npm-cli-gh-issue-preparator",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
package/renovate.json CHANGED
@@ -11,12 +11,6 @@
11
11
  "branchConcurrentLimit": 2,
12
12
  "ignorePaths": ["**/generated/*", "**/_gen/*"],
13
13
  "packageRules": [
14
- {
15
- "matchPackagePatterns": ["*"],
16
- "matchUpdateTypes": ["minor", "patch"],
17
- "groupName": "all non-major dependencies",
18
- "groupSlug": "all-minor-patch"
19
- },
20
14
  {
21
15
  "matchPackageNames": ["eslint"],
22
16
  "allowedVersions": "<9.0.0"
@@ -32,6 +26,10 @@
32
26
  {
33
27
  "matchPackageNames": ["eslint-plugin-unused-imports"],
34
28
  "allowedVersions": "<4.0.0"
29
+ },
30
+ {
31
+ "matchPackageNames": ["@google/clasp"],
32
+ "allowedVersions": "3.1.0"
35
33
  }
36
34
  ]
37
35
  }
@@ -270,6 +270,123 @@ describe('GitHubIssueRepository', () => {
270
270
  expect(issues).toEqual([]);
271
271
  });
272
272
 
273
+ it('should skip items with content but undefined url', async () => {
274
+ mockFetch.mockResolvedValueOnce({
275
+ ok: true,
276
+ json: jest.fn().mockResolvedValue({
277
+ data: {
278
+ organization: {
279
+ projectV2: {
280
+ items: {
281
+ totalCount: 2,
282
+ pageInfo: {
283
+ endCursor: null,
284
+ hasNextPage: false,
285
+ },
286
+ nodes: [
287
+ {
288
+ id: 'item-with-undefined-url',
289
+ content: {
290
+ url: undefined,
291
+ title: 'Item Without URL',
292
+ number: 1,
293
+ labels: {
294
+ nodes: [],
295
+ },
296
+ },
297
+ fieldValues: {
298
+ nodes: [
299
+ {
300
+ name: 'Done',
301
+ field: {
302
+ name: 'Status',
303
+ },
304
+ },
305
+ ],
306
+ },
307
+ },
308
+ {
309
+ id: 'valid-issue',
310
+ content: {
311
+ url: 'https://github.com/owner/repo/issues/5',
312
+ title: 'Valid Issue',
313
+ number: 5,
314
+ labels: {
315
+ nodes: [],
316
+ },
317
+ },
318
+ fieldValues: {
319
+ nodes: [
320
+ {
321
+ name: 'Done',
322
+ field: {
323
+ name: 'Status',
324
+ },
325
+ },
326
+ ],
327
+ },
328
+ },
329
+ ],
330
+ },
331
+ },
332
+ },
333
+ },
334
+ }),
335
+ });
336
+
337
+ const issues = await repository.getAllOpened(mockProject);
338
+
339
+ expect(issues).toHaveLength(1);
340
+ expect(issues[0].id).toBe('valid-issue');
341
+ });
342
+
343
+ it('should handle items with null labels nodes', async () => {
344
+ mockFetch.mockResolvedValueOnce({
345
+ ok: true,
346
+ json: jest.fn().mockResolvedValue({
347
+ data: {
348
+ organization: {
349
+ projectV2: {
350
+ items: {
351
+ totalCount: 1,
352
+ pageInfo: {
353
+ endCursor: null,
354
+ hasNextPage: false,
355
+ },
356
+ nodes: [
357
+ {
358
+ id: 'issue-no-labels',
359
+ content: {
360
+ url: 'https://github.com/owner/repo/issues/10',
361
+ title: 'Issue Without Labels',
362
+ number: 10,
363
+ labels: null,
364
+ },
365
+ fieldValues: {
366
+ nodes: [
367
+ {
368
+ name: 'Done',
369
+ field: {
370
+ name: 'Status',
371
+ },
372
+ },
373
+ ],
374
+ },
375
+ },
376
+ ],
377
+ },
378
+ },
379
+ },
380
+ },
381
+ }),
382
+ });
383
+
384
+ const issues = await repository.getAllOpened(mockProject);
385
+
386
+ expect(issues).toHaveLength(1);
387
+ expect(issues[0].labels).toEqual([]);
388
+ });
389
+
273
390
  it('should handle issue without Status field', async () => {
274
391
  mockFetch.mockResolvedValueOnce({
275
392
  ok: true,
@@ -343,6 +460,10 @@ describe('GitHubIssueRepository', () => {
343
460
  organization: {
344
461
  projectV2: {
345
462
  fields: {
463
+ pageInfo: {
464
+ hasNextPage: false,
465
+ endCursor: null,
466
+ },
346
467
  nodes: [
347
468
  {
348
469
  id: 'field-1',
@@ -400,6 +521,10 @@ describe('GitHubIssueRepository', () => {
400
521
  organization: {
401
522
  projectV2: {
402
523
  fields: {
524
+ pageInfo: {
525
+ hasNextPage: false,
526
+ endCursor: null,
527
+ },
403
528
  nodes: [
404
529
  {
405
530
  id: 'field-1',
@@ -453,6 +578,10 @@ describe('GitHubIssueRepository', () => {
453
578
  organization: {
454
579
  projectV2: {
455
580
  fields: {
581
+ pageInfo: {
582
+ hasNextPage: false,
583
+ endCursor: null,
584
+ },
456
585
  nodes: [
457
586
  {
458
587
  id: 'field-1',
@@ -525,6 +654,10 @@ describe('GitHubIssueRepository', () => {
525
654
  organization: {
526
655
  projectV2: {
527
656
  fields: {
657
+ pageInfo: {
658
+ hasNextPage: false,
659
+ endCursor: null,
660
+ },
528
661
  nodes: [
529
662
  {
530
663
  id: 'field-1',
@@ -560,6 +693,10 @@ describe('GitHubIssueRepository', () => {
560
693
  organization: {
561
694
  projectV2: {
562
695
  fields: {
696
+ pageInfo: {
697
+ hasNextPage: false,
698
+ endCursor: null,
699
+ },
563
700
  nodes: [
564
701
  {
565
702
  id: 'field-1',
@@ -600,6 +737,10 @@ describe('GitHubIssueRepository', () => {
600
737
  organization: {
601
738
  projectV2: {
602
739
  fields: {
740
+ pageInfo: {
741
+ hasNextPage: false,
742
+ endCursor: null,
743
+ },
603
744
  nodes: [
604
745
  {
605
746
  id: 'field-1',
@@ -642,6 +783,10 @@ describe('GitHubIssueRepository', () => {
642
783
  organization: {
643
784
  projectV2: {
644
785
  fields: {
786
+ pageInfo: {
787
+ hasNextPage: false,
788
+ endCursor: null,
789
+ },
645
790
  nodes: [
646
791
  {
647
792
  id: 'field-1',
@@ -682,6 +827,10 @@ describe('GitHubIssueRepository', () => {
682
827
  user: {
683
828
  projectV2: {
684
829
  fields: {
830
+ pageInfo: {
831
+ hasNextPage: false,
832
+ endCursor: null,
833
+ },
685
834
  nodes: [
686
835
  {
687
836
  id: 'field-1',
@@ -1032,5 +1181,60 @@ describe('GitHubIssueRepository', () => {
1032
1181
 
1033
1182
  expect(result).toBeNull();
1034
1183
  });
1184
+
1185
+ it('should handle issue with null labels in get method', async () => {
1186
+ mockFetch.mockResolvedValueOnce({
1187
+ ok: true,
1188
+ json: jest.fn().mockResolvedValue({
1189
+ data: {
1190
+ organization: {
1191
+ projectV2: {
1192
+ items: {
1193
+ totalCount: 1,
1194
+ pageInfo: {
1195
+ endCursor: null,
1196
+ hasNextPage: false,
1197
+ },
1198
+ nodes: [
1199
+ {
1200
+ id: 'issue-null-labels',
1201
+ content: {
1202
+ url: 'https://github.com/owner/repo/issues/1',
1203
+ title: 'Issue With Null Labels',
1204
+ number: 1,
1205
+ labels: null,
1206
+ },
1207
+ fieldValues: {
1208
+ nodes: [
1209
+ {
1210
+ name: 'Done',
1211
+ field: {
1212
+ name: 'Status',
1213
+ },
1214
+ },
1215
+ ],
1216
+ },
1217
+ },
1218
+ ],
1219
+ },
1220
+ },
1221
+ },
1222
+ },
1223
+ }),
1224
+ });
1225
+
1226
+ const result = await repository.get(
1227
+ 'https://github.com/owner/repo/issues/1',
1228
+ mockProject,
1229
+ );
1230
+
1231
+ expect(result).toEqual({
1232
+ id: 'issue-null-labels',
1233
+ url: 'https://github.com/owner/repo/issues/1',
1234
+ title: 'Issue With Null Labels',
1235
+ labels: [],
1236
+ status: 'Done',
1237
+ });
1238
+ });
1035
1239
  });
1036
1240
  });
@@ -67,7 +67,7 @@ describe('StartPreparationUseCase', () => {
67
67
  expect(mockIssueRepository.update.mock.calls[0][1]).toBe(mockProject);
68
68
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
69
69
  expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
70
- 'aw https://github.com/user/repo url1 impl',
70
+ 'aw url1 impl https://github.com/user/repo',
71
71
  );
72
72
  });
73
73
  it('should assign workspace to awaiting issues', async () => {
@@ -100,18 +100,13 @@ describe('StartPreparationUseCase', () => {
100
100
  preparationStatus: 'Preparation',
101
101
  defaultAgentName: 'agent1',
102
102
  });
103
- expect(mockIssueRepository.update.mock.calls).toHaveLength(2);
103
+ expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
104
104
  expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
105
- id: '1',
106
- status: 'Preparation',
107
- });
108
- expect(mockIssueRepository.update.mock.calls[0][1]).toBe(mockProject);
109
- expect(mockIssueRepository.update.mock.calls[1][0]).toMatchObject({
110
105
  id: '2',
111
106
  status: 'Preparation',
112
107
  });
113
- expect(mockIssueRepository.update.mock.calls[1][1]).toBe(mockProject);
114
- expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(2);
108
+ expect(mockIssueRepository.update.mock.calls[0][1]).toBe(mockProject);
109
+ expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
115
110
  });
116
111
  it('should not assign workspace if maximum preparing issues reached', async () => {
117
112
  const preparationIssues: Issue[] = Array.from({ length: 6 }, (_, i) => ({
@@ -147,4 +142,45 @@ describe('StartPreparationUseCase', () => {
147
142
  expect(issue7UpdateCalls).toHaveLength(0);
148
143
  expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(0);
149
144
  });
145
+ it('should handle defensive break when pop returns undefined', async () => {
146
+ const awaitingIssue: Issue = {
147
+ id: '1',
148
+ url: 'url1',
149
+ title: 'Issue 1',
150
+ labels: [],
151
+ status: 'Awaiting Workspace',
152
+ };
153
+ let popCallCount = 0;
154
+ const issuesWithMockedPop: Issue[] = [awaitingIssue, awaitingIssue];
155
+ const mockedPop = jest.fn((): Issue | undefined => {
156
+ popCallCount++;
157
+ if (popCallCount === 1) {
158
+ return awaitingIssue;
159
+ }
160
+ return undefined;
161
+ });
162
+ Object.defineProperty(issuesWithMockedPop, 'pop', { value: mockedPop });
163
+ Object.defineProperty(issuesWithMockedPop, 'filter', {
164
+ value: () => issuesWithMockedPop,
165
+ });
166
+ const allIssues: Issue[] = [];
167
+ Object.defineProperty(allIssues, 'filter', {
168
+ value: jest.fn(() => issuesWithMockedPop),
169
+ });
170
+ mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
171
+ mockIssueRepository.getAllOpened.mockResolvedValueOnce(allIssues);
172
+ mockLocalCommandRunner.runCommand.mockResolvedValue({
173
+ stdout: '',
174
+ stderr: '',
175
+ exitCode: 0,
176
+ });
177
+ await useCase.run({
178
+ projectUrl: 'https://github.com/user/repo',
179
+ awaitingWorkspaceStatus: 'Awaiting Workspace',
180
+ preparationStatus: 'Preparation',
181
+ defaultAgentName: 'agent1',
182
+ });
183
+ expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
184
+ expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
185
+ });
150
186
  });