github-issue-tower-defence-management 1.105.0 → 1.107.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +5 -0
  3. package/bin/adapter/entry-points/cli/index.js +18 -1
  4. package/bin/adapter/entry-points/cli/index.js.map +1 -1
  5. package/bin/adapter/entry-points/console/dashboardComposeService.js +178 -0
  6. package/bin/adapter/entry-points/console/dashboardComposeService.js.map +1 -0
  7. package/bin/adapter/entry-points/console/webServer.js +29 -7
  8. package/bin/adapter/entry-points/console/webServer.js.map +1 -1
  9. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js +13 -0
  10. package/bin/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.js.map +1 -1
  11. package/bin/adapter/entry-points/handlers/staleTmuxSessionCleaner.js +18 -0
  12. package/bin/adapter/entry-points/handlers/staleTmuxSessionCleaner.js.map +1 -0
  13. package/bin/adapter/repositories/NodeTmuxSessionRepository.js +22 -0
  14. package/bin/adapter/repositories/NodeTmuxSessionRepository.js.map +1 -1
  15. package/bin/domain/entities/LiveTmuxSession.js +3 -0
  16. package/bin/domain/entities/LiveTmuxSession.js.map +1 -0
  17. package/bin/domain/usecases/StaleTmuxSessionKillUseCase.js +58 -0
  18. package/bin/domain/usecases/StaleTmuxSessionKillUseCase.js.map +1 -0
  19. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js +161 -0
  20. package/bin/domain/usecases/dashboard/ComposeDashboardUseCase.js.map +1 -0
  21. package/package.json +1 -1
  22. package/src/adapter/entry-points/cli/index.ts +36 -2
  23. package/src/adapter/entry-points/console/dashboardComposeService.test.ts +443 -0
  24. package/src/adapter/entry-points/console/dashboardComposeService.ts +200 -0
  25. package/src/adapter/entry-points/console/ui/e2e/consoleTestHarness.ts +2 -0
  26. package/src/adapter/entry-points/console/webServer.test.ts +255 -54
  27. package/src/adapter/entry-points/console/webServer.ts +43 -10
  28. package/src/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.ts +17 -0
  29. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.test.ts +170 -0
  30. package/src/adapter/entry-points/handlers/staleTmuxSessionCleaner.ts +40 -0
  31. package/src/adapter/repositories/NodeTmuxSessionRepository.test.ts +81 -0
  32. package/src/adapter/repositories/NodeTmuxSessionRepository.ts +35 -0
  33. package/src/domain/entities/LiveTmuxSession.ts +4 -0
  34. package/src/domain/usecases/StaleTmuxSessionKillUseCase.test.ts +286 -0
  35. package/src/domain/usecases/StaleTmuxSessionKillUseCase.ts +102 -0
  36. package/src/domain/usecases/adapter-interfaces/TmuxSessionRepository.ts +4 -0
  37. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.test.ts +405 -0
  38. package/src/domain/usecases/dashboard/ComposeDashboardUseCase.ts +220 -0
  39. package/src/domain/usecases/intmux/InTmuxByHumanSessionReconcileUseCase.test.ts +6 -0
  40. package/types/adapter/entry-points/cli/index.d.ts.map +1 -1
  41. package/types/adapter/entry-points/console/dashboardComposeService.d.ts +9 -0
  42. package/types/adapter/entry-points/console/dashboardComposeService.d.ts.map +1 -0
  43. package/types/adapter/entry-points/console/webServer.d.ts +4 -0
  44. package/types/adapter/entry-points/console/webServer.d.ts.map +1 -1
  45. package/types/adapter/entry-points/handlers/HandleScheduledEventUseCaseHandler.d.ts.map +1 -1
  46. package/types/adapter/entry-points/handlers/staleTmuxSessionCleaner.d.ts +12 -0
  47. package/types/adapter/entry-points/handlers/staleTmuxSessionCleaner.d.ts.map +1 -0
  48. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts +3 -0
  49. package/types/adapter/repositories/NodeTmuxSessionRepository.d.ts.map +1 -1
  50. package/types/domain/entities/LiveTmuxSession.d.ts +5 -0
  51. package/types/domain/entities/LiveTmuxSession.d.ts.map +1 -0
  52. package/types/domain/usecases/StaleTmuxSessionKillUseCase.d.ts +19 -0
  53. package/types/domain/usecases/StaleTmuxSessionKillUseCase.d.ts.map +1 -0
  54. package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts +3 -0
  55. package/types/domain/usecases/adapter-interfaces/TmuxSessionRepository.d.ts.map +1 -1
  56. package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts +28 -0
  57. package/types/domain/usecases/dashboard/ComposeDashboardUseCase.d.ts.map +1 -0
