@epic-web/workshop-utils 6.70.0 → 6.71.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -7,10 +7,18 @@ type DevProcessesMap = Map<string, {
|
|
|
7
7
|
process: ChildProcess;
|
|
8
8
|
port: number;
|
|
9
9
|
}>;
|
|
10
|
-
type
|
|
10
|
+
type SidecarOutputLine = {
|
|
11
|
+
type: 'stdout' | 'stderr';
|
|
12
|
+
content: string;
|
|
13
|
+
timestamp: number;
|
|
14
|
+
};
|
|
15
|
+
type SidecarProcessEntry = {
|
|
11
16
|
color: (typeof colors)[number];
|
|
12
17
|
process: ChildProcess;
|
|
13
|
-
|
|
18
|
+
command: string;
|
|
19
|
+
output: Array<SidecarOutputLine>;
|
|
20
|
+
};
|
|
21
|
+
type SidecarProcessesMap = Map<string, SidecarProcessEntry>;
|
|
14
22
|
type OutputLine = {
|
|
15
23
|
type: 'stdout' | 'stderr';
|
|
16
24
|
content: string;
|
|
@@ -78,6 +86,8 @@ export declare function getProcesses(): {
|
|
|
78
86
|
};
|
|
79
87
|
export declare function startSidecarProcesses(processes: Record<string, string>): void;
|
|
80
88
|
export declare function startSidecarProcess(name: string, command: string): void;
|
|
89
|
+
export declare function getSidecarLogs(name: string, lineCount?: number): string;
|
|
90
|
+
export declare function restartSidecarProcess(name: string): Promise<boolean>;
|
|
81
91
|
export declare function stopSidecarProcesses(): void;
|
|
82
92
|
export declare function closeProcess(key: string): Promise<void>;
|
|
83
93
|
export declare function stopPort(port: string | number): Promise<void>;
|
|
@@ -274,14 +274,21 @@ export function startSidecarProcesses(processes) {
|
|
|
274
274
|
startSidecarProcess(name, command);
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
|
+
// Maximum number of log entries to keep per sidecar process
|
|
278
|
+
const MAX_SIDECAR_LOG_ENTRIES = 1000;
|
|
277
279
|
export function startSidecarProcess(name, command) {
|
|
278
280
|
if (isDeployed)
|
|
279
281
|
throw new Error('cannot run sidecar processes in deployed mode');
|
|
280
282
|
// if the process is already running, don't start it again
|
|
281
|
-
|
|
283
|
+
const existingEntry = sidecarProcesses.get(name);
|
|
284
|
+
if (existingEntry && existingEntry.process.exitCode === null) {
|
|
282
285
|
console.log(`Sidecar process ${name} is already running`);
|
|
283
286
|
return;
|
|
284
287
|
}
|
|
288
|
+
// If there's an old exited entry, clean it up first
|
|
289
|
+
if (existingEntry) {
|
|
290
|
+
sidecarProcesses.delete(name);
|
|
291
|
+
}
|
|
285
292
|
const color = getNextAvailableColor();
|
|
286
293
|
// Spawn the command using shell to handle complex commands properly
|
|
287
294
|
const workshopRoot = getEnv().EPICSHOP_CONTEXT_CWD;
|
|
@@ -295,23 +302,38 @@ export function startSidecarProcess(name, command) {
|
|
|
295
302
|
},
|
|
296
303
|
});
|
|
297
304
|
const prefix = chalk[color](`[${name}]`);
|
|
305
|
+
const output = [];
|
|
306
|
+
function addOutputLine(type, content) {
|
|
307
|
+
output.push({ type, content, timestamp: Date.now() });
|
|
308
|
+
// Keep only the last MAX_SIDECAR_LOG_ENTRIES entries
|
|
309
|
+
if (output.length > MAX_SIDECAR_LOG_ENTRIES) {
|
|
310
|
+
output.shift();
|
|
311
|
+
}
|
|
312
|
+
}
|
|
298
313
|
function handleStdOutData(data) {
|
|
299
|
-
|
|
300
|
-
|
|
314
|
+
const content = data.toString('utf-8');
|
|
315
|
+
addOutputLine('stdout', content);
|
|
316
|
+
console.log(content
|
|
301
317
|
.split('\n')
|
|
302
318
|
.map((line) => `${prefix} ${line}`)
|
|
303
319
|
.join('\n'));
|
|
304
320
|
}
|
|
305
321
|
sidecarProcess.stdout?.on('data', handleStdOutData);
|
|
306
322
|
function handleStdErrData(data) {
|
|
307
|
-
|
|
308
|
-
|
|
323
|
+
const content = data.toString('utf-8');
|
|
324
|
+
addOutputLine('stderr', content);
|
|
325
|
+
console.error(content
|
|
309
326
|
.split('\n')
|
|
310
327
|
.map((line) => `${prefix} ${line}`)
|
|
311
328
|
.join('\n'));
|
|
312
329
|
}
|
|
313
330
|
sidecarProcess.stderr?.on('data', handleStdErrData);
|
|
314
|
-
sidecarProcesses.set(name, {
|
|
331
|
+
sidecarProcesses.set(name, {
|
|
332
|
+
color,
|
|
333
|
+
process: sidecarProcess,
|
|
334
|
+
command,
|
|
335
|
+
output,
|
|
336
|
+
});
|
|
315
337
|
sidecarProcess.on('exit', (code, signal) => {
|
|
316
338
|
sidecarProcess.stdout?.off('data', handleStdOutData);
|
|
317
339
|
sidecarProcess.stderr?.off('data', handleStdErrData);
|
|
@@ -321,20 +343,73 @@ export function startSidecarProcess(name, command) {
|
|
|
321
343
|
else {
|
|
322
344
|
console.log(`${prefix} exited with code ${code}${signal ? ` (signal: ${signal})` : ''}`);
|
|
323
345
|
}
|
|
324
|
-
|
|
346
|
+
// Don't delete the entry so we can still access logs after exit
|
|
347
|
+
// Just mark it as not running by keeping the process reference
|
|
325
348
|
});
|
|
326
349
|
sidecarProcess.on('error', (error) => {
|
|
327
350
|
console.error(`${prefix} failed to start: ${error.message}`);
|
|
328
|
-
|
|
351
|
+
addOutputLine('stderr', `Failed to start: ${error.message}`);
|
|
329
352
|
});
|
|
330
353
|
console.log(`${prefix} started`);
|
|
331
354
|
}
|
|
355
|
+
export function getSidecarLogs(name, lineCount = 50) {
|
|
356
|
+
const entry = sidecarProcesses.get(name);
|
|
357
|
+
if (!entry)
|
|
358
|
+
return '';
|
|
359
|
+
// Get the last N lines of output
|
|
360
|
+
const logs = entry.output.slice(-lineCount);
|
|
361
|
+
return logs.map((line) => line.content).join('');
|
|
362
|
+
}
|
|
363
|
+
export async function restartSidecarProcess(name) {
|
|
364
|
+
if (isDeployed)
|
|
365
|
+
throw new Error('cannot restart sidecar processes in deployed mode');
|
|
366
|
+
const entry = sidecarProcesses.get(name);
|
|
367
|
+
if (!entry) {
|
|
368
|
+
console.log(`Sidecar process ${name} not found`);
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
const { command, process: proc, output: oldOutput } = entry;
|
|
372
|
+
// Remove the entry immediately to prevent concurrent restarts
|
|
373
|
+
sidecarProcesses.delete(name);
|
|
374
|
+
// Kill the existing process if it's still running
|
|
375
|
+
if (proc.exitCode === null) {
|
|
376
|
+
console.log(`Stopping sidecar process: ${name}`);
|
|
377
|
+
proc.kill();
|
|
378
|
+
// Wait for the process to exit
|
|
379
|
+
await new Promise((resolve) => {
|
|
380
|
+
const timeout = setTimeout(() => {
|
|
381
|
+
// Force kill if it doesn't exit in time
|
|
382
|
+
proc.kill('SIGKILL');
|
|
383
|
+
resolve();
|
|
384
|
+
}, 5000);
|
|
385
|
+
proc.once('exit', () => {
|
|
386
|
+
clearTimeout(timeout);
|
|
387
|
+
resolve();
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
// Start a new process with the same command
|
|
392
|
+
startSidecarProcess(name, command);
|
|
393
|
+
// Preserve logs from the old process by prepending them to the new array
|
|
394
|
+
// We must modify the existing array (not replace it) because event handlers
|
|
395
|
+
// have already captured it in their closure
|
|
396
|
+
const newEntry = sidecarProcesses.get(name);
|
|
397
|
+
if (newEntry) {
|
|
398
|
+
// Prepend old logs to the existing array that handlers are writing to
|
|
399
|
+
newEntry.output.unshift(...oldOutput);
|
|
400
|
+
// Trim to max entries if needed
|
|
401
|
+
if (newEntry.output.length > MAX_SIDECAR_LOG_ENTRIES) {
|
|
402
|
+
newEntry.output.splice(0, newEntry.output.length - MAX_SIDECAR_LOG_ENTRIES);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return true;
|
|
406
|
+
}
|
|
332
407
|
export function stopSidecarProcesses() {
|
|
333
408
|
if (isDeployed)
|
|
334
409
|
throw new Error('cannot stop sidecar processes in deployed mode');
|
|
335
|
-
for (const [name,
|
|
410
|
+
for (const [name, entry] of sidecarProcesses.entries()) {
|
|
336
411
|
console.log(`Stopping sidecar process: ${name}`);
|
|
337
|
-
|
|
412
|
+
entry.process.kill();
|
|
338
413
|
}
|
|
339
414
|
sidecarProcesses.clear();
|
|
340
415
|
}
|