github-issue-tower-defence-management 1.109.1 → 1.109.2
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 +3 -3
- package/bin/adapter/entry-points/cli/index.js +10 -10
- package/bin/adapter/entry-points/cli/index.js.map +1 -1
- package/bin/adapter/entry-points/console/dashboardComposeService.js +7 -6
- package/bin/adapter/entry-points/console/dashboardComposeService.js.map +1 -1
- package/bin/adapter/entry-points/console/webServer.js +5 -4
- package/bin/adapter/entry-points/console/webServer.js.map +1 -1
- package/bin/domain/usecases/dashboard/DashboardProjectCode.js +19 -0
- package/bin/domain/usecases/dashboard/DashboardProjectCode.js.map +1 -0
- package/package.json +1 -1
- package/src/adapter/entry-points/cli/index.ts +14 -14
- package/src/adapter/entry-points/console/dashboardComposeService.test.ts +31 -28
- package/src/adapter/entry-points/console/dashboardComposeService.ts +13 -10
- package/src/adapter/entry-points/console/dashboardEmitterComposerKey.test.ts +175 -0
- package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +1 -1
- package/src/adapter/entry-points/console/webServer.test.ts +23 -23
- package/src/adapter/entry-points/console/webServer.ts +5 -4
- package/src/domain/usecases/dashboard/DashboardProjectCode.test.ts +32 -0
- package/src/domain/usecases/dashboard/DashboardProjectCode.ts +20 -0
- package/types/adapter/entry-points/console/dashboardComposeService.d.ts +1 -1
- package/types/adapter/entry-points/console/dashboardComposeService.d.ts.map +1 -1
- package/types/adapter/entry-points/console/webServer.d.ts +2 -2
- package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
- package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts +4 -0
- package/types/domain/usecases/dashboard/DashboardProjectCode.d.ts.map +1 -0
|
@@ -22,8 +22,8 @@ describe('buildComposeDashboardInput', () => {
|
|
|
22
22
|
it('reads every requested project code in order and yields null for absent files', () => {
|
|
23
23
|
const dataDir = makeDataDir();
|
|
24
24
|
try {
|
|
25
|
-
writeProject(dataDir, '
|
|
26
|
-
pjcode: '
|
|
25
|
+
writeProject(dataDir, 'umino', {
|
|
26
|
+
pjcode: 'umino',
|
|
27
27
|
capturedAt: 'x',
|
|
28
28
|
unread: 3,
|
|
29
29
|
todo: 1,
|
|
@@ -36,7 +36,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
36
36
|
});
|
|
37
37
|
const input = buildComposeDashboardInput({
|
|
38
38
|
dashboardDataDir: dataDir,
|
|
39
|
-
|
|
39
|
+
projectNames: ['umino', 'xcare'],
|
|
40
40
|
});
|
|
41
41
|
expect(input.projects).toEqual([
|
|
42
42
|
{
|
|
@@ -62,10 +62,10 @@ describe('buildComposeDashboardInput', () => {
|
|
|
62
62
|
it('treats a project file with a missing field as an absent row', () => {
|
|
63
63
|
const dataDir = makeDataDir();
|
|
64
64
|
try {
|
|
65
|
-
writeProject(dataDir, '
|
|
65
|
+
writeProject(dataDir, 'umino', { unread: 1, todo: 1 });
|
|
66
66
|
const input = buildComposeDashboardInput({
|
|
67
67
|
dashboardDataDir: dataDir,
|
|
68
|
-
|
|
68
|
+
projectNames: ['umino'],
|
|
69
69
|
});
|
|
70
70
|
expect(input.projects[0].row).toBeNull();
|
|
71
71
|
} finally {
|
|
@@ -77,10 +77,13 @@ describe('buildComposeDashboardInput', () => {
|
|
|
77
77
|
const dataDir = makeDataDir();
|
|
78
78
|
try {
|
|
79
79
|
fs.mkdirSync(path.join(dataDir, 'projects'), { recursive: true });
|
|
80
|
-
fs.writeFileSync(
|
|
80
|
+
fs.writeFileSync(
|
|
81
|
+
path.join(dataDir, 'projects', 'umino.json'),
|
|
82
|
+
'not json',
|
|
83
|
+
);
|
|
81
84
|
const input = buildComposeDashboardInput({
|
|
82
85
|
dashboardDataDir: dataDir,
|
|
83
|
-
|
|
86
|
+
projectNames: ['umino'],
|
|
84
87
|
});
|
|
85
88
|
expect(input.projects[0].row).toBeNull();
|
|
86
89
|
} finally {
|
|
@@ -104,7 +107,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
104
107
|
);
|
|
105
108
|
const input = buildComposeDashboardInput({
|
|
106
109
|
dashboardDataDir: dataDir,
|
|
107
|
-
|
|
110
|
+
projectNames: [],
|
|
108
111
|
});
|
|
109
112
|
expect(input.machineStatus).toEqual({
|
|
110
113
|
memPct: 55,
|
|
@@ -134,7 +137,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
134
137
|
);
|
|
135
138
|
const input = buildComposeDashboardInput({
|
|
136
139
|
dashboardDataDir: dataDir,
|
|
137
|
-
|
|
140
|
+
projectNames: [],
|
|
138
141
|
});
|
|
139
142
|
expect(input.machineStatus?.cycleMinutes).toBeNull();
|
|
140
143
|
} finally {
|
|
@@ -147,7 +150,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
147
150
|
try {
|
|
148
151
|
const input = buildComposeDashboardInput({
|
|
149
152
|
dashboardDataDir: dataDir,
|
|
150
|
-
|
|
153
|
+
projectNames: [],
|
|
151
154
|
});
|
|
152
155
|
expect(input.machineStatus).toBeNull();
|
|
153
156
|
} finally {
|
|
@@ -179,7 +182,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
179
182
|
);
|
|
180
183
|
const input = buildComposeDashboardInput({
|
|
181
184
|
dashboardDataDir: dataDir,
|
|
182
|
-
|
|
185
|
+
projectNames: [],
|
|
183
186
|
});
|
|
184
187
|
expect(input.tokens).toEqual([
|
|
185
188
|
{
|
|
@@ -221,7 +224,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
221
224
|
);
|
|
222
225
|
const input = buildComposeDashboardInput({
|
|
223
226
|
dashboardDataDir: dataDir,
|
|
224
|
-
|
|
227
|
+
projectNames: [],
|
|
225
228
|
});
|
|
226
229
|
expect(input.tokens[0].color).toBe('Y');
|
|
227
230
|
expect(input.tokens[0].fiveHourUtilizationPercent).toBeNull();
|
|
@@ -235,7 +238,7 @@ describe('buildComposeDashboardInput', () => {
|
|
|
235
238
|
try {
|
|
236
239
|
const input = buildComposeDashboardInput({
|
|
237
240
|
dashboardDataDir: dataDir,
|
|
238
|
-
|
|
241
|
+
projectNames: [],
|
|
239
242
|
});
|
|
240
243
|
expect(input.tokens).toEqual([]);
|
|
241
244
|
} finally {
|
|
@@ -248,8 +251,8 @@ describe('composeDashboardText', () => {
|
|
|
248
251
|
it('composes the full byte-identical dashboard text from the data files', () => {
|
|
249
252
|
const dataDir = makeDataDir();
|
|
250
253
|
try {
|
|
251
|
-
writeProject(dataDir, '
|
|
252
|
-
pjcode: '
|
|
254
|
+
writeProject(dataDir, 'umino', {
|
|
255
|
+
pjcode: 'umino',
|
|
253
256
|
capturedAt: 'x',
|
|
254
257
|
unread: 3,
|
|
255
258
|
todo: 1,
|
|
@@ -292,7 +295,7 @@ describe('composeDashboardText', () => {
|
|
|
292
295
|
expect(
|
|
293
296
|
composeDashboardText({
|
|
294
297
|
dashboardDataDir: dataDir,
|
|
295
|
-
|
|
298
|
+
projectNames: ['umino', 'xcare'],
|
|
296
299
|
}),
|
|
297
300
|
).toBe(
|
|
298
301
|
'<tt>M55% C62% D89% cy14</tt><br>\n' +
|
|
@@ -329,7 +332,7 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
329
332
|
};
|
|
330
333
|
|
|
331
334
|
const minimalProject = {
|
|
332
|
-
pjcode: '
|
|
335
|
+
pjcode: 'umino',
|
|
333
336
|
capturedAt: 'x',
|
|
334
337
|
unread: 0,
|
|
335
338
|
todo: 0,
|
|
@@ -345,12 +348,12 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
345
348
|
const dataDir = makeDataDir();
|
|
346
349
|
try {
|
|
347
350
|
writeMachineAndToken(dataDir);
|
|
348
|
-
writeProject(dataDir, '
|
|
349
|
-
writeProject(dataDir, '
|
|
351
|
+
writeProject(dataDir, 'umino', minimalProject);
|
|
352
|
+
writeProject(dataDir, 'xcare', { ...minimalProject, pjcode: 'xcare' });
|
|
350
353
|
expect(
|
|
351
354
|
dashboardComposeFilesPresent({
|
|
352
355
|
dashboardDataDir: dataDir,
|
|
353
|
-
|
|
356
|
+
projectNames: ['umino', 'xcare'],
|
|
354
357
|
}),
|
|
355
358
|
).toBe(true);
|
|
356
359
|
} finally {
|
|
@@ -364,7 +367,7 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
364
367
|
expect(
|
|
365
368
|
dashboardComposeFilesPresent({
|
|
366
369
|
dashboardDataDir: dataDir,
|
|
367
|
-
|
|
370
|
+
projectNames: ['umino'],
|
|
368
371
|
}),
|
|
369
372
|
).toBe(false);
|
|
370
373
|
} finally {
|
|
@@ -376,11 +379,11 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
376
379
|
const dataDir = makeDataDir();
|
|
377
380
|
try {
|
|
378
381
|
writeMachineAndToken(dataDir);
|
|
379
|
-
writeProject(dataDir, '
|
|
382
|
+
writeProject(dataDir, 'umino', minimalProject);
|
|
380
383
|
expect(
|
|
381
384
|
dashboardComposeFilesPresent({
|
|
382
385
|
dashboardDataDir: dataDir,
|
|
383
|
-
|
|
386
|
+
projectNames: ['umino', 'xcare'],
|
|
384
387
|
}),
|
|
385
388
|
).toBe(false);
|
|
386
389
|
} finally {
|
|
@@ -395,11 +398,11 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
395
398
|
path.join(dataDir, 'token-status.json'),
|
|
396
399
|
JSON.stringify({ tokens: [], capturedAt: 'x' }),
|
|
397
400
|
);
|
|
398
|
-
writeProject(dataDir, '
|
|
401
|
+
writeProject(dataDir, 'umino', minimalProject);
|
|
399
402
|
expect(
|
|
400
403
|
dashboardComposeFilesPresent({
|
|
401
404
|
dashboardDataDir: dataDir,
|
|
402
|
-
|
|
405
|
+
projectNames: ['umino'],
|
|
403
406
|
}),
|
|
404
407
|
).toBe(false);
|
|
405
408
|
} finally {
|
|
@@ -421,11 +424,11 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
421
424
|
capturedAt: 'x',
|
|
422
425
|
}),
|
|
423
426
|
);
|
|
424
|
-
writeProject(dataDir, '
|
|
427
|
+
writeProject(dataDir, 'umino', minimalProject);
|
|
425
428
|
expect(
|
|
426
429
|
dashboardComposeFilesPresent({
|
|
427
430
|
dashboardDataDir: dataDir,
|
|
428
|
-
|
|
431
|
+
projectNames: ['umino'],
|
|
429
432
|
}),
|
|
430
433
|
).toBe(false);
|
|
431
434
|
} finally {
|
|
@@ -440,7 +443,7 @@ describe('dashboardComposeFilesPresent', () => {
|
|
|
440
443
|
expect(
|
|
441
444
|
dashboardComposeFilesPresent({
|
|
442
445
|
dashboardDataDir: dataDir,
|
|
443
|
-
|
|
446
|
+
projectNames: [],
|
|
444
447
|
}),
|
|
445
448
|
).toBe(false);
|
|
446
449
|
} finally {
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
ComposeDashboardProject,
|
|
7
7
|
ComposeDashboardUseCase,
|
|
8
8
|
} from '../../../domain/usecases/dashboard/ComposeDashboardUseCase';
|
|
9
|
+
import { toDashboardDisplayLabel } from '../../../domain/usecases/dashboard/DashboardProjectCode';
|
|
9
10
|
import { DashboardRow } from '../../../domain/usecases/dashboard/GenerateDashboardRowUseCase';
|
|
10
11
|
import {
|
|
11
12
|
TokenStatus,
|
|
@@ -14,7 +15,7 @@ import {
|
|
|
14
15
|
|
|
15
16
|
export type DashboardComposeOptions = {
|
|
16
17
|
dashboardDataDir: string;
|
|
17
|
-
|
|
18
|
+
projectNames: string[];
|
|
18
19
|
};
|
|
19
20
|
|
|
20
21
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
@@ -66,10 +67,12 @@ const parseDashboardRow = (value: unknown): DashboardRow | null => {
|
|
|
66
67
|
|
|
67
68
|
const readProjectRow = (
|
|
68
69
|
dashboardDataDir: string,
|
|
69
|
-
|
|
70
|
+
projectName: string,
|
|
70
71
|
): DashboardRow | null =>
|
|
71
72
|
parseDashboardRow(
|
|
72
|
-
readJsonFile(
|
|
73
|
+
readJsonFile(
|
|
74
|
+
path.join(dashboardDataDir, 'projects', `${projectName}.json`),
|
|
75
|
+
),
|
|
73
76
|
);
|
|
74
77
|
|
|
75
78
|
const parseLoad = (value: unknown): [number, number, number] | null => {
|
|
@@ -158,10 +161,10 @@ const readTokenStatuses = (dashboardDataDir: string): TokenStatus[] => {
|
|
|
158
161
|
export const buildComposeDashboardInput = (
|
|
159
162
|
options: DashboardComposeOptions,
|
|
160
163
|
): ComposeDashboardInput => {
|
|
161
|
-
const projects: ComposeDashboardProject[] = options.
|
|
162
|
-
(
|
|
163
|
-
code,
|
|
164
|
-
row: readProjectRow(options.dashboardDataDir,
|
|
164
|
+
const projects: ComposeDashboardProject[] = options.projectNames.map(
|
|
165
|
+
(projectName) => ({
|
|
166
|
+
code: toDashboardDisplayLabel(projectName),
|
|
167
|
+
row: readProjectRow(options.dashboardDataDir, projectName),
|
|
165
168
|
}),
|
|
166
169
|
);
|
|
167
170
|
return {
|
|
@@ -182,14 +185,14 @@ const isExistingFile = (filePath: string): boolean => {
|
|
|
182
185
|
export const dashboardComposeFilesPresent = (
|
|
183
186
|
options: DashboardComposeOptions,
|
|
184
187
|
): boolean => {
|
|
185
|
-
if (options.
|
|
188
|
+
if (options.projectNames.length === 0) {
|
|
186
189
|
return false;
|
|
187
190
|
}
|
|
188
191
|
const requiredFiles = [
|
|
189
192
|
path.join(options.dashboardDataDir, 'machine-status.json'),
|
|
190
193
|
path.join(options.dashboardDataDir, 'token-status.json'),
|
|
191
|
-
...options.
|
|
192
|
-
path.join(options.dashboardDataDir, 'projects', `${
|
|
194
|
+
...options.projectNames.map((projectName) =>
|
|
195
|
+
path.join(options.dashboardDataDir, 'projects', `${projectName}.json`),
|
|
193
196
|
),
|
|
194
197
|
];
|
|
195
198
|
return requiredFiles.every((filePath) => isExistingFile(filePath));
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as os from 'os';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { Issue } from '../../../domain/entities/Issue';
|
|
5
|
+
import {
|
|
6
|
+
DASHBOARD_PROJECT_NAMES,
|
|
7
|
+
toDashboardDisplayLabel,
|
|
8
|
+
} from '../../../domain/usecases/dashboard/DashboardProjectCode';
|
|
9
|
+
import { writeDashboardRow } from '../handlers/dashboardRowWriter';
|
|
10
|
+
import {
|
|
11
|
+
composeDashboardText,
|
|
12
|
+
dashboardComposeFilesPresent,
|
|
13
|
+
} from './dashboardComposeService';
|
|
14
|
+
import { DEFAULT_DASHBOARD_PROJECT_NAMES } from './webServer';
|
|
15
|
+
|
|
16
|
+
const ASSIGNEE = 'HiromiShikata';
|
|
17
|
+
|
|
18
|
+
let issueCounter = 0;
|
|
19
|
+
const makeIssue = (overrides: Partial<Issue>): Issue => {
|
|
20
|
+
issueCounter += 1;
|
|
21
|
+
return {
|
|
22
|
+
nameWithOwner: 'demo/repo',
|
|
23
|
+
number: issueCounter,
|
|
24
|
+
title: `Issue ${issueCounter}`,
|
|
25
|
+
state: 'OPEN',
|
|
26
|
+
status: null,
|
|
27
|
+
story: null,
|
|
28
|
+
nextActionDate: null,
|
|
29
|
+
nextActionHour: null,
|
|
30
|
+
estimationMinutes: null,
|
|
31
|
+
dependedIssueUrls: [],
|
|
32
|
+
completionDate50PercentConfidence: null,
|
|
33
|
+
url: `https://github.com/demo/repo/issues/${issueCounter}`,
|
|
34
|
+
assignees: [ASSIGNEE],
|
|
35
|
+
labels: [],
|
|
36
|
+
org: 'demo',
|
|
37
|
+
repo: 'repo',
|
|
38
|
+
body: '',
|
|
39
|
+
itemId: `item-${issueCounter}`,
|
|
40
|
+
isPr: false,
|
|
41
|
+
isInProgress: false,
|
|
42
|
+
isClosed: false,
|
|
43
|
+
createdAt: new Date('2026-06-13T08:18:45.000Z'),
|
|
44
|
+
author: 'someone',
|
|
45
|
+
closingIssueReferenceUrls: [],
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe('dashboard emitter filename matches composer lookup key', () => {
|
|
51
|
+
let dir: string;
|
|
52
|
+
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
issueCounter = 0;
|
|
55
|
+
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'dashboard-key-'));
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it.each(DASHBOARD_PROJECT_NAMES)(
|
|
63
|
+
'emits projects/%s.json under the full project name the composer reads',
|
|
64
|
+
(projectName) => {
|
|
65
|
+
writeDashboardRow({
|
|
66
|
+
dashboardDataDir: dir,
|
|
67
|
+
pjcode: projectName,
|
|
68
|
+
assigneeLogin: ASSIGNEE,
|
|
69
|
+
issues: [makeIssue({ status: 'Unread' })],
|
|
70
|
+
generatedAt: '2026-06-26T12:00:00.000Z',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const composerLookupPath = path.join(
|
|
74
|
+
dir,
|
|
75
|
+
'projects',
|
|
76
|
+
`${projectName}.json`,
|
|
77
|
+
);
|
|
78
|
+
expect(fs.existsSync(composerLookupPath)).toBe(true);
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
it('detects compose files present when every project name file exists', () => {
|
|
83
|
+
for (const projectName of DASHBOARD_PROJECT_NAMES) {
|
|
84
|
+
writeDashboardRow({
|
|
85
|
+
dashboardDataDir: dir,
|
|
86
|
+
pjcode: projectName,
|
|
87
|
+
assigneeLogin: ASSIGNEE,
|
|
88
|
+
issues: [makeIssue({ status: 'Unread' })],
|
|
89
|
+
generatedAt: '2026-06-26T12:00:00.000Z',
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
fs.writeFileSync(
|
|
93
|
+
path.join(dir, 'machine-status.json'),
|
|
94
|
+
JSON.stringify({
|
|
95
|
+
memPct: 1,
|
|
96
|
+
cpuPct: 2,
|
|
97
|
+
diskPct: 3,
|
|
98
|
+
load: [0, 0, 0],
|
|
99
|
+
cycleMinutes: 1,
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
fs.writeFileSync(
|
|
103
|
+
path.join(dir, 'token-status.json'),
|
|
104
|
+
JSON.stringify({ tokens: [] }),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
expect(
|
|
108
|
+
dashboardComposeFilesPresent({
|
|
109
|
+
dashboardDataDir: dir,
|
|
110
|
+
projectNames: DEFAULT_DASHBOARD_PROJECT_NAMES,
|
|
111
|
+
}),
|
|
112
|
+
).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('composes the full grid with no fallback row and shows the 2-char display labels', () => {
|
|
116
|
+
const issuesByName: Record<string, Issue[]> = {
|
|
117
|
+
umino: [makeIssue({ status: 'Unread' }), makeIssue({ status: 'Unread' })],
|
|
118
|
+
xmile: [makeIssue({ status: 'Awaiting Quality Check' })],
|
|
119
|
+
xcare: [makeIssue({ status: 'Awaiting Workspace' })],
|
|
120
|
+
utage3: [makeIssue({ status: 'Todo by human' })],
|
|
121
|
+
};
|
|
122
|
+
for (const projectName of DASHBOARD_PROJECT_NAMES) {
|
|
123
|
+
writeDashboardRow({
|
|
124
|
+
dashboardDataDir: dir,
|
|
125
|
+
pjcode: projectName,
|
|
126
|
+
assigneeLogin: ASSIGNEE,
|
|
127
|
+
issues: issuesByName[projectName],
|
|
128
|
+
generatedAt: '2026-06-26T12:00:00.000Z',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
fs.writeFileSync(
|
|
132
|
+
path.join(dir, 'machine-status.json'),
|
|
133
|
+
JSON.stringify({
|
|
134
|
+
memPct: 55,
|
|
135
|
+
cpuPct: 62,
|
|
136
|
+
diskPct: 93,
|
|
137
|
+
load: [16, 23, 40],
|
|
138
|
+
cycleMinutes: 13,
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
141
|
+
fs.writeFileSync(
|
|
142
|
+
path.join(dir, 'token-status.json'),
|
|
143
|
+
JSON.stringify({ tokens: [] }),
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const composed = composeDashboardText({
|
|
147
|
+
dashboardDataDir: dir,
|
|
148
|
+
projectNames: DEFAULT_DASHBOARD_PROJECT_NAMES,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const wrap = (line: string): string =>
|
|
152
|
+
`<tt>${line.replace(/ /g, ' ')}</tt><br>`;
|
|
153
|
+
const expected =
|
|
154
|
+
[
|
|
155
|
+
wrap('M55% C62% D93% cy13'),
|
|
156
|
+
wrap('LA 16 23 40'),
|
|
157
|
+
wrap('pj unr tdo aqc fal prp aws dep'),
|
|
158
|
+
wrap(
|
|
159
|
+
`🟢${toDashboardDisplayLabel('umino')} 2 0 0 0 0 0 0`,
|
|
160
|
+
),
|
|
161
|
+
wrap(
|
|
162
|
+
`🟢${toDashboardDisplayLabel('xmile')} 0 0 1 0 0 0 0`,
|
|
163
|
+
),
|
|
164
|
+
wrap(
|
|
165
|
+
`🟢${toDashboardDisplayLabel('xcare')} 0 0 0 0 0 1 0`,
|
|
166
|
+
),
|
|
167
|
+
wrap(
|
|
168
|
+
`🟢${toDashboardDisplayLabel('utage3')} 0 1 0 0 0 0 0`,
|
|
169
|
+
),
|
|
170
|
+
wrap(''),
|
|
171
|
+
].join('\n') + '\n';
|
|
172
|
+
expect(composed).toBe(expected);
|
|
173
|
+
expect(composed).not.toContain('--');
|
|
174
|
+
});
|
|
175
|
+
});
|
|
@@ -182,7 +182,7 @@ describe('webServer integration', () => {
|
|
|
182
182
|
inTmuxDataDir: null,
|
|
183
183
|
dashboardDir: null,
|
|
184
184
|
dashboardDataDir: null,
|
|
185
|
-
|
|
185
|
+
dashboardProjectNames: [],
|
|
186
186
|
port: 0,
|
|
187
187
|
});
|
|
188
188
|
const address = server.address();
|
|
@@ -201,7 +201,7 @@ describe('webServer integration', () => {
|
|
|
201
201
|
inTmuxDataDir: null,
|
|
202
202
|
dashboardDir: null,
|
|
203
203
|
dashboardDataDir: null,
|
|
204
|
-
|
|
204
|
+
dashboardProjectNames: [],
|
|
205
205
|
port: 0,
|
|
206
206
|
});
|
|
207
207
|
try {
|
|
@@ -229,7 +229,7 @@ describe('webServer integration', () => {
|
|
|
229
229
|
inTmuxDataDir: null,
|
|
230
230
|
dashboardDir: null,
|
|
231
231
|
dashboardDataDir: null,
|
|
232
|
-
|
|
232
|
+
dashboardProjectNames: [],
|
|
233
233
|
port: 0,
|
|
234
234
|
});
|
|
235
235
|
try {
|
|
@@ -261,7 +261,7 @@ describe('webServer integration', () => {
|
|
|
261
261
|
inTmuxDataDir: null,
|
|
262
262
|
dashboardDir: null,
|
|
263
263
|
dashboardDataDir: null,
|
|
264
|
-
|
|
264
|
+
dashboardProjectNames: [],
|
|
265
265
|
port: 0,
|
|
266
266
|
});
|
|
267
267
|
try {
|
|
@@ -295,7 +295,7 @@ describe('webServer integration', () => {
|
|
|
295
295
|
inTmuxDataDir: null,
|
|
296
296
|
dashboardDir: null,
|
|
297
297
|
dashboardDataDir: null,
|
|
298
|
-
|
|
298
|
+
dashboardProjectNames: [],
|
|
299
299
|
port: 0,
|
|
300
300
|
});
|
|
301
301
|
try {
|
|
@@ -326,7 +326,7 @@ describe('webServer integration', () => {
|
|
|
326
326
|
inTmuxDataDir: null,
|
|
327
327
|
dashboardDir: null,
|
|
328
328
|
dashboardDataDir: null,
|
|
329
|
-
|
|
329
|
+
dashboardProjectNames: [],
|
|
330
330
|
port: 0,
|
|
331
331
|
});
|
|
332
332
|
try {
|
|
@@ -351,7 +351,7 @@ describe('webServer integration', () => {
|
|
|
351
351
|
inTmuxDataDir: null,
|
|
352
352
|
dashboardDir: null,
|
|
353
353
|
dashboardDataDir: null,
|
|
354
|
-
|
|
354
|
+
dashboardProjectNames: [],
|
|
355
355
|
port: 0,
|
|
356
356
|
});
|
|
357
357
|
try {
|
|
@@ -379,7 +379,7 @@ describe('webServer integration', () => {
|
|
|
379
379
|
inTmuxDataDir: null,
|
|
380
380
|
dashboardDir: null,
|
|
381
381
|
dashboardDataDir: null,
|
|
382
|
-
|
|
382
|
+
dashboardProjectNames: [],
|
|
383
383
|
port: 0,
|
|
384
384
|
});
|
|
385
385
|
try {
|
|
@@ -519,7 +519,7 @@ describe('webServer new routes integration', () => {
|
|
|
519
519
|
inTmuxDataDir: null,
|
|
520
520
|
dashboardDir: null,
|
|
521
521
|
dashboardDataDir: null,
|
|
522
|
-
|
|
522
|
+
dashboardProjectNames: [],
|
|
523
523
|
port: 0,
|
|
524
524
|
});
|
|
525
525
|
try {
|
|
@@ -558,7 +558,7 @@ describe('webServer new routes integration', () => {
|
|
|
558
558
|
inTmuxDataDir: null,
|
|
559
559
|
dashboardDir: null,
|
|
560
560
|
dashboardDataDir: null,
|
|
561
|
-
|
|
561
|
+
dashboardProjectNames: [],
|
|
562
562
|
issueRepository,
|
|
563
563
|
issueTitleStateCache: new IssueTitleStateCache(),
|
|
564
564
|
port: 0,
|
|
@@ -594,7 +594,7 @@ describe('webServer new routes integration', () => {
|
|
|
594
594
|
inTmuxDataDir: null,
|
|
595
595
|
dashboardDir: null,
|
|
596
596
|
dashboardDataDir: null,
|
|
597
|
-
|
|
597
|
+
dashboardProjectNames: [],
|
|
598
598
|
issueRepository,
|
|
599
599
|
resolveProject: async (pjcode) =>
|
|
600
600
|
pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
|
|
@@ -645,7 +645,7 @@ describe('webServer new routes integration', () => {
|
|
|
645
645
|
inTmuxDataDir: null,
|
|
646
646
|
dashboardDir: null,
|
|
647
647
|
dashboardDataDir: null,
|
|
648
|
-
|
|
648
|
+
dashboardProjectNames: [],
|
|
649
649
|
issueRepository,
|
|
650
650
|
resolveProject: async (pjcode) =>
|
|
651
651
|
pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
|
|
@@ -693,7 +693,7 @@ describe('webServer new routes integration', () => {
|
|
|
693
693
|
inTmuxDataDir: null,
|
|
694
694
|
dashboardDir: null,
|
|
695
695
|
dashboardDataDir: null,
|
|
696
|
-
|
|
696
|
+
dashboardProjectNames: [],
|
|
697
697
|
issueRepository,
|
|
698
698
|
resolveProject: async (pjcode) =>
|
|
699
699
|
pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
|
|
@@ -731,7 +731,7 @@ describe('webServer new routes integration', () => {
|
|
|
731
731
|
inTmuxDataDir: null,
|
|
732
732
|
dashboardDir: null,
|
|
733
733
|
dashboardDataDir: null,
|
|
734
|
-
|
|
734
|
+
dashboardProjectNames: [],
|
|
735
735
|
issueRepository,
|
|
736
736
|
resolveProject: async (pjcode) =>
|
|
737
737
|
pjcode === 'umino' ? { pjcode, project: buildProject() } : null,
|
|
@@ -784,7 +784,7 @@ describe('webServer new routes integration', () => {
|
|
|
784
784
|
inTmuxDataDir: null,
|
|
785
785
|
dashboardDir: null,
|
|
786
786
|
dashboardDataDir: null,
|
|
787
|
-
|
|
787
|
+
dashboardProjectNames: [],
|
|
788
788
|
port: 0,
|
|
789
789
|
});
|
|
790
790
|
try {
|
|
@@ -932,7 +932,7 @@ describe('webServer flat in-tmux-by-human route integration', () => {
|
|
|
932
932
|
inTmuxDataDir,
|
|
933
933
|
dashboardDir: null,
|
|
934
934
|
dashboardDataDir: null,
|
|
935
|
-
|
|
935
|
+
dashboardProjectNames: [],
|
|
936
936
|
port: 0,
|
|
937
937
|
});
|
|
938
938
|
return { server, tmpDir, inTmuxDataDir };
|
|
@@ -1048,7 +1048,7 @@ describe('webServer flat in-tmux-by-human route integration', () => {
|
|
|
1048
1048
|
inTmuxDataDir: null,
|
|
1049
1049
|
dashboardDir: null,
|
|
1050
1050
|
dashboardDataDir: null,
|
|
1051
|
-
|
|
1051
|
+
dashboardProjectNames: [],
|
|
1052
1052
|
port: 0,
|
|
1053
1053
|
});
|
|
1054
1054
|
try {
|
|
@@ -1121,9 +1121,9 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
|
|
|
1121
1121
|
const writeDataFiles = (dataDir: string): void => {
|
|
1122
1122
|
fs.mkdirSync(path.join(dataDir, 'projects'), { recursive: true });
|
|
1123
1123
|
fs.writeFileSync(
|
|
1124
|
-
path.join(dataDir, 'projects', '
|
|
1124
|
+
path.join(dataDir, 'projects', 'umino.json'),
|
|
1125
1125
|
JSON.stringify({
|
|
1126
|
-
pjcode: '
|
|
1126
|
+
pjcode: 'umino',
|
|
1127
1127
|
capturedAt: '2026-06-26T00:00:00.000Z',
|
|
1128
1128
|
unread: 3,
|
|
1129
1129
|
todo: 1,
|
|
@@ -1136,9 +1136,9 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
|
|
|
1136
1136
|
}),
|
|
1137
1137
|
);
|
|
1138
1138
|
fs.writeFileSync(
|
|
1139
|
-
path.join(dataDir, 'projects', '
|
|
1139
|
+
path.join(dataDir, 'projects', 'xcare.json'),
|
|
1140
1140
|
JSON.stringify({
|
|
1141
|
-
pjcode: '
|
|
1141
|
+
pjcode: 'xcare',
|
|
1142
1142
|
capturedAt: '2026-06-26T00:00:00.000Z',
|
|
1143
1143
|
unread: 0,
|
|
1144
1144
|
todo: 0,
|
|
@@ -1209,7 +1209,7 @@ describe('webServer dashboard /tdpm.txt route integration', () => {
|
|
|
1209
1209
|
inTmuxDataDir: null,
|
|
1210
1210
|
dashboardDir: overrides.dashboardDir,
|
|
1211
1211
|
dashboardDataDir: overrides.dashboardDataDir,
|
|
1212
|
-
|
|
1212
|
+
dashboardProjectNames: ['umino', 'xcare'],
|
|
1213
1213
|
port: 0,
|
|
1214
1214
|
});
|
|
1215
1215
|
return { server, tmpDir };
|
|
@@ -1453,7 +1453,7 @@ describe('webServer image proxy', () => {
|
|
|
1453
1453
|
inTmuxDataDir: null,
|
|
1454
1454
|
dashboardDir: null,
|
|
1455
1455
|
dashboardDataDir: null,
|
|
1456
|
-
|
|
1456
|
+
dashboardProjectNames: [],
|
|
1457
1457
|
githubToken: token,
|
|
1458
1458
|
imageFetcher: fetcher,
|
|
1459
1459
|
port: 0,
|
|
@@ -30,10 +30,11 @@ import {
|
|
|
30
30
|
composeDashboardText,
|
|
31
31
|
dashboardComposeFilesPresent,
|
|
32
32
|
} from './dashboardComposeService';
|
|
33
|
+
import { DASHBOARD_PROJECT_NAMES } from '../../../domain/usecases/dashboard/DashboardProjectCode';
|
|
33
34
|
|
|
34
35
|
export const DEFAULT_WEB_PORT = 9981;
|
|
35
36
|
|
|
36
|
-
export const
|
|
37
|
+
export const DEFAULT_DASHBOARD_PROJECT_NAMES = DASHBOARD_PROJECT_NAMES;
|
|
37
38
|
|
|
38
39
|
export const CONSOLE_TOKEN_HEADER = 'x-pv-token';
|
|
39
40
|
|
|
@@ -165,7 +166,7 @@ export type WebServerOptions = {
|
|
|
165
166
|
inTmuxDataDir: string | null;
|
|
166
167
|
dashboardDir: string | null;
|
|
167
168
|
dashboardDataDir: string | null;
|
|
168
|
-
|
|
169
|
+
dashboardProjectNames: string[];
|
|
169
170
|
githubToken?: string | null;
|
|
170
171
|
imageFetcher?: ImageFetcher | null;
|
|
171
172
|
issueRepository?: IssueRepository | null;
|
|
@@ -555,12 +556,12 @@ export const resolveDashboardContent = (
|
|
|
555
556
|
options.dashboardDataDir !== null &&
|
|
556
557
|
dashboardComposeFilesPresent({
|
|
557
558
|
dashboardDataDir: options.dashboardDataDir,
|
|
558
|
-
|
|
559
|
+
projectNames: options.dashboardProjectNames,
|
|
559
560
|
})
|
|
560
561
|
) {
|
|
561
562
|
const dashboardText = composeDashboardText({
|
|
562
563
|
dashboardDataDir: options.dashboardDataDir,
|
|
563
|
-
|
|
564
|
+
projectNames: options.dashboardProjectNames,
|
|
564
565
|
});
|
|
565
566
|
return Buffer.from(dashboardText, 'utf-8');
|
|
566
567
|
}
|