@vizzly-testing/cli 0.35.0 → 0.35.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/README.md +31 -7
- package/dist/api/core.js +16 -3
- package/dist/api/endpoints.js +38 -15
- package/dist/api/index.js +1 -1
- package/dist/cli.js +252 -189
- package/dist/commands/preview.js +19 -17
- package/dist/commands/run.js +7 -4
- package/dist/commands/status.js +14 -1
- package/dist/commands/tdd-daemon-process.js +5 -1
- package/dist/commands/tdd-daemon.js +217 -144
- package/dist/commands/tdd.js +2 -1
- package/dist/reporter/reporter-bundle.iife.js +5 -5
- package/dist/tdd/tdd-service.js +6 -2
- package/dist/test-runner/core.js +4 -2
- package/dist/test-runner/operations.js +17 -2
- package/dist/utils/config-loader.js +1 -0
- package/dist/utils/output.js +3 -0
- package/package.json +2 -2
- package/skills/vizzly/SKILL.md +12 -4
- package/skills/vizzly/references/cli-context.md +8 -2
- package/skills/vizzly/references/setup-ci.md +3 -1
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { homedir } from 'node:os';
|
|
4
3
|
import { basename, join } from 'node:path';
|
|
5
4
|
import { getServerRegistry } from '../tdd/server-registry.js';
|
|
6
5
|
import { withTimeout } from '../utils/async-utils.js';
|
|
7
6
|
import * as output from '../utils/output.js';
|
|
8
7
|
import { tddCommand } from './tdd.js';
|
|
9
|
-
import { resolveDaemonPid, waitForProcessExit } from './tdd-daemon-process.js';
|
|
8
|
+
import { parsePositiveInteger, readDaemonPidFile, resolveDaemonPid, waitForProcessExit } from './tdd-daemon-process.js';
|
|
10
9
|
export { findDaemonPidByPort, readDaemonPidFile, resolveDaemonPid, waitForProcessExit } from './tdd-daemon-process.js';
|
|
11
10
|
let defaultTimers = {
|
|
12
11
|
setTimeout,
|
|
@@ -40,59 +39,30 @@ export function cleanupLocalDaemonFiles(directory = process.cwd(), deps = {}) {
|
|
|
40
39
|
serverFileRemoved: removeFileIfExists(serverFile, deps)
|
|
41
40
|
};
|
|
42
41
|
}
|
|
43
|
-
export function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
} = {}) {
|
|
67
|
-
let globalVizzlyDir = join(home(), '.vizzly');
|
|
68
|
-
if (!exists(globalVizzlyDir)) {
|
|
69
|
-
mkdir(globalVizzlyDir, {
|
|
70
|
-
recursive: true
|
|
71
|
-
});
|
|
42
|
+
export function readLocalDaemonInfo(directory = process.cwd(), deps = {}) {
|
|
43
|
+
let {
|
|
44
|
+
serverFile
|
|
45
|
+
} = getLocalDaemonFiles(directory);
|
|
46
|
+
let fileExists = deps.existsSync || existsSync;
|
|
47
|
+
let readFile = deps.readFileSync || readFileSync;
|
|
48
|
+
if (!fileExists(serverFile)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
let info = JSON.parse(readFile(serverFile, 'utf8'));
|
|
53
|
+
let port = parsePositiveInteger(info.port);
|
|
54
|
+
let pid = parsePositiveInteger(info.pid);
|
|
55
|
+
if (!port) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
...info,
|
|
60
|
+
port,
|
|
61
|
+
pid
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
return null;
|
|
72
65
|
}
|
|
73
|
-
let globalServerFile = join(globalVizzlyDir, 'server.json');
|
|
74
|
-
let serverInfo = buildLegacyServerInfo({
|
|
75
|
-
pid,
|
|
76
|
-
port,
|
|
77
|
-
failOnDiff,
|
|
78
|
-
now
|
|
79
|
-
});
|
|
80
|
-
writeFile(globalServerFile, JSON.stringify(serverInfo, null, 2));
|
|
81
|
-
return {
|
|
82
|
-
path: globalServerFile,
|
|
83
|
-
serverInfo
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
export function cleanupLegacyGlobalServerFile({
|
|
87
|
-
home = homedir,
|
|
88
|
-
exists = existsSync,
|
|
89
|
-
unlink = unlinkSync
|
|
90
|
-
} = {}) {
|
|
91
|
-
let globalServerFile = join(home(), '.vizzly', 'server.json');
|
|
92
|
-
return removeFileIfExists(globalServerFile, {
|
|
93
|
-
existsSync: exists,
|
|
94
|
-
unlinkSync: unlink
|
|
95
|
-
});
|
|
96
66
|
}
|
|
97
67
|
export function unregisterDaemonServer({
|
|
98
68
|
port,
|
|
@@ -109,10 +79,12 @@ export function cleanupDaemonState({
|
|
|
109
79
|
directory = process.cwd(),
|
|
110
80
|
registry = getServerRegistry(),
|
|
111
81
|
localFileDeps = {},
|
|
112
|
-
|
|
82
|
+
cleanLocalFiles = true
|
|
113
83
|
} = {}) {
|
|
114
|
-
let
|
|
115
|
-
|
|
84
|
+
let cleanupResult = cleanLocalFiles ? cleanupLocalDaemonFiles(directory, localFileDeps) : {
|
|
85
|
+
pidFileRemoved: false,
|
|
86
|
+
serverFileRemoved: false
|
|
87
|
+
};
|
|
116
88
|
try {
|
|
117
89
|
if (port !== undefined) {
|
|
118
90
|
unregisterDaemonServer({
|
|
@@ -128,11 +100,42 @@ export function cleanupDaemonState({
|
|
|
128
100
|
} catch {
|
|
129
101
|
// Non-fatal; stale file cleanup is still useful on its own.
|
|
130
102
|
}
|
|
103
|
+
return cleanupResult;
|
|
104
|
+
}
|
|
105
|
+
function resolveDaemonTarget(options = {}, directory = process.cwd()) {
|
|
106
|
+
let registry = getServerRegistry();
|
|
107
|
+
let requestedPort = options.port ? Number(options.port) : null;
|
|
108
|
+
let localInfo = readLocalDaemonInfo(directory);
|
|
109
|
+
let localInfoMatches = !requestedPort || localInfo?.port === requestedPort ? localInfo : null;
|
|
110
|
+
let registryServer = requestedPort ? registry.find({
|
|
111
|
+
port: requestedPort
|
|
112
|
+
}) : registry.find({
|
|
113
|
+
directory
|
|
114
|
+
});
|
|
115
|
+
let serverInfo = localInfoMatches || registryServer || (requestedPort ? {
|
|
116
|
+
port: requestedPort
|
|
117
|
+
} : null);
|
|
118
|
+
let port = requestedPort || serverInfo?.port || 47392;
|
|
131
119
|
return {
|
|
132
|
-
|
|
133
|
-
|
|
120
|
+
registry,
|
|
121
|
+
requestedPort,
|
|
122
|
+
localInfo: localInfoMatches,
|
|
123
|
+
registryServer,
|
|
124
|
+
serverInfo,
|
|
125
|
+
port,
|
|
126
|
+
directory: registryServer?.directory || directory,
|
|
127
|
+
useLocalPidFile: !requestedPort || Boolean(localInfoMatches),
|
|
128
|
+
cleanLocalFiles: !requestedPort || Boolean(localInfoMatches || registryServer)
|
|
134
129
|
};
|
|
135
130
|
}
|
|
131
|
+
function cleanupDaemonTarget(target) {
|
|
132
|
+
return cleanupDaemonState({
|
|
133
|
+
port: target.port,
|
|
134
|
+
directory: target.directory,
|
|
135
|
+
registry: target.registry,
|
|
136
|
+
cleanLocalFiles: target.cleanLocalFiles
|
|
137
|
+
});
|
|
138
|
+
}
|
|
136
139
|
export function buildDashboardUrl(port = 47392) {
|
|
137
140
|
return `http://localhost:${port}`;
|
|
138
141
|
}
|
|
@@ -200,29 +203,62 @@ export async function waitForDaemonChildInit(child, {
|
|
|
200
203
|
timers = defaultTimers
|
|
201
204
|
} = {}) {
|
|
202
205
|
let cleanup = () => {};
|
|
206
|
+
let stderrOutput = '';
|
|
203
207
|
let initPromise = new Promise(resolve => {
|
|
208
|
+
let handleStderr = data => {
|
|
209
|
+
stderrOutput += data.toString();
|
|
210
|
+
};
|
|
211
|
+
let handleMessage = message => {
|
|
212
|
+
if (message?.type !== 'error') {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
let error = new Error(message.message || 'Daemon child failed to start');
|
|
216
|
+
if (message.code) {
|
|
217
|
+
error.code = message.code;
|
|
218
|
+
}
|
|
219
|
+
if (message.stack) {
|
|
220
|
+
error.stack = message.stack;
|
|
221
|
+
}
|
|
222
|
+
cleanup();
|
|
223
|
+
resolve({
|
|
224
|
+
ok: false,
|
|
225
|
+
reason: 'error',
|
|
226
|
+
error
|
|
227
|
+
});
|
|
228
|
+
};
|
|
204
229
|
let handleDisconnect = () => {
|
|
205
230
|
cleanup();
|
|
206
231
|
resolve({
|
|
207
232
|
ok: true
|
|
208
233
|
});
|
|
209
234
|
};
|
|
210
|
-
let handleExit = () => {
|
|
235
|
+
let handleExit = (code, signal) => {
|
|
236
|
+
let error = new Error(stderrOutput.trim() || 'Daemon child exited before startup');
|
|
237
|
+
error.code = code ?? signal ?? null;
|
|
211
238
|
cleanup();
|
|
212
239
|
resolve({
|
|
213
240
|
ok: false,
|
|
214
|
-
reason: 'exit'
|
|
241
|
+
reason: 'exit',
|
|
242
|
+
error
|
|
215
243
|
});
|
|
216
244
|
};
|
|
217
245
|
cleanup = () => {
|
|
246
|
+
child.stderr?.off?.('data', handleStderr);
|
|
247
|
+
child.off('message', handleMessage);
|
|
218
248
|
child.off('disconnect', handleDisconnect);
|
|
219
249
|
child.off('exit', handleExit);
|
|
220
250
|
};
|
|
251
|
+
child.stderr?.on?.('data', handleStderr);
|
|
252
|
+
child.on('message', handleMessage);
|
|
221
253
|
child.on('disconnect', handleDisconnect);
|
|
222
254
|
child.on('exit', handleExit);
|
|
223
255
|
});
|
|
224
256
|
try {
|
|
225
|
-
|
|
257
|
+
let result = await withTimeout(initPromise, timeoutMs, 'TDD server initialization timed out', timers);
|
|
258
|
+
if (result.ok) {
|
|
259
|
+
child.stderr?.unref?.();
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
226
262
|
} catch (error) {
|
|
227
263
|
cleanup();
|
|
228
264
|
return {
|
|
@@ -305,7 +341,7 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
305
341
|
let autoAllocated = false;
|
|
306
342
|
if (options.port) {
|
|
307
343
|
// User specified a port - use it (will fail if busy)
|
|
308
|
-
port = options.port;
|
|
344
|
+
port = Number(options.port);
|
|
309
345
|
|
|
310
346
|
// Check if user-specified port is in use
|
|
311
347
|
if (await isServerRunning(port)) {
|
|
@@ -343,14 +379,15 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
343
379
|
output.startSpinner(`Downloading baselines from build ${options.baselineBuild}...`);
|
|
344
380
|
}
|
|
345
381
|
|
|
346
|
-
//
|
|
382
|
+
// Keep daemon stdio detached so callers that capture output do not hang
|
|
383
|
+
// waiting for the long-lived child to close inherited pipes.
|
|
347
384
|
let child = spawn(process.execPath, buildDaemonChildArgs({
|
|
348
385
|
port,
|
|
349
386
|
options,
|
|
350
387
|
globalOptions
|
|
351
388
|
}), {
|
|
352
389
|
detached: true,
|
|
353
|
-
stdio: ['ignore', '
|
|
390
|
+
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
354
391
|
cwd: process.cwd()
|
|
355
392
|
});
|
|
356
393
|
let initResult = await waitForDaemonChildInit(child);
|
|
@@ -358,7 +395,7 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
358
395
|
if (options.baselineBuild && !globalOptions.verbose) {
|
|
359
396
|
output.stopSpinner();
|
|
360
397
|
}
|
|
361
|
-
output.error('TDD server failed to start');
|
|
398
|
+
output.error(initResult.error?.message || 'TDD server failed to start');
|
|
362
399
|
process.exit(1);
|
|
363
400
|
}
|
|
364
401
|
|
|
@@ -398,17 +435,6 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
398
435
|
// Non-fatal
|
|
399
436
|
}
|
|
400
437
|
|
|
401
|
-
// Also write legacy server.json for SDK discovery (backwards compatibility)
|
|
402
|
-
try {
|
|
403
|
-
writeLegacyGlobalServerFile({
|
|
404
|
-
pid: child.pid,
|
|
405
|
-
port,
|
|
406
|
-
failOnDiff: options.failOnDiff || false
|
|
407
|
-
});
|
|
408
|
-
} catch {
|
|
409
|
-
// Non-fatal, SDK can still use health check
|
|
410
|
-
}
|
|
411
|
-
|
|
412
438
|
// JSON output for successful start
|
|
413
439
|
let dashboardUrl = buildDashboardUrl(port);
|
|
414
440
|
if (globalOptions.json) {
|
|
@@ -447,7 +473,8 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
447
473
|
|
|
448
474
|
// Always show stop hint
|
|
449
475
|
output.blank();
|
|
450
|
-
|
|
476
|
+
let stopCommand = port === 47392 ? 'vizzly tdd stop' : `vizzly tdd stop --port ${port}`;
|
|
477
|
+
output.hint(`Stop with: ${stopCommand}`);
|
|
451
478
|
if (options.open) {
|
|
452
479
|
openDashboard(port);
|
|
453
480
|
}
|
|
@@ -480,6 +507,7 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
|
|
|
480
507
|
try {
|
|
481
508
|
// Use existing tddCommand but with daemon mode
|
|
482
509
|
let {
|
|
510
|
+
result,
|
|
483
511
|
cleanup
|
|
484
512
|
} = await tddCommand(null,
|
|
485
513
|
// No test command - server only
|
|
@@ -487,6 +515,9 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
|
|
|
487
515
|
...options,
|
|
488
516
|
daemon: true
|
|
489
517
|
}, globalOptions);
|
|
518
|
+
if (result?.success === false) {
|
|
519
|
+
throw new Error(result.error || 'TDD server failed to start');
|
|
520
|
+
}
|
|
490
521
|
|
|
491
522
|
// Disconnect IPC after successful initialization to signal parent
|
|
492
523
|
if (process.send) {
|
|
@@ -526,11 +557,29 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
|
|
|
526
557
|
// Keep process alive
|
|
527
558
|
process.stdin.resume();
|
|
528
559
|
} catch (error) {
|
|
529
|
-
|
|
560
|
+
await sendDaemonChildError(error);
|
|
530
561
|
console.error(`Fatal error: ${error.message}`);
|
|
531
562
|
process.exit(1);
|
|
532
563
|
}
|
|
533
564
|
}
|
|
565
|
+
async function sendDaemonChildError(error) {
|
|
566
|
+
if (!process.send) {
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
let message = {
|
|
570
|
+
type: 'error',
|
|
571
|
+
message: error.message,
|
|
572
|
+
code: error.code || null,
|
|
573
|
+
stack: error.stack || null
|
|
574
|
+
};
|
|
575
|
+
await new Promise(resolve => {
|
|
576
|
+
try {
|
|
577
|
+
process.send(message, () => resolve());
|
|
578
|
+
} catch {
|
|
579
|
+
resolve();
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
}
|
|
534
583
|
|
|
535
584
|
/**
|
|
536
585
|
* Stop TDD daemon server
|
|
@@ -543,10 +592,16 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
|
|
|
543
592
|
verbose: globalOptions.verbose,
|
|
544
593
|
color: !globalOptions.noColor
|
|
545
594
|
});
|
|
546
|
-
let
|
|
547
|
-
let pid = await resolveDaemonPid({
|
|
548
|
-
port
|
|
549
|
-
});
|
|
595
|
+
let target = resolveDaemonTarget(options);
|
|
596
|
+
let pid = target.useLocalPidFile ? await resolveDaemonPid({
|
|
597
|
+
port: target.port
|
|
598
|
+
}) : null;
|
|
599
|
+
|
|
600
|
+
// Stop is intentionally file/registry-driven. Do not rediscover arbitrary
|
|
601
|
+
// processes by port here; a non-Vizzly server may be using that port.
|
|
602
|
+
if (!pid) {
|
|
603
|
+
pid = target.serverInfo?.pid || null;
|
|
604
|
+
}
|
|
550
605
|
if (!pid) {
|
|
551
606
|
// JSON output for not running
|
|
552
607
|
if (globalOptions.json) {
|
|
@@ -559,9 +614,7 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
|
|
|
559
614
|
}
|
|
560
615
|
|
|
561
616
|
// Clean up any stale files
|
|
562
|
-
|
|
563
|
-
port
|
|
564
|
-
});
|
|
617
|
+
cleanupDaemonTarget(target);
|
|
565
618
|
return;
|
|
566
619
|
}
|
|
567
620
|
try {
|
|
@@ -581,16 +634,14 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
|
|
|
581
634
|
}
|
|
582
635
|
|
|
583
636
|
// Clean up files
|
|
584
|
-
|
|
585
|
-
port
|
|
586
|
-
});
|
|
637
|
+
cleanupDaemonTarget(target);
|
|
587
638
|
|
|
588
639
|
// JSON output for successful stop
|
|
589
640
|
if (globalOptions.json) {
|
|
590
641
|
output.data({
|
|
591
642
|
status: 'stopped',
|
|
592
643
|
pid,
|
|
593
|
-
port
|
|
644
|
+
port: target.port
|
|
594
645
|
});
|
|
595
646
|
return;
|
|
596
647
|
}
|
|
@@ -598,10 +649,16 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
|
|
|
598
649
|
} catch (error) {
|
|
599
650
|
if (error.code === 'ESRCH') {
|
|
600
651
|
// Process not found - clean up stale files
|
|
652
|
+
cleanupDaemonTarget(target);
|
|
653
|
+
if (globalOptions.json) {
|
|
654
|
+
output.data({
|
|
655
|
+
status: 'stale',
|
|
656
|
+
stopped: false,
|
|
657
|
+
message: 'TDD server was not running; cleaned up stale files'
|
|
658
|
+
});
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
601
661
|
output.warn('TDD server was not running (cleaning up stale files)');
|
|
602
|
-
cleanupDaemonState({
|
|
603
|
-
port
|
|
604
|
-
});
|
|
605
662
|
} else {
|
|
606
663
|
output.error('Error stopping TDD server', error);
|
|
607
664
|
}
|
|
@@ -613,17 +670,53 @@ export async function tddStopCommand(options = {}, globalOptions = {}) {
|
|
|
613
670
|
* @param {Object} options - Command options
|
|
614
671
|
* @param {Object} globalOptions - Global CLI options
|
|
615
672
|
*/
|
|
616
|
-
|
|
673
|
+
function printRunningStatus({
|
|
674
|
+
serverInfo,
|
|
675
|
+
port,
|
|
676
|
+
pid,
|
|
677
|
+
uptimeMs = null,
|
|
678
|
+
uptimeStr = '',
|
|
679
|
+
dashboardUrl = buildDashboardUrl(port),
|
|
680
|
+
globalOptions = {}
|
|
681
|
+
}) {
|
|
682
|
+
if (globalOptions.json) {
|
|
683
|
+
output.data({
|
|
684
|
+
running: true,
|
|
685
|
+
port,
|
|
686
|
+
pid,
|
|
687
|
+
uptimeMs,
|
|
688
|
+
uptime: uptimeStr || null,
|
|
689
|
+
dashboardUrl
|
|
690
|
+
});
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
let colors = output.getColors();
|
|
694
|
+
output.header('tdd', 'local');
|
|
695
|
+
output.print(` ${output.statusDot('success')} Running ${uptimeStr ? colors.brand.textTertiary(`· ${uptimeStr}`) : ''}`);
|
|
696
|
+
output.blank();
|
|
697
|
+
output.printBox(colors.brand.info(colors.underline(dashboardUrl)), {
|
|
698
|
+
title: 'Dashboard',
|
|
699
|
+
style: 'branded'
|
|
700
|
+
});
|
|
701
|
+
if (globalOptions.verbose && pid) {
|
|
702
|
+
output.blank();
|
|
703
|
+
output.print(` ${colors.brand.textTertiary('PID:')} ${pid}`);
|
|
704
|
+
}
|
|
705
|
+
if (globalOptions.verbose && serverInfo?.logFile) {
|
|
706
|
+
output.print(` ${colors.brand.textTertiary('Log:')} ${serverInfo.logFile}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
export async function tddStatusCommand(options = {}, globalOptions = {}) {
|
|
617
710
|
output.configure({
|
|
618
711
|
json: globalOptions.json,
|
|
619
712
|
verbose: globalOptions.verbose,
|
|
620
713
|
color: !globalOptions.noColor
|
|
621
714
|
});
|
|
622
715
|
let {
|
|
623
|
-
pidFile
|
|
624
|
-
serverFile
|
|
716
|
+
pidFile
|
|
625
717
|
} = getLocalDaemonFiles();
|
|
626
|
-
|
|
718
|
+
let target = resolveDaemonTarget(options);
|
|
719
|
+
if (!target.serverInfo && !existsSync(pidFile)) {
|
|
627
720
|
// JSON output for not running
|
|
628
721
|
if (globalOptions.json) {
|
|
629
722
|
output.data({
|
|
@@ -636,31 +729,27 @@ export async function tddStatusCommand(_options, globalOptions = {}) {
|
|
|
636
729
|
return;
|
|
637
730
|
}
|
|
638
731
|
try {
|
|
639
|
-
let pid =
|
|
732
|
+
let pid = target.useLocalPidFile ? readDaemonPidFile(pidFile) : null;
|
|
733
|
+
if (!pid) {
|
|
734
|
+
pid = target.serverInfo?.pid || null;
|
|
735
|
+
}
|
|
640
736
|
if (!pid) {
|
|
641
|
-
output.warn('TDD server
|
|
642
|
-
|
|
737
|
+
output.warn('TDD server not running (cleaning up stale files)');
|
|
738
|
+
cleanupDaemonTarget(target);
|
|
643
739
|
return;
|
|
644
740
|
}
|
|
645
741
|
|
|
646
742
|
// Check if process is actually running
|
|
647
743
|
process.kill(pid, 0); // Signal 0 just checks if process exists
|
|
648
744
|
|
|
649
|
-
let serverInfo = {
|
|
650
|
-
port: 47392
|
|
651
|
-
};
|
|
652
|
-
if (existsSync(serverFile)) {
|
|
653
|
-
serverInfo = JSON.parse(readFileSync(serverFile, 'utf8'));
|
|
654
|
-
}
|
|
655
|
-
|
|
656
745
|
// Try to check health endpoint
|
|
657
|
-
let health = await checkServerHealth(
|
|
746
|
+
let health = await checkServerHealth(target.port);
|
|
658
747
|
if (health.running) {
|
|
659
748
|
// Calculate uptime
|
|
660
749
|
let uptimeMs = null;
|
|
661
750
|
let uptimeStr = '';
|
|
662
|
-
if (serverInfo
|
|
663
|
-
uptimeMs = Date.now() - serverInfo.startTime;
|
|
751
|
+
if (target.serverInfo?.startTime) {
|
|
752
|
+
uptimeMs = Date.now() - target.serverInfo.startTime;
|
|
664
753
|
let uptime = Math.floor(uptimeMs / 1000);
|
|
665
754
|
let hours = Math.floor(uptime / 3600);
|
|
666
755
|
let minutes = Math.floor(uptime % 3600 / 60);
|
|
@@ -669,47 +758,31 @@ export async function tddStatusCommand(_options, globalOptions = {}) {
|
|
|
669
758
|
if (minutes > 0 || hours > 0) uptimeStr += `${minutes}m `;
|
|
670
759
|
uptimeStr += `${seconds}s`;
|
|
671
760
|
}
|
|
672
|
-
let dashboardUrl = buildDashboardUrl(
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
uptime: uptimeStr || null,
|
|
682
|
-
dashboardUrl
|
|
683
|
-
});
|
|
684
|
-
return;
|
|
685
|
-
}
|
|
686
|
-
let colors = output.getColors();
|
|
687
|
-
|
|
688
|
-
// Show header
|
|
689
|
-
output.header('tdd', 'local');
|
|
690
|
-
|
|
691
|
-
// Show running status with uptime
|
|
692
|
-
output.print(` ${output.statusDot('success')} Running ${uptimeStr ? colors.brand.textTertiary(`· ${uptimeStr}`) : ''}`);
|
|
693
|
-
output.blank();
|
|
694
|
-
|
|
695
|
-
// Show dashboard URL in a branded box
|
|
696
|
-
output.printBox(colors.brand.info(colors.underline(dashboardUrl)), {
|
|
697
|
-
title: 'Dashboard',
|
|
698
|
-
style: 'branded'
|
|
761
|
+
let dashboardUrl = buildDashboardUrl(target.port);
|
|
762
|
+
printRunningStatus({
|
|
763
|
+
serverInfo: target.serverInfo,
|
|
764
|
+
port: target.port,
|
|
765
|
+
pid,
|
|
766
|
+
uptimeMs,
|
|
767
|
+
uptimeStr,
|
|
768
|
+
dashboardUrl,
|
|
769
|
+
globalOptions
|
|
699
770
|
});
|
|
700
|
-
|
|
701
|
-
// Verbose mode: show PID
|
|
702
|
-
if (globalOptions.verbose) {
|
|
703
|
-
output.blank();
|
|
704
|
-
output.print(` ${colors.brand.textTertiary('PID:')} ${pid}`);
|
|
705
|
-
}
|
|
706
771
|
} else {
|
|
707
772
|
output.warn('TDD server process exists but not responding to health checks');
|
|
708
773
|
}
|
|
709
774
|
} catch (error) {
|
|
710
775
|
if (error.code === 'ESRCH') {
|
|
776
|
+
cleanupDaemonTarget(target);
|
|
777
|
+
if (globalOptions.json) {
|
|
778
|
+
output.data({
|
|
779
|
+
status: 'stale',
|
|
780
|
+
running: false,
|
|
781
|
+
message: 'TDD server process not found; cleaned up stale files'
|
|
782
|
+
});
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
711
785
|
output.warn('TDD server process not found (cleaning up stale files)');
|
|
712
|
-
cleanupDaemonState();
|
|
713
786
|
} else {
|
|
714
787
|
output.error('Error checking TDD server status', error);
|
|
715
788
|
}
|
package/dist/commands/tdd.js
CHANGED
|
@@ -160,7 +160,8 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {},
|
|
|
160
160
|
allowNoToken: config.allowNoToken || false,
|
|
161
161
|
baselineBuildId: config.baselineBuildId,
|
|
162
162
|
baselineComparisonId: config.baselineComparisonId,
|
|
163
|
-
wait: false
|
|
163
|
+
wait: false,
|
|
164
|
+
json: globalOptions.json
|
|
164
165
|
};
|
|
165
166
|
|
|
166
167
|
// In daemon mode, just start the server without running tests
|