github-issue-tower-defence-management 1.83.0 → 1.84.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 +63 -0
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +19 -2
- package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
- package/bin/adapter/entry-points/handlers/consoleListsWriter.js +43 -0
- package/bin/adapter/entry-points/handlers/consoleListsWriter.js.map +1 -0
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js +101 -0
- package/bin/domain/usecases/console/GenerateConsoleListsUseCase.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +18 -0
- package/src/adapter/entry-points/handlers/consoleListsWriter.test.ts +167 -0
- package/src/adapter/entry-points/handlers/consoleListsWriter.ts +60 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.test.ts +372 -0
- package/src/domain/usecases/console/GenerateConsoleListsUseCase.ts +206 -0
- package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
- package/types/adapter/entry-points/handlers/consoleListsWriter.d.ts +13 -0
- package/types/adapter/entry-points/handlers/consoleListsWriter.d.ts.map +1 -0
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts +63 -0
- package/types/domain/usecases/console/GenerateConsoleListsUseCase.d.ts.map +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [1.84.0](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.83.0...v1.84.0) (2026-06-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **console:** generate per-tab Console list.json files in scheduled cycle ([#831](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/issues/831)) ([0368fb7](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/commit/0368fb74d572a5d9b73966440da2c62669d1d4f4))
|
|
7
|
+
|
|
1
8
|
# [1.83.0](https://github.com/HiromiShikata/npm-cli-github-issue-tower-defence-management/compare/v1.82.1...v1.83.0) (2026-06-14)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -199,6 +199,7 @@ claudeCodeOauthTokenListJsonPath?: string # Optional: Path to a JSON file listin
|
|
|
199
199
|
awLogDirectoryPath?: string # Optional: Directory path where aw log files named {org}_{repo}_{number}_* are written. Used with awLogStaleThresholdMinutes to detect zombie-wrapper orphans
|
|
200
200
|
awLogStaleThresholdMinutes?: number # Optional: Minutes since last aw log mtime after which a Preparation issue is considered orphaned even when pgrep still returns 0. Requires awLogDirectoryPath
|
|
201
201
|
labelsAsLlmAgentName?: string[] # Optional: List of issue labels that are themselves agent names. When an issue carries any label that is included in this list, that label name is used as the agent name. Selection precedence is: (1) explicit `llm-agent:` label, (2) labelsAsLlmAgentName entry match, (3) `category:` label, (4) defaultLlmAgentName, (5) defaultAgentName
|
|
202
|
+
consoleDataOutputDir?: string # Optional: Base output directory for the per-project Console list.json files written each schedule cycle. When unset, Console list generation is skipped
|
|
202
203
|
changeTargetPathAliases?: # Optional: Map of short alias keys to full repository-root-relative directory paths. Allows `change-target:<alias>` labels to reference deeply nested paths that exceed GitHub's 50-character label limit. When a `change-target:` label's value matches a key in this map, it is expanded to the corresponding full path before confinement checking. Values with leading or trailing slashes are normalized automatically. Example below
|
|
203
204
|
adapter-interfaces: src/domain/usecases/adapter-interfaces
|
|
204
205
|
```
|
|
@@ -331,6 +332,68 @@ This file is written atomically (written to a `.tmp` file then renamed) so exter
|
|
|
331
332
|
|
|
332
333
|
System metrics are read from `/proc/meminfo` at snapshot write time.
|
|
333
334
|
|
|
335
|
+
## Per-Project Console Lists
|
|
336
|
+
|
|
337
|
+
When `consoleDataOutputDir` is configured, each schedule cycle also writes four per-tab Console list files, generated from the same in-memory project and issue data already loaded for the cycle (no additional GitHub API calls):
|
|
338
|
+
|
|
339
|
+
```
|
|
340
|
+
{consoleDataOutputDir}/{projectName}/{tab}/list.json
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
for `tab` in `prs`, `triage`, `unread`, and `failed-preparation`. Each file is written atomically (written to a `.tmp` file then renamed) so external readers never see a partial write. When `consoleDataOutputDir` is unset the generation is skipped, and any error during generation is logged and swallowed so the schedule cycle is never affected.
|
|
344
|
+
|
|
345
|
+
### Item Selection
|
|
346
|
+
|
|
347
|
+
Every tab applies a common actionable filter to the project's issues: the issue is open, is assigned to the project manager, has no depended issue URLs, and has neither a next action date nor a next action hour set. Each tab then applies its own selector:
|
|
348
|
+
|
|
349
|
+
- `prs`: status equals `Awaiting Quality Check` (case-insensitive)
|
|
350
|
+
- `unread`: status equals `Unread` (case-insensitive)
|
|
351
|
+
- `failed-preparation`: status equals `Failed Preparation` (exact case)
|
|
352
|
+
- `triage`: story name contains `no story` (case-insensitive)
|
|
353
|
+
|
|
354
|
+
### JSON Shape
|
|
355
|
+
|
|
356
|
+
The `prs`, `unread`, and `failed-preparation` tabs share this shape:
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"pjcode": "my-project",
|
|
361
|
+
"generatedAt": "2026-06-14T07:22:33Z",
|
|
362
|
+
"statusOptions": [
|
|
363
|
+
{ "id": "...", "name": "Awaiting Workspace", "color": "BLUE" }
|
|
364
|
+
],
|
|
365
|
+
"storyOrder": ["Story Alpha", "Story Beta"],
|
|
366
|
+
"storyColors": { "Story Alpha": { "color": "BLUE" } },
|
|
367
|
+
"items": [
|
|
368
|
+
{
|
|
369
|
+
"number": 1,
|
|
370
|
+
"title": "Example",
|
|
371
|
+
"url": "https://github.com/owner/repo/issues/1",
|
|
372
|
+
"repo": "owner/repo",
|
|
373
|
+
"nameWithOwner": "owner/repo",
|
|
374
|
+
"projectItemId": "...",
|
|
375
|
+
"itemId": "...",
|
|
376
|
+
"isPr": false,
|
|
377
|
+
"story": "Story Alpha",
|
|
378
|
+
"labels": ["bug"],
|
|
379
|
+
"createdAt": "2026-06-13T08:18:45.000Z"
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
The `triage` tab omits `statusOptions`, adds `storyOptions` (all story field options), and uses plain color string values in `storyColors` (for example `"Story Alpha": "BLUE"`).
|
|
386
|
+
|
|
387
|
+
### Field Descriptions
|
|
388
|
+
|
|
389
|
+
- `pjcode`: The configured project name.
|
|
390
|
+
- `generatedAt`: UTC timestamp (no milliseconds) when the lists were generated. Item `createdAt` values keep milliseconds.
|
|
391
|
+
- `statusOptions`: Project status field options offered as routing buttons. The current-status option and `Done` are excluded; `failed-preparation` additionally excludes `Preparation`, `Icebox`, `Unread`, and `In Tmux by human`.
|
|
392
|
+
- `storyOptions` (triage tab only): All story field options.
|
|
393
|
+
- `storyOrder`: Story field option names in field order (empty array when the project has no story field).
|
|
394
|
+
- `storyColors`: Map from story name to its color. Object value (`{ "color": ... }`) for `prs`/`unread`/`failed-preparation`; plain string value for `triage`.
|
|
395
|
+
- `items`: Selected issues, stable-sorted by their story's position in `storyOrder` (unknown stories sorted last). No item carries a `body` field.
|
|
396
|
+
|
|
334
397
|
## Token Rotation Order File
|
|
335
398
|
|
|
336
399
|
After each schedule cycle where Claude OAuth token rotation is active, TDPM writes the computed rotation order to:
|
|
@@ -43,6 +43,7 @@ const yaml_1 = __importDefault(require("yaml"));
|
|
|
43
43
|
const typia_1 = __importDefault(require("typia"));
|
|
44
44
|
const fs_1 = __importDefault(require("fs"));
|
|
45
45
|
const situationFileWriter_1 = require("./situationFileWriter");
|
|
46
|
+
const consoleListsWriter_1 = require("./consoleListsWriter");
|
|
46
47
|
const rotationOrderFileWriter_1 = require("./rotationOrderFileWriter");
|
|
47
48
|
const projectConfig_1 = require("../cli/projectConfig");
|
|
48
49
|
const SystemDateRepository_1 = require("../../repositories/SystemDateRepository");
|
|
@@ -86,13 +87,13 @@ class HandleScheduledEventUseCaseHandler {
|
|
|
86
87
|
this.handle = async (configFilePath, _verbose) => {
|
|
87
88
|
const configFileContent = fs_1.default.readFileSync(configFilePath, 'utf8');
|
|
88
89
|
const input = yaml_1.default.parse(configFileContent);
|
|
89
|
-
if (!(() => { const _io0 = input => "string" === typeof input.projectName && "string" === typeof input.org && "string" === typeof input.projectUrl && "string" === typeof input.manager && ("object" === typeof input.workingReport && null !== input.workingReport && _io1(input.workingReport)) && "string" === typeof input.urlOfStoryView && "boolean" === typeof input.disabled && "number" === typeof input.allowIssueCacheMinutes && (null === input.labelsAsLlmAgentName || undefined === input.labelsAsLlmAgentName || Array.isArray(input.labelsAsLlmAgentName) && input.labelsAsLlmAgentName.every(elem => "string" === typeof elem)) && (null === input.changeTargetPathAliases || undefined === input.changeTargetPathAliases || "object" === typeof input.changeTargetPathAliases && null !== input.changeTargetPathAliases && false === Array.isArray(input.changeTargetPathAliases) && _io2(input.changeTargetPathAliases)) && (null === input.startPreparation || undefined === input.startPreparation || "object" === typeof input.startPreparation && null !== input.startPreparation && _io3(input.startPreparation)) && (undefined === input.thresholdForAutoReject || "number" === typeof input.thresholdForAutoReject) && (null === input.dailySecurityScan || undefined === input.dailySecurityScan || "object" === typeof input.dailySecurityScan && null !== input.dailySecurityScan && _io4(input.dailySecurityScan)) && (undefined === input.claudeCodeOauthTokenListJsonPath || "string" === typeof input.claudeCodeOauthTokenListJsonPath) && ("object" === typeof input.credentials && null !== input.credentials && _io5(input.credentials)); const _io1 = input => "string" === typeof input.repo && (Array.isArray(input.members) && input.members.every(elem => "string" === typeof elem)) && "string" === typeof input.spreadsheetUrl; const _io2 = input => Object.keys(input).every(key => {
|
|
90
|
+
if (!(() => { const _io0 = input => "string" === typeof input.projectName && "string" === typeof input.org && "string" === typeof input.projectUrl && "string" === typeof input.manager && ("object" === typeof input.workingReport && null !== input.workingReport && _io1(input.workingReport)) && "string" === typeof input.urlOfStoryView && "boolean" === typeof input.disabled && "number" === typeof input.allowIssueCacheMinutes && (null === input.labelsAsLlmAgentName || undefined === input.labelsAsLlmAgentName || Array.isArray(input.labelsAsLlmAgentName) && input.labelsAsLlmAgentName.every(elem => "string" === typeof elem)) && (null === input.changeTargetPathAliases || undefined === input.changeTargetPathAliases || "object" === typeof input.changeTargetPathAliases && null !== input.changeTargetPathAliases && false === Array.isArray(input.changeTargetPathAliases) && _io2(input.changeTargetPathAliases)) && (null === input.startPreparation || undefined === input.startPreparation || "object" === typeof input.startPreparation && null !== input.startPreparation && _io3(input.startPreparation)) && (undefined === input.thresholdForAutoReject || "number" === typeof input.thresholdForAutoReject) && (null === input.dailySecurityScan || undefined === input.dailySecurityScan || "object" === typeof input.dailySecurityScan && null !== input.dailySecurityScan && _io4(input.dailySecurityScan)) && (undefined === input.claudeCodeOauthTokenListJsonPath || "string" === typeof input.claudeCodeOauthTokenListJsonPath) && (undefined === input.consoleDataOutputDir || "string" === typeof input.consoleDataOutputDir) && ("object" === typeof input.credentials && null !== input.credentials && _io5(input.credentials)); const _io1 = input => "string" === typeof input.repo && (Array.isArray(input.members) && input.members.every(elem => "string" === typeof elem)) && "string" === typeof input.spreadsheetUrl; const _io2 = input => Object.keys(input).every(key => {
|
|
90
91
|
const value = input[key];
|
|
91
92
|
if (undefined === value)
|
|
92
93
|
return true;
|
|
93
94
|
return "string" === typeof value;
|
|
94
95
|
}); const _io3 = input => "string" === typeof input.defaultAgentName && (null === input.defaultLlmModelName || undefined === input.defaultLlmModelName || "string" === typeof input.defaultLlmModelName) && (null === input.fallbackLlmModelName || undefined === input.fallbackLlmModelName || "string" === typeof input.fallbackLlmModelName) && (null === input.defaultLlmAgentName || undefined === input.defaultLlmAgentName || "string" === typeof input.defaultLlmAgentName) && "string" === typeof input.configFilePath && (null === input.maximumPreparingIssuesCount || "number" === typeof input.maximumPreparingIssuesCount) && (undefined === input.utilizationPercentageThreshold || "number" === typeof input.utilizationPercentageThreshold) && (null === input.allowedIssueAuthors || undefined === input.allowedIssueAuthors || Array.isArray(input.allowedIssueAuthors) && input.allowedIssueAuthors.every(elem => "string" === typeof elem)) && (undefined === input.preparationProcessCheckCommand || "string" === typeof input.preparationProcessCheckCommand) && (null === input.codexHomeCandidates || undefined === input.codexHomeCandidates || Array.isArray(input.codexHomeCandidates) && input.codexHomeCandidates.every(elem => "string" === typeof elem)) && (undefined === input.awLogDirectoryPath || "string" === typeof input.awLogDirectoryPath) && (undefined === input.awLogStaleThresholdMinutes || "number" === typeof input.awLogStaleThresholdMinutes) && (null === input.awaitingQualityCheckStatus || undefined === input.awaitingQualityCheckStatus || "string" === typeof input.awaitingQualityCheckStatus) && (null === input.labelsAsLlmAgentName || undefined === input.labelsAsLlmAgentName || Array.isArray(input.labelsAsLlmAgentName) && input.labelsAsLlmAgentName.every(elem => "string" === typeof elem)); const _io4 = input => "string" === typeof input.scanBaseDirectory && "number" === typeof input.targetHourUtc && (undefined === input.enableKevNvdReport || "boolean" === typeof input.enableKevNvdReport) && (undefined === input.kevReportRepo || "string" === typeof input.kevReportRepo); const _io5 = input => "object" === typeof input.manager && null !== input.manager && _io6(input.manager) && ("object" === typeof input.bot && null !== input.bot && _io10(input.bot)); const _io6 = input => "object" === typeof input.github && null !== input.github && _io7(input.github) && ("object" === typeof input.slack && null !== input.slack && _io8(input.slack)) && ("object" === typeof input.googleServiceAccount && null !== input.googleServiceAccount && _io9(input.googleServiceAccount)); const _io7 = input => "string" === typeof input.token; const _io8 = input => "string" === typeof input.userToken; const _io9 = input => "string" === typeof input.serviceAccountKey; const _io10 = input => "object" === typeof input.github && null !== input.github && _io11(input.github); const _io11 = input => "string" === typeof input.token; return input => "object" === typeof input && null !== input && _io0(input); })()(input)) {
|
|
95
|
-
throw new Error(`Invalid input: ${JSON.stringify(input)}\n\n${JSON.stringify((() => { const _io0 = input => "string" === typeof input.projectName && "string" === typeof input.org && "string" === typeof input.projectUrl && "string" === typeof input.manager && ("object" === typeof input.workingReport && null !== input.workingReport && _io1(input.workingReport)) && "string" === typeof input.urlOfStoryView && "boolean" === typeof input.disabled && "number" === typeof input.allowIssueCacheMinutes && (null === input.labelsAsLlmAgentName || undefined === input.labelsAsLlmAgentName || Array.isArray(input.labelsAsLlmAgentName) && input.labelsAsLlmAgentName.every(elem => "string" === typeof elem)) && (null === input.changeTargetPathAliases || undefined === input.changeTargetPathAliases || "object" === typeof input.changeTargetPathAliases && null !== input.changeTargetPathAliases && false === Array.isArray(input.changeTargetPathAliases) && _io2(input.changeTargetPathAliases)) && (null === input.startPreparation || undefined === input.startPreparation || "object" === typeof input.startPreparation && null !== input.startPreparation && _io3(input.startPreparation)) && (undefined === input.thresholdForAutoReject || "number" === typeof input.thresholdForAutoReject) && (null === input.dailySecurityScan || undefined === input.dailySecurityScan || "object" === typeof input.dailySecurityScan && null !== input.dailySecurityScan && _io4(input.dailySecurityScan)) && (undefined === input.claudeCodeOauthTokenListJsonPath || "string" === typeof input.claudeCodeOauthTokenListJsonPath) && ("object" === typeof input.credentials && null !== input.credentials && _io5(input.credentials)); const _io1 = input => "string" === typeof input.repo && (Array.isArray(input.members) && input.members.every(elem => "string" === typeof elem)) && "string" === typeof input.spreadsheetUrl; const _io2 = input => Object.keys(input).every(key => {
|
|
96
|
+
throw new Error(`Invalid input: ${JSON.stringify(input)}\n\n${JSON.stringify((() => { const _io0 = input => "string" === typeof input.projectName && "string" === typeof input.org && "string" === typeof input.projectUrl && "string" === typeof input.manager && ("object" === typeof input.workingReport && null !== input.workingReport && _io1(input.workingReport)) && "string" === typeof input.urlOfStoryView && "boolean" === typeof input.disabled && "number" === typeof input.allowIssueCacheMinutes && (null === input.labelsAsLlmAgentName || undefined === input.labelsAsLlmAgentName || Array.isArray(input.labelsAsLlmAgentName) && input.labelsAsLlmAgentName.every(elem => "string" === typeof elem)) && (null === input.changeTargetPathAliases || undefined === input.changeTargetPathAliases || "object" === typeof input.changeTargetPathAliases && null !== input.changeTargetPathAliases && false === Array.isArray(input.changeTargetPathAliases) && _io2(input.changeTargetPathAliases)) && (null === input.startPreparation || undefined === input.startPreparation || "object" === typeof input.startPreparation && null !== input.startPreparation && _io3(input.startPreparation)) && (undefined === input.thresholdForAutoReject || "number" === typeof input.thresholdForAutoReject) && (null === input.dailySecurityScan || undefined === input.dailySecurityScan || "object" === typeof input.dailySecurityScan && null !== input.dailySecurityScan && _io4(input.dailySecurityScan)) && (undefined === input.claudeCodeOauthTokenListJsonPath || "string" === typeof input.claudeCodeOauthTokenListJsonPath) && (undefined === input.consoleDataOutputDir || "string" === typeof input.consoleDataOutputDir) && ("object" === typeof input.credentials && null !== input.credentials && _io5(input.credentials)); const _io1 = input => "string" === typeof input.repo && (Array.isArray(input.members) && input.members.every(elem => "string" === typeof elem)) && "string" === typeof input.spreadsheetUrl; const _io2 = input => Object.keys(input).every(key => {
|
|
96
97
|
const value = input[key];
|
|
97
98
|
if (undefined === value)
|
|
98
99
|
return true;
|
|
@@ -177,6 +178,10 @@ class HandleScheduledEventUseCaseHandler {
|
|
|
177
178
|
path: _path + ".claudeCodeOauthTokenListJsonPath",
|
|
178
179
|
expected: "(string | undefined)",
|
|
179
180
|
value: input.claudeCodeOauthTokenListJsonPath
|
|
181
|
+
}), undefined === input.consoleDataOutputDir || "string" === typeof input.consoleDataOutputDir || _report(_exceptionable, {
|
|
182
|
+
path: _path + ".consoleDataOutputDir",
|
|
183
|
+
expected: "(string | undefined)",
|
|
184
|
+
value: input.consoleDataOutputDir
|
|
180
185
|
}), ("object" === typeof input.credentials && null !== input.credentials || _report(_exceptionable, {
|
|
181
186
|
path: _path + ".credentials",
|
|
182
187
|
expected: "__type.o2",
|
|
@@ -522,6 +527,18 @@ class HandleScheduledEventUseCaseHandler {
|
|
|
522
527
|
preparationProcessCheckCommand: mergedInput.startPreparation?.preparationProcessCheckCommand ?? null,
|
|
523
528
|
localCommandRunner: nodeLocalCommandRunner,
|
|
524
529
|
});
|
|
530
|
+
try {
|
|
531
|
+
(0, consoleListsWriter_1.writeConsoleLists)({
|
|
532
|
+
consoleDataOutputDir: mergedInput.consoleDataOutputDir ?? null,
|
|
533
|
+
pjcode: input.projectName,
|
|
534
|
+
assigneeLogin: input.manager,
|
|
535
|
+
project: result.project,
|
|
536
|
+
issues: result.issues,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
console.error(`Failed to write console lists: ${error instanceof Error ? error.message : String(error)}`);
|
|
541
|
+
}
|
|
525
542
|
}
|
|
526
543
|
return result;
|
|
527
544
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HandleScheduledEventUseCaseHandler.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,gDAAwB;AACxB,kDAA0B;AAC1B,4CAAoB;AACpB,+DAA2D;AAC3D,uEAAmE;AACnE,wDAG8B;AAC9B,kFAA+E;AAC/E,sFAAmF;AACnF,gGAA6F;AAC7F,0FAAuF;AACvF,wFAAqF;AACrF,sFAAmF;AACnF,wGAAqG;AACrG,8GAA2G;AAC3G,sGAAmG;AACnG,gGAA6F;AAC7F,kGAA+F;AAC/F,gIAA6H;AAC7H,oHAAiH;AACjH,wGAAqG;AAIrG,0FAAuF;AACvF,wGAAqG;AACrG,gIAA6H;AAC7H,wGAAqG;AACrG,kIAA+H;AAC/H,8GAA2G;AAC3G,0GAAuG;AACvG,wGAAqG;AACrG,0HAAuH;AACvH,8GAA2G;AAC3G,8FAA2F;AAC3F,sFAAmF;AACnF,wGAAqG;AACrG,oGAAiG;AACjG,sGAAmG;AACnG,gHAA6G;AAC7G,0HAAuH;AACvH,kGAA+F;AAC/F,8GAA2G;AAC3G,gGAA6F;AAC7F,0EAAuE;AACvE,4EAKiD;AAEjD,MAAa,kCAAkC;IAA/C;QACE,WAAM,GAAG,KAAK,EACZ,cAAsB,EACtB,QAAiB,EAMT,EAAE;YACV,MAAM,iBAAiB,GAAG,YAAE,CAAC,YAAY,CAAC,cAAc,EAAE,MAAM,CAAC,CAAC;YAClE,MAAM,KAAK,GAAY,cAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"HandleScheduledEventUseCaseHandler.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.writeConsoleLists = exports.formatConsoleGeneratedAt = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const GenerateConsoleListsUseCase_1 = require("../../../domain/usecases/console/GenerateConsoleListsUseCase");
|
|
10
|
+
const CONSOLE_TAB_NAMES = [
|
|
11
|
+
'prs',
|
|
12
|
+
'triage',
|
|
13
|
+
'unread',
|
|
14
|
+
'failed-preparation',
|
|
15
|
+
];
|
|
16
|
+
const formatConsoleGeneratedAt = (date) => date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
17
|
+
exports.formatConsoleGeneratedAt = formatConsoleGeneratedAt;
|
|
18
|
+
const writeJsonAtomic = (filePath, data) => {
|
|
19
|
+
const dir = path_1.default.dirname(filePath);
|
|
20
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
21
|
+
const tmpPath = `${filePath}.tmp`;
|
|
22
|
+
fs_1.default.writeFileSync(tmpPath, JSON.stringify(data));
|
|
23
|
+
fs_1.default.renameSync(tmpPath, filePath);
|
|
24
|
+
};
|
|
25
|
+
const writeConsoleLists = (params) => {
|
|
26
|
+
const { consoleDataOutputDir, pjcode, assigneeLogin } = params;
|
|
27
|
+
if (!consoleDataOutputDir || !pjcode || !assigneeLogin) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const generatedAt = params.generatedAt ?? (0, exports.formatConsoleGeneratedAt)(new Date());
|
|
31
|
+
const lists = new GenerateConsoleListsUseCase_1.GenerateConsoleListsUseCase().run({
|
|
32
|
+
project: params.project,
|
|
33
|
+
issues: params.issues,
|
|
34
|
+
pjcode,
|
|
35
|
+
assigneeLogin,
|
|
36
|
+
generatedAt,
|
|
37
|
+
});
|
|
38
|
+
for (const tab of CONSOLE_TAB_NAMES) {
|
|
39
|
+
writeJsonAtomic(path_1.default.join(consoleDataOutputDir, pjcode, tab, 'list.json'), lists[tab]);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
exports.writeConsoleLists = writeConsoleLists;
|
|
43
|
+
//# sourceMappingURL=consoleListsWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consoleListsWriter.js","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/consoleListsWriter.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AAGxB,8GAIsE;AAWtE,MAAM,iBAAiB,GAAqB;IAC1C,KAAK;IACL,QAAQ;IACR,QAAQ;IACR,oBAAoB;CACrB,CAAC;AAEK,MAAM,wBAAwB,GAAG,CAAC,IAAU,EAAU,EAAE,CAC7D,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC;AADlC,QAAA,wBAAwB,4BACU;AAE/C,MAAM,eAAe,GAAG,CAAC,QAAgB,EAAE,IAAa,EAAQ,EAAE;IAChE,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,YAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAClC,YAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAChD,YAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC,CAAC;AAEK,MAAM,iBAAiB,GAAG,CAAC,MAAgC,EAAQ,EAAE;IAC1E,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,MAAM,CAAC;IAC/D,IAAI,CAAC,oBAAoB,IAAI,CAAC,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QACvD,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GACf,MAAM,CAAC,WAAW,IAAI,IAAA,gCAAwB,EAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC7D,MAAM,KAAK,GAAiB,IAAI,yDAA2B,EAAE,CAAC,GAAG,CAAC;QAChE,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM;QACN,aAAa;QACb,WAAW;KACZ,CAAC,CAAC;IAEH,KAAK,MAAM,GAAG,IAAI,iBAAiB,EAAE,CAAC;QACpC,eAAe,CACb,cAAI,CAAC,IAAI,CAAC,oBAAoB,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC,EACzD,KAAK,CAAC,GAAG,CAAC,CACX,CAAC;IACJ,CAAC;AACH,CAAC,CAAC;AAtBW,QAAA,iBAAiB,qBAsB5B"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GenerateConsoleListsUseCase = void 0;
|
|
4
|
+
const UNKNOWN_STORY_SORT_INDEX = 999999;
|
|
5
|
+
class GenerateConsoleListsUseCase {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.run = (input) => {
|
|
8
|
+
const { project, issues, pjcode, assigneeLogin, generatedAt } = input;
|
|
9
|
+
const storyOptions = project.story ? project.story.stories : [];
|
|
10
|
+
const storyOrder = storyOptions.map((option) => option.name);
|
|
11
|
+
const statusOptions = project.status.statuses;
|
|
12
|
+
const actionableIssues = issues.filter((issue) => this.isActionable(issue, assigneeLogin));
|
|
13
|
+
const buildStatusTab = (selector, excludedStatusNames) => ({
|
|
14
|
+
pjcode,
|
|
15
|
+
generatedAt,
|
|
16
|
+
statusOptions: this.buildFieldOptions(statusOptions, excludedStatusNames),
|
|
17
|
+
storyOrder,
|
|
18
|
+
storyColors: this.buildStoryColorsObject(storyOptions),
|
|
19
|
+
items: this.sortByStoryOrder(actionableIssues
|
|
20
|
+
.filter(selector)
|
|
21
|
+
.map((issue) => this.projectItem(issue)), storyOrder),
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
prs: buildStatusTab((issue) => issue.status !== null &&
|
|
25
|
+
issue.status.toLowerCase() === 'awaiting quality check', ['awaiting quality check', 'done']),
|
|
26
|
+
unread: buildStatusTab((issue) => issue.status !== null && issue.status.toLowerCase() === 'unread', ['unread', 'done']),
|
|
27
|
+
'failed-preparation': buildStatusTab((issue) => issue.status === 'Failed Preparation', [
|
|
28
|
+
'failed preparation',
|
|
29
|
+
'done',
|
|
30
|
+
'preparation',
|
|
31
|
+
'icebox',
|
|
32
|
+
'unread',
|
|
33
|
+
'in tmux by human',
|
|
34
|
+
]),
|
|
35
|
+
triage: {
|
|
36
|
+
pjcode,
|
|
37
|
+
generatedAt,
|
|
38
|
+
storyOptions: this.buildFieldOptions(storyOptions, []),
|
|
39
|
+
storyOrder,
|
|
40
|
+
storyColors: this.buildStoryColorsString(storyOptions),
|
|
41
|
+
items: this.sortByStoryOrder(actionableIssues
|
|
42
|
+
.filter((issue) => issue.story !== null &&
|
|
43
|
+
issue.story.toLowerCase().includes('no story'))
|
|
44
|
+
.map((issue) => this.projectItem(issue)), storyOrder),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
this.isActionable = (issue, assigneeLogin) => issue.isClosed === false &&
|
|
49
|
+
issue.assignees.includes(assigneeLogin) &&
|
|
50
|
+
issue.dependedIssueUrls.length === 0 &&
|
|
51
|
+
issue.nextActionDate === null &&
|
|
52
|
+
issue.nextActionHour === null;
|
|
53
|
+
this.projectItem = (issue) => ({
|
|
54
|
+
number: issue.number,
|
|
55
|
+
title: issue.title,
|
|
56
|
+
url: issue.url,
|
|
57
|
+
repo: issue.nameWithOwner,
|
|
58
|
+
nameWithOwner: issue.nameWithOwner,
|
|
59
|
+
projectItemId: issue.itemId,
|
|
60
|
+
itemId: issue.itemId,
|
|
61
|
+
isPr: issue.isPr,
|
|
62
|
+
story: issue.story ?? '',
|
|
63
|
+
labels: issue.labels,
|
|
64
|
+
createdAt: issue.createdAt.toISOString(),
|
|
65
|
+
});
|
|
66
|
+
this.buildFieldOptions = (options, excludedLowerCaseNames) => options
|
|
67
|
+
.filter((option) => !excludedLowerCaseNames.includes(option.name.toLowerCase()))
|
|
68
|
+
.map((option) => ({
|
|
69
|
+
id: option.id,
|
|
70
|
+
name: option.name,
|
|
71
|
+
color: option.color,
|
|
72
|
+
}));
|
|
73
|
+
this.buildStoryColorsObject = (options) => {
|
|
74
|
+
const result = {};
|
|
75
|
+
for (const option of options) {
|
|
76
|
+
result[option.name] = { color: option.color };
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
this.buildStoryColorsString = (options) => {
|
|
81
|
+
const result = {};
|
|
82
|
+
for (const option of options) {
|
|
83
|
+
result[option.name] = option.color;
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
};
|
|
87
|
+
this.sortByStoryOrder = (items, storyOrder) => {
|
|
88
|
+
const indexByStory = new Map(storyOrder.map((name, index) => [name, index]));
|
|
89
|
+
return items
|
|
90
|
+
.map((item, position) => ({
|
|
91
|
+
item,
|
|
92
|
+
position,
|
|
93
|
+
sortKey: indexByStory.get(item.story) ?? UNKNOWN_STORY_SORT_INDEX,
|
|
94
|
+
}))
|
|
95
|
+
.sort((a, b) => a.sortKey - b.sortKey || a.position - b.position)
|
|
96
|
+
.map((entry) => entry.item);
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.GenerateConsoleListsUseCase = GenerateConsoleListsUseCase;
|
|
101
|
+
//# sourceMappingURL=GenerateConsoleListsUseCase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GenerateConsoleListsUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":";;;AA4DA,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAExC,MAAa,2BAA2B;IAAxC;QACE,QAAG,GAAG,CAAC,KAAgC,EAAgB,EAAE;YACvD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,WAAW,EAAE,GAAG,KAAK,CAAC;YAEtE,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YAChE,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC7D,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC;YAE9C,MAAM,gBAAgB,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAC/C,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,aAAa,CAAC,CACxC,CAAC;YAEF,MAAM,cAAc,GAAG,CACrB,QAAmC,EACnC,mBAA6B,EACX,EAAE,CAAC,CAAC;gBACtB,MAAM;gBACN,WAAW;gBACX,aAAa,EAAE,IAAI,CAAC,iBAAiB,CAAC,aAAa,EAAE,mBAAmB,CAAC;gBACzE,UAAU;gBACV,WAAW,EAAE,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC;gBACtD,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAC1B,gBAAgB;qBACb,MAAM,CAAC,QAAQ,CAAC;qBAChB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAC1C,UAAU,CACX;aACF,CAAC,CAAC;YAEH,OAAO;gBACL,GAAG,EAAE,cAAc,CACjB,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,KAAK,IAAI;oBACrB,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,wBAAwB,EACzD,CAAC,wBAAwB,EAAE,MAAM,CAAC,CACnC;gBACD,MAAM,EAAE,cAAc,CACpB,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,QAAQ,EAClE,CAAC,QAAQ,EAAE,MAAM,CAAC,CACnB;gBACD,oBAAoB,EAAE,cAAc,CAClC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,oBAAoB,EAChD;oBACE,oBAAoB;oBACpB,MAAM;oBACN,aAAa;oBACb,QAAQ;oBACR,QAAQ;oBACR,kBAAkB;iBACnB,CACF;gBACD,MAAM,EAAE;oBACN,MAAM;oBACN,WAAW;oBACX,YAAY,EAAE,IAAI,CAAC,iBAAiB,CAAC,YAAY,EAAE,EAAE,CAAC;oBACtD,UAAU;oBACV,WAAW,EAAE,IAAI,CAAC,sBAAsB,CAAC,YAAY,CAAC;oBACtD,KAAK,EAAE,IAAI,CAAC,gBAAgB,CAC1B,gBAAgB;yBACb,MAAM,CACL,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,KAAK,KAAK,IAAI;wBACpB,KAAK,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CACjD;yBACA,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAC1C,UAAU,CACX;iBACF;aACF,CAAC;QACJ,CAAC,CAAC;QAEM,iBAAY,GAAG,CAAC,KAAY,EAAE,aAAqB,EAAW,EAAE,CACtE,KAAK,CAAC,QAAQ,KAAK,KAAK;YACxB,KAAK,CAAC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC;YACvC,KAAK,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC;YACpC,KAAK,CAAC,cAAc,KAAK,IAAI;YAC7B,KAAK,CAAC,cAAc,KAAK,IAAI,CAAC;QAExB,gBAAW,GAAG,CAAC,KAAY,EAAmB,EAAE,CAAC,CAAC;YACxD,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE,KAAK,CAAC,aAAa;YACzB,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,aAAa,EAAE,KAAK,CAAC,MAAM;YAC3B,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE;SACzC,CAAC,CAAC;QAEK,sBAAiB,GAAG,CAC1B,OAAsB,EACtB,sBAAgC,EACV,EAAE,CACxB,OAAO;aACJ,MAAM,CACL,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,sBAAsB,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CACxE;aACA,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YAChB,EAAE,EAAE,MAAM,CAAC,EAAE;YACb,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC,CAAC,CAAC;QAEA,2BAAsB,GAAG,CAC/B,OAAsB,EACmB,EAAE;YAC3C,MAAM,MAAM,GAA4C,EAAE,CAAC;YAC3D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;YAChD,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;QAEM,2BAAsB,GAAG,CAC/B,OAAsB,EACQ,EAAE;YAChC,MAAM,MAAM,GAAiC,EAAE,CAAC;YAChD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;YACrC,CAAC;YACD,OAAO,MAAM,CAAC;QAChB,CAAC,CAAC;QAEM,qBAAgB,GAAG,CACzB,KAAwB,EACxB,UAAoB,EACD,EAAE;YACrB,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,UAAU,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAC/C,CAAC;YACF,OAAO,KAAK;iBACT,GAAG,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;gBACxB,IAAI;gBACJ,QAAQ;gBACR,OAAO,EAAE,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,wBAAwB;aAClE,CAAC,CAAC;iBACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;iBAChE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC,CAAC;IACJ,CAAC;CAAA;AA/ID,kEA+IC"}
|
package/package.json
CHANGED
|
@@ -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 { writeConsoleLists } from './consoleListsWriter';
|
|
5
6
|
import { writeRotationOrderFile } from './rotationOrderFileWriter';
|
|
6
7
|
import {
|
|
7
8
|
fetchProjectReadme,
|
|
@@ -66,6 +67,7 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
66
67
|
const input: unknown = YAML.parse(configFileContent);
|
|
67
68
|
type inputType = Parameters<HandleScheduledEventUseCase['run']>[0] & {
|
|
68
69
|
claudeCodeOauthTokenListJsonPath?: string;
|
|
70
|
+
consoleDataOutputDir?: string;
|
|
69
71
|
credentials: {
|
|
70
72
|
manager: {
|
|
71
73
|
github: {
|
|
@@ -365,6 +367,22 @@ export class HandleScheduledEventUseCaseHandler {
|
|
|
365
367
|
mergedInput.startPreparation?.preparationProcessCheckCommand ?? null,
|
|
366
368
|
localCommandRunner: nodeLocalCommandRunner,
|
|
367
369
|
});
|
|
370
|
+
|
|
371
|
+
try {
|
|
372
|
+
writeConsoleLists({
|
|
373
|
+
consoleDataOutputDir: mergedInput.consoleDataOutputDir ?? null,
|
|
374
|
+
pjcode: input.projectName,
|
|
375
|
+
assigneeLogin: input.manager,
|
|
376
|
+
project: result.project,
|
|
377
|
+
issues: result.issues,
|
|
378
|
+
});
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error(
|
|
381
|
+
`Failed to write console lists: ${
|
|
382
|
+
error instanceof Error ? error.message : String(error)
|
|
383
|
+
}`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
368
386
|
}
|
|
369
387
|
return result;
|
|
370
388
|
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { mock } from 'jest-mock-extended';
|
|
5
|
+
import { Issue } from '../../../domain/entities/Issue';
|
|
6
|
+
import { FieldOption, Project } from '../../../domain/entities/Project';
|
|
7
|
+
import {
|
|
8
|
+
formatConsoleGeneratedAt,
|
|
9
|
+
writeConsoleLists,
|
|
10
|
+
} from './consoleListsWriter';
|
|
11
|
+
|
|
12
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
13
|
+
value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
14
|
+
|
|
15
|
+
const isUnknownArray = (value: unknown): value is unknown[] =>
|
|
16
|
+
Array.isArray(value);
|
|
17
|
+
|
|
18
|
+
const ASSIGNEE = 'owner-login';
|
|
19
|
+
|
|
20
|
+
const option = (
|
|
21
|
+
id: string,
|
|
22
|
+
name: string,
|
|
23
|
+
color: FieldOption['color'],
|
|
24
|
+
): FieldOption => ({ id, name, color, description: '' });
|
|
25
|
+
|
|
26
|
+
const project: Project = {
|
|
27
|
+
...mock<Project>(),
|
|
28
|
+
status: {
|
|
29
|
+
name: 'Status',
|
|
30
|
+
fieldId: 'status-field',
|
|
31
|
+
statuses: [
|
|
32
|
+
option('st-unread', 'Unread', 'ORANGE'),
|
|
33
|
+
option('st-aw', 'Awaiting Workspace', 'BLUE'),
|
|
34
|
+
option('st-aqc', 'Awaiting Quality Check', 'GREEN'),
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
story: {
|
|
38
|
+
name: 'story',
|
|
39
|
+
fieldId: 'story-field',
|
|
40
|
+
databaseId: 2,
|
|
41
|
+
stories: [option('s1', 'Story Alpha', 'BLUE')],
|
|
42
|
+
workflowManagementStory: { id: 'wm', name: 'workflow management' },
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const makeIssue = (overrides: Partial<Issue>): Issue => ({
|
|
47
|
+
...mock<Issue>(),
|
|
48
|
+
number: 1,
|
|
49
|
+
title: 'Issue 1',
|
|
50
|
+
nameWithOwner: 'demo/repo',
|
|
51
|
+
url: 'https://github.com/demo/repo/issues/1',
|
|
52
|
+
status: null,
|
|
53
|
+
story: null,
|
|
54
|
+
nextActionDate: null,
|
|
55
|
+
nextActionHour: null,
|
|
56
|
+
dependedIssueUrls: [],
|
|
57
|
+
assignees: [ASSIGNEE],
|
|
58
|
+
labels: [],
|
|
59
|
+
body: 'should never be written',
|
|
60
|
+
itemId: 'item-1',
|
|
61
|
+
isPr: false,
|
|
62
|
+
isClosed: false,
|
|
63
|
+
createdAt: new Date('2026-06-13T08:18:45.000Z'),
|
|
64
|
+
...overrides,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('writeConsoleLists', () => {
|
|
68
|
+
let outDir: string;
|
|
69
|
+
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
outDir = fs.mkdtempSync(path.join(os.tmpdir(), 'console-out-'));
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
afterEach(() => {
|
|
75
|
+
fs.rmSync(outDir, { recursive: true, force: true });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const tabFile = (tab: string): string =>
|
|
79
|
+
path.join(outDir, 'demo', tab, 'list.json');
|
|
80
|
+
|
|
81
|
+
it('writes all four tab list.json files', () => {
|
|
82
|
+
writeConsoleLists({
|
|
83
|
+
consoleDataOutputDir: outDir,
|
|
84
|
+
pjcode: 'demo',
|
|
85
|
+
assigneeLogin: ASSIGNEE,
|
|
86
|
+
project,
|
|
87
|
+
issues: [makeIssue({ status: 'Unread' })],
|
|
88
|
+
generatedAt: '2026-06-14T07:22:33Z',
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
for (const tab of ['prs', 'triage', 'unread', 'failed-preparation']) {
|
|
92
|
+
expect(fs.existsSync(tabFile(tab))).toBe(true);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('writes items with no body field', () => {
|
|
97
|
+
writeConsoleLists({
|
|
98
|
+
consoleDataOutputDir: outDir,
|
|
99
|
+
pjcode: 'demo',
|
|
100
|
+
assigneeLogin: ASSIGNEE,
|
|
101
|
+
project,
|
|
102
|
+
issues: [makeIssue({ status: 'Unread' })],
|
|
103
|
+
generatedAt: '2026-06-14T07:22:33Z',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const raw: unknown = JSON.parse(fs.readFileSync(tabFile('unread'), 'utf8'));
|
|
107
|
+
expect(isRecord(raw)).toBe(true);
|
|
108
|
+
const items: unknown = isRecord(raw) ? raw.items : undefined;
|
|
109
|
+
expect(isUnknownArray(items)).toBe(true);
|
|
110
|
+
const firstItem: unknown = isUnknownArray(items) ? items[0] : undefined;
|
|
111
|
+
expect(isRecord(firstItem)).toBe(true);
|
|
112
|
+
expect(firstItem).not.toHaveProperty('body');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('does not leave a temp file behind after writing', () => {
|
|
116
|
+
writeConsoleLists({
|
|
117
|
+
consoleDataOutputDir: outDir,
|
|
118
|
+
pjcode: 'demo',
|
|
119
|
+
assigneeLogin: ASSIGNEE,
|
|
120
|
+
project,
|
|
121
|
+
issues: [],
|
|
122
|
+
generatedAt: '2026-06-14T07:22:33Z',
|
|
123
|
+
});
|
|
124
|
+
expect(fs.existsSync(`${tabFile('prs')}.tmp`)).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('is a no-op when consoleDataOutputDir is unset', () => {
|
|
128
|
+
writeConsoleLists({
|
|
129
|
+
consoleDataOutputDir: undefined,
|
|
130
|
+
pjcode: 'demo',
|
|
131
|
+
assigneeLogin: ASSIGNEE,
|
|
132
|
+
project,
|
|
133
|
+
issues: [makeIssue({ status: 'Unread' })],
|
|
134
|
+
});
|
|
135
|
+
expect(fs.readdirSync(outDir)).toHaveLength(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('is a no-op when pjcode is unset', () => {
|
|
139
|
+
writeConsoleLists({
|
|
140
|
+
consoleDataOutputDir: outDir,
|
|
141
|
+
pjcode: '',
|
|
142
|
+
assigneeLogin: ASSIGNEE,
|
|
143
|
+
project,
|
|
144
|
+
issues: [makeIssue({ status: 'Unread' })],
|
|
145
|
+
});
|
|
146
|
+
expect(fs.readdirSync(outDir)).toHaveLength(0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('is a no-op when assigneeLogin is unset', () => {
|
|
150
|
+
writeConsoleLists({
|
|
151
|
+
consoleDataOutputDir: outDir,
|
|
152
|
+
pjcode: 'demo',
|
|
153
|
+
assigneeLogin: null,
|
|
154
|
+
project,
|
|
155
|
+
issues: [makeIssue({ status: 'Unread' })],
|
|
156
|
+
});
|
|
157
|
+
expect(fs.readdirSync(outDir)).toHaveLength(0);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe('formatConsoleGeneratedAt', () => {
|
|
162
|
+
it('strips milliseconds and keeps the trailing Z', () => {
|
|
163
|
+
expect(formatConsoleGeneratedAt(new Date('2026-06-14T07:22:33.456Z'))).toBe(
|
|
164
|
+
'2026-06-14T07:22:33Z',
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import type { Issue } from '../../../domain/entities/Issue';
|
|
4
|
+
import type { Project } from '../../../domain/entities/Project';
|
|
5
|
+
import {
|
|
6
|
+
ConsoleLists,
|
|
7
|
+
ConsoleTabName,
|
|
8
|
+
GenerateConsoleListsUseCase,
|
|
9
|
+
} from '../../../domain/usecases/console/GenerateConsoleListsUseCase';
|
|
10
|
+
|
|
11
|
+
export type ConsoleListsWriterParams = {
|
|
12
|
+
consoleDataOutputDir: string | null | undefined;
|
|
13
|
+
pjcode: string | null | undefined;
|
|
14
|
+
assigneeLogin: string | null | undefined;
|
|
15
|
+
project: Project;
|
|
16
|
+
issues: Issue[];
|
|
17
|
+
generatedAt?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const CONSOLE_TAB_NAMES: ConsoleTabName[] = [
|
|
21
|
+
'prs',
|
|
22
|
+
'triage',
|
|
23
|
+
'unread',
|
|
24
|
+
'failed-preparation',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
export const formatConsoleGeneratedAt = (date: Date): string =>
|
|
28
|
+
date.toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
29
|
+
|
|
30
|
+
const writeJsonAtomic = (filePath: string, data: unknown): void => {
|
|
31
|
+
const dir = path.dirname(filePath);
|
|
32
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
33
|
+
const tmpPath = `${filePath}.tmp`;
|
|
34
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data));
|
|
35
|
+
fs.renameSync(tmpPath, filePath);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const writeConsoleLists = (params: ConsoleListsWriterParams): void => {
|
|
39
|
+
const { consoleDataOutputDir, pjcode, assigneeLogin } = params;
|
|
40
|
+
if (!consoleDataOutputDir || !pjcode || !assigneeLogin) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const generatedAt =
|
|
45
|
+
params.generatedAt ?? formatConsoleGeneratedAt(new Date());
|
|
46
|
+
const lists: ConsoleLists = new GenerateConsoleListsUseCase().run({
|
|
47
|
+
project: params.project,
|
|
48
|
+
issues: params.issues,
|
|
49
|
+
pjcode,
|
|
50
|
+
assigneeLogin,
|
|
51
|
+
generatedAt,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
for (const tab of CONSOLE_TAB_NAMES) {
|
|
55
|
+
writeJsonAtomic(
|
|
56
|
+
path.join(consoleDataOutputDir, pjcode, tab, 'list.json'),
|
|
57
|
+
lists[tab],
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { Issue } from '../../entities/Issue';
|
|
2
|
+
import { FieldOption, Project } from '../../entities/Project';
|
|
3
|
+
import { GenerateConsoleListsUseCase } from './GenerateConsoleListsUseCase';
|
|
4
|
+
|
|
5
|
+
const ASSIGNEE = 'owner-login';
|
|
6
|
+
|
|
7
|
+
const storyOption = (
|
|
8
|
+
id: string,
|
|
9
|
+
name: string,
|
|
10
|
+
color: FieldOption['color'],
|
|
11
|
+
): FieldOption => ({ id, name, color, description: '' });
|
|
12
|
+
|
|
13
|
+
const STORY_OPTIONS: FieldOption[] = [
|
|
14
|
+
storyOption('s1', 'regular / NO STORY; SET STORY FIELD', 'RED'),
|
|
15
|
+
storyOption('s2', 'Story Alpha', 'BLUE'),
|
|
16
|
+
storyOption('s3', 'Story Beta', 'GREEN'),
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const STATUS_OPTIONS: FieldOption[] = [
|
|
20
|
+
storyOption('st-unread', 'Unread', 'ORANGE'),
|
|
21
|
+
storyOption('st-aw', 'Awaiting Workspace', 'BLUE'),
|
|
22
|
+
storyOption('st-prep', 'Preparation', 'YELLOW'),
|
|
23
|
+
storyOption('st-failed', 'Failed Preparation', 'RED'),
|
|
24
|
+
storyOption('st-aqc', 'Awaiting Quality Check', 'GREEN'),
|
|
25
|
+
storyOption('st-todo', 'Todo by human', 'PINK'),
|
|
26
|
+
storyOption('st-tmux', 'In Tmux by human', 'RED'),
|
|
27
|
+
storyOption('st-done', 'Done', 'PURPLE'),
|
|
28
|
+
storyOption('st-icebox', 'Icebox', 'GRAY'),
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const baseProject = (story: Project['story']): Project => ({
|
|
32
|
+
id: 'project-node-id',
|
|
33
|
+
url: 'https://github.com/orgs/demo/projects/1',
|
|
34
|
+
databaseId: 1,
|
|
35
|
+
name: 'demo',
|
|
36
|
+
status: {
|
|
37
|
+
name: 'Status',
|
|
38
|
+
fieldId: 'status-field',
|
|
39
|
+
statuses: STATUS_OPTIONS,
|
|
40
|
+
},
|
|
41
|
+
nextActionDate: null,
|
|
42
|
+
nextActionHour: null,
|
|
43
|
+
story,
|
|
44
|
+
remainingEstimationMinutes: null,
|
|
45
|
+
dependedIssueUrlSeparatedByComma: null,
|
|
46
|
+
completionDate50PercentConfidence: null,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const projectWithStory: Project = baseProject({
|
|
50
|
+
name: 'story',
|
|
51
|
+
fieldId: 'story-field',
|
|
52
|
+
databaseId: 2,
|
|
53
|
+
stories: STORY_OPTIONS,
|
|
54
|
+
workflowManagementStory: { id: 'wm', name: 'workflow management' },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
let issueCounter = 0;
|
|
58
|
+
const makeIssue = (overrides: Partial<Issue>): Issue => {
|
|
59
|
+
issueCounter += 1;
|
|
60
|
+
return {
|
|
61
|
+
nameWithOwner: 'demo/repo',
|
|
62
|
+
number: issueCounter,
|
|
63
|
+
title: `Issue ${issueCounter}`,
|
|
64
|
+
state: 'OPEN',
|
|
65
|
+
status: null,
|
|
66
|
+
story: null,
|
|
67
|
+
nextActionDate: null,
|
|
68
|
+
nextActionHour: null,
|
|
69
|
+
estimationMinutes: null,
|
|
70
|
+
dependedIssueUrls: [],
|
|
71
|
+
completionDate50PercentConfidence: null,
|
|
72
|
+
url: `https://github.com/demo/repo/issues/${issueCounter}`,
|
|
73
|
+
assignees: [ASSIGNEE],
|
|
74
|
+
labels: [],
|
|
75
|
+
org: 'demo',
|
|
76
|
+
repo: 'repo',
|
|
77
|
+
body: 'should never be projected',
|
|
78
|
+
itemId: `item-${issueCounter}`,
|
|
79
|
+
isPr: false,
|
|
80
|
+
isInProgress: false,
|
|
81
|
+
isClosed: false,
|
|
82
|
+
createdAt: new Date('2026-06-13T08:18:45.000Z'),
|
|
83
|
+
author: 'someone',
|
|
84
|
+
...overrides,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
describe('GenerateConsoleListsUseCase', () => {
|
|
89
|
+
const usecase = new GenerateConsoleListsUseCase();
|
|
90
|
+
const generatedAt = '2026-06-14T07:22:33Z';
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
issueCounter = 0;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const run = (issues: Issue[], project: Project = projectWithStory) =>
|
|
97
|
+
usecase.run({
|
|
98
|
+
project,
|
|
99
|
+
issues,
|
|
100
|
+
pjcode: 'demo',
|
|
101
|
+
assigneeLogin: ASSIGNEE,
|
|
102
|
+
generatedAt,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('common actionable filter', () => {
|
|
106
|
+
it('rejects closed issues', () => {
|
|
107
|
+
const result = run([makeIssue({ status: 'Unread', isClosed: true })]);
|
|
108
|
+
expect(result.unread.items).toHaveLength(0);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('rejects issues not assigned to the assignee login', () => {
|
|
112
|
+
const result = run([
|
|
113
|
+
makeIssue({ status: 'Unread', assignees: ['other-person'] }),
|
|
114
|
+
]);
|
|
115
|
+
expect(result.unread.items).toHaveLength(0);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('rejects issues with a depended issue url', () => {
|
|
119
|
+
const result = run([
|
|
120
|
+
makeIssue({
|
|
121
|
+
status: 'Unread',
|
|
122
|
+
dependedIssueUrls: ['https://github.com/demo/repo/issues/99'],
|
|
123
|
+
}),
|
|
124
|
+
]);
|
|
125
|
+
expect(result.unread.items).toHaveLength(0);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('rejects issues with a next action date', () => {
|
|
129
|
+
const result = run([
|
|
130
|
+
makeIssue({
|
|
131
|
+
status: 'Unread',
|
|
132
|
+
nextActionDate: new Date('2026-07-01T00:00:00.000Z'),
|
|
133
|
+
}),
|
|
134
|
+
]);
|
|
135
|
+
expect(result.unread.items).toHaveLength(0);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('rejects issues with a next action hour', () => {
|
|
139
|
+
const result = run([makeIssue({ status: 'Unread', nextActionHour: 9 })]);
|
|
140
|
+
expect(result.unread.items).toHaveLength(0);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('accepts an issue that passes every actionable condition', () => {
|
|
144
|
+
const result = run([makeIssue({ status: 'Unread' })]);
|
|
145
|
+
expect(result.unread.items).toHaveLength(1);
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('per-tab selectors', () => {
|
|
150
|
+
it('selects awaiting quality check items case-insensitively for prs', () => {
|
|
151
|
+
const result = run([
|
|
152
|
+
makeIssue({ status: 'awaiting quality check' }),
|
|
153
|
+
makeIssue({ status: 'Awaiting Quality Check' }),
|
|
154
|
+
makeIssue({ status: 'Unread' }),
|
|
155
|
+
]);
|
|
156
|
+
expect(result.prs.items).toHaveLength(2);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it('selects unread items case-insensitively', () => {
|
|
160
|
+
const result = run([
|
|
161
|
+
makeIssue({ status: 'UNREAD' }),
|
|
162
|
+
makeIssue({ status: 'Awaiting Quality Check' }),
|
|
163
|
+
]);
|
|
164
|
+
expect(result.unread.items).toHaveLength(1);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('selects failed preparation items only with exact case', () => {
|
|
168
|
+
const result = run([
|
|
169
|
+
makeIssue({ status: 'Failed Preparation' }),
|
|
170
|
+
makeIssue({ status: 'failed preparation' }),
|
|
171
|
+
makeIssue({ status: 'FAILED PREPARATION' }),
|
|
172
|
+
]);
|
|
173
|
+
expect(result['failed-preparation'].items).toHaveLength(1);
|
|
174
|
+
expect(result['failed-preparation'].items[0].number).toBe(1);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('selects no-story items case-insensitively for triage', () => {
|
|
178
|
+
const result = run([
|
|
179
|
+
makeIssue({ story: 'regular / NO STORY; SET STORY FIELD' }),
|
|
180
|
+
makeIssue({ story: 'no story please' }),
|
|
181
|
+
makeIssue({ story: 'Story Alpha' }),
|
|
182
|
+
makeIssue({ story: null }),
|
|
183
|
+
]);
|
|
184
|
+
expect(result.triage.items).toHaveLength(2);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe('common item projection', () => {
|
|
189
|
+
it('projects the expected keys and never includes a body field', () => {
|
|
190
|
+
const result = run([
|
|
191
|
+
makeIssue({
|
|
192
|
+
status: 'Unread',
|
|
193
|
+
story: 'Story Alpha',
|
|
194
|
+
labels: ['bug', 'p1'],
|
|
195
|
+
isPr: true,
|
|
196
|
+
}),
|
|
197
|
+
]);
|
|
198
|
+
const item = result.unread.items[0];
|
|
199
|
+
expect(Object.keys(item).sort()).toEqual(
|
|
200
|
+
[
|
|
201
|
+
'createdAt',
|
|
202
|
+
'isPr',
|
|
203
|
+
'itemId',
|
|
204
|
+
'labels',
|
|
205
|
+
'nameWithOwner',
|
|
206
|
+
'number',
|
|
207
|
+
'projectItemId',
|
|
208
|
+
'repo',
|
|
209
|
+
'story',
|
|
210
|
+
'title',
|
|
211
|
+
'url',
|
|
212
|
+
].sort(),
|
|
213
|
+
);
|
|
214
|
+
expect(item).not.toHaveProperty('body');
|
|
215
|
+
expect(item.repo).toBe('demo/repo');
|
|
216
|
+
expect(item.nameWithOwner).toBe('demo/repo');
|
|
217
|
+
expect(item.projectItemId).toBe(item.itemId);
|
|
218
|
+
expect(item.isPr).toBe(true);
|
|
219
|
+
expect(item.story).toBe('Story Alpha');
|
|
220
|
+
expect(item.labels).toEqual(['bug', 'p1']);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('maps a null story to an empty string', () => {
|
|
224
|
+
const result = run([makeIssue({ status: 'Unread', story: null })]);
|
|
225
|
+
expect(result.unread.items[0].story).toBe('');
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it('serializes createdAt as an ISO string keeping milliseconds', () => {
|
|
229
|
+
const result = run([
|
|
230
|
+
makeIssue({
|
|
231
|
+
status: 'Unread',
|
|
232
|
+
createdAt: new Date('2026-06-13T08:18:45.000Z'),
|
|
233
|
+
}),
|
|
234
|
+
]);
|
|
235
|
+
expect(result.unread.items[0].createdAt).toBe('2026-06-13T08:18:45.000Z');
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe('story order stable sort', () => {
|
|
240
|
+
it('sorts by story field order and places unknown stories last', () => {
|
|
241
|
+
const result = run([
|
|
242
|
+
makeIssue({ status: 'Unread', story: 'Story Beta' }),
|
|
243
|
+
makeIssue({ status: 'Unread', story: 'Unmapped Story' }),
|
|
244
|
+
makeIssue({ status: 'Unread', story: 'Story Alpha' }),
|
|
245
|
+
makeIssue({ status: 'Unread', story: 'Story Beta' }),
|
|
246
|
+
]);
|
|
247
|
+
expect(result.unread.items.map((i) => i.story)).toEqual([
|
|
248
|
+
'Story Alpha',
|
|
249
|
+
'Story Beta',
|
|
250
|
+
'Story Beta',
|
|
251
|
+
'Unmapped Story',
|
|
252
|
+
]);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('keeps original order between items sharing the same story (stable)', () => {
|
|
256
|
+
const result = run([
|
|
257
|
+
makeIssue({ status: 'Unread', story: 'Story Alpha', title: 'first' }),
|
|
258
|
+
makeIssue({ status: 'Unread', story: 'Story Alpha', title: 'second' }),
|
|
259
|
+
]);
|
|
260
|
+
expect(result.unread.items.map((i) => i.title)).toEqual([
|
|
261
|
+
'first',
|
|
262
|
+
'second',
|
|
263
|
+
]);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe('options construction', () => {
|
|
268
|
+
it('excludes awaiting quality check and done from prs status options', () => {
|
|
269
|
+
const names = run([]).prs.statusOptions.map((o) => o.name);
|
|
270
|
+
expect(names).not.toContain('Awaiting Quality Check');
|
|
271
|
+
expect(names).not.toContain('Done');
|
|
272
|
+
expect(names).toContain('Unread');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('excludes unread and done from unread status options', () => {
|
|
276
|
+
const names = run([]).unread.statusOptions.map((o) => o.name);
|
|
277
|
+
expect(names).not.toContain('Unread');
|
|
278
|
+
expect(names).not.toContain('Done');
|
|
279
|
+
expect(names).toContain('Awaiting Workspace');
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it('excludes the failed-preparation routing-excluded set', () => {
|
|
283
|
+
const names = run([])['failed-preparation'].statusOptions.map(
|
|
284
|
+
(o) => o.name,
|
|
285
|
+
);
|
|
286
|
+
for (const excluded of [
|
|
287
|
+
'Failed Preparation',
|
|
288
|
+
'Done',
|
|
289
|
+
'Preparation',
|
|
290
|
+
'Icebox',
|
|
291
|
+
'Unread',
|
|
292
|
+
'In Tmux by human',
|
|
293
|
+
]) {
|
|
294
|
+
expect(names).not.toContain(excluded);
|
|
295
|
+
}
|
|
296
|
+
expect(names).toContain('Awaiting Workspace');
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('emits all story options for triage and no statusOptions key', () => {
|
|
300
|
+
const triage = run([]).triage;
|
|
301
|
+
expect(triage.storyOptions.map((o) => o.name)).toEqual([
|
|
302
|
+
'regular / NO STORY; SET STORY FIELD',
|
|
303
|
+
'Story Alpha',
|
|
304
|
+
'Story Beta',
|
|
305
|
+
]);
|
|
306
|
+
expect(triage).not.toHaveProperty('statusOptions');
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
it('builds storyOrder from story field option order', () => {
|
|
310
|
+
expect(run([]).prs.storyOrder).toEqual([
|
|
311
|
+
'regular / NO STORY; SET STORY FIELD',
|
|
312
|
+
'Story Alpha',
|
|
313
|
+
'Story Beta',
|
|
314
|
+
]);
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
describe('storyColors shape per tab', () => {
|
|
319
|
+
it('uses object color values for prs, unread and failed-preparation', () => {
|
|
320
|
+
const result = run([]);
|
|
321
|
+
expect(result.prs.storyColors['Story Alpha']).toEqual({ color: 'BLUE' });
|
|
322
|
+
expect(result.unread.storyColors['Story Beta']).toEqual({
|
|
323
|
+
color: 'GREEN',
|
|
324
|
+
});
|
|
325
|
+
expect(result['failed-preparation'].storyColors['Story Alpha']).toEqual({
|
|
326
|
+
color: 'BLUE',
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it('uses plain string color values for triage', () => {
|
|
331
|
+
const result = run([]);
|
|
332
|
+
expect(result.triage.storyColors['Story Alpha']).toBe('BLUE');
|
|
333
|
+
expect(result.triage.storyColors['Story Beta']).toBe('GREEN');
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
describe('generatedAt and pjcode passthrough', () => {
|
|
338
|
+
it('writes the provided generatedAt without milliseconds on every tab', () => {
|
|
339
|
+
const result = run([]);
|
|
340
|
+
expect(result.prs.generatedAt).toBe(generatedAt);
|
|
341
|
+
expect(result.triage.generatedAt).toBe(generatedAt);
|
|
342
|
+
expect(result.unread.generatedAt).toBe(generatedAt);
|
|
343
|
+
expect(result['failed-preparation'].generatedAt).toBe(generatedAt);
|
|
344
|
+
expect(generatedAt).not.toMatch(/\.\d{3}Z$/);
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('writes the configured pjcode on every tab', () => {
|
|
348
|
+
const result = run([]);
|
|
349
|
+
expect(result.prs.pjcode).toBe('demo');
|
|
350
|
+
expect(result.triage.pjcode).toBe('demo');
|
|
351
|
+
expect(result.unread.pjcode).toBe('demo');
|
|
352
|
+
expect(result['failed-preparation'].pjcode).toBe('demo');
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe('project without a story field', () => {
|
|
357
|
+
const projectNoStory = baseProject(null);
|
|
358
|
+
|
|
359
|
+
it('degrades story order, colors and triage options gracefully', () => {
|
|
360
|
+
const result = run(
|
|
361
|
+
[makeIssue({ status: 'Unread', story: 'no story' })],
|
|
362
|
+
projectNoStory,
|
|
363
|
+
);
|
|
364
|
+
expect(result.prs.storyOrder).toEqual([]);
|
|
365
|
+
expect(result.prs.storyColors).toEqual({});
|
|
366
|
+
expect(result.triage.storyOptions).toEqual([]);
|
|
367
|
+
expect(result.triage.storyColors).toEqual({});
|
|
368
|
+
expect(result.unread.items).toHaveLength(1);
|
|
369
|
+
expect(result.triage.items).toHaveLength(1);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Issue } from '../../entities/Issue';
|
|
2
|
+
import { FieldOption, Project } from '../../entities/Project';
|
|
3
|
+
|
|
4
|
+
export type ConsoleColor = FieldOption['color'];
|
|
5
|
+
|
|
6
|
+
export type ConsoleListItem = {
|
|
7
|
+
number: number;
|
|
8
|
+
title: string;
|
|
9
|
+
url: string;
|
|
10
|
+
repo: string;
|
|
11
|
+
nameWithOwner: string;
|
|
12
|
+
projectItemId: string;
|
|
13
|
+
itemId: string;
|
|
14
|
+
isPr: boolean;
|
|
15
|
+
story: string;
|
|
16
|
+
labels: string[];
|
|
17
|
+
createdAt: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type ConsoleFieldOption = {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
color: ConsoleColor;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ConsoleStatusTab = {
|
|
27
|
+
pjcode: string;
|
|
28
|
+
generatedAt: string;
|
|
29
|
+
statusOptions: ConsoleFieldOption[];
|
|
30
|
+
storyOrder: string[];
|
|
31
|
+
storyColors: Record<string, { color: ConsoleColor }>;
|
|
32
|
+
items: ConsoleListItem[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ConsoleTriageTab = {
|
|
36
|
+
pjcode: string;
|
|
37
|
+
generatedAt: string;
|
|
38
|
+
storyOptions: ConsoleFieldOption[];
|
|
39
|
+
storyOrder: string[];
|
|
40
|
+
storyColors: Record<string, ConsoleColor>;
|
|
41
|
+
items: ConsoleListItem[];
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type ConsoleTabName = 'prs' | 'triage' | 'unread' | 'failed-preparation';
|
|
45
|
+
|
|
46
|
+
export type ConsoleLists = {
|
|
47
|
+
prs: ConsoleStatusTab;
|
|
48
|
+
triage: ConsoleTriageTab;
|
|
49
|
+
unread: ConsoleStatusTab;
|
|
50
|
+
'failed-preparation': ConsoleStatusTab;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type GenerateConsoleListsInput = {
|
|
54
|
+
project: Project;
|
|
55
|
+
issues: Issue[];
|
|
56
|
+
pjcode: string;
|
|
57
|
+
assigneeLogin: string;
|
|
58
|
+
generatedAt: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const UNKNOWN_STORY_SORT_INDEX = 999999;
|
|
62
|
+
|
|
63
|
+
export class GenerateConsoleListsUseCase {
|
|
64
|
+
run = (input: GenerateConsoleListsInput): ConsoleLists => {
|
|
65
|
+
const { project, issues, pjcode, assigneeLogin, generatedAt } = input;
|
|
66
|
+
|
|
67
|
+
const storyOptions = project.story ? project.story.stories : [];
|
|
68
|
+
const storyOrder = storyOptions.map((option) => option.name);
|
|
69
|
+
const statusOptions = project.status.statuses;
|
|
70
|
+
|
|
71
|
+
const actionableIssues = issues.filter((issue) =>
|
|
72
|
+
this.isActionable(issue, assigneeLogin),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const buildStatusTab = (
|
|
76
|
+
selector: (issue: Issue) => boolean,
|
|
77
|
+
excludedStatusNames: string[],
|
|
78
|
+
): ConsoleStatusTab => ({
|
|
79
|
+
pjcode,
|
|
80
|
+
generatedAt,
|
|
81
|
+
statusOptions: this.buildFieldOptions(statusOptions, excludedStatusNames),
|
|
82
|
+
storyOrder,
|
|
83
|
+
storyColors: this.buildStoryColorsObject(storyOptions),
|
|
84
|
+
items: this.sortByStoryOrder(
|
|
85
|
+
actionableIssues
|
|
86
|
+
.filter(selector)
|
|
87
|
+
.map((issue) => this.projectItem(issue)),
|
|
88
|
+
storyOrder,
|
|
89
|
+
),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
prs: buildStatusTab(
|
|
94
|
+
(issue) =>
|
|
95
|
+
issue.status !== null &&
|
|
96
|
+
issue.status.toLowerCase() === 'awaiting quality check',
|
|
97
|
+
['awaiting quality check', 'done'],
|
|
98
|
+
),
|
|
99
|
+
unread: buildStatusTab(
|
|
100
|
+
(issue) =>
|
|
101
|
+
issue.status !== null && issue.status.toLowerCase() === 'unread',
|
|
102
|
+
['unread', 'done'],
|
|
103
|
+
),
|
|
104
|
+
'failed-preparation': buildStatusTab(
|
|
105
|
+
(issue) => issue.status === 'Failed Preparation',
|
|
106
|
+
[
|
|
107
|
+
'failed preparation',
|
|
108
|
+
'done',
|
|
109
|
+
'preparation',
|
|
110
|
+
'icebox',
|
|
111
|
+
'unread',
|
|
112
|
+
'in tmux by human',
|
|
113
|
+
],
|
|
114
|
+
),
|
|
115
|
+
triage: {
|
|
116
|
+
pjcode,
|
|
117
|
+
generatedAt,
|
|
118
|
+
storyOptions: this.buildFieldOptions(storyOptions, []),
|
|
119
|
+
storyOrder,
|
|
120
|
+
storyColors: this.buildStoryColorsString(storyOptions),
|
|
121
|
+
items: this.sortByStoryOrder(
|
|
122
|
+
actionableIssues
|
|
123
|
+
.filter(
|
|
124
|
+
(issue) =>
|
|
125
|
+
issue.story !== null &&
|
|
126
|
+
issue.story.toLowerCase().includes('no story'),
|
|
127
|
+
)
|
|
128
|
+
.map((issue) => this.projectItem(issue)),
|
|
129
|
+
storyOrder,
|
|
130
|
+
),
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
private isActionable = (issue: Issue, assigneeLogin: string): boolean =>
|
|
136
|
+
issue.isClosed === false &&
|
|
137
|
+
issue.assignees.includes(assigneeLogin) &&
|
|
138
|
+
issue.dependedIssueUrls.length === 0 &&
|
|
139
|
+
issue.nextActionDate === null &&
|
|
140
|
+
issue.nextActionHour === null;
|
|
141
|
+
|
|
142
|
+
private projectItem = (issue: Issue): ConsoleListItem => ({
|
|
143
|
+
number: issue.number,
|
|
144
|
+
title: issue.title,
|
|
145
|
+
url: issue.url,
|
|
146
|
+
repo: issue.nameWithOwner,
|
|
147
|
+
nameWithOwner: issue.nameWithOwner,
|
|
148
|
+
projectItemId: issue.itemId,
|
|
149
|
+
itemId: issue.itemId,
|
|
150
|
+
isPr: issue.isPr,
|
|
151
|
+
story: issue.story ?? '',
|
|
152
|
+
labels: issue.labels,
|
|
153
|
+
createdAt: issue.createdAt.toISOString(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
private buildFieldOptions = (
|
|
157
|
+
options: FieldOption[],
|
|
158
|
+
excludedLowerCaseNames: string[],
|
|
159
|
+
): ConsoleFieldOption[] =>
|
|
160
|
+
options
|
|
161
|
+
.filter(
|
|
162
|
+
(option) => !excludedLowerCaseNames.includes(option.name.toLowerCase()),
|
|
163
|
+
)
|
|
164
|
+
.map((option) => ({
|
|
165
|
+
id: option.id,
|
|
166
|
+
name: option.name,
|
|
167
|
+
color: option.color,
|
|
168
|
+
}));
|
|
169
|
+
|
|
170
|
+
private buildStoryColorsObject = (
|
|
171
|
+
options: FieldOption[],
|
|
172
|
+
): Record<string, { color: ConsoleColor }> => {
|
|
173
|
+
const result: Record<string, { color: ConsoleColor }> = {};
|
|
174
|
+
for (const option of options) {
|
|
175
|
+
result[option.name] = { color: option.color };
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
private buildStoryColorsString = (
|
|
181
|
+
options: FieldOption[],
|
|
182
|
+
): Record<string, ConsoleColor> => {
|
|
183
|
+
const result: Record<string, ConsoleColor> = {};
|
|
184
|
+
for (const option of options) {
|
|
185
|
+
result[option.name] = option.color;
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
private sortByStoryOrder = (
|
|
191
|
+
items: ConsoleListItem[],
|
|
192
|
+
storyOrder: string[],
|
|
193
|
+
): ConsoleListItem[] => {
|
|
194
|
+
const indexByStory = new Map(
|
|
195
|
+
storyOrder.map((name, index) => [name, index]),
|
|
196
|
+
);
|
|
197
|
+
return items
|
|
198
|
+
.map((item, position) => ({
|
|
199
|
+
item,
|
|
200
|
+
position,
|
|
201
|
+
sortKey: indexByStory.get(item.story) ?? UNKNOWN_STORY_SORT_INDEX,
|
|
202
|
+
}))
|
|
203
|
+
.sort((a, b) => a.sortKey - b.sortKey || a.position - b.position)
|
|
204
|
+
.map((entry) => entry.item);
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HandleScheduledEventUseCaseHandler.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AA8B3D,qBAAa,kCAAkC;IAC7C,MAAM,GACJ,gBAAgB,MAAM,EACtB,UAAU,OAAO,KAChB,OAAO,CAAC;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,MAAM,EAAE,KAAK,EAAE,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,eAAe,EAAE,IAAI,EAAE,CAAC;KACzB,GAAG,IAAI,CAAC,CAmUP;CACH"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Issue } from '../../../domain/entities/Issue';
|
|
2
|
+
import type { Project } from '../../../domain/entities/Project';
|
|
3
|
+
export type ConsoleListsWriterParams = {
|
|
4
|
+
consoleDataOutputDir: string | null | undefined;
|
|
5
|
+
pjcode: string | null | undefined;
|
|
6
|
+
assigneeLogin: string | null | undefined;
|
|
7
|
+
project: Project;
|
|
8
|
+
issues: Issue[];
|
|
9
|
+
generatedAt?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const formatConsoleGeneratedAt: (date: Date) => string;
|
|
12
|
+
export declare const writeConsoleLists: (params: ConsoleListsWriterParams) => void;
|
|
13
|
+
//# sourceMappingURL=consoleListsWriter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consoleListsWriter.d.ts","sourceRoot":"","sources":["../../../../src/adapter/entry-points/handlers/consoleListsWriter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kCAAkC,CAAC;AAOhE,MAAM,MAAM,wBAAwB,GAAG;IACrC,oBAAoB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChD,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AASF,eAAO,MAAM,wBAAwB,GAAI,MAAM,IAAI,KAAG,MACR,CAAC;AAU/C,eAAO,MAAM,iBAAiB,GAAI,QAAQ,wBAAwB,KAAG,IAsBpE,CAAC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Issue } from '../../entities/Issue';
|
|
2
|
+
import { FieldOption, Project } from '../../entities/Project';
|
|
3
|
+
export type ConsoleColor = FieldOption['color'];
|
|
4
|
+
export type ConsoleListItem = {
|
|
5
|
+
number: number;
|
|
6
|
+
title: string;
|
|
7
|
+
url: string;
|
|
8
|
+
repo: string;
|
|
9
|
+
nameWithOwner: string;
|
|
10
|
+
projectItemId: string;
|
|
11
|
+
itemId: string;
|
|
12
|
+
isPr: boolean;
|
|
13
|
+
story: string;
|
|
14
|
+
labels: string[];
|
|
15
|
+
createdAt: string;
|
|
16
|
+
};
|
|
17
|
+
export type ConsoleFieldOption = {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
color: ConsoleColor;
|
|
21
|
+
};
|
|
22
|
+
export type ConsoleStatusTab = {
|
|
23
|
+
pjcode: string;
|
|
24
|
+
generatedAt: string;
|
|
25
|
+
statusOptions: ConsoleFieldOption[];
|
|
26
|
+
storyOrder: string[];
|
|
27
|
+
storyColors: Record<string, {
|
|
28
|
+
color: ConsoleColor;
|
|
29
|
+
}>;
|
|
30
|
+
items: ConsoleListItem[];
|
|
31
|
+
};
|
|
32
|
+
export type ConsoleTriageTab = {
|
|
33
|
+
pjcode: string;
|
|
34
|
+
generatedAt: string;
|
|
35
|
+
storyOptions: ConsoleFieldOption[];
|
|
36
|
+
storyOrder: string[];
|
|
37
|
+
storyColors: Record<string, ConsoleColor>;
|
|
38
|
+
items: ConsoleListItem[];
|
|
39
|
+
};
|
|
40
|
+
export type ConsoleTabName = 'prs' | 'triage' | 'unread' | 'failed-preparation';
|
|
41
|
+
export type ConsoleLists = {
|
|
42
|
+
prs: ConsoleStatusTab;
|
|
43
|
+
triage: ConsoleTriageTab;
|
|
44
|
+
unread: ConsoleStatusTab;
|
|
45
|
+
'failed-preparation': ConsoleStatusTab;
|
|
46
|
+
};
|
|
47
|
+
export type GenerateConsoleListsInput = {
|
|
48
|
+
project: Project;
|
|
49
|
+
issues: Issue[];
|
|
50
|
+
pjcode: string;
|
|
51
|
+
assigneeLogin: string;
|
|
52
|
+
generatedAt: string;
|
|
53
|
+
};
|
|
54
|
+
export declare class GenerateConsoleListsUseCase {
|
|
55
|
+
run: (input: GenerateConsoleListsInput) => ConsoleLists;
|
|
56
|
+
private isActionable;
|
|
57
|
+
private projectItem;
|
|
58
|
+
private buildFieldOptions;
|
|
59
|
+
private buildStoryColorsObject;
|
|
60
|
+
private buildStoryColorsString;
|
|
61
|
+
private sortByStoryOrder;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=GenerateConsoleListsUseCase.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GenerateConsoleListsUseCase.d.ts","sourceRoot":"","sources":["../../../../src/domain/usecases/console/GenerateConsoleListsUseCase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAE9D,MAAM,MAAM,YAAY,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,kBAAkB,EAAE,CAAC;IACpC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,YAAY,CAAA;KAAE,CAAC,CAAC;IACrD,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IAC1C,KAAK,EAAE,eAAe,EAAE,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,oBAAoB,CAAC;AAEhF,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,gBAAgB,CAAC;IACtB,MAAM,EAAE,gBAAgB,CAAC;IACzB,MAAM,EAAE,gBAAgB,CAAC;IACzB,oBAAoB,EAAE,gBAAgB,CAAC;CACxC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAIF,qBAAa,2BAA2B;IACtC,GAAG,GAAI,OAAO,yBAAyB,KAAG,YAAY,CAqEpD;IAEF,OAAO,CAAC,YAAY,CAKY;IAEhC,OAAO,CAAC,WAAW,CAYhB;IAEH,OAAO,CAAC,iBAAiB,CAYjB;IAER,OAAO,CAAC,sBAAsB,CAQ5B;IAEF,OAAO,CAAC,sBAAsB,CAQ5B;IAEF,OAAO,CAAC,gBAAgB,CAetB;CACH"}
|