github-issue-tower-defence-management 1.60.1 → 1.63.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.
- package/.github/workflows/publish.yml +13 -0
- package/.github/workflows/test.yml +0 -4
- package/CHANGELOG.md +14 -0
- package/README.md +53 -10
- package/bin/adapter/entry-points/cli/index.js +11 -11
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js +3 -22
- package/bin/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +8 -22
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js +56 -0
- package/bin/adapter/entry-points/handlers/rotationOrderFileWriter.js.map +1 -0
- package/bin/adapter/entry-points/handlers/situationFileWriter.js +5 -0
- package/bin/adapter/entry-points/handlers/situationFileWriter.js.map +1 -1
- package/bin/adapter/proxy/TokenListLoader.js +21 -6
- package/bin/adapter/proxy/TokenListLoader.js.map +1 -1
- package/bin/adapter/proxy/proxyEntry.js +1 -0
- package/bin/adapter/proxy/proxyEntry.js.map +1 -1
- package/bin/adapter/repositories/BaseGitHubRepository.js +1 -113
- package/bin/adapter/repositories/BaseGitHubRepository.js.map +1 -1
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js +5 -3
- package/bin/adapter/repositories/ProxyClaudeTokenUsageRepository.js.map +1 -1
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js +8 -7
- package/bin/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.js.map +1 -1
- package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js +19 -9
- package/bin/domain/usecases/CreateNewStoryByLabelUseCase.js.map +1 -1
- package/bin/domain/usecases/HandleScheduledEventUseCase.js +15 -3
- package/bin/domain/usecases/HandleScheduledEventUseCase.js.map +1 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js +8 -1
- package/bin/domain/usecases/IssueRejectionEvaluator.js.map +1 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js +5 -1
- package/bin/domain/usecases/NotifyFinishedIssuePreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js +1 -1
- package/bin/domain/usecases/RevertOrphanedPreparationUseCase.js.map +1 -1
- package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js +32 -1
- package/bin/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.js.map +1 -1
- package/bin/domain/usecases/StartPreparationUseCase.js +91 -12
- package/bin/domain/usecases/StartPreparationUseCase.js.map +1 -1
- package/package.json +1 -4
- package/src/adapter/entry-points/cli/index.test.ts +16 -16
- package/src/adapter/entry-points/cli/index.ts +8 -11
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.test.ts +2 -55
- package/src/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.ts +1 -11
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.test.ts +6 -56
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +7 -11
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.test.ts +177 -0
- package/src/adapter/entry-points/handlers/rotationOrderFileWriter.ts +20 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.test.ts +36 -0
- package/src/adapter/entry-points/handlers/situationFileWriter.ts +8 -0
- package/src/adapter/proxy/TokenListLoader.test.ts +50 -1
- package/src/adapter/proxy/TokenListLoader.ts +25 -5
- package/src/adapter/proxy/proxyEntry.test.ts +270 -1
- package/src/adapter/proxy/proxyEntry.ts +2 -1
- package/src/adapter/repositories/BaseGitHubRepository.test.ts +1 -186
- package/src/adapter/repositories/BaseGitHubRepository.ts +1 -139
- package/src/adapter/repositories/GraphqlProjectRepository.errorHandling.test.ts +0 -1
- package/src/adapter/repositories/GraphqlProjectRepository.fetchProjectId.test.ts +4 -1
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.test.ts +60 -19
- package/src/adapter/repositories/ProxyClaudeTokenUsageRepository.ts +6 -4
- package/src/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.ts +23 -13
- package/src/adapter/repositories/issue/ApiV3IssueRepository.test.ts +0 -1
- package/src/adapter/repositories/issue/GraphqlProjectItemRepository.test.ts +0 -8
- package/src/adapter/repositories/issue/RestIssueRepository.test.ts +0 -1
- package/src/domain/entities/ClaudeTokenUsage.ts +1 -0
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.test.ts +196 -11
- package/src/domain/usecases/CreateNewStoryByLabelUseCase.ts +32 -15
- package/src/domain/usecases/HandleScheduledEventUseCase.test.ts +4 -0
- package/src/domain/usecases/HandleScheduledEventUseCase.ts +21 -5
- package/src/domain/usecases/IssueRejectionEvaluator.test.ts +153 -0
- package/src/domain/usecases/IssueRejectionEvaluator.ts +8 -0
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.test.ts +175 -31
- package/src/domain/usecases/NotifyFinishedIssuePreparationUseCase.ts +7 -1
- package/src/domain/usecases/RevertNotReadyAwaitingQualityCheckUseCase.test.ts +32 -0
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.test.ts +39 -5
- package/src/domain/usecases/RevertOrphanedPreparationUseCase.ts +1 -1
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.test.ts +139 -20
- package/src/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.ts +62 -2
- package/src/domain/usecases/StartPreparationUseCase.test.ts +404 -21
- package/src/domain/usecases/StartPreparationUseCase.ts +152 -16
- package/src/domain/usecases/adapter-interfaces/IssueRepository.ts +16 -0
- package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/GetStoryObjectMapUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts +3 -0
- package/types/adapter/entry-points/handlers/rotationOrderFileWriter.d.ts.map +1 -0
- package/types/adapter/entry-points/handlers/situationFileWriter.d.ts +1 -0
- package/types/adapter/entry-points/handlers/situationFileWriter.d.ts.map +1 -1
- package/types/adapter/proxy/TokenListLoader.d.ts +5 -0
- package/types/adapter/proxy/TokenListLoader.d.ts.map +1 -1
- package/types/adapter/proxy/proxyEntry.d.ts +2 -1
- package/types/adapter/proxy/proxyEntry.d.ts.map +1 -1
- package/types/adapter/repositories/BaseGitHubRepository.d.ts +1 -23
- package/types/adapter/repositories/BaseGitHubRepository.d.ts.map +1 -1
- package/types/adapter/repositories/ProxyClaudeTokenUsageRepository.d.ts.map +1 -1
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts +14 -5
- package/types/adapter/repositories/issue/ApiV3CheerioRestIssueRepository.d.ts.map +1 -1
- package/types/domain/entities/ClaudeTokenUsage.d.ts +1 -0
- package/types/domain/entities/ClaudeTokenUsage.d.ts.map +1 -1
- package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts +4 -2
- package/types/domain/usecases/CreateNewStoryByLabelUseCase.d.ts.map +1 -1
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts +5 -2
- package/types/domain/usecases/HandleScheduledEventUseCase.d.ts.map +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts +1 -1
- package/types/domain/usecases/IssueRejectionEvaluator.d.ts.map +1 -1
- package/types/domain/usecases/NotifyFinishedIssuePreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts +5 -2
- package/types/domain/usecases/SetWorkflowManagementIssueToStoryUseCase.d.ts.map +1 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts +15 -1
- package/types/domain/usecases/StartPreparationUseCase.d.ts.map +1 -1
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts +14 -0
- package/types/domain/usecases/adapter-interfaces/IssueRepository.d.ts.map +1 -1
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js +0 -136
- package/bin/adapter/repositories/issue/CheerioIssueRepository.js.map +0 -1
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js +0 -1606
- package/bin/adapter/repositories/issue/InternalGraphqlIssueRepository.js.map +0 -1
- package/src/adapter/repositories/issue/CheerioIssueRepository.test.ts +0 -6552
- package/src/adapter/repositories/issue/CheerioIssueRepository.ts +0 -142
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.test.ts +0 -118
- package/src/adapter/repositories/issue/InternalGraphqlIssueRepository.ts +0 -584
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts +0 -40
- package/types/adapter/repositories/issue/CheerioIssueRepository.d.ts.map +0 -1
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts +0 -220
- package/types/adapter/repositories/issue/InternalGraphqlIssueRepository.d.ts.map +0 -1
|
@@ -475,7 +475,7 @@ codexHomeCandidates:
|
|
|
475
475
|
|
|
476
476
|
describe('startDaemon', () => {
|
|
477
477
|
it('should read parameters from config file', async () => {
|
|
478
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
478
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
479
479
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
480
480
|
StartPreparationUseCase,
|
|
481
481
|
);
|
|
@@ -511,7 +511,7 @@ codexHomeCandidates:
|
|
|
511
511
|
});
|
|
512
512
|
|
|
513
513
|
it('should allow CLI args to override config file values', async () => {
|
|
514
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
514
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
515
515
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
516
516
|
StartPreparationUseCase,
|
|
517
517
|
);
|
|
@@ -558,7 +558,7 @@ codexHomeCandidates:
|
|
|
558
558
|
};
|
|
559
559
|
writeConfig(configWithLlm);
|
|
560
560
|
|
|
561
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
561
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
562
562
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
563
563
|
StartPreparationUseCase,
|
|
564
564
|
);
|
|
@@ -593,7 +593,7 @@ codexHomeCandidates:
|
|
|
593
593
|
};
|
|
594
594
|
writeConfig(configWithCount);
|
|
595
595
|
|
|
596
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
596
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
597
597
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
598
598
|
StartPreparationUseCase,
|
|
599
599
|
);
|
|
@@ -621,7 +621,7 @@ codexHomeCandidates:
|
|
|
621
621
|
});
|
|
622
622
|
|
|
623
623
|
it('should pass maximumPreparingIssuesCount from CLI overriding config', async () => {
|
|
624
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
624
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
625
625
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
626
626
|
StartPreparationUseCase,
|
|
627
627
|
);
|
|
@@ -815,7 +815,7 @@ codexHomeCandidates:
|
|
|
815
815
|
});
|
|
816
816
|
const mockRun = jest.fn().mockImplementation(() => {
|
|
817
817
|
callOrder.push('useCase.run');
|
|
818
|
-
return Promise.resolve(
|
|
818
|
+
return Promise.resolve({ rotationOrder: null });
|
|
819
819
|
});
|
|
820
820
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
821
821
|
StartPreparationUseCase,
|
|
@@ -857,7 +857,7 @@ codexHomeCandidates:
|
|
|
857
857
|
].join('\n');
|
|
858
858
|
mockFetchReturningReadme(readmeContent);
|
|
859
859
|
|
|
860
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
860
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
861
861
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
862
862
|
StartPreparationUseCase,
|
|
863
863
|
);
|
|
@@ -891,7 +891,7 @@ codexHomeCandidates:
|
|
|
891
891
|
};
|
|
892
892
|
writeConfig(configWithCandidates);
|
|
893
893
|
|
|
894
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
894
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
895
895
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
896
896
|
StartPreparationUseCase,
|
|
897
897
|
);
|
|
@@ -919,7 +919,7 @@ codexHomeCandidates:
|
|
|
919
919
|
});
|
|
920
920
|
|
|
921
921
|
it('should pass codexHomeCandidates as null when not in config', async () => {
|
|
922
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
922
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
923
923
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
924
924
|
StartPreparationUseCase,
|
|
925
925
|
);
|
|
@@ -958,7 +958,7 @@ codexHomeCandidates:
|
|
|
958
958
|
].join('\n');
|
|
959
959
|
mockFetchReturningReadme(readmeContent);
|
|
960
960
|
|
|
961
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
961
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
962
962
|
const MockedStartPreparationUseCase = jest.mocked(
|
|
963
963
|
StartPreparationUseCase,
|
|
964
964
|
);
|
|
@@ -988,7 +988,7 @@ codexHomeCandidates:
|
|
|
988
988
|
|
|
989
989
|
describe('notifyFinishedIssuePreparation', () => {
|
|
990
990
|
it('should read parameters from config file', async () => {
|
|
991
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
991
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
992
992
|
const MockedNotifyFinishedUseCase = jest.mocked(
|
|
993
993
|
NotifyFinishedIssuePreparationUseCase,
|
|
994
994
|
);
|
|
@@ -1020,7 +1020,7 @@ codexHomeCandidates:
|
|
|
1020
1020
|
});
|
|
1021
1021
|
|
|
1022
1022
|
it('should allow CLI args to override config file values', async () => {
|
|
1023
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
1023
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
1024
1024
|
const MockedNotifyFinishedUseCase = jest.mocked(
|
|
1025
1025
|
NotifyFinishedIssuePreparationUseCase,
|
|
1026
1026
|
);
|
|
@@ -1059,7 +1059,7 @@ codexHomeCandidates:
|
|
|
1059
1059
|
};
|
|
1060
1060
|
writeConfig(configWithThreshold);
|
|
1061
1061
|
|
|
1062
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
1062
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
1063
1063
|
const MockedNotifyFinishedUseCase = jest.mocked(
|
|
1064
1064
|
NotifyFinishedIssuePreparationUseCase,
|
|
1065
1065
|
);
|
|
@@ -1089,7 +1089,7 @@ codexHomeCandidates:
|
|
|
1089
1089
|
});
|
|
1090
1090
|
|
|
1091
1091
|
it('should pass custom thresholdForAutoReject from CLI overriding config', async () => {
|
|
1092
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
1092
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
1093
1093
|
const MockedNotifyFinishedUseCase = jest.mocked(
|
|
1094
1094
|
NotifyFinishedIssuePreparationUseCase,
|
|
1095
1095
|
);
|
|
@@ -1221,7 +1221,7 @@ codexHomeCandidates:
|
|
|
1221
1221
|
};
|
|
1222
1222
|
writeConfig(configWithWebhook);
|
|
1223
1223
|
|
|
1224
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
1224
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
1225
1225
|
const MockedNotifyFinishedUseCase = jest.mocked(
|
|
1226
1226
|
NotifyFinishedIssuePreparationUseCase,
|
|
1227
1227
|
);
|
|
@@ -1261,7 +1261,7 @@ codexHomeCandidates:
|
|
|
1261
1261
|
].join('\n');
|
|
1262
1262
|
mockFetchReturningReadme(readmeContent);
|
|
1263
1263
|
|
|
1264
|
-
const mockRun = jest.fn().mockResolvedValue(
|
|
1264
|
+
const mockRun = jest.fn().mockResolvedValue({ rotationOrder: null });
|
|
1265
1265
|
const MockedNotifyFinishedUseCase = jest.mocked(
|
|
1266
1266
|
NotifyFinishedIssuePreparationUseCase,
|
|
1267
1267
|
);
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
fetchProjectReadme,
|
|
17
17
|
} from './projectConfig';
|
|
18
18
|
import { StartPreparationUseCase } from '../../../domain/usecases/StartPreparationUseCase';
|
|
19
|
+
import { writeRotationOrderFile } from '../handlers/rotationOrderFileWriter';
|
|
19
20
|
import { ProxyClaudeTokenUsageRepository } from '../../repositories/ProxyClaudeTokenUsageRepository';
|
|
20
21
|
import { NotifyFinishedIssuePreparationUseCase } from '../../../domain/usecases/NotifyFinishedIssuePreparationUseCase';
|
|
21
22
|
import { LocalStorageRepository } from '../../repositories/LocalStorageRepository';
|
|
@@ -54,15 +55,10 @@ type NotifyFinishedOptions = {
|
|
|
54
55
|
|
|
55
56
|
const buildGithubRepositoryParams = (
|
|
56
57
|
localStorageRepository: LocalStorageRepository,
|
|
57
|
-
cachePath: string,
|
|
58
58
|
token: string,
|
|
59
59
|
): ConstructorParameters<typeof BaseGitHubRepository> => [
|
|
60
60
|
localStorageRepository,
|
|
61
|
-
`${cachePath}/github.com.cookies.json`,
|
|
62
61
|
token,
|
|
63
|
-
undefined,
|
|
64
|
-
undefined,
|
|
65
|
-
undefined,
|
|
66
62
|
];
|
|
67
63
|
|
|
68
64
|
interface ScheduleOptions {
|
|
@@ -113,7 +109,7 @@ program
|
|
|
113
109
|
.option('--defaultLlmAgentName <name>', 'Default LLM agent name')
|
|
114
110
|
.option(
|
|
115
111
|
'--maximumPreparingIssuesCount <count>',
|
|
116
|
-
'Maximum number of issues in preparation status (default: 6)',
|
|
112
|
+
'Maximum number of issues in preparation status (default: 6 per available Claude OAuth token, otherwise 6)',
|
|
117
113
|
)
|
|
118
114
|
.option(
|
|
119
115
|
'--allowIssueCacheMinutes <minutes>',
|
|
@@ -121,7 +117,7 @@ program
|
|
|
121
117
|
)
|
|
122
118
|
.option(
|
|
123
119
|
'--utilizationPercentageThreshold <percent>',
|
|
124
|
-
'Claude utilization
|
|
120
|
+
'Legacy Claude utilization threshold setting; token process slots decay from 80% utilization to 0 at 95% (default: 90)',
|
|
125
121
|
)
|
|
126
122
|
.option(
|
|
127
123
|
'--allowedIssueAuthors <authors>',
|
|
@@ -211,7 +207,7 @@ program
|
|
|
211
207
|
const allowIssueCacheMinutes = config.allowIssueCacheMinutes ?? 10;
|
|
212
208
|
|
|
213
209
|
console.log(
|
|
214
|
-
`maximumPreparingIssuesCount: ${maximumPreparingIssuesCount ?? 'null (default: 6)'}`,
|
|
210
|
+
`maximumPreparingIssuesCount: ${maximumPreparingIssuesCount ?? 'null (default: 6 per available Claude OAuth token, otherwise 6)'}`,
|
|
215
211
|
);
|
|
216
212
|
|
|
217
213
|
const projectName = config.projectName ?? 'default';
|
|
@@ -223,7 +219,6 @@ program
|
|
|
223
219
|
);
|
|
224
220
|
const githubRepositoryParams = buildGithubRepositoryParams(
|
|
225
221
|
localStorageRepository,
|
|
226
|
-
cachePath,
|
|
227
222
|
token,
|
|
228
223
|
);
|
|
229
224
|
const projectRepository = new GraphqlProjectRepository(
|
|
@@ -291,7 +286,7 @@ program
|
|
|
291
286
|
? config.codexHomeCandidates
|
|
292
287
|
: null;
|
|
293
288
|
|
|
294
|
-
await useCase.run({
|
|
289
|
+
const preparationResult = await useCase.run({
|
|
295
290
|
projectUrl,
|
|
296
291
|
defaultAgentName,
|
|
297
292
|
defaultLlmModelName: config.defaultLlmModelName ?? null,
|
|
@@ -304,6 +299,9 @@ program
|
|
|
304
299
|
codexHomeCandidates,
|
|
305
300
|
allowIssueCacheMinutes,
|
|
306
301
|
});
|
|
302
|
+
if (preparationResult.rotationOrder !== null) {
|
|
303
|
+
writeRotationOrderFile(preparationResult.rotationOrder);
|
|
304
|
+
}
|
|
307
305
|
});
|
|
308
306
|
|
|
309
307
|
program
|
|
@@ -396,7 +394,6 @@ program
|
|
|
396
394
|
);
|
|
397
395
|
const githubRepositoryParams = buildGithubRepositoryParams(
|
|
398
396
|
localStorageRepository,
|
|
399
|
-
cachePath,
|
|
400
397
|
token,
|
|
401
398
|
);
|
|
402
399
|
const projectRepository = new GraphqlProjectRepository(
|
|
@@ -93,43 +93,17 @@ describe('GetStoryObjectMapUseCaseHandler', () => {
|
|
|
93
93
|
);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
it('should pass bot
|
|
97
|
-
const configWithCredentials = {
|
|
98
|
-
...validConfig,
|
|
99
|
-
credentials: {
|
|
100
|
-
bot: {
|
|
101
|
-
github: {
|
|
102
|
-
token: 'test-token',
|
|
103
|
-
name: 'bot-user',
|
|
104
|
-
password: 'bot-pass',
|
|
105
|
-
authenticatorKey: 'bot-auth-key',
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
jest
|
|
111
|
-
.mocked(fs.readFileSync)
|
|
112
|
-
.mockReturnValue(YAML.stringify(configWithCredentials));
|
|
113
|
-
|
|
96
|
+
it('should pass bot token to repository constructors', async () => {
|
|
114
97
|
const handler = new GetStoryObjectMapUseCaseHandler();
|
|
115
98
|
await handler.handle('config.yml', false);
|
|
116
99
|
|
|
117
|
-
const expectedCookiePath = `./tmp/cache/${validConfig.projectName}/github.com.cookies.json`;
|
|
118
|
-
|
|
119
100
|
for (const MockedClass of [
|
|
120
101
|
MockedGraphqlProjectRepository,
|
|
121
102
|
MockedApiV3IssueRepository,
|
|
122
103
|
MockedRestIssueRepository,
|
|
123
104
|
MockedGraphqlProjectItemRepository,
|
|
124
105
|
]) {
|
|
125
|
-
expect(MockedClass).toHaveBeenCalledWith(
|
|
126
|
-
expect.anything(),
|
|
127
|
-
expectedCookiePath,
|
|
128
|
-
'test-token',
|
|
129
|
-
'bot-user',
|
|
130
|
-
'bot-pass',
|
|
131
|
-
'bot-auth-key',
|
|
132
|
-
);
|
|
106
|
+
expect(MockedClass).toHaveBeenCalledWith(expect.anything(), 'test-token');
|
|
133
107
|
}
|
|
134
108
|
|
|
135
109
|
expect(MockedApiV3CheerioRestIssueRepository).toHaveBeenCalledWith(
|
|
@@ -138,34 +112,7 @@ describe('GetStoryObjectMapUseCaseHandler', () => {
|
|
|
138
112
|
expect.anything(),
|
|
139
113
|
expect.anything(),
|
|
140
114
|
expect.anything(),
|
|
141
|
-
expectedCookiePath,
|
|
142
115
|
'test-token',
|
|
143
|
-
'bot-user',
|
|
144
|
-
'bot-pass',
|
|
145
|
-
'bot-auth-key',
|
|
146
116
|
);
|
|
147
117
|
});
|
|
148
|
-
|
|
149
|
-
it('should pass undefined credentials when not provided in config', async () => {
|
|
150
|
-
const handler = new GetStoryObjectMapUseCaseHandler();
|
|
151
|
-
await handler.handle('config.yml', false);
|
|
152
|
-
|
|
153
|
-
const expectedCookiePath = `./tmp/cache/${validConfig.projectName}/github.com.cookies.json`;
|
|
154
|
-
|
|
155
|
-
for (const MockedClass of [
|
|
156
|
-
MockedGraphqlProjectRepository,
|
|
157
|
-
MockedApiV3IssueRepository,
|
|
158
|
-
MockedRestIssueRepository,
|
|
159
|
-
MockedGraphqlProjectItemRepository,
|
|
160
|
-
]) {
|
|
161
|
-
expect(MockedClass).toHaveBeenCalledWith(
|
|
162
|
-
expect.anything(),
|
|
163
|
-
expectedCookiePath,
|
|
164
|
-
'test-token',
|
|
165
|
-
undefined,
|
|
166
|
-
undefined,
|
|
167
|
-
undefined,
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
});
|
|
171
118
|
});
|
|
@@ -33,9 +33,6 @@ export class GetStoryObjectMapUseCaseHandler {
|
|
|
33
33
|
bot: {
|
|
34
34
|
github: {
|
|
35
35
|
token: string;
|
|
36
|
-
name?: string;
|
|
37
|
-
password?: string;
|
|
38
|
-
authenticatorKey?: string;
|
|
39
36
|
};
|
|
40
37
|
};
|
|
41
38
|
};
|
|
@@ -54,14 +51,7 @@ export class GetStoryObjectMapUseCaseHandler {
|
|
|
54
51
|
);
|
|
55
52
|
const githubRepositoryParams: ConstructorParameters<
|
|
56
53
|
typeof BaseGitHubRepository
|
|
57
|
-
> = [
|
|
58
|
-
localStorageRepository,
|
|
59
|
-
`${cachePath}/github.com.cookies.json`,
|
|
60
|
-
input.credentials.bot.github.token,
|
|
61
|
-
input.credentials.bot.github.name,
|
|
62
|
-
input.credentials.bot.github.password,
|
|
63
|
-
input.credentials.bot.github.authenticatorKey,
|
|
64
|
-
];
|
|
54
|
+
> = [localStorageRepository, input.credentials.bot.github.token];
|
|
65
55
|
const projectRepository = {
|
|
66
56
|
...new GraphqlProjectRepository(...githubRepositoryParams),
|
|
67
57
|
};
|
|
@@ -23,6 +23,7 @@ const mockRun = jest.fn().mockImplementation((...args: Parameters<RunFn>) => {
|
|
|
23
23
|
issues: [],
|
|
24
24
|
cacheUsed: false,
|
|
25
25
|
targetDateTimes: [],
|
|
26
|
+
rotationOrder: null,
|
|
26
27
|
});
|
|
27
28
|
});
|
|
28
29
|
|
|
@@ -114,6 +115,9 @@ jest.mock('../../repositories/GitHubIssueCommentRepository', () => ({
|
|
|
114
115
|
jest.mock('./situationFileWriter', () => ({
|
|
115
116
|
writeSituationFile: jest.fn().mockResolvedValue(undefined),
|
|
116
117
|
}));
|
|
118
|
+
jest.mock('./rotationOrderFileWriter', () => ({
|
|
119
|
+
writeRotationOrderFile: jest.fn(),
|
|
120
|
+
}));
|
|
117
121
|
|
|
118
122
|
import { HandleScheduledEventUseCaseHandler } from './HandleScheduledEventUseCaseHandler';
|
|
119
123
|
import { writeSituationFile } from './situationFileWriter';
|
|
@@ -185,44 +189,17 @@ describe('HandleScheduledEventUseCaseHandler', () => {
|
|
|
185
189
|
mockFetchReturningReadme(null);
|
|
186
190
|
});
|
|
187
191
|
|
|
188
|
-
it('should pass bot
|
|
189
|
-
const configWithCredentials = {
|
|
190
|
-
...validConfig,
|
|
191
|
-
credentials: {
|
|
192
|
-
...validConfig.credentials,
|
|
193
|
-
bot: {
|
|
194
|
-
github: {
|
|
195
|
-
token: 'test-token',
|
|
196
|
-
name: 'bot-user',
|
|
197
|
-
password: 'bot-pass',
|
|
198
|
-
authenticatorKey: 'bot-auth-key',
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
jest
|
|
204
|
-
.mocked(fs.readFileSync)
|
|
205
|
-
.mockReturnValue(YAML.stringify(configWithCredentials));
|
|
206
|
-
|
|
192
|
+
it('should pass bot token to repository constructors', async () => {
|
|
207
193
|
const handler = new HandleScheduledEventUseCaseHandler();
|
|
208
194
|
await handler.handle('config.yml', false);
|
|
209
195
|
|
|
210
|
-
const expectedCookiePath = `./tmp/cache/${validConfig.projectName}/github.com.cookies.json`;
|
|
211
|
-
|
|
212
196
|
for (const MockedClass of [
|
|
213
197
|
MockedGraphqlProjectRepository,
|
|
214
198
|
MockedApiV3IssueRepository,
|
|
215
199
|
MockedRestIssueRepository,
|
|
216
200
|
MockedGraphqlProjectItemRepository,
|
|
217
201
|
]) {
|
|
218
|
-
expect(MockedClass).toHaveBeenCalledWith(
|
|
219
|
-
expect.anything(),
|
|
220
|
-
expectedCookiePath,
|
|
221
|
-
'test-token',
|
|
222
|
-
'bot-user',
|
|
223
|
-
'bot-pass',
|
|
224
|
-
'bot-auth-key',
|
|
225
|
-
);
|
|
202
|
+
expect(MockedClass).toHaveBeenCalledWith(expect.anything(), 'test-token');
|
|
226
203
|
}
|
|
227
204
|
|
|
228
205
|
expect(MockedApiV3CheerioRestIssueRepository).toHaveBeenCalledWith(
|
|
@@ -231,37 +208,10 @@ describe('HandleScheduledEventUseCaseHandler', () => {
|
|
|
231
208
|
expect.anything(),
|
|
232
209
|
expect.anything(),
|
|
233
210
|
expect.anything(),
|
|
234
|
-
expectedCookiePath,
|
|
235
211
|
'test-token',
|
|
236
|
-
'bot-user',
|
|
237
|
-
'bot-pass',
|
|
238
|
-
'bot-auth-key',
|
|
239
212
|
);
|
|
240
213
|
});
|
|
241
214
|
|
|
242
|
-
it('should pass undefined credentials when not provided in config', async () => {
|
|
243
|
-
const handler = new HandleScheduledEventUseCaseHandler();
|
|
244
|
-
await handler.handle('config.yml', false);
|
|
245
|
-
|
|
246
|
-
const expectedCookiePath = `./tmp/cache/${validConfig.projectName}/github.com.cookies.json`;
|
|
247
|
-
|
|
248
|
-
for (const MockedClass of [
|
|
249
|
-
MockedGraphqlProjectRepository,
|
|
250
|
-
MockedApiV3IssueRepository,
|
|
251
|
-
MockedRestIssueRepository,
|
|
252
|
-
MockedGraphqlProjectItemRepository,
|
|
253
|
-
]) {
|
|
254
|
-
expect(MockedClass).toHaveBeenCalledWith(
|
|
255
|
-
expect.anything(),
|
|
256
|
-
expectedCookiePath,
|
|
257
|
-
'test-token',
|
|
258
|
-
undefined,
|
|
259
|
-
undefined,
|
|
260
|
-
undefined,
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
215
|
it('should write situation file after successful run with resolved config values', async () => {
|
|
266
216
|
const configWithPreparation = {
|
|
267
217
|
...validConfig,
|
|
@@ -2,6 +2,7 @@ import YAML from 'yaml';
|
|
|
2
2
|
import TYPIA from 'typia';
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import { writeSituationFile } from './situationFileWriter';
|
|
5
|
+
import { writeRotationOrderFile } from './rotationOrderFileWriter';
|
|
5
6
|
import {
|
|
6
7
|
fetchProjectReadme,
|
|
7
8
|
parseProjectReadmeConfig,
|
|
@@ -44,6 +45,7 @@ import { SetupTowerDefenceProjectUseCase } from '../../../domain/usecases/SetupT
|
|
|
44
45
|
import {
|
|
45
46
|
AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
46
47
|
AWAITING_WORKSPACE_STATUS_NAME,
|
|
48
|
+
FAILED_PREPARATION_STATUS_NAME,
|
|
47
49
|
PREPARATION_STATUS_NAME,
|
|
48
50
|
} from '../../../domain/entities/WorkflowStatus';
|
|
49
51
|
|
|
@@ -76,9 +78,6 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
76
78
|
bot: {
|
|
77
79
|
github: {
|
|
78
80
|
token: string;
|
|
79
|
-
name?: string;
|
|
80
|
-
password?: string;
|
|
81
|
-
authenticatorKey?: string;
|
|
82
81
|
};
|
|
83
82
|
};
|
|
84
83
|
};
|
|
@@ -151,14 +150,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
151
150
|
);
|
|
152
151
|
const githubRepositoryParams: ConstructorParameters<
|
|
153
152
|
typeof BaseGitHubRepository
|
|
154
|
-
> = [
|
|
155
|
-
localStorageRepository,
|
|
156
|
-
`${cachePath}/github.com.cookies.json`,
|
|
157
|
-
input.credentials.bot.github.token,
|
|
158
|
-
input.credentials.bot.github.name,
|
|
159
|
-
input.credentials.bot.github.password,
|
|
160
|
-
input.credentials.bot.github.authenticatorKey,
|
|
161
|
-
];
|
|
153
|
+
> = [localStorageRepository, input.credentials.bot.github.token];
|
|
162
154
|
const projectRepository = new GraphqlProjectRepository(
|
|
163
155
|
...githubRepositoryParams,
|
|
164
156
|
);
|
|
@@ -281,6 +273,9 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
281
273
|
|
|
282
274
|
const result = await handleScheduledEventUseCase.run(mergedInput);
|
|
283
275
|
if (result) {
|
|
276
|
+
if (result.rotationOrder !== null) {
|
|
277
|
+
writeRotationOrderFile(result.rotationOrder);
|
|
278
|
+
}
|
|
284
279
|
await writeSituationFile({
|
|
285
280
|
cachePath,
|
|
286
281
|
projectId: result.project.id,
|
|
@@ -289,6 +284,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
289
284
|
awaitingQualityCheckStatus: AWAITING_QUALITY_CHECK_STATUS_NAME,
|
|
290
285
|
preparationStatus: PREPARATION_STATUS_NAME,
|
|
291
286
|
awaitingWorkspaceStatus: AWAITING_WORKSPACE_STATUS_NAME,
|
|
287
|
+
failedPreparationStatus: FAILED_PREPARATION_STATUS_NAME,
|
|
292
288
|
},
|
|
293
289
|
config: {
|
|
294
290
|
maximumPreparingIssuesCount:
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { writeRotationOrderFile } from './rotationOrderFileWriter';
|
|
5
|
+
import type { RotationOrderEntry } from '../../../domain/usecases/StartPreparationUseCase';
|
|
6
|
+
|
|
7
|
+
jest.mock('fs');
|
|
8
|
+
|
|
9
|
+
const TOKEN_A = 'sk-ant-secret-token-a-value';
|
|
10
|
+
const TOKEN_B = 'sk-ant-secret-token-b-value';
|
|
11
|
+
|
|
12
|
+
describe('writeRotationOrderFile', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
jest.clearAllMocks();
|
|
15
|
+
jest.mocked(fs.mkdirSync).mockReturnValue(undefined);
|
|
16
|
+
jest.mocked(fs.writeFileSync).mockReturnValue(undefined);
|
|
17
|
+
jest.mocked(fs.renameSync).mockReturnValue(undefined);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('writes rotation order entries sorted selected-first to the stable path under XDG_CACHE_HOME', () => {
|
|
21
|
+
const originalXdg = process.env.XDG_CACHE_HOME;
|
|
22
|
+
process.env.XDG_CACHE_HOME = '/custom/cache';
|
|
23
|
+
|
|
24
|
+
const entries: RotationOrderEntry[] = [
|
|
25
|
+
{
|
|
26
|
+
name: 'personal-1',
|
|
27
|
+
fiveHourUtilization: 0.2,
|
|
28
|
+
blocked: false,
|
|
29
|
+
rejected: false,
|
|
30
|
+
thresholdExcluded: false,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
writeRotationOrderFile(entries);
|
|
35
|
+
|
|
36
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
37
|
+
'/custom/cache/tdpm/rotation-order.json.tmp',
|
|
38
|
+
expect.any(String),
|
|
39
|
+
);
|
|
40
|
+
expect(jest.mocked(fs.renameSync)).toHaveBeenCalledWith(
|
|
41
|
+
'/custom/cache/tdpm/rotation-order.json.tmp',
|
|
42
|
+
'/custom/cache/tdpm/rotation-order.json',
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
process.env.XDG_CACHE_HOME = originalXdg;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('falls back to ~/.cache/tdpm/rotation-order.json when XDG_CACHE_HOME is unset', () => {
|
|
49
|
+
const originalXdg = process.env.XDG_CACHE_HOME;
|
|
50
|
+
delete process.env.XDG_CACHE_HOME;
|
|
51
|
+
|
|
52
|
+
const home = os.homedir();
|
|
53
|
+
const expectedPath = path.join(
|
|
54
|
+
home,
|
|
55
|
+
'.cache',
|
|
56
|
+
'tdpm',
|
|
57
|
+
'rotation-order.json',
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
writeRotationOrderFile([]);
|
|
61
|
+
|
|
62
|
+
expect(jest.mocked(fs.renameSync)).toHaveBeenCalledWith(
|
|
63
|
+
`${expectedPath}.tmp`,
|
|
64
|
+
expectedPath,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
process.env.XDG_CACHE_HOME = originalXdg;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('includes name, fiveHourUtilization, blocked, rejected, and thresholdExcluded in output', () => {
|
|
71
|
+
process.env.XDG_CACHE_HOME = '/cache';
|
|
72
|
+
|
|
73
|
+
const entries: RotationOrderEntry[] = [
|
|
74
|
+
{
|
|
75
|
+
name: 'personal-1',
|
|
76
|
+
fiveHourUtilization: 0.3,
|
|
77
|
+
blocked: false,
|
|
78
|
+
rejected: false,
|
|
79
|
+
thresholdExcluded: false,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'personal-2',
|
|
83
|
+
fiveHourUtilization: 0.95,
|
|
84
|
+
blocked: false,
|
|
85
|
+
rejected: false,
|
|
86
|
+
thresholdExcluded: true,
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
writeRotationOrderFile(entries);
|
|
91
|
+
|
|
92
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
93
|
+
expect.any(String),
|
|
94
|
+
expect.stringContaining('"name":"personal-1"'),
|
|
95
|
+
);
|
|
96
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
97
|
+
expect.any(String),
|
|
98
|
+
expect.stringContaining('"name":"personal-2"'),
|
|
99
|
+
);
|
|
100
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
101
|
+
expect.any(String),
|
|
102
|
+
expect.stringContaining('"fiveHourUtilization":0.3'),
|
|
103
|
+
);
|
|
104
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
105
|
+
expect.any(String),
|
|
106
|
+
expect.stringContaining('"thresholdExcluded":true'),
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
delete process.env.XDG_CACHE_HOME;
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('does not write raw token values to the output file', () => {
|
|
113
|
+
process.env.XDG_CACHE_HOME = '/cache';
|
|
114
|
+
|
|
115
|
+
const entries: RotationOrderEntry[] = [
|
|
116
|
+
{
|
|
117
|
+
name: 'personal-1',
|
|
118
|
+
fiveHourUtilization: 0.1,
|
|
119
|
+
blocked: false,
|
|
120
|
+
rejected: false,
|
|
121
|
+
thresholdExcluded: false,
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
writeRotationOrderFile(entries);
|
|
126
|
+
|
|
127
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
128
|
+
expect.any(String),
|
|
129
|
+
expect.not.stringContaining(TOKEN_A),
|
|
130
|
+
);
|
|
131
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
132
|
+
expect.any(String),
|
|
133
|
+
expect.not.stringContaining(TOKEN_B),
|
|
134
|
+
);
|
|
135
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
136
|
+
expect.any(String),
|
|
137
|
+
expect.not.stringContaining('sk-ant-'),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
delete process.env.XDG_CACHE_HOME;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('writes atomically: mkdirSync before writeFileSync before renameSync', () => {
|
|
144
|
+
process.env.XDG_CACHE_HOME = '/cache';
|
|
145
|
+
|
|
146
|
+
const callOrder: string[] = [];
|
|
147
|
+
jest.mocked(fs.mkdirSync).mockImplementation((): undefined => {
|
|
148
|
+
callOrder.push('mkdir');
|
|
149
|
+
return undefined;
|
|
150
|
+
});
|
|
151
|
+
jest.mocked(fs.writeFileSync).mockImplementation((): void => {
|
|
152
|
+
callOrder.push('write');
|
|
153
|
+
});
|
|
154
|
+
jest.mocked(fs.renameSync).mockImplementation((): void => {
|
|
155
|
+
callOrder.push('rename');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
writeRotationOrderFile([]);
|
|
159
|
+
|
|
160
|
+
expect(callOrder).toEqual(['mkdir', 'write', 'rename']);
|
|
161
|
+
|
|
162
|
+
delete process.env.XDG_CACHE_HOME;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('writes an empty array when no rotation entries are provided', () => {
|
|
166
|
+
process.env.XDG_CACHE_HOME = '/cache';
|
|
167
|
+
|
|
168
|
+
writeRotationOrderFile([]);
|
|
169
|
+
|
|
170
|
+
expect(jest.mocked(fs.writeFileSync)).toHaveBeenCalledWith(
|
|
171
|
+
expect.any(String),
|
|
172
|
+
'[]',
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
delete process.env.XDG_CACHE_HOME;
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import type { RotationOrderEntry } from '../../../domain/usecases/StartPreparationUseCase';
|
|
5
|
+
|
|
6
|
+
const rotationOrderFilePath = (): string => {
|
|
7
|
+
const base = process.env.XDG_CACHE_HOME ?? path.join(os.homedir(), '.cache');
|
|
8
|
+
return path.join(base, 'tdpm', 'rotation-order.json');
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const writeRotationOrderFile = (
|
|
12
|
+
rotationOrder: RotationOrderEntry[],
|
|
13
|
+
): void => {
|
|
14
|
+
const filePath = rotationOrderFilePath();
|
|
15
|
+
const dir = path.dirname(filePath);
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
const tmpPath = `${filePath}.tmp`;
|
|
18
|
+
fs.writeFileSync(tmpPath, JSON.stringify(rotationOrder));
|
|
19
|
+
fs.renameSync(tmpPath, filePath);
|
|
20
|
+
};
|