github-issue-tower-defence-management 1.41.0 → 1.42.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/CHANGELOG.md +7 -0
- package/README.md +6 -0
- package/bin/adapter/entry-points/cli/index.js +23 -0
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +18 -6
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +1 -0
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +10 -2
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.test.ts +158 -0
- package/src/adapter/entry-points/cli/index.ts +31 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +2 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +42 -0
- package/src/domain/usecases/StartPreparationUseCase.test.ts +277 -0
- package/src/domain/usecases/StartPreparationUseCase.ts +16 -2
- package/types/adapter/entry-points/cli/index.d.ts +1 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +1 -0
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +1 -0
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
|
@@ -37,6 +37,7 @@ type ConfigFile = {
|
|
|
37
37
|
workflowBlockerResolvedWebhookUrl?: string;
|
|
38
38
|
projectName?: string;
|
|
39
39
|
preparationProcessCheckCommand?: string;
|
|
40
|
+
codexHomeCandidates?: string[];
|
|
40
41
|
};
|
|
41
42
|
|
|
42
43
|
type StartDaemonOptions = {
|
|
@@ -81,6 +82,24 @@ const getNumberValue = (
|
|
|
81
82
|
return typeof value === 'number' ? value : undefined;
|
|
82
83
|
};
|
|
83
84
|
|
|
85
|
+
const getStringArrayValue = (
|
|
86
|
+
obj: Record<string, unknown>,
|
|
87
|
+
key: string,
|
|
88
|
+
): string[] | undefined => {
|
|
89
|
+
const value = obj[key];
|
|
90
|
+
if (!Array.isArray(value)) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const strings: string[] = [];
|
|
94
|
+
for (const item of value) {
|
|
95
|
+
if (typeof item !== 'string') {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
strings.push(item);
|
|
99
|
+
}
|
|
100
|
+
return strings;
|
|
101
|
+
};
|
|
102
|
+
|
|
84
103
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
85
104
|
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
86
105
|
|
|
@@ -125,6 +144,7 @@ export const loadConfigFile = (configFilePath: string): ConfigFile => {
|
|
|
125
144
|
parsed,
|
|
126
145
|
'preparationProcessCheckCommand',
|
|
127
146
|
),
|
|
147
|
+
codexHomeCandidates: getStringArrayValue(parsed, 'codexHomeCandidates'),
|
|
128
148
|
};
|
|
129
149
|
} catch (error) {
|
|
130
150
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -183,6 +203,7 @@ export const parseProjectReadmeConfig = (readme: string): ConfigFile => {
|
|
|
183
203
|
parsed,
|
|
184
204
|
'preparationProcessCheckCommand',
|
|
185
205
|
),
|
|
206
|
+
codexHomeCandidates: getStringArrayValue(parsed, 'codexHomeCandidates'),
|
|
186
207
|
};
|
|
187
208
|
} catch {
|
|
188
209
|
console.warn('Failed to parse YAML from project README config section');
|
|
@@ -249,6 +270,10 @@ export const mergeConfigs = (
|
|
|
249
270
|
readmeOverrides.preparationProcessCheckCommand ??
|
|
250
271
|
cliOverrides.preparationProcessCheckCommand ??
|
|
251
272
|
configFile.preparationProcessCheckCommand,
|
|
273
|
+
codexHomeCandidates:
|
|
274
|
+
readmeOverrides.codexHomeCandidates ??
|
|
275
|
+
cliOverrides.codexHomeCandidates ??
|
|
276
|
+
configFile.codexHomeCandidates,
|
|
252
277
|
});
|
|
253
278
|
|
|
254
279
|
type GraphqlProjectV2ReadmeResponse = {
|
|
@@ -585,6 +610,11 @@ program
|
|
|
585
610
|
.filter(Boolean)
|
|
586
611
|
: null;
|
|
587
612
|
|
|
613
|
+
const codexHomeCandidates =
|
|
614
|
+
config.codexHomeCandidates && config.codexHomeCandidates.length > 0
|
|
615
|
+
? config.codexHomeCandidates
|
|
616
|
+
: null;
|
|
617
|
+
|
|
588
618
|
await useCase.run({
|
|
589
619
|
projectUrl,
|
|
590
620
|
awaitingWorkspaceStatus,
|
|
@@ -597,6 +627,7 @@ program
|
|
|
597
627
|
utilizationPercentageThreshold:
|
|
598
628
|
config.utilizationPercentageThreshold ?? 90,
|
|
599
629
|
allowedIssueAuthors,
|
|
630
|
+
codexHomeCandidates,
|
|
600
631
|
});
|
|
601
632
|
});
|
|
602
633
|
|
|
@@ -80,6 +80,7 @@ export class HandleScheduledEventUseCase {
|
|
|
80
80
|
utilizationPercentageThreshold?: number;
|
|
81
81
|
allowedIssueAuthors?: string[] | null;
|
|
82
82
|
preparationProcessCheckCommand?: string;
|
|
83
|
+
codexHomeCandidates?: string[] | null;
|
|
83
84
|
} | null;
|
|
84
85
|
notifyFinishedPreparation?: {
|
|
85
86
|
preparationStatus: string;
|
|
@@ -356,6 +357,7 @@ ${JSON.stringify(e)}
|
|
|
356
357
|
utilizationPercentageThreshold:
|
|
357
358
|
input.startPreparation.utilizationPercentageThreshold ?? 90,
|
|
358
359
|
allowedIssueAuthors: input.startPreparation.allowedIssueAuthors ?? null,
|
|
360
|
+
codexHomeCandidates: input.startPreparation.codexHomeCandidates ?? null,
|
|
359
361
|
});
|
|
360
362
|
}
|
|
361
363
|
if (input.notifyFinishedPreparation) {
|
|
@@ -259,6 +259,48 @@ describe('RevertOrphanedPreparationUseCase', () => {
|
|
|
259
259
|
).rejects.toThrow('Project not found');
|
|
260
260
|
});
|
|
261
261
|
|
|
262
|
+
it('should throw when getProject returns null after findProjectIdByUrl succeeds', async () => {
|
|
263
|
+
mockProjectRepository.findProjectIdByUrl.mockResolvedValue('project-1');
|
|
264
|
+
mockProjectRepository.getProject.mockResolvedValue(null);
|
|
265
|
+
|
|
266
|
+
await expect(
|
|
267
|
+
useCase.run({
|
|
268
|
+
projectUrl: 'https://github.com/user/repo',
|
|
269
|
+
preparationStatus: 'Preparation',
|
|
270
|
+
awaitingWorkspaceStatus: 'Awaiting Workspace',
|
|
271
|
+
allowIssueCacheMinutes: 0,
|
|
272
|
+
preparationProcessCheckCommand: 'check {URL}',
|
|
273
|
+
}),
|
|
274
|
+
).rejects.toThrow('Project not found. projectId: project-1');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should do nothing when awaitingWorkspaceStatus is not found in project statuses', async () => {
|
|
278
|
+
const preparationIssue = createMockIssue({
|
|
279
|
+
url: 'https://github.com/user/repo/issues/10',
|
|
280
|
+
status: 'Preparation',
|
|
281
|
+
});
|
|
282
|
+
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
283
|
+
issues: [preparationIssue],
|
|
284
|
+
cacheUsed: false,
|
|
285
|
+
});
|
|
286
|
+
mockLocalCommandRunner.runCommand.mockResolvedValue({
|
|
287
|
+
stdout: '',
|
|
288
|
+
stderr: '',
|
|
289
|
+
exitCode: 1,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await useCase.run({
|
|
293
|
+
projectUrl: 'https://github.com/user/repo',
|
|
294
|
+
preparationStatus: 'Preparation',
|
|
295
|
+
awaitingWorkspaceStatus: 'NonExistentStatus',
|
|
296
|
+
allowIssueCacheMinutes: 0,
|
|
297
|
+
preparationProcessCheckCommand: 'check {URL}',
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(mockIssueRepository.updateStatus.mock.calls).toHaveLength(0);
|
|
301
|
+
expect(mockIssueRepository.createComment.mock.calls).toHaveLength(0);
|
|
302
|
+
});
|
|
303
|
+
|
|
262
304
|
it('should do nothing when there are no Preparation issues', async () => {
|
|
263
305
|
mockIssueRepository.getAllIssues.mockResolvedValue({
|
|
264
306
|
issues: [
|