@@ -14,6 +14,22 @@ class NodeTmuxSessionRepository {
14
14
  .map((line) => line.trim())
15
15
  .filter((line) => line.length > 0);
16
16
  };
17
+ this.listLiveSessionsWithActivity = async () => {
18
+ const { stdout, exitCode } = await this.localCommandRunner.runCommand('tmux', ['list-sessions', '-F', '#{session_name} #{session_activity}']);
19
+ if (exitCode !== 0) {
20
+ return [];
21
+ }
22
+ return stdout
23
+ .split('\n')
24
+ .map((line) => line.trim())
25
+ .filter((line) => line.length > 0)
26
+ .map((line) => {
27
+ const separatorIndex = line.lastIndexOf(' ');
28
+ const sessionName = line.slice(0, separatorIndex);
29
+ const activityEpochSeconds = Number(line.slice(separatorIndex + 1));
30
+ return { sessionName, activityEpochSeconds };
31
+ });
32
+ };
17
33
  this.listInteractiveProcessCommandLines = async () => {
18
34
  const { stdout, exitCode } = await this.localCommandRunner.runCommand('ps', ['-eo', 'args=']);
19
35
  if (exitCode !== 0) {
@@ -39,6 +55,12 @@ class NodeTmuxSessionRepository {
39
55
  issueUrl,
40
56
  ]);
41
57
  };
58
+ this.killSession = async (sessionName) => {
59
+ const { stderr, exitCode } = await this.localCommandRunner.runCommand('tmux', ['kill-session', '-t', sessionName]);
60
+ if (exitCode !== 0) {
61
+ throw new Error(`Failed to kill tmux session "${sessionName}": exit code ${exitCode}${stderr ? `: ${stderr}` : ''}`);
62
+ }
63
+ };
42
64
  }
43
65
  }
44
66
  exports.NodeTmuxSessionRepository = NodeTmuxSessionRepository;
