npm-cli-gh-issue-preparator 1.4.0 → 1.6.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.
- package/.github/copilot-instructions.md +0 -0
- package/.github/instructions/code-review.instructions.md +43 -0
- package/.github/workflows/umino-project.yml +1 -1
- package/CHANGELOG.md +14 -0
- package/bin/adapter/entry-points/cli/index.js +13 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/repositories/GitHubProjectRepository.js +71 -0
- package/bin/adapter/repositories/GitHubProjectRepository.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +2 -2
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +180 -2
- package/src/adapter/entry-points/cli/index.ts +22 -0
- package/src/adapter/repositories/GitHubIssueRepository.test.ts +2 -0
- package/src/adapter/repositories/GitHubProjectRepository.test.ts +152 -1
- package/src/adapter/repositories/GitHubProjectRepository.ts +87 -0
- package/src/domain/entities/Project.ts +1 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +2 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +58 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +3 -2
- package/src/domain/usecases/adapter-interfaces/ProjectRepository.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/repositories/GitHubProjectRepository.d.ts +1 -0
- package/types/adapter/repositories/GitHubProjectRepository.d.ts.map +1 -1
- package/types/domain/entities/Project.d.ts +1 -0
- package/types/domain/entities/Project.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts +1 -0
- package/types/domain/usecases/adapter-interfaces/ProjectRepository.d.ts.map +1 -1
|
File without changes
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
applyTo: '**'
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# Code Review Instructions
|
|
6
|
+
|
|
7
|
+
## Scope Integrity
|
|
8
|
+
|
|
9
|
+
Flag pull requests that contain changes unrelated to the stated PR title or description.
|
|
10
|
+
Each PR must address a single topic or feature.
|
|
11
|
+
If unrelated modifications are detected, request that they be split into separate issues first.
|
|
12
|
+
|
|
13
|
+
## Lint and Static Analysis Bypass Detection
|
|
14
|
+
|
|
15
|
+
Flag any code that disables, ignores, or bypasses linting rules.
|
|
16
|
+
Report usage of eslint-disable, @ts-ignore, @ts-expect-error, noqa, or similar suppression comments.
|
|
17
|
+
Flag modifications to linter configuration files that weaken existing rules.
|
|
18
|
+
|
|
19
|
+
## Error Handling Violations
|
|
20
|
+
|
|
21
|
+
Flag code that catches exceptions but does nothing with them (empty catch blocks).
|
|
22
|
+
Flag API endpoints that return HTTP 200 status codes when errors have occurred.
|
|
23
|
+
Flag code that converts errors into success responses or hides error states.
|
|
24
|
+
Flag use of console.log for error handling instead of proper error propagation.
|
|
25
|
+
Flag code that swallows errors by returning default values without logging or reporting.
|
|
26
|
+
|
|
27
|
+
## Test Coverage Requirements
|
|
28
|
+
|
|
29
|
+
Flag new features or bug fixes that lack corresponding unit tests.
|
|
30
|
+
Flag modifications to existing code where related tests have not been updated.
|
|
31
|
+
Flag deletion or disabling of existing tests without justification.
|
|
32
|
+
Flag test files that use skip, only, or other mechanisms to bypass test execution.
|
|
33
|
+
Flag mock implementations that always return success without testing failure scenarios.
|
|
34
|
+
Report when test coverage for modified files appears insufficient.
|
|
35
|
+
Report when test assertion for modified files appears insufficient.
|
|
36
|
+
|
|
37
|
+
## Code Quality Red Flags
|
|
38
|
+
|
|
39
|
+
Flag hardcoded credentials, API keys, or secrets.
|
|
40
|
+
Flag commented-out code blocks.
|
|
41
|
+
Flag TODO or FIXME comments without associated issue references.
|
|
42
|
+
Flag any use of type assertions (as any, as unknown) that bypass type safety.
|
|
43
|
+
Flag functions with excessive cyclomatic complexity.
|
|
@@ -41,7 +41,7 @@ env:
|
|
|
41
41
|
|
|
42
42
|
jobs:
|
|
43
43
|
umino-job:
|
|
44
|
-
if: github.event_name != 'issue_comment' || github.event.comment.user.login != 'umino-bot'
|
|
44
|
+
if: (github.event_name != 'issue_comment' || github.event.comment.user.login != 'umino-bot') && github.event.action != 'labeled'
|
|
45
45
|
runs-on: ubuntu-latest
|
|
46
46
|
steps:
|
|
47
47
|
- name: Move issue to ${{ env.unread }}
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.6.0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.5.0...v1.6.0) (2026-01-26)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **core:** add prepareStatus method to ProjectRepository ([931b2ed](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/commit/931b2ed9e4298235b48afa0b6a82d81a49ea78d2))
|
|
7
|
+
|
|
8
|
+
# [1.5.0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.4.0...v1.5.0) (2026-01-20)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **cli:** add maximumPreparingIssuesCount as cli parameter for startDaemon ([a3f1e98](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/commit/a3f1e985c65cdda426538abd5d1552344f33da0e))
|
|
14
|
+
|
|
1
15
|
# [1.4.0](https://github.com/HiromiShikata/npm-cli-gh-issue-preparator/compare/v1.3.0...v1.4.0) (2026-01-14)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -26,6 +26,7 @@ program
|
|
|
26
26
|
.requiredOption('--preparationStatus <status>', 'Status for issues in preparation')
|
|
27
27
|
.requiredOption('--defaultAgentName <name>', 'Default agent name')
|
|
28
28
|
.option('--logFilePath <path>', 'Path to log file')
|
|
29
|
+
.option('--maximumPreparingIssuesCount <count>', 'Maximum number of issues in preparation status (default: 6)')
|
|
29
30
|
.action(async (options) => {
|
|
30
31
|
const token = process.env.GH_TOKEN;
|
|
31
32
|
if (!token) {
|
|
@@ -36,12 +37,24 @@ program
|
|
|
36
37
|
const issueRepository = new GitHubIssueRepository_1.GitHubIssueRepository(token);
|
|
37
38
|
const localCommandRunner = new NodeLocalCommandRunner_1.NodeLocalCommandRunner();
|
|
38
39
|
const useCase = new StartPreparationUseCase_1.StartPreparationUseCase(projectRepository, issueRepository, localCommandRunner);
|
|
40
|
+
let maximumPreparingIssuesCount = null;
|
|
41
|
+
if (options.maximumPreparingIssuesCount !== undefined) {
|
|
42
|
+
const parsedCount = Number(options.maximumPreparingIssuesCount);
|
|
43
|
+
if (!Number.isFinite(parsedCount) ||
|
|
44
|
+
!Number.isInteger(parsedCount) ||
|
|
45
|
+
parsedCount <= 0) {
|
|
46
|
+
console.error('Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
maximumPreparingIssuesCount = parsedCount;
|
|
50
|
+
}
|
|
39
51
|
await useCase.run({
|
|
40
52
|
projectUrl: options.projectUrl,
|
|
41
53
|
awaitingWorkspaceStatus: options.awaitingWorkspaceStatus,
|
|
42
54
|
preparationStatus: options.preparationStatus,
|
|
43
55
|
defaultAgentName: options.defaultAgentName,
|
|
44
56
|
logFilePath: options.logFilePath,
|
|
57
|
+
maximumPreparingIssuesCount,
|
|
45
58
|
});
|
|
46
59
|
});
|
|
47
60
|
program
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";;;;;;;AACA,oDAA4B;AAC5B,gBAAM,CAAC,MAAM,EAAE,CAAC;AAEhB,yCAAoC;AACpC,8FAA2F;AAC3F,0HAAuH;AACvH,wFAAqF;AACrF,oFAAiF;AACjF,sFAAmF;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,12 +46,14 @@ class GitHubProjectRepository {
|
|
|
46
46
|
fields(first: 100) {
|
|
47
47
|
nodes {
|
|
48
48
|
... on ProjectV2SingleSelectField {
|
|
49
|
+
id
|
|
49
50
|
name
|
|
50
51
|
options {
|
|
51
52
|
name
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
... on ProjectV2Field {
|
|
56
|
+
id
|
|
55
57
|
name
|
|
56
58
|
}
|
|
57
59
|
}
|
|
@@ -66,12 +68,14 @@ class GitHubProjectRepository {
|
|
|
66
68
|
fields(first: 100) {
|
|
67
69
|
nodes {
|
|
68
70
|
... on ProjectV2SingleSelectField {
|
|
71
|
+
id
|
|
69
72
|
name
|
|
70
73
|
options {
|
|
71
74
|
name
|
|
72
75
|
}
|
|
73
76
|
}
|
|
74
77
|
... on ProjectV2Field {
|
|
78
|
+
id
|
|
75
79
|
name
|
|
76
80
|
}
|
|
77
81
|
}
|
|
@@ -116,6 +120,73 @@ class GitHubProjectRepository {
|
|
|
116
120
|
name: project.title,
|
|
117
121
|
statuses,
|
|
118
122
|
customFieldNames: fields.map((f) => f.name),
|
|
123
|
+
statusFieldId: statusField?.id ?? null,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
async prepareStatus(name, project) {
|
|
127
|
+
if (project.statuses.includes(name)) {
|
|
128
|
+
return project;
|
|
129
|
+
}
|
|
130
|
+
if (!project.statusFieldId) {
|
|
131
|
+
throw new Error(`Status field not found in project "${project.name}". ` +
|
|
132
|
+
`Cannot add status "${name}".`);
|
|
133
|
+
}
|
|
134
|
+
const existingOptions = project.statuses.map((statusName) => ({
|
|
135
|
+
name: statusName,
|
|
136
|
+
color: 'GRAY',
|
|
137
|
+
description: '',
|
|
138
|
+
}));
|
|
139
|
+
const newOptions = [
|
|
140
|
+
...existingOptions,
|
|
141
|
+
{ name, color: 'GRAY', description: '' },
|
|
142
|
+
];
|
|
143
|
+
const mutation = `
|
|
144
|
+
mutation($fieldId: ID!, $singleSelectOptions: [ProjectV2SingleSelectFieldOptionInput!]!) {
|
|
145
|
+
updateProjectV2Field(input: {
|
|
146
|
+
fieldId: $fieldId
|
|
147
|
+
singleSelectOptions: $singleSelectOptions
|
|
148
|
+
}) {
|
|
149
|
+
projectV2Field {
|
|
150
|
+
... on ProjectV2SingleSelectField {
|
|
151
|
+
id
|
|
152
|
+
name
|
|
153
|
+
options {
|
|
154
|
+
name
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
`;
|
|
161
|
+
const response = await fetch('https://api.github.com/graphql', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
Authorization: `Bearer ${this.token}`,
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
},
|
|
167
|
+
body: JSON.stringify({
|
|
168
|
+
query: mutation,
|
|
169
|
+
variables: {
|
|
170
|
+
fieldId: project.statusFieldId,
|
|
171
|
+
singleSelectOptions: newOptions,
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
});
|
|
175
|
+
const responseData = await response.json();
|
|
176
|
+
if (!isGitHubApiResponse(responseData)) {
|
|
177
|
+
throw new Error('Invalid API response format');
|
|
178
|
+
}
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
throw new Error(`GitHub API error: ${JSON.stringify(responseData)}`);
|
|
181
|
+
}
|
|
182
|
+
if (typeof responseData === 'object' &&
|
|
183
|
+
responseData !== null &&
|
|
184
|
+
'errors' in responseData) {
|
|
185
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(responseData.errors)}`);
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
...project,
|
|
189
|
+
statuses: [...project.statuses, name],
|
|
119
190
|
};
|
|
120
191
|
}
|
|
121
192
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubProjectRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubProjectRepository.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"GitHubProjectRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubProjectRepository.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,14 +6,14 @@ class StartPreparationUseCase {
|
|
|
6
6
|
this.projectRepository = projectRepository;
|
|
7
7
|
this.issueRepository = issueRepository;
|
|
8
8
|
this.localCommandRunner = localCommandRunner;
|
|
9
|
-
this.maximumPreparingIssuesCount = 6;
|
|
10
9
|
this.run = async (params) => {
|
|
10
|
+
const maximumPreparingIssuesCount = params.maximumPreparingIssuesCount ?? 6;
|
|
11
11
|
const project = await this.projectRepository.getByUrl(params.projectUrl);
|
|
12
12
|
const allIssues = await this.issueRepository.getAllOpened(project);
|
|
13
13
|
const awaitingWorkspaceIssues = allIssues.filter((issue) => issue.status === params.awaitingWorkspaceStatus);
|
|
14
14
|
const currentPreparationIssueCount = allIssues.filter((issue) => issue.status === params.preparationStatus).length;
|
|
15
15
|
for (let i = currentPreparationIssueCount; i <
|
|
16
|
-
Math.min(
|
|
16
|
+
Math.min(maximumPreparingIssuesCount, awaitingWorkspaceIssues.length + currentPreparationIssueCount); i++) {
|
|
17
17
|
const issue = awaitingWorkspaceIssues.pop();
|
|
18
18
|
if (!issue) {
|
|
19
19
|
break;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,uBAAuB;
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":";;;AAIA,MAAa,uBAAuB;IAClC,YACmB,iBAAoC,EACpC,eAAgC,EAChC,kBAAsC;QAFtC,sBAAiB,GAAjB,iBAAiB,CAAmB;QACpC,oBAAe,GAAf,eAAe,CAAiB;QAChC,uBAAkB,GAAlB,kBAAkB,CAAoB;QAGzD,QAAG,GAAG,KAAK,EAAE,MAOZ,EAAiB,EAAE;YAClB,MAAM,2BAA2B,GAAG,MAAM,CAAC,2BAA2B,IAAI,CAAC,CAAC;YAC5E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YAEzE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAEnE,MAAM,uBAAuB,GAAG,SAAS,CAAC,MAAM,CAC9C,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,uBAAuB,CAC3D,CAAC;YACF,MAAM,4BAA4B,GAAG,SAAS,CAAC,MAAM,CACnD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,iBAAiB,CACrD,CAAC,MAAM,CAAC;YAET,KACE,IAAI,CAAC,GAAG,4BAA4B,EACpC,CAAC;gBACD,IAAI,CAAC,GAAG,CACN,2BAA2B,EAC3B,uBAAuB,CAAC,MAAM,GAAG,4BAA4B,CAC9D,EACD,CAAC,EAAE,EACH,CAAC;gBACD,MAAM,KAAK,GAAG,uBAAuB,CAAC,GAAG,EAAE,CAAC;gBAC5C,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,MAAM;gBACR,CAAC;gBACD,MAAM,KAAK,GACT,KAAK,CAAC,MAAM;qBACT,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC;oBAC/C,EAAE,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC;qBACzB,IAAI,EAAE,IAAI,MAAM,CAAC,gBAAgB,CAAC;gBACvC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,iBAAiB,CAAC;gBACxC,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAElD,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW;oBACvC,CAAC,CAAC,iBAAiB,MAAM,CAAC,WAAW,EAAE;oBACvC,CAAC,CAAC,EAAE,CAAC;gBACP,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CACtC,MAAM,KAAK,CAAC,GAAG,IAAI,KAAK,IAAI,OAAO,CAAC,GAAG,GAAG,cAAc,CAAC,CAAC,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACvF,CAAC;YACJ,CAAC;QACH,CAAC,CAAC;IAlDC,CAAC;CAmDL;AAxDD,0DAwDC"}
|
package/package.json
CHANGED
|
@@ -32,7 +32,6 @@ describe('CLI', () => {
|
|
|
32
32
|
this: StartPreparationUseCase,
|
|
33
33
|
) {
|
|
34
34
|
this.run = mockRun;
|
|
35
|
-
this.maximumPreparingIssuesCount = 6;
|
|
36
35
|
return this;
|
|
37
36
|
});
|
|
38
37
|
|
|
@@ -57,6 +56,7 @@ describe('CLI', () => {
|
|
|
57
56
|
preparationStatus: 'Preparing',
|
|
58
57
|
defaultAgentName: 'agent1',
|
|
59
58
|
logFilePath: undefined,
|
|
59
|
+
maximumPreparingIssuesCount: null,
|
|
60
60
|
});
|
|
61
61
|
});
|
|
62
62
|
|
|
@@ -68,7 +68,6 @@ describe('CLI', () => {
|
|
|
68
68
|
this: StartPreparationUseCase,
|
|
69
69
|
) {
|
|
70
70
|
this.run = mockRun;
|
|
71
|
-
this.maximumPreparingIssuesCount = 6;
|
|
72
71
|
return this;
|
|
73
72
|
});
|
|
74
73
|
|
|
@@ -95,9 +94,188 @@ describe('CLI', () => {
|
|
|
95
94
|
preparationStatus: 'Preparing',
|
|
96
95
|
defaultAgentName: 'agent1',
|
|
97
96
|
logFilePath: '/path/to/log.txt',
|
|
97
|
+
maximumPreparingIssuesCount: null,
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
100
|
|
|
101
|
+
it('should pass maximumPreparingIssuesCount to StartPreparationUseCase when provided', async () => {
|
|
102
|
+
const mockRun = jest.fn().mockResolvedValue(undefined);
|
|
103
|
+
const MockedStartPreparationUseCase = jest.mocked(StartPreparationUseCase);
|
|
104
|
+
|
|
105
|
+
MockedStartPreparationUseCase.mockImplementation(function (
|
|
106
|
+
this: StartPreparationUseCase,
|
|
107
|
+
) {
|
|
108
|
+
this.run = mockRun;
|
|
109
|
+
return this;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
await program.parseAsync([
|
|
113
|
+
'node',
|
|
114
|
+
'test',
|
|
115
|
+
'startDaemon',
|
|
116
|
+
'--projectUrl',
|
|
117
|
+
'https://github.com/test/project',
|
|
118
|
+
'--awaitingWorkspaceStatus',
|
|
119
|
+
'Awaiting',
|
|
120
|
+
'--preparationStatus',
|
|
121
|
+
'Preparing',
|
|
122
|
+
'--defaultAgentName',
|
|
123
|
+
'agent1',
|
|
124
|
+
'--maximumPreparingIssuesCount',
|
|
125
|
+
'10',
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
expect(mockRun).toHaveBeenCalledTimes(1);
|
|
129
|
+
expect(mockRun).toHaveBeenCalledWith({
|
|
130
|
+
projectUrl: 'https://github.com/test/project',
|
|
131
|
+
awaitingWorkspaceStatus: 'Awaiting',
|
|
132
|
+
preparationStatus: 'Preparing',
|
|
133
|
+
defaultAgentName: 'agent1',
|
|
134
|
+
logFilePath: undefined,
|
|
135
|
+
maximumPreparingIssuesCount: 10,
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should exit with error for non-numeric maximumPreparingIssuesCount', async () => {
|
|
140
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
141
|
+
const processExitSpy = jest
|
|
142
|
+
.spyOn(process, 'exit')
|
|
143
|
+
.mockImplementation(() => {
|
|
144
|
+
throw new Error('process.exit called');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await expect(
|
|
148
|
+
program.parseAsync([
|
|
149
|
+
'node',
|
|
150
|
+
'test',
|
|
151
|
+
'startDaemon',
|
|
152
|
+
'--projectUrl',
|
|
153
|
+
'https://github.com/test/project',
|
|
154
|
+
'--awaitingWorkspaceStatus',
|
|
155
|
+
'Awaiting',
|
|
156
|
+
'--preparationStatus',
|
|
157
|
+
'Preparing',
|
|
158
|
+
'--defaultAgentName',
|
|
159
|
+
'agent1',
|
|
160
|
+
'--maximumPreparingIssuesCount',
|
|
161
|
+
'abc',
|
|
162
|
+
]),
|
|
163
|
+
).rejects.toThrow('process.exit called');
|
|
164
|
+
|
|
165
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
166
|
+
'Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.',
|
|
167
|
+
);
|
|
168
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
169
|
+
|
|
170
|
+
consoleErrorSpy.mockRestore();
|
|
171
|
+
processExitSpy.mockRestore();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should exit with error for negative maximumPreparingIssuesCount', async () => {
|
|
175
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
176
|
+
const processExitSpy = jest
|
|
177
|
+
.spyOn(process, 'exit')
|
|
178
|
+
.mockImplementation(() => {
|
|
179
|
+
throw new Error('process.exit called');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await expect(
|
|
183
|
+
program.parseAsync([
|
|
184
|
+
'node',
|
|
185
|
+
'test',
|
|
186
|
+
'startDaemon',
|
|
187
|
+
'--projectUrl',
|
|
188
|
+
'https://github.com/test/project',
|
|
189
|
+
'--awaitingWorkspaceStatus',
|
|
190
|
+
'Awaiting',
|
|
191
|
+
'--preparationStatus',
|
|
192
|
+
'Preparing',
|
|
193
|
+
'--defaultAgentName',
|
|
194
|
+
'agent1',
|
|
195
|
+
'--maximumPreparingIssuesCount',
|
|
196
|
+
'-5',
|
|
197
|
+
]),
|
|
198
|
+
).rejects.toThrow('process.exit called');
|
|
199
|
+
|
|
200
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
201
|
+
'Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.',
|
|
202
|
+
);
|
|
203
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
204
|
+
|
|
205
|
+
consoleErrorSpy.mockRestore();
|
|
206
|
+
processExitSpy.mockRestore();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('should exit with error for zero maximumPreparingIssuesCount', async () => {
|
|
210
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
211
|
+
const processExitSpy = jest
|
|
212
|
+
.spyOn(process, 'exit')
|
|
213
|
+
.mockImplementation(() => {
|
|
214
|
+
throw new Error('process.exit called');
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
await expect(
|
|
218
|
+
program.parseAsync([
|
|
219
|
+
'node',
|
|
220
|
+
'test',
|
|
221
|
+
'startDaemon',
|
|
222
|
+
'--projectUrl',
|
|
223
|
+
'https://github.com/test/project',
|
|
224
|
+
'--awaitingWorkspaceStatus',
|
|
225
|
+
'Awaiting',
|
|
226
|
+
'--preparationStatus',
|
|
227
|
+
'Preparing',
|
|
228
|
+
'--defaultAgentName',
|
|
229
|
+
'agent1',
|
|
230
|
+
'--maximumPreparingIssuesCount',
|
|
231
|
+
'0',
|
|
232
|
+
]),
|
|
233
|
+
).rejects.toThrow('process.exit called');
|
|
234
|
+
|
|
235
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
236
|
+
'Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.',
|
|
237
|
+
);
|
|
238
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
239
|
+
|
|
240
|
+
consoleErrorSpy.mockRestore();
|
|
241
|
+
processExitSpy.mockRestore();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('should exit with error for decimal maximumPreparingIssuesCount', async () => {
|
|
245
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
246
|
+
const processExitSpy = jest
|
|
247
|
+
.spyOn(process, 'exit')
|
|
248
|
+
.mockImplementation(() => {
|
|
249
|
+
throw new Error('process.exit called');
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await expect(
|
|
253
|
+
program.parseAsync([
|
|
254
|
+
'node',
|
|
255
|
+
'test',
|
|
256
|
+
'startDaemon',
|
|
257
|
+
'--projectUrl',
|
|
258
|
+
'https://github.com/test/project',
|
|
259
|
+
'--awaitingWorkspaceStatus',
|
|
260
|
+
'Awaiting',
|
|
261
|
+
'--preparationStatus',
|
|
262
|
+
'Preparing',
|
|
263
|
+
'--defaultAgentName',
|
|
264
|
+
'agent1',
|
|
265
|
+
'--maximumPreparingIssuesCount',
|
|
266
|
+
'3.5',
|
|
267
|
+
]),
|
|
268
|
+
).rejects.toThrow('process.exit called');
|
|
269
|
+
|
|
270
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
271
|
+
'Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.',
|
|
272
|
+
);
|
|
273
|
+
expect(processExitSpy).toHaveBeenCalledWith(1);
|
|
274
|
+
|
|
275
|
+
consoleErrorSpy.mockRestore();
|
|
276
|
+
processExitSpy.mockRestore();
|
|
277
|
+
});
|
|
278
|
+
|
|
101
279
|
it('should pass correct parameters to NotifyFinishedIssuePreparationUseCase', async () => {
|
|
102
280
|
const mockRun = jest.fn().mockResolvedValue(undefined);
|
|
103
281
|
const MockedNotifyFinishedUseCase = jest.mocked(
|
|
@@ -15,6 +15,7 @@ type StartDaemonOptions = {
|
|
|
15
15
|
preparationStatus: string;
|
|
16
16
|
defaultAgentName: string;
|
|
17
17
|
logFilePath?: string;
|
|
18
|
+
maximumPreparingIssuesCount?: string;
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
type NotifyFinishedOptions = {
|
|
@@ -43,6 +44,10 @@ program
|
|
|
43
44
|
)
|
|
44
45
|
.requiredOption('--defaultAgentName <name>', 'Default agent name')
|
|
45
46
|
.option('--logFilePath <path>', 'Path to log file')
|
|
47
|
+
.option(
|
|
48
|
+
'--maximumPreparingIssuesCount <count>',
|
|
49
|
+
'Maximum number of issues in preparation status (default: 6)',
|
|
50
|
+
)
|
|
46
51
|
.action(async (options: StartDaemonOptions) => {
|
|
47
52
|
const token = process.env.GH_TOKEN;
|
|
48
53
|
if (!token) {
|
|
@@ -60,12 +65,29 @@ program
|
|
|
60
65
|
localCommandRunner,
|
|
61
66
|
);
|
|
62
67
|
|
|
68
|
+
let maximumPreparingIssuesCount: number | null = null;
|
|
69
|
+
if (options.maximumPreparingIssuesCount !== undefined) {
|
|
70
|
+
const parsedCount = Number(options.maximumPreparingIssuesCount);
|
|
71
|
+
if (
|
|
72
|
+
!Number.isFinite(parsedCount) ||
|
|
73
|
+
!Number.isInteger(parsedCount) ||
|
|
74
|
+
parsedCount <= 0
|
|
75
|
+
) {
|
|
76
|
+
console.error(
|
|
77
|
+
'Invalid value for --maximumPreparingIssuesCount. It must be a positive integer.',
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
maximumPreparingIssuesCount = parsedCount;
|
|
82
|
+
}
|
|
83
|
+
|
|
63
84
|
await useCase.run({
|
|
64
85
|
projectUrl: options.projectUrl,
|
|
65
86
|
awaitingWorkspaceStatus: options.awaitingWorkspaceStatus,
|
|
66
87
|
preparationStatus: options.preparationStatus,
|
|
67
88
|
defaultAgentName: options.defaultAgentName,
|
|
68
89
|
logFilePath: options.logFilePath,
|
|
90
|
+
maximumPreparingIssuesCount,
|
|
69
91
|
});
|
|
70
92
|
});
|
|
71
93
|
|
|
@@ -12,6 +12,7 @@ describe('GitHubIssueRepository', () => {
|
|
|
12
12
|
name: 'Test Project',
|
|
13
13
|
statuses: ['Awaiting Workspace', 'Preparation', 'Done'],
|
|
14
14
|
customFieldNames: ['Status', 'workspace'],
|
|
15
|
+
statusFieldId: 'status-field-id',
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
const mockUserProject: Project = {
|
|
@@ -20,6 +21,7 @@ describe('GitHubIssueRepository', () => {
|
|
|
20
21
|
name: 'User Project',
|
|
21
22
|
statuses: ['Todo', 'Done'],
|
|
22
23
|
customFieldNames: ['Status'],
|
|
24
|
+
statusFieldId: 'status-field-id-user',
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
beforeEach(() => {
|
|
@@ -27,6 +27,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
27
27
|
fields: {
|
|
28
28
|
nodes: [
|
|
29
29
|
{
|
|
30
|
+
id: 'status-field-id',
|
|
30
31
|
name: 'Status',
|
|
31
32
|
options: [
|
|
32
33
|
{ name: 'Awaiting workspace' },
|
|
@@ -34,7 +35,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
34
35
|
{ name: 'Done' },
|
|
35
36
|
],
|
|
36
37
|
},
|
|
37
|
-
{ name: 'workspace' },
|
|
38
|
+
{ id: 'workspace-field-id', name: 'workspace' },
|
|
38
39
|
],
|
|
39
40
|
},
|
|
40
41
|
},
|
|
@@ -51,6 +52,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
51
52
|
name: 'Test Project',
|
|
52
53
|
statuses: ['Awaiting workspace', 'Preparation', 'Done'],
|
|
53
54
|
customFieldNames: ['Status', 'workspace'],
|
|
55
|
+
statusFieldId: 'status-field-id',
|
|
54
56
|
});
|
|
55
57
|
|
|
56
58
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
@@ -80,6 +82,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
80
82
|
fields: {
|
|
81
83
|
nodes: [
|
|
82
84
|
{
|
|
85
|
+
id: 'user-status-field-id',
|
|
83
86
|
name: 'Status',
|
|
84
87
|
options: [{ name: 'Todo' }, { name: 'Done' }],
|
|
85
88
|
},
|
|
@@ -99,6 +102,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
99
102
|
name: 'User Project',
|
|
100
103
|
statuses: ['Todo', 'Done'],
|
|
101
104
|
customFieldNames: ['Status'],
|
|
105
|
+
statusFieldId: 'user-status-field-id',
|
|
102
106
|
});
|
|
103
107
|
|
|
104
108
|
expect(mockFetch).toHaveBeenCalledWith(
|
|
@@ -192,6 +196,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
192
196
|
fields: {
|
|
193
197
|
nodes: [
|
|
194
198
|
{
|
|
199
|
+
id: 'repo-status-field-id',
|
|
195
200
|
name: 'Status',
|
|
196
201
|
options: [{ name: 'Open' }, { name: 'Closed' }],
|
|
197
202
|
},
|
|
@@ -211,6 +216,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
211
216
|
name: 'Repo Project',
|
|
212
217
|
statuses: ['Open', 'Closed'],
|
|
213
218
|
customFieldNames: ['Status'],
|
|
219
|
+
statusFieldId: 'repo-status-field-id',
|
|
214
220
|
});
|
|
215
221
|
});
|
|
216
222
|
|
|
@@ -229,6 +235,7 @@ describe('GitHubProjectRepository', () => {
|
|
|
229
235
|
fields: {
|
|
230
236
|
nodes: [
|
|
231
237
|
{
|
|
238
|
+
id: 'priority-field-id',
|
|
232
239
|
name: 'Priority',
|
|
233
240
|
},
|
|
234
241
|
],
|
|
@@ -247,6 +254,150 @@ describe('GitHubProjectRepository', () => {
|
|
|
247
254
|
name: 'Project Without Status',
|
|
248
255
|
statuses: [],
|
|
249
256
|
customFieldNames: ['Priority'],
|
|
257
|
+
statusFieldId: null,
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
describe('prepareStatus', () => {
|
|
262
|
+
it('should return project as-is when status already exists', async () => {
|
|
263
|
+
const project = {
|
|
264
|
+
id: 'project-id',
|
|
265
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
266
|
+
name: 'Test Project',
|
|
267
|
+
statuses: ['Todo', 'In Progress', 'Done'],
|
|
268
|
+
customFieldNames: ['Status'],
|
|
269
|
+
statusFieldId: 'status-field-id',
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
const result = await repository.prepareStatus('In Progress', project);
|
|
273
|
+
|
|
274
|
+
expect(result).toEqual(project);
|
|
275
|
+
expect(mockFetch).not.toHaveBeenCalled();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should add new status when it does not exist', async () => {
|
|
279
|
+
const project = {
|
|
280
|
+
id: 'project-id',
|
|
281
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
282
|
+
name: 'Test Project',
|
|
283
|
+
statuses: ['Todo', 'Done'],
|
|
284
|
+
customFieldNames: ['Status'],
|
|
285
|
+
statusFieldId: 'status-field-id',
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
mockFetch.mockResolvedValueOnce({
|
|
289
|
+
ok: true,
|
|
290
|
+
json: jest.fn().mockResolvedValue({
|
|
291
|
+
data: {
|
|
292
|
+
updateProjectV2Field: {
|
|
293
|
+
projectV2Field: {
|
|
294
|
+
id: 'status-field-id',
|
|
295
|
+
name: 'Status',
|
|
296
|
+
options: [
|
|
297
|
+
{ name: 'Todo' },
|
|
298
|
+
{ name: 'Done' },
|
|
299
|
+
{ name: 'In Progress' },
|
|
300
|
+
],
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
},
|
|
304
|
+
}),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const result = await repository.prepareStatus('In Progress', project);
|
|
308
|
+
|
|
309
|
+
expect(result).toEqual({
|
|
310
|
+
...project,
|
|
311
|
+
statuses: ['Todo', 'Done', 'In Progress'],
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
expect(mockFetch).toHaveBeenCalledWith(
|
|
315
|
+
'https://api.github.com/graphql',
|
|
316
|
+
expect.objectContaining({
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: {
|
|
319
|
+
Authorization: 'Bearer test-token',
|
|
320
|
+
'Content-Type': 'application/json',
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should throw error when status field is not found', async () => {
|
|
327
|
+
const project = {
|
|
328
|
+
id: 'project-id',
|
|
329
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
330
|
+
name: 'Test Project',
|
|
331
|
+
statuses: [],
|
|
332
|
+
customFieldNames: ['Priority'],
|
|
333
|
+
statusFieldId: null,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
await expect(
|
|
337
|
+
repository.prepareStatus('New Status', project),
|
|
338
|
+
).rejects.toThrow('Status field not found in project "Test Project"');
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should throw error when GitHub API returns error', async () => {
|
|
342
|
+
const project = {
|
|
343
|
+
id: 'project-id',
|
|
344
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
345
|
+
name: 'Test Project',
|
|
346
|
+
statuses: ['Todo'],
|
|
347
|
+
customFieldNames: ['Status'],
|
|
348
|
+
statusFieldId: 'status-field-id',
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
mockFetch.mockResolvedValueOnce({
|
|
352
|
+
ok: false,
|
|
353
|
+
json: jest.fn().mockResolvedValue({ errors: ['API Error'] }),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
await expect(
|
|
357
|
+
repository.prepareStatus('New Status', project),
|
|
358
|
+
).rejects.toThrow('GitHub API error');
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('should throw error when response format is invalid', async () => {
|
|
362
|
+
const project = {
|
|
363
|
+
id: 'project-id',
|
|
364
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
365
|
+
name: 'Test Project',
|
|
366
|
+
statuses: ['Todo'],
|
|
367
|
+
customFieldNames: ['Status'],
|
|
368
|
+
statusFieldId: 'status-field-id',
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
mockFetch.mockResolvedValueOnce({
|
|
372
|
+
ok: true,
|
|
373
|
+
json: jest.fn().mockResolvedValue(null),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await expect(
|
|
377
|
+
repository.prepareStatus('New Status', project),
|
|
378
|
+
).rejects.toThrow('Invalid API response format');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('should throw error when response has GraphQL errors', async () => {
|
|
382
|
+
const project = {
|
|
383
|
+
id: 'project-id',
|
|
384
|
+
url: 'https://github.com/orgs/test-org/projects/1',
|
|
385
|
+
name: 'Test Project',
|
|
386
|
+
statuses: ['Todo'],
|
|
387
|
+
customFieldNames: ['Status'],
|
|
388
|
+
statusFieldId: 'status-field-id',
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
mockFetch.mockResolvedValueOnce({
|
|
392
|
+
ok: true,
|
|
393
|
+
json: jest.fn().mockResolvedValue({
|
|
394
|
+
errors: [{ message: 'Permission denied' }],
|
|
395
|
+
}),
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
await expect(
|
|
399
|
+
repository.prepareStatus('New Status', project),
|
|
400
|
+
).rejects.toThrow('GraphQL errors');
|
|
250
401
|
});
|
|
251
402
|
});
|
|
252
403
|
});
|
|
@@ -2,6 +2,7 @@ import { ProjectRepository } from '../../domain/usecases/adapter-interfaces/Proj
|
|
|
2
2
|
import { Project } from '../../domain/entities/Project';
|
|
3
3
|
|
|
4
4
|
type GitHubProjectField = {
|
|
5
|
+
id?: string;
|
|
5
6
|
name: string;
|
|
6
7
|
options?: Array<{ name: string }>;
|
|
7
8
|
};
|
|
@@ -80,12 +81,14 @@ export class GitHubProjectRepository implements ProjectRepository {
|
|
|
80
81
|
fields(first: 100) {
|
|
81
82
|
nodes {
|
|
82
83
|
... on ProjectV2SingleSelectField {
|
|
84
|
+
id
|
|
83
85
|
name
|
|
84
86
|
options {
|
|
85
87
|
name
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
... on ProjectV2Field {
|
|
91
|
+
id
|
|
89
92
|
name
|
|
90
93
|
}
|
|
91
94
|
}
|
|
@@ -100,12 +103,14 @@ export class GitHubProjectRepository implements ProjectRepository {
|
|
|
100
103
|
fields(first: 100) {
|
|
101
104
|
nodes {
|
|
102
105
|
... on ProjectV2SingleSelectField {
|
|
106
|
+
id
|
|
103
107
|
name
|
|
104
108
|
options {
|
|
105
109
|
name
|
|
106
110
|
}
|
|
107
111
|
}
|
|
108
112
|
... on ProjectV2Field {
|
|
113
|
+
id
|
|
109
114
|
name
|
|
110
115
|
}
|
|
111
116
|
}
|
|
@@ -157,6 +162,88 @@ export class GitHubProjectRepository implements ProjectRepository {
|
|
|
157
162
|
name: project.title,
|
|
158
163
|
statuses,
|
|
159
164
|
customFieldNames: fields.map((f) => f.name),
|
|
165
|
+
statusFieldId: statusField?.id ?? null,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async prepareStatus(name: string, project: Project): Promise<Project> {
|
|
170
|
+
if (project.statuses.includes(name)) {
|
|
171
|
+
return project;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!project.statusFieldId) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Status field not found in project "${project.name}". ` +
|
|
177
|
+
`Cannot add status "${name}".`,
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const existingOptions = project.statuses.map((statusName) => ({
|
|
182
|
+
name: statusName,
|
|
183
|
+
color: 'GRAY',
|
|
184
|
+
description: '',
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
const newOptions = [
|
|
188
|
+
...existingOptions,
|
|
189
|
+
{ name, color: 'GRAY', description: '' },
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
const mutation = `
|
|
193
|
+
mutation($fieldId: ID!, $singleSelectOptions: [ProjectV2SingleSelectFieldOptionInput!]!) {
|
|
194
|
+
updateProjectV2Field(input: {
|
|
195
|
+
fieldId: $fieldId
|
|
196
|
+
singleSelectOptions: $singleSelectOptions
|
|
197
|
+
}) {
|
|
198
|
+
projectV2Field {
|
|
199
|
+
... on ProjectV2SingleSelectField {
|
|
200
|
+
id
|
|
201
|
+
name
|
|
202
|
+
options {
|
|
203
|
+
name
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
`;
|
|
210
|
+
|
|
211
|
+
const response = await fetch('https://api.github.com/graphql', {
|
|
212
|
+
method: 'POST',
|
|
213
|
+
headers: {
|
|
214
|
+
Authorization: `Bearer ${this.token}`,
|
|
215
|
+
'Content-Type': 'application/json',
|
|
216
|
+
},
|
|
217
|
+
body: JSON.stringify({
|
|
218
|
+
query: mutation,
|
|
219
|
+
variables: {
|
|
220
|
+
fieldId: project.statusFieldId,
|
|
221
|
+
singleSelectOptions: newOptions,
|
|
222
|
+
},
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const responseData: unknown = await response.json();
|
|
227
|
+
|
|
228
|
+
if (!isGitHubApiResponse(responseData)) {
|
|
229
|
+
throw new Error('Invalid API response format');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!response.ok) {
|
|
233
|
+
throw new Error(`GitHub API error: ${JSON.stringify(responseData)}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (
|
|
237
|
+
typeof responseData === 'object' &&
|
|
238
|
+
responseData !== null &&
|
|
239
|
+
'errors' in responseData
|
|
240
|
+
) {
|
|
241
|
+
throw new Error(`GraphQL errors: ${JSON.stringify(responseData.errors)}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
...project,
|
|
246
|
+
statuses: [...project.statuses, name],
|
|
160
247
|
};
|
|
161
248
|
}
|
|
162
249
|
}
|
|
@@ -17,6 +17,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
17
17
|
name: 'Test Project',
|
|
18
18
|
statuses: ['Preparation', 'Awaiting Quality Check', 'Done'],
|
|
19
19
|
customFieldNames: ['workspace'],
|
|
20
|
+
statusFieldId: 'status-field-id',
|
|
20
21
|
};
|
|
21
22
|
|
|
22
23
|
beforeEach(() => {
|
|
@@ -24,6 +25,7 @@ describe('NotifyFinishedIssuePreparationUseCase', () => {
|
|
|
24
25
|
|
|
25
26
|
mockProjectRepository = {
|
|
26
27
|
getByUrl: jest.fn(),
|
|
28
|
+
prepareStatus: jest.fn(),
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
mockIssueRepository = {
|
|
@@ -16,11 +16,13 @@ describe('StartPreparationUseCase', () => {
|
|
|
16
16
|
name: 'Test Project',
|
|
17
17
|
statuses: ['Awaiting Workspace', 'Preparation', 'Done'],
|
|
18
18
|
customFieldNames: ['workspace'],
|
|
19
|
+
statusFieldId: 'status-field-id',
|
|
19
20
|
};
|
|
20
21
|
beforeEach(() => {
|
|
21
22
|
jest.resetAllMocks();
|
|
22
23
|
mockProjectRepository = {
|
|
23
24
|
getByUrl: jest.fn(),
|
|
25
|
+
prepareStatus: jest.fn(),
|
|
24
26
|
};
|
|
25
27
|
mockIssueRepository = {
|
|
26
28
|
getAllOpened: jest.fn(),
|
|
@@ -58,6 +60,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
58
60
|
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
59
61
|
preparationStatus: 'Preparation',
|
|
60
62
|
defaultAgentName: 'agent1',
|
|
63
|
+
maximumPreparingIssuesCount: null,
|
|
61
64
|
});
|
|
62
65
|
expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
|
|
63
66
|
expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
|
|
@@ -99,6 +102,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
99
102
|
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
100
103
|
preparationStatus: 'Preparation',
|
|
101
104
|
defaultAgentName: 'agent1',
|
|
105
|
+
maximumPreparingIssuesCount: null,
|
|
102
106
|
});
|
|
103
107
|
expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
|
|
104
108
|
expect(mockIssueRepository.update.mock.calls[0][0]).toMatchObject({
|
|
@@ -135,6 +139,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
135
139
|
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
136
140
|
preparationStatus: 'Preparation',
|
|
137
141
|
defaultAgentName: 'agent1',
|
|
142
|
+
maximumPreparingIssuesCount: null,
|
|
138
143
|
});
|
|
139
144
|
const issue7UpdateCalls = mockIssueRepository.update.mock.calls.filter(
|
|
140
145
|
(call) => call[0].id === '7',
|
|
@@ -165,6 +170,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
165
170
|
preparationStatus: 'Preparation',
|
|
166
171
|
defaultAgentName: 'agent1',
|
|
167
172
|
logFilePath: '/path/to/log.txt',
|
|
173
|
+
maximumPreparingIssuesCount: null,
|
|
168
174
|
});
|
|
169
175
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
170
176
|
expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
|
|
@@ -193,6 +199,7 @@ describe('StartPreparationUseCase', () => {
|
|
|
193
199
|
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
194
200
|
preparationStatus: 'Preparation',
|
|
195
201
|
defaultAgentName: 'agent1',
|
|
202
|
+
maximumPreparingIssuesCount: null,
|
|
196
203
|
});
|
|
197
204
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
198
205
|
expect(mockLocalCommandRunner.runCommand.mock.calls[0][0]).toBe(
|
|
@@ -236,8 +243,59 @@ describe('StartPreparationUseCase', () => {
|
|
|
236
243
|
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
237
244
|
preparationStatus: 'Preparation',
|
|
238
245
|
defaultAgentName: 'agent1',
|
|
246
|
+
maximumPreparingIssuesCount: null,
|
|
239
247
|
});
|
|
240
248
|
expect(mockIssueRepository.update.mock.calls).toHaveLength(1);
|
|
241
249
|
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(1);
|
|
242
250
|
});
|
|
251
|
+
it('should use custom maximumPreparingIssuesCount when provided', async () => {
|
|
252
|
+
const awaitingIssues: Issue[] = Array.from({ length: 10 }, (_, i) => ({
|
|
253
|
+
id: `${i + 1}`,
|
|
254
|
+
url: `url${i + 1}`,
|
|
255
|
+
title: `Issue ${i + 1}`,
|
|
256
|
+
labels: [],
|
|
257
|
+
status: 'Awaiting Workspace',
|
|
258
|
+
}));
|
|
259
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
260
|
+
mockIssueRepository.getAllOpened.mockResolvedValueOnce(awaitingIssues);
|
|
261
|
+
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
262
|
+
stdout: '',
|
|
263
|
+
stderr: '',
|
|
264
|
+
exitCode: 0,
|
|
265
|
+
});
|
|
266
|
+
await useCase.run({
|
|
267
|
+
projectUrl: 'https://github.com/user/repo',
|
|
268
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
269
|
+
preparationStatus: 'Preparation',
|
|
270
|
+
defaultAgentName: 'agent1',
|
|
271
|
+
maximumPreparingIssuesCount: 3,
|
|
272
|
+
});
|
|
273
|
+
expect(mockIssueRepository.update.mock.calls).toHaveLength(3);
|
|
274
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(3);
|
|
275
|
+
});
|
|
276
|
+
it('should use default maximumPreparingIssuesCount of 6 when null is provided', async () => {
|
|
277
|
+
const awaitingIssues: Issue[] = Array.from({ length: 12 }, (_, i) => ({
|
|
278
|
+
id: `${i + 1}`,
|
|
279
|
+
url: `url${i + 1}`,
|
|
280
|
+
title: `Issue ${i + 1}`,
|
|
281
|
+
labels: [],
|
|
282
|
+
status: 'Awaiting Workspace',
|
|
283
|
+
}));
|
|
284
|
+
mockProjectRepository.getByUrl.mockResolvedValue(mockProject);
|
|
285
|
+
mockIssueRepository.getAllOpened.mockResolvedValueOnce(awaitingIssues);
|
|
286
|
+
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
287
|
+
stdout: '',
|
|
288
|
+
stderr: '',
|
|
289
|
+
exitCode: 0,
|
|
290
|
+
});
|
|
291
|
+
await useCase.run({
|
|
292
|
+
projectUrl: 'https://github.com/user/repo',
|
|
293
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
294
|
+
preparationStatus: 'Preparation',
|
|
295
|
+
defaultAgentName: 'agent1',
|
|
296
|
+
maximumPreparingIssuesCount: null,
|
|
297
|
+
});
|
|
298
|
+
expect(mockIssueRepository.update.mock.calls).toHaveLength(6);
|
|
299
|
+
expect(mockLocalCommandRunner.runCommand.mock.calls).toHaveLength(6);
|
|
300
|
+
});
|
|
243
301
|
});
|
|
@@ -3,7 +3,6 @@ import { ProjectRepository } from './adapter-interfaces/ProjectRepository';
|
|
|
3
3
|
import { LocalCommandRunner } from './adapter-interfaces/LocalCommandRunner';
|
|
4
4
|
|
|
5
5
|
export class StartPreparationUseCase {
|
|
6
|
-
maximumPreparingIssuesCount = 6;
|
|
7
6
|
constructor(
|
|
8
7
|
private readonly projectRepository: ProjectRepository,
|
|
9
8
|
private readonly issueRepository: IssueRepository,
|
|
@@ -16,7 +15,9 @@ export class StartPreparationUseCase {
|
|
|
16
15
|
preparationStatus: string;
|
|
17
16
|
defaultAgentName: string;
|
|
18
17
|
logFilePath?: string;
|
|
18
|
+
maximumPreparingIssuesCount: number | null;
|
|
19
19
|
}): Promise<void> => {
|
|
20
|
+
const maximumPreparingIssuesCount = params.maximumPreparingIssuesCount ?? 6;
|
|
20
21
|
const project = await this.projectRepository.getByUrl(params.projectUrl);
|
|
21
22
|
|
|
22
23
|
const allIssues = await this.issueRepository.getAllOpened(project);
|
|
@@ -32,7 +33,7 @@ export class StartPreparationUseCase {
|
|
|
32
33
|
let i = currentPreparationIssueCount;
|
|
33
34
|
i <
|
|
34
35
|
Math.min(
|
|
35
|
-
|
|
36
|
+
maximumPreparingIssuesCount,
|
|
36
37
|
awaitingWorkspaceIssues.length + currentPreparationIssueCount,
|
|
37
38
|
);
|
|
38
39
|
i++
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/cli/index.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AA2G9B,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
|
@@ -5,5 +5,6 @@ export declare class GitHubProjectRepository implements ProjectRepository {
|
|
|
5
5
|
constructor(token: string);
|
|
6
6
|
private parseGitHubProjectUrl;
|
|
7
7
|
getByUrl(url: string): Promise<Project>;
|
|
8
|
+
prepareStatus(name: string, project: Project): Promise<Project>;
|
|
8
9
|
}
|
|
9
10
|
//# sourceMappingURL=GitHubProjectRepository.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GitHubProjectRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubProjectRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"GitHubProjectRepository.d.ts","sourceRoot":"","sources":["../../../src/adapter/repositories/GitHubProjectRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,4DAA4D,CAAC;AAC/F,OAAO,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAiCxD,qBAAa,uBAAwB,YAAW,iBAAiB;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,MAAM;IAE1C,OAAO,CAAC,qBAAqB;IAiCvB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAkGvC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;CAgFtE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/Project.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"Project.d.ts","sourceRoot":"","sources":["../../../src/domain/entities/Project.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B,CAAC"}
|
|
@@ -5,7 +5,6 @@ export declare class StartPreparationUseCase {
|
|
|
5
5
|
private readonly projectRepository;
|
|
6
6
|
private readonly issueRepository;
|
|
7
7
|
private readonly localCommandRunner;
|
|
8
|
-
maximumPreparingIssuesCount: number;
|
|
9
8
|
constructor(projectRepository: ProjectRepository, issueRepository: IssueRepository, localCommandRunner: LocalCommandRunner);
|
|
10
9
|
run: (params: {
|
|
11
10
|
projectUrl: string;
|
|
@@ -13,6 +12,7 @@ export declare class StartPreparationUseCase {
|
|
|
13
12
|
preparationStatus: string;
|
|
14
13
|
defaultAgentName: string;
|
|
15
14
|
logFilePath?: string;
|
|
15
|
+
maximumPreparingIssuesCount: number | null;
|
|
16
16
|
}) => Promise<void>;
|
|
17
17
|
}
|
|
18
18
|
//# sourceMappingURL=StartPreparationUseCase.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,qBAAa,uBAAuB;
|
|
1
|
+
{"version":3,"file":"StartPreparationUseCase.d.ts","sourceRoot":"","sources":["../../../src/domain/usecases/StartPreparationUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAC3E,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAE7E,qBAAa,uBAAuB;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,kBAAkB;gBAFlB,iBAAiB,EAAE,iBAAiB,EACpC,eAAe,EAAE,eAAe,EAChC,kBAAkB,EAAE,kBAAkB;IAGzD,GAAG,GAAU,QAAQ;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,uBAAuB,EAAE,MAAM,CAAC;QAChC,iBAAiB,EAAE,MAAM,CAAC;QAC1B,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;KAC5C,KAAG,OAAO,CAAC,IAAI,CAAC,CAyCf;CACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectRepository.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ProjectRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"ProjectRepository.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/adapter-interfaces/ProjectRepository.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjE"}
|