lightman-agent 1.0.5 → 1.0.6
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/agent.config.template.json +30 -30
- package/package.json +52 -52
- package/public/assets/index-CcBNCz6h.css +1 -1
- package/public/assets/index-D9QHMG8k.js +1 -0
- package/public/assets/index-H-8HDl46.js +1 -1
- package/public/assets/index-YodeiCia.css +1 -0
- package/public/assets/index-legacy-DWtNM8y7.js +41 -0
- package/public/assets/museum-map-CwVDA2z1.svg +4182 -0
- package/public/assets/polyfills-legacy-DyVYWHbW.js +4 -0
- package/public/index.html +7 -2
- package/public/templates/custom08/elements/back-button.svg +20 -0
- package/public/templates/custom08/elements/base-map-background.svg +37 -0
- package/public/templates/custom08/elements/base-map.svg +1191 -0
- package/public/templates/custom08/elements/gallery-1-2-3-info-panel.svg +236 -0
- package/public/templates/custom08/elements/gallery-4-5-6-7-info-panel.svg +266 -0
- package/public/templates/custom08/elements/gallery-8-9-info-panel.svg +274 -0
- package/public/templates/custom08/elements/gallery-labels/_nav-map-styles.css +554 -0
- package/public/templates/custom08/elements/gallery-labels/_styles.css +556 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-1.svg +35 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-2.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-3.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-4.svg +37 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-5.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-6.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-7.svg +34 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-8.svg +37 -0
- package/public/templates/custom08/elements/gallery-labels/gallery-9.svg +34 -0
- package/public/templates/custom08/elements/hand-hint.png +0 -0
- package/public/templates/custom08/elements/idle-screen-bg.svg +5 -0
- package/public/templates/custom08/elements/idle-screen-map.svg +627 -0
- package/public/templates/custom08/elements/idle-screen-text.svg +350 -0
- package/public/templates/custom08/elements/key-map-1.svg +986 -0
- package/public/templates/custom08/elements/key-map-2.svg +1018 -0
- package/public/templates/custom08/elements/key-map-3.svg +1019 -0
- package/public/templates/custom08/elements/key-map-combined.svg +1001 -0
- package/public/templates/custom08/elements/map-highlight-marker.svg +11 -0
- package/public/templates/custom08/elements/map-pin-marker.svg +15 -0
- package/public/templates/custom08/elements/map-teardrop-star-marker.svg +13 -0
- package/public/templates/custom08/elements/nav-circle-galleries-1-3.svg +21 -0
- package/public/templates/custom08/elements/nav-circle-galleries-4-7.svg +24 -0
- package/public/templates/custom08/elements/nav-circle-galleries-8-9.svg +20 -0
- package/public/templates/custom08/elements/section1-map.svg +1435 -0
- package/public/templates/custom08/elements/section2-map.svg +1724 -0
- package/public/templates/custom08/elements/section3-map.svg +1295 -0
- package/public/templates/custom08/fonts/CabinetGrotesk-Variable.ttf +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.12_PM.png +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.23.56_PM.png +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-05_at_7.24.24_PM.png +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.31.58_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.11_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.36_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.48_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.32.59_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.15_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.27_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.34_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.42_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.50_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.33.58_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.04_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.11_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.20_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.34.57_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.03_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.16_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/Screenshot_2026-03-24_at_11.35.23_PM.jpg +0 -0
- package/public/templates/custom08/images/highlights/prologue-highlight.png +0 -0
- package/scripts/guardian.ps1 +75 -75
- package/scripts/install-linux.sh +134 -134
- package/scripts/install-rpi.sh +117 -117
- package/scripts/install-windows.ps1 +505 -505
- package/scripts/launch-kiosk.vbs +101 -101
- package/scripts/lightman-agent.logrotate +12 -12
- package/scripts/lightman-agent.service +38 -38
- package/scripts/lightman-shell.bat +107 -107
- package/scripts/reinstall-windows.ps1 +26 -26
- package/scripts/restore-desktop.ps1 +32 -32
- package/scripts/setup.ps1 +116 -116
- package/scripts/setup.sh +115 -115
- package/scripts/sync-display.mjs +20 -0
- package/scripts/uninstall-linux.sh +50 -50
- package/scripts/uninstall-windows.ps1 +54 -54
- package/src/commands/display.ts +177 -177
- package/src/commands/kiosk.ts +113 -113
- package/src/commands/maintenance.ts +106 -106
- package/src/commands/network.ts +129 -129
- package/src/commands/power.ts +163 -163
- package/src/commands/rpi.ts +45 -45
- package/src/commands/screenshot.ts +166 -166
- package/src/commands/serial.ts +17 -17
- package/src/commands/update.ts +124 -124
- package/src/index.ts +652 -652
- package/src/lib/config.ts +69 -69
- package/src/lib/identity.ts +40 -40
- package/src/lib/logger.ts +137 -137
- package/src/lib/platform.ts +10 -10
- package/src/lib/rpi.ts +180 -180
- package/src/lib/screens.ts +128 -128
- package/src/lib/types.ts +176 -176
- package/src/services/commands.ts +107 -107
- package/src/services/health.ts +161 -161
- package/src/services/kiosk.ts +384 -384
- package/src/services/localEvents.ts +60 -60
- package/src/services/logForwarder.ts +72 -72
- package/src/services/multiScreenKiosk.ts +324 -324
- package/src/services/oscBridge.ts +186 -186
- package/src/services/powerScheduler.ts +260 -260
- package/src/services/provisioning.ts +120 -120
- package/src/services/serialBridge.ts +230 -230
- package/src/services/serviceLauncher.ts +183 -183
- package/src/services/staticServer.ts +226 -226
- package/src/services/updater.ts +249 -249
- package/src/services/watchdog.ts +310 -310
- package/src/services/websocket.ts +152 -152
- package/tsconfig.json +28 -28
|
@@ -1,183 +1,183 @@
|
|
|
1
|
-
import { spawn, execSync } from 'child_process';
|
|
2
|
-
import type { ChildProcess } from 'child_process';
|
|
3
|
-
import { resolve } from 'path';
|
|
4
|
-
import type { Logger } from '../lib/logger.js';
|
|
5
|
-
|
|
6
|
-
interface ManagedService {
|
|
7
|
-
name: string;
|
|
8
|
-
cwd: string;
|
|
9
|
-
command: string;
|
|
10
|
-
args: string[];
|
|
11
|
-
port: number;
|
|
12
|
-
process: ChildProcess | null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class ServiceLauncher {
|
|
16
|
-
private services: ManagedService[] = [];
|
|
17
|
-
private logger: Logger;
|
|
18
|
-
private projectRoot: string;
|
|
19
|
-
|
|
20
|
-
constructor(logger: Logger, projectRoot: string) {
|
|
21
|
-
this.logger = logger;
|
|
22
|
-
this.projectRoot = projectRoot;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Kill any existing processes on managed ports, then start fresh.
|
|
27
|
-
*/
|
|
28
|
-
async startAll(): Promise<void> {
|
|
29
|
-
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
30
|
-
|
|
31
|
-
this.services = [
|
|
32
|
-
{
|
|
33
|
-
name: 'server',
|
|
34
|
-
cwd: resolve(this.projectRoot, 'server'),
|
|
35
|
-
command: npmCmd,
|
|
36
|
-
args: ['run', 'dev'],
|
|
37
|
-
port: 3401,
|
|
38
|
-
process: null,
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
name: 'display',
|
|
42
|
-
cwd: resolve(this.projectRoot, 'display'),
|
|
43
|
-
command: npmCmd,
|
|
44
|
-
args: ['run', 'dev'],
|
|
45
|
-
port: 3403,
|
|
46
|
-
process: null,
|
|
47
|
-
},
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
// Kill anything already on these ports for a clean start
|
|
51
|
-
for (const svc of this.services) {
|
|
52
|
-
await this.killProcessOnPort(svc.port);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Small delay to let ports free up
|
|
56
|
-
await new Promise((r) => setTimeout(r, 1_000));
|
|
57
|
-
|
|
58
|
-
for (const svc of this.services) {
|
|
59
|
-
this.logger.info(`Starting ${svc.name} (port ${svc.port})...`);
|
|
60
|
-
svc.process = spawn(svc.command, svc.args, {
|
|
61
|
-
cwd: svc.cwd,
|
|
62
|
-
stdio: 'pipe',
|
|
63
|
-
detached: false,
|
|
64
|
-
shell: true,
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
svc.process.stdout?.on('data', (data: Buffer) => {
|
|
68
|
-
const lines = data.toString().trim();
|
|
69
|
-
if (lines) this.logger.debug(`[${svc.name}] ${lines}`);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
svc.process.stderr?.on('data', (data: Buffer) => {
|
|
73
|
-
const lines = data.toString().trim();
|
|
74
|
-
if (lines) this.logger.debug(`[${svc.name}:err] ${lines}`);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
svc.process.on('exit', (code) => {
|
|
78
|
-
this.logger.warn(`${svc.name} exited with code ${code}`);
|
|
79
|
-
svc.process = null;
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
svc.process.on('error', (err) => {
|
|
83
|
-
this.logger.error(`${svc.name} spawn error: ${err.message}`);
|
|
84
|
-
svc.process = null;
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Wait for each service to become reachable
|
|
89
|
-
for (const svc of this.services) {
|
|
90
|
-
await this.waitForPort(svc.name, svc.port, 60_000);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.logger.info('All services started successfully');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Stop all managed services.
|
|
98
|
-
*/
|
|
99
|
-
stopAll(): void {
|
|
100
|
-
for (const svc of this.services) {
|
|
101
|
-
if (svc.process) {
|
|
102
|
-
this.logger.info(`Stopping ${svc.name} (pid ${svc.process.pid})...`);
|
|
103
|
-
try {
|
|
104
|
-
if (process.platform === 'win32') {
|
|
105
|
-
spawn('taskkill', ['/pid', String(svc.process.pid), '/T', '/F'], {
|
|
106
|
-
stdio: 'ignore',
|
|
107
|
-
shell: true,
|
|
108
|
-
});
|
|
109
|
-
} else {
|
|
110
|
-
svc.process.kill('SIGTERM');
|
|
111
|
-
}
|
|
112
|
-
} catch {
|
|
113
|
-
// already dead
|
|
114
|
-
}
|
|
115
|
-
svc.process = null;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
isRunning(name: string): boolean {
|
|
121
|
-
const svc = this.services.find((s) => s.name === name);
|
|
122
|
-
return svc?.process !== null && svc?.process?.exitCode === null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
private async killProcessOnPort(port: number): Promise<void> {
|
|
126
|
-
try {
|
|
127
|
-
if (process.platform === 'win32') {
|
|
128
|
-
// Find PID using the port on Windows
|
|
129
|
-
const result = execSync(
|
|
130
|
-
`netstat -ano | findstr :${port} | findstr LISTENING`,
|
|
131
|
-
{ encoding: 'utf-8', timeout: 5_000 }
|
|
132
|
-
).trim();
|
|
133
|
-
const lines = result.split('\n');
|
|
134
|
-
const pids = new Set<string>();
|
|
135
|
-
for (const line of lines) {
|
|
136
|
-
const parts = line.trim().split(/\s+/);
|
|
137
|
-
const pid = parts[parts.length - 1];
|
|
138
|
-
if (pid && pid !== '0') pids.add(pid);
|
|
139
|
-
}
|
|
140
|
-
for (const pid of pids) {
|
|
141
|
-
this.logger.info(`Killing existing process on port ${port} (pid ${pid})`);
|
|
142
|
-
try {
|
|
143
|
-
execSync(`taskkill /pid ${pid} /T /F`, { timeout: 5_000 });
|
|
144
|
-
} catch {
|
|
145
|
-
// process may already be gone
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
} else {
|
|
149
|
-
// Unix: use fuser or lsof
|
|
150
|
-
try {
|
|
151
|
-
execSync(`fuser -k ${port}/tcp`, { timeout: 5_000 });
|
|
152
|
-
this.logger.info(`Killed existing process on port ${port}`);
|
|
153
|
-
} catch {
|
|
154
|
-
// no process on port, that's fine
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
} catch {
|
|
158
|
-
// No process found on port — that's fine
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
private async waitForPort(name: string, port: number, timeoutMs: number): Promise<void> {
|
|
163
|
-
const start = Date.now();
|
|
164
|
-
const interval = 1_500;
|
|
165
|
-
|
|
166
|
-
while (Date.now() - start < timeoutMs) {
|
|
167
|
-
try {
|
|
168
|
-
const res = await fetch(`http://localhost:${port}/`, {
|
|
169
|
-
signal: AbortSignal.timeout(2_000),
|
|
170
|
-
});
|
|
171
|
-
if (res.status > 0) {
|
|
172
|
-
this.logger.info(`${name} is ready on port ${port}`);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
} catch {
|
|
176
|
-
// not ready yet
|
|
177
|
-
}
|
|
178
|
-
await new Promise((r) => setTimeout(r, interval));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
this.logger.warn(`${name} did not become ready on port ${port} within ${timeoutMs}ms, continuing anyway`);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
1
|
+
import { spawn, execSync } from 'child_process';
|
|
2
|
+
import type { ChildProcess } from 'child_process';
|
|
3
|
+
import { resolve } from 'path';
|
|
4
|
+
import type { Logger } from '../lib/logger.js';
|
|
5
|
+
|
|
6
|
+
interface ManagedService {
|
|
7
|
+
name: string;
|
|
8
|
+
cwd: string;
|
|
9
|
+
command: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
port: number;
|
|
12
|
+
process: ChildProcess | null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class ServiceLauncher {
|
|
16
|
+
private services: ManagedService[] = [];
|
|
17
|
+
private logger: Logger;
|
|
18
|
+
private projectRoot: string;
|
|
19
|
+
|
|
20
|
+
constructor(logger: Logger, projectRoot: string) {
|
|
21
|
+
this.logger = logger;
|
|
22
|
+
this.projectRoot = projectRoot;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Kill any existing processes on managed ports, then start fresh.
|
|
27
|
+
*/
|
|
28
|
+
async startAll(): Promise<void> {
|
|
29
|
+
const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
30
|
+
|
|
31
|
+
this.services = [
|
|
32
|
+
{
|
|
33
|
+
name: 'server',
|
|
34
|
+
cwd: resolve(this.projectRoot, 'server'),
|
|
35
|
+
command: npmCmd,
|
|
36
|
+
args: ['run', 'dev'],
|
|
37
|
+
port: 3401,
|
|
38
|
+
process: null,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'display',
|
|
42
|
+
cwd: resolve(this.projectRoot, 'display'),
|
|
43
|
+
command: npmCmd,
|
|
44
|
+
args: ['run', 'dev'],
|
|
45
|
+
port: 3403,
|
|
46
|
+
process: null,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
// Kill anything already on these ports for a clean start
|
|
51
|
+
for (const svc of this.services) {
|
|
52
|
+
await this.killProcessOnPort(svc.port);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Small delay to let ports free up
|
|
56
|
+
await new Promise((r) => setTimeout(r, 1_000));
|
|
57
|
+
|
|
58
|
+
for (const svc of this.services) {
|
|
59
|
+
this.logger.info(`Starting ${svc.name} (port ${svc.port})...`);
|
|
60
|
+
svc.process = spawn(svc.command, svc.args, {
|
|
61
|
+
cwd: svc.cwd,
|
|
62
|
+
stdio: 'pipe',
|
|
63
|
+
detached: false,
|
|
64
|
+
shell: true,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
svc.process.stdout?.on('data', (data: Buffer) => {
|
|
68
|
+
const lines = data.toString().trim();
|
|
69
|
+
if (lines) this.logger.debug(`[${svc.name}] ${lines}`);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
svc.process.stderr?.on('data', (data: Buffer) => {
|
|
73
|
+
const lines = data.toString().trim();
|
|
74
|
+
if (lines) this.logger.debug(`[${svc.name}:err] ${lines}`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
svc.process.on('exit', (code) => {
|
|
78
|
+
this.logger.warn(`${svc.name} exited with code ${code}`);
|
|
79
|
+
svc.process = null;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
svc.process.on('error', (err) => {
|
|
83
|
+
this.logger.error(`${svc.name} spawn error: ${err.message}`);
|
|
84
|
+
svc.process = null;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Wait for each service to become reachable
|
|
89
|
+
for (const svc of this.services) {
|
|
90
|
+
await this.waitForPort(svc.name, svc.port, 60_000);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.logger.info('All services started successfully');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Stop all managed services.
|
|
98
|
+
*/
|
|
99
|
+
stopAll(): void {
|
|
100
|
+
for (const svc of this.services) {
|
|
101
|
+
if (svc.process) {
|
|
102
|
+
this.logger.info(`Stopping ${svc.name} (pid ${svc.process.pid})...`);
|
|
103
|
+
try {
|
|
104
|
+
if (process.platform === 'win32') {
|
|
105
|
+
spawn('taskkill', ['/pid', String(svc.process.pid), '/T', '/F'], {
|
|
106
|
+
stdio: 'ignore',
|
|
107
|
+
shell: true,
|
|
108
|
+
});
|
|
109
|
+
} else {
|
|
110
|
+
svc.process.kill('SIGTERM');
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
// already dead
|
|
114
|
+
}
|
|
115
|
+
svc.process = null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isRunning(name: string): boolean {
|
|
121
|
+
const svc = this.services.find((s) => s.name === name);
|
|
122
|
+
return svc?.process !== null && svc?.process?.exitCode === null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private async killProcessOnPort(port: number): Promise<void> {
|
|
126
|
+
try {
|
|
127
|
+
if (process.platform === 'win32') {
|
|
128
|
+
// Find PID using the port on Windows
|
|
129
|
+
const result = execSync(
|
|
130
|
+
`netstat -ano | findstr :${port} | findstr LISTENING`,
|
|
131
|
+
{ encoding: 'utf-8', timeout: 5_000 }
|
|
132
|
+
).trim();
|
|
133
|
+
const lines = result.split('\n');
|
|
134
|
+
const pids = new Set<string>();
|
|
135
|
+
for (const line of lines) {
|
|
136
|
+
const parts = line.trim().split(/\s+/);
|
|
137
|
+
const pid = parts[parts.length - 1];
|
|
138
|
+
if (pid && pid !== '0') pids.add(pid);
|
|
139
|
+
}
|
|
140
|
+
for (const pid of pids) {
|
|
141
|
+
this.logger.info(`Killing existing process on port ${port} (pid ${pid})`);
|
|
142
|
+
try {
|
|
143
|
+
execSync(`taskkill /pid ${pid} /T /F`, { timeout: 5_000 });
|
|
144
|
+
} catch {
|
|
145
|
+
// process may already be gone
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
// Unix: use fuser or lsof
|
|
150
|
+
try {
|
|
151
|
+
execSync(`fuser -k ${port}/tcp`, { timeout: 5_000 });
|
|
152
|
+
this.logger.info(`Killed existing process on port ${port}`);
|
|
153
|
+
} catch {
|
|
154
|
+
// no process on port, that's fine
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// No process found on port — that's fine
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private async waitForPort(name: string, port: number, timeoutMs: number): Promise<void> {
|
|
163
|
+
const start = Date.now();
|
|
164
|
+
const interval = 1_500;
|
|
165
|
+
|
|
166
|
+
while (Date.now() - start < timeoutMs) {
|
|
167
|
+
try {
|
|
168
|
+
const res = await fetch(`http://localhost:${port}/`, {
|
|
169
|
+
signal: AbortSignal.timeout(2_000),
|
|
170
|
+
});
|
|
171
|
+
if (res.status > 0) {
|
|
172
|
+
this.logger.info(`${name} is ready on port ${port}`);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
// not ready yet
|
|
177
|
+
}
|
|
178
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
this.logger.warn(`${name} did not become ready on port ${port} within ${timeoutMs}ms, continuing anyway`);
|
|
182
|
+
}
|
|
183
|
+
}
|