@@ -1 +1 @@
1
- {"version":3,"file":"NodeTmuxSessionRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/NodeTmuxSessionRepository.ts"],"names":[],"mappings":";;;AAGA,MAAa,yBAAyB;IACpC,YAA6B,kBAAsC;QAAtC,uBAAkB,GAAlB,kBAAkB,CAAoB;QAEnE,yBAAoB,GAAG,KAAK,IAAuB,EAAE;YACnD,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CACnE,MAAM,EACN,CAAC,eAAe,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAC3C,CAAC;YACF,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,OAAO,MAAM;iBACV,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC;QAEF,uCAAkC,GAAG,KAAK,IAAuB,EAAE;YACjE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CACnE,IAAI,EACJ,CAAC,KAAK,EAAE,OAAO,CAAC,CACjB,CAAC;YACF,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;gBACnB,OAAO,EAAE,CAAC;YACZ,CAAC;YACD,OAAO,MAAM;iBACV,KAAK,CAAC,IAAI,CAAC;iBACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iBAC1B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACvC,CAAC,CAAC;QAEF,0BAAqB,GAAG,KAAK,EAC3B,WAAmB,EACnB,eAAuB,EACvB,QAAgB,EACD,EAAE;YACjB,MAAM,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,MAAM,EAAE;gBAC/C,aAAa;gBACb,IAAI;gBACJ,IAAI;gBACJ,IAAI;gBACJ,WAAW;gBACX,IAAI;gBACJ,KAAK;gBACL,gBAAgB;gBAChB,IAAI;gBACJ,eAAe;gBACf,QAAQ;aACT,CAAC,CAAC;QACL,CAAC,CAAC;IAhDoE,CAAC;CAiDxE;AAlDD,8DAkDC"}
1
+ {"version":3,"file":"NodeTmuxSessionRepository.js","sourceRoot":"","sources":["../../../src/adapter/repositories/NodeTmuxSessionRepository.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=LiveTmuxSession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LiveTmuxSession.js","sourceRoot":"","sources":["../../../src/domain/entities/LiveTmuxSession.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StaleTmuxSessionKillUseCase = exports.DEFAULT_IDLE_THRESHOLD_SECONDS = exports.DEFAULT_EXCLUDED_STATUS = void 0;
4
+ const WorkflowStatus_1 = require("../entities/WorkflowStatus");
5
+ const InTmuxByHumanSessionReconcileUseCase_1 = require("./intmux/InTmuxByHumanSessionReconcileUseCase");
6
+ exports.DEFAULT_EXCLUDED_STATUS = WorkflowStatus_1.IN_TMUX_STATUS_NAME;
7
+ exports.DEFAULT_IDLE_THRESHOLD_SECONDS = 24 * 60 * 60;
8
+ class StaleTmuxSessionKillUseCase {
9
+ constructor(issueRepository, tmuxSessionRepository) {
10
+ this.issueRepository = issueRepository;
11
+ this.tmuxSessionRepository = tmuxSessionRepository;
12
+ this.run = async (params) => {
13
+ const liveSessions = await this.tmuxSessionRepository.listLiveSessionsWithActivity();
14
+ const openIssues = await this.issueRepository.getAllOpened(params.project, params.allowCacheMinutes);
15
+ const issueBySessionName = new Map();
16
+ for (const issue of openIssues) {
17
+ issueBySessionName.set((0, InTmuxByHumanSessionReconcileUseCase_1.toTmuxSessionName)(issue.url), issue);
18
+ }
19
+ const nowEpochSeconds = Math.floor(params.now.getTime() / 1000);
20
+ const killCandidates = [];
21
+ for (const session of liveSessions) {
22
+ const reason = this.evaluateKillReason(session, issueBySessionName.get(session.sessionName) ?? null, nowEpochSeconds, params.excludedStatus, params.idleThresholdSeconds);
23
+ if (reason !== null) {
24
+ killCandidates.push({ sessionName: session.sessionName, reason });
25
+ }
26
+ }
27
+ console.log(`Stale tmux session cleanup: ${killCandidates.length} kill candidate(s) of ${liveSessions.length} live session(s).`);
28
+ for (const candidate of killCandidates) {
29
+ console.log(`Kill candidate: ${candidate.sessionName} (${candidate.reason})`);
30
+ }
31
+ for (const candidate of killCandidates) {
32
+ await this.tmuxSessionRepository.killSession(candidate.sessionName);
33
+ console.log(`Killed tmux session: ${candidate.sessionName} (${candidate.reason})`);
34
+ }
35
+ };
36
+ this.evaluateKillReason = (session, issue, nowEpochSeconds, excludedStatus, idleThresholdSeconds) => {
37
+ if (issue !== null) {
38
+ if (issue.status !== excludedStatus) {
39
+ return `mapped to open issue ${issue.url} with status "${issue.status ?? 'null'}" which is not the excluded status "${excludedStatus}"`;
40
+ }
41
+ if (issue.nextActionDate !== null) {
42
+ return `mapped to open issue ${issue.url} which has a next action date set`;
43
+ }
44
+ if (issue.nextActionHour !== null) {
45
+ return `mapped to open issue ${issue.url} which has a next action hour set`;
46
+ }
47
+ return null;
48
+ }
49
+ const idleSeconds = nowEpochSeconds - session.activityEpochSeconds;
50
+ if (idleSeconds >= idleThresholdSeconds) {
51
+ return `maps to no open issue and has been idle for ${idleSeconds} seconds (threshold ${idleThresholdSeconds} seconds)`;
52
+ }
53
+ return null;
54
+ };
55
+ }
56
+ }
57
+ exports.StaleTmuxSessionKillUseCase = StaleTmuxSessionKillUseCase;
58
+ //# sourceMappingURL=StaleTmuxSessionKillUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StaleTmuxSessionKillUseCase.js","sourceRoot":"","sources":["../../../src/domain/usecases/StaleTmuxSessionKillUseCase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ComposeDashboardUseCase = exports.formatTokenRowLine = exports.formatProjectRowLine = exports.formatProjectHeaderLine = exports.formatMachineStatusLine = exports.formatResetCountdown = exports.roundHalfToEven = exports.PROJECT_ROW_WIDTH_BUDGET = void 0;
4
+ exports.PROJECT_ROW_WIDTH_BUDGET = 32;
5
+ const PROJECT_COLUMNS = [
6
+ { header: 'unr', key: 'unread' },
7
+ { header: 'tdo', key: 'todo' },
8
+ { header: 'aqc', key: 'qc' },
9
+ { header: 'fal', key: 'fail' },
10
+ { header: 'prp', key: 'pr' },
11
+ { header: 'aws', key: 'ws' },
12
+ { header: 'dep', key: 'dep' },
13
+ ];
14
+ const PROJECT_COLUMN_WIDTH = 3;
15
+ const SEVERITY_BLANK = ' ';
16
+ const TOKEN_COLOR_DOT = {
17
+ G: '🟢',
18
+ Y: '🟡',
19
+ K: '⚪',
20
+ };
21
+ const padEnd = (value, width, fill) => {
22
+ let result = value;
23
+ while (result.length < width) {
24
+ result = result + fill;
25
+ }
26
+ return result;
27
+ };
28
+ const padStart = (value, width) => {
29
+ let result = value;
30
+ while (result.length < width) {
31
+ result = ' ' + result;
32
+ }
33
+ return result;
34
+ };
35
+ const padStartZero = (value, width) => {
36
+ let result = value;
37
+ while (result.length < width) {
38
+ result = '0' + result;
39
+ }
40
+ return result;
41
+ };
42
+ const roundHalfToEven = (value) => {
43
+ const floor = Math.floor(value);
44
+ const difference = value - floor;
45
+ if (difference < 0.5) {
46
+ return floor;
47
+ }
48
+ if (difference > 0.5) {
49
+ return floor + 1;
50
+ }
51
+ return floor % 2 === 0 ? floor : floor + 1;
52
+ };
53
+ exports.roundHalfToEven = roundHalfToEven;
54
+ const formatResetCountdown = (totalSeconds) => {
55
+ if (totalSeconds < 0) {
56
+ return '0d00h00';
57
+ }
58
+ const whole = Math.trunc(totalSeconds);
59
+ const days = Math.trunc(whole / 86400);
60
+ const afterDays = whole % 86400;
61
+ const hours = Math.trunc(afterDays / 3600);
62
+ const minutes = Math.trunc((afterDays % 3600) / 60);
63
+ return `${days}d${padStartZero(String(hours), 2)}h${padStartZero(String(minutes), 2)}`;
64
+ };
65
+ exports.formatResetCountdown = formatResetCountdown;
66
+ const formatMachineStatusLine = (machineStatus) => {
67
+ const memText = machineStatus !== null && machineStatus.memPct !== null
68
+ ? `${machineStatus.memPct}%`
69
+ : '?%';
70
+ const cpuText = machineStatus !== null && machineStatus.cpuPct !== null
71
+ ? `${machineStatus.cpuPct}%`
72
+ : '?%';
73
+ const load = machineStatus !== null ? machineStatus.load : null;
74
+ const oneMinute = load === null ? '?' : String((0, exports.roundHalfToEven)(load[0]));
75
+ const fiveMinute = load === null ? '?' : String((0, exports.roundHalfToEven)(load[1]));
76
+ const fifteenMinute = load === null ? '?' : String((0, exports.roundHalfToEven)(load[2]));
77
+ const cycle = machineStatus !== null && machineStatus.cycleMinutes !== null
78
+ ? `cy${machineStatus.cycleMinutes}`
79
+ : 'cy-';
80
+ return `M${memText} C${cpuText} LA ${oneMinute} ${fiveMinute} ${fifteenMinute} ${cycle}`;
81
+ };
82
+ exports.formatMachineStatusLine = formatMachineStatusLine;
83
+ const capThreeDigits = (value) => value > 999 ? '999' : String(value);
84
+ const formatProjectHeaderLine = () => {
85
+ const head = padEnd('pj', 4, ' ');
86
+ const columns = PROJECT_COLUMNS.map((column) => ' ' + padStart(column.header, PROJECT_COLUMN_WIDTH)).join('');
87
+ return head + columns;
88
+ };
89
+ exports.formatProjectHeaderLine = formatProjectHeaderLine;
90
+ const severityDot = (row) => {
91
+ if (row.blocker >= 2) {
92
+ return '🔴';
93
+ }
94
+ if (row.blocker === 1) {
95
+ return '🟣';
96
+ }
97
+ if (row.unread >= 10 || row.qc >= 15 || row.fail >= 5) {
98
+ return '🟠';
99
+ }
100
+ if (row.unread >= 5 || row.qc >= 10 || row.fail >= 3) {
101
+ return '🟡';
102
+ }
103
+ return '🟢';
104
+ };
105
+ const formatProjectRowLine = (project) => {
106
+ const mark = project.row === null ? SEVERITY_BLANK : severityDot(project.row);
107
+ const cells = PROJECT_COLUMNS.map((column) => {
108
+ const cell = project.row === null ? '--' : capThreeDigits(project.row[column.key]);
109
+ return ' ' + padStart(cell, PROJECT_COLUMN_WIDTH);
110
+ }).join('');
111
+ return mark + padEnd(project.code, 2, ' ') + cells;
112
+ };
113
+ exports.formatProjectRowLine = formatProjectRowLine;
114
+ const formatUtilization = (percent) => padStart(percent === null ? '?' : `${percent}%`, 4);
115
+ const formatReset = (resetSeconds) => resetSeconds === null ? '?' : (0, exports.formatResetCountdown)(resetSeconds);
116
+ const tokenSortKey = (token) => token.sevenDayResetSeconds === null
117
+ ? [1, 0]
118
+ : [0, token.sevenDayResetSeconds];
119
+ const sortTokens = (tokens) => tokens
120
+ .map((token, index) => ({ token, index }))
121
+ .sort((left, right) => {
122
+ const leftKey = tokenSortKey(left.token);
123
+ const rightKey = tokenSortKey(right.token);
124
+ if (leftKey[0] !== rightKey[0]) {
125
+ return leftKey[0] - rightKey[0];
126
+ }
127
+ if (leftKey[1] !== rightKey[1]) {
128
+ return leftKey[1] - rightKey[1];
129
+ }
130
+ return left.index - right.index;
131
+ })
132
+ .map((entry) => entry.token);
133
+ const formatTokenRowLine = (token) => {
134
+ const dot = TOKEN_COLOR_DOT[token.color];
135
+ const name = padEnd(token.name, 4, '_');
136
+ const fiveHourUtilization = formatUtilization(token.fiveHourUtilizationPercent);
137
+ const fiveHourReset = formatReset(token.fiveHourResetSeconds);
138
+ const sevenDayUtilization = formatUtilization(token.sevenDayUtilizationPercent);
139
+ const sevenDayReset = formatReset(token.sevenDayResetSeconds);
140
+ const prep = String(token.prep);
141
+ const hum = String(token.hum);
142
+ return `${dot}${name} ${fiveHourUtilization} ${fiveHourReset} ${sevenDayUtilization} ${sevenDayReset} ${prep} ${hum}`;
143
+ };
144
+ exports.formatTokenRowLine = formatTokenRowLine;
145
+ const wrapLine = (line) => `<tt>${line.replace(/ /g, '&nbsp;')}</tt><br>`;
146
+ class ComposeDashboardUseCase {
147
+ constructor() {
148
+ this.run = (input) => {
149
+ const statsLine = (0, exports.formatMachineStatusLine)(input.machineStatus);
150
+ const projectLines = [
151
+ (0, exports.formatProjectHeaderLine)(),
152
+ ...input.projects.map((project) => (0, exports.formatProjectRowLine)(project)),
153
+ ];
154
+ const tokenLines = sortTokens(input.tokens).map((token) => (0, exports.formatTokenRowLine)(token));
155
+ const lines = [statsLine, ...projectLines, '', ...tokenLines];
156
+ return lines.map((line) => wrapLine(line)).join('\n') + '\n';
157
+ };
158
+ }
159
+ }
160
+ exports.ComposeDashboardUseCase = ComposeDashboardUseCase;
161
+ //# sourceMappingURL=ComposeDashboardUseCase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ComposeDashboardUseCase.js","sourceRoot":"","sources":["../../../../src/domain/usecases/dashboard/ComposeDashboardUseCase.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-issue-tower-defence-management",
3
- "version": "1.105.0",
3
+ "version": "1.107.0",
4
4
  "description": "",
5
5
  "main": "bin/index.js",
6
6
  "scripts": {
@@ -32,7 +32,11 @@ import { GitHubIssueCommentRepository } from '../../repositories/GitHubIssueComm
32
32
  import { FetchWebhookRepository } from '../../repositories/FetchWebhookRepository';
33
33
  import { RevertOrphanedPreparationUseCase } from '../../../domain/usecases/RevertOrphanedPreparationUseCase';
34
34
  import * as path from 'path';
35
- import { DEFAULT_WEB_PORT, startWebServer } from '../console/webServer';
35
+ import {
36
+ DEFAULT_DASHBOARD_PROJECT_CODES,
37
+ DEFAULT_WEB_PORT,
38
+ startWebServer,
39
+ } from '../console/webServer';
36
40
  import { IssueTitleStateCache } from '../console/consoleReadApi';
37
41
  import {
38
42
  buildPjcodeToProjectUrl,
@@ -76,6 +80,8 @@ type ServeWebOptions = {
76
80
  consoleDataOutputDir?: string;
77
81
  inTmuxDataDir?: string;
78
82
  dashboardDir?: string;
83
+ dashboardDataDir?: string;
84
+ dashboardProjectCodes?: string;
79
85
  };
80
86
 
81
87
  const DEFAULT_IN_TMUX_DATA_DIR =
@@ -83,6 +89,19 @@ const DEFAULT_IN_TMUX_DATA_DIR =
83
89
 
84
90
  const DEFAULT_DASHBOARD_DIR = '/home/hiromi/0_workspaces/workspace1/jsonpub';
85
91
 
92
+ const DEFAULT_DASHBOARD_DATA_DIR: string | null = null;
93
+
94
+ const parseDashboardProjectCodes = (raw: string | undefined): string[] => {
95
+ if (raw === undefined) {
96
+ return DEFAULT_DASHBOARD_PROJECT_CODES;
97
+ }
98
+ const codes = raw
99
+ .split(',')
100
+ .map((code) => code.trim())
101
+ .filter((code) => code.length > 0);
102
+ return codes.length > 0 ? codes : DEFAULT_DASHBOARD_PROJECT_CODES;
103
+ };
104
+
86
105
  type SelectOauthTokenOptions = {
87
106
  tokenListJsonPath?: string;
88
107
  cacheDir?: string;
@@ -695,6 +714,11 @@ const runServeWeb = async (options: ServeWebOptions): Promise<void> => {
695
714
  const consoleDataOutputDir = options.consoleDataOutputDir ?? null;
696
715
  const inTmuxDataDir = options.inTmuxDataDir ?? DEFAULT_IN_TMUX_DATA_DIR;
697
716
  const dashboardDir = options.dashboardDir ?? DEFAULT_DASHBOARD_DIR;
717
+ const dashboardDataDir =
718
+ options.dashboardDataDir ?? DEFAULT_DASHBOARD_DATA_DIR;
719
+ const dashboardProjectCodes = parseDashboardProjectCodes(
720
+ options.dashboardProjectCodes,
721
+ );
698
722
 
699
723
  await startWebServer({
700
724
  accessToken,
@@ -702,6 +726,8 @@ const runServeWeb = async (options: ServeWebOptions): Promise<void> => {
702
726
  consoleDataOutputDir,
703
727
  inTmuxDataDir,
704
728
  dashboardDir,
729
+ dashboardDataDir,
730
+ dashboardProjectCodes,
705
731
  githubToken: token,
706
732
  issueRepository,
707
733
  resolveProject,
@@ -731,7 +757,15 @@ const addServeWebOptions = (command: Command): Command =>
731
757
  )
732
758
  .option(
733
759
  '--dashboardDir <path>',
734
- `Directory containing the dashboard HTML fragment tdpm.txt served unauthenticated at /tdpm.txt (default: ${DEFAULT_DASHBOARD_DIR})`,
760
+ `Directory containing the static dashboard HTML fragment tdpm.txt served at /tdpm.txt when compose mode is not active (default: ${DEFAULT_DASHBOARD_DIR})`,
761
+ )
762
+ .option(
763
+ '--dashboardDataDir <path>',
764
+ 'Directory containing the dashboard data files (projects/<pjcode>.json, machine-status.json, token-status.json); when set and every required file is present the server composes the /tdpm.txt fragment from them at request time, otherwise it falls back to serving the static tdpm.txt from --dashboardDir (unset when not configured)',
765
+ )
766
+ .option(
767
+ '--dashboardProjectCodes <codes>',
768
+ `Comma-separated project codes, in display order, for the dashboard project grid (default: ${DEFAULT_DASHBOARD_PROJECT_CODES.join(',')})`,
735
769
  );
736
770
 
737
771
  addServeWebOptions(program.command('serveWeb'))