@vizzly-testing/cli 0.10.1 → 0.10.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/dist/commands/tdd-daemon.js +51 -15
- package/dist/commands/tdd.js +10 -3
- package/dist/services/tdd-service.js +30 -13
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync
|
|
1
|
+
import { writeFileSync, readFileSync, existsSync, unlinkSync, mkdirSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { spawn } from 'child_process';
|
|
4
4
|
import { ConsoleUI } from '../utils/console-ui.js';
|
|
@@ -36,21 +36,56 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
36
36
|
}
|
|
37
37
|
const port = options.port || 47392;
|
|
38
38
|
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
// Show loading indicator if downloading baselines (but not in verbose mode since child shows progress)
|
|
40
|
+
if (options.baselineBuild && !globalOptions.verbose) {
|
|
41
|
+
ui.startSpinner(`Downloading baselines from build ${options.baselineBuild}...`);
|
|
42
|
+
}
|
|
42
43
|
|
|
43
|
-
// Spawn
|
|
44
|
+
// Spawn child process with stdio inherited during init for direct error visibility
|
|
44
45
|
const child = spawn(process.execPath, [process.argv[1],
|
|
45
46
|
// CLI entry point
|
|
46
47
|
'tdd', 'start', '--daemon-child',
|
|
47
48
|
// Special flag for child process
|
|
48
49
|
'--port', port.toString(), ...(options.open ? ['--open'] : []), ...(options.baselineBuild ? ['--baseline-build', options.baselineBuild] : []), ...(options.baselineComparison ? ['--baseline-comparison', options.baselineComparison] : []), ...(options.environment ? ['--environment', options.environment] : []), ...(options.threshold !== undefined ? ['--threshold', options.threshold.toString()] : []), ...(options.timeout ? ['--timeout', options.timeout] : []), ...(options.token ? ['--token', options.token] : []), ...(globalOptions.json ? ['--json'] : []), ...(globalOptions.verbose ? ['--verbose'] : []), ...(globalOptions.noColor ? ['--no-color'] : [])], {
|
|
49
50
|
detached: true,
|
|
50
|
-
stdio: ['ignore',
|
|
51
|
+
stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
|
|
51
52
|
cwd: process.cwd()
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
// Wait for child to signal successful init or exit with error
|
|
56
|
+
let initComplete = false;
|
|
57
|
+
let initFailed = false;
|
|
58
|
+
await new Promise(resolve => {
|
|
59
|
+
// Child disconnects IPC when initialization succeeds
|
|
60
|
+
child.on('disconnect', () => {
|
|
61
|
+
initComplete = true;
|
|
62
|
+
resolve();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Child exits before disconnecting = initialization failed
|
|
66
|
+
child.on('exit', () => {
|
|
67
|
+
if (!initComplete) {
|
|
68
|
+
initFailed = true;
|
|
69
|
+
resolve();
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Timeout after 30 seconds to prevent indefinite wait
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
if (!initComplete && !initFailed) {
|
|
76
|
+
initFailed = true;
|
|
77
|
+
resolve();
|
|
78
|
+
}
|
|
79
|
+
}, 30000);
|
|
80
|
+
});
|
|
81
|
+
if (initFailed) {
|
|
82
|
+
if (options.baselineBuild && !globalOptions.verbose) {
|
|
83
|
+
ui.stopSpinner();
|
|
84
|
+
}
|
|
85
|
+
ui.error('TDD server failed to start');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
54
89
|
// Unref so parent can exit
|
|
55
90
|
child.unref();
|
|
56
91
|
|
|
@@ -62,6 +97,9 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
|
|
|
62
97
|
await new Promise(resolve => setTimeout(resolve, retryDelay * (i + 1)));
|
|
63
98
|
running = await isServerRunning(port);
|
|
64
99
|
}
|
|
100
|
+
if (options.baselineBuild && !globalOptions.verbose) {
|
|
101
|
+
ui.stopSpinner();
|
|
102
|
+
}
|
|
65
103
|
if (!running) {
|
|
66
104
|
ui.error('Failed to start TDD server - server not responding to health checks');
|
|
67
105
|
process.exit(1);
|
|
@@ -106,6 +144,11 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
|
|
|
106
144
|
daemon: true
|
|
107
145
|
}, globalOptions);
|
|
108
146
|
|
|
147
|
+
// Disconnect IPC after successful initialization to signal parent
|
|
148
|
+
if (process.send) {
|
|
149
|
+
process.disconnect();
|
|
150
|
+
}
|
|
151
|
+
|
|
109
152
|
// Store our PID for the stop command
|
|
110
153
|
const pidFile = join(vizzlyDir, 'server.pid');
|
|
111
154
|
writeFileSync(pidFile, process.pid.toString());
|
|
@@ -139,15 +182,8 @@ export async function runDaemonChild(options = {}, globalOptions = {}) {
|
|
|
139
182
|
// Keep process alive
|
|
140
183
|
process.stdin.resume();
|
|
141
184
|
} catch (error) {
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
try {
|
|
145
|
-
writeFileSync(logFile, `[${new Date().toISOString()}] ${error.stack || error}\n`, {
|
|
146
|
-
flag: 'a'
|
|
147
|
-
});
|
|
148
|
-
} catch {
|
|
149
|
-
// Silent failure if we can't write log
|
|
150
|
-
}
|
|
185
|
+
// Most errors shown via inherited stdio, but catch any that weren't
|
|
186
|
+
console.error(`Fatal error: ${error.message}`);
|
|
151
187
|
process.exit(1);
|
|
152
188
|
}
|
|
153
189
|
}
|
package/dist/commands/tdd.js
CHANGED
|
@@ -53,7 +53,9 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
|
|
|
53
53
|
// Collect git metadata
|
|
54
54
|
const branch = await detectBranch(options.branch);
|
|
55
55
|
const commit = await detectCommit(options.commit);
|
|
56
|
-
|
|
56
|
+
|
|
57
|
+
// Only show config in verbose mode for non-daemon (daemon shows baseline info instead)
|
|
58
|
+
if (globalOptions.verbose && !options.daemon) {
|
|
57
59
|
ui.info('TDD Configuration loaded', {
|
|
58
60
|
testCommand,
|
|
59
61
|
port: config.server.port,
|
|
@@ -97,8 +99,13 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
|
|
|
97
99
|
ui.info(`TDD screenshot server running on port ${serverInfo.port}`);
|
|
98
100
|
ui.info(`Dashboard: http://localhost:${serverInfo.port}/dashboard`);
|
|
99
101
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
// Verbose server details only in non-daemon mode
|
|
103
|
+
if (globalOptions.verbose && !options.daemon) {
|
|
104
|
+
ui.info('Server started', {
|
|
105
|
+
port: serverInfo.port,
|
|
106
|
+
pid: serverInfo.pid,
|
|
107
|
+
uptime: serverInfo.uptime
|
|
108
|
+
});
|
|
102
109
|
}
|
|
103
110
|
});
|
|
104
111
|
testRunner.on('screenshot-captured', screenshotInfo => {
|
|
@@ -90,8 +90,6 @@ export class TddService {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
async downloadBaselines(environment = 'test', branch = null, buildId = null, comparisonId = null) {
|
|
93
|
-
logger.info('🔍 Looking for baseline build...');
|
|
94
|
-
|
|
95
93
|
// If no branch specified, try to detect the default branch
|
|
96
94
|
if (!branch) {
|
|
97
95
|
branch = await getDefaultBranch();
|
|
@@ -107,7 +105,6 @@ export class TddService {
|
|
|
107
105
|
let baselineBuild;
|
|
108
106
|
if (buildId) {
|
|
109
107
|
// Use specific build ID - get it with screenshots in one call
|
|
110
|
-
logger.info(`📌 Using specified build: ${buildId}`);
|
|
111
108
|
const apiResponse = await this.api.getBuild(buildId, 'screenshots');
|
|
112
109
|
|
|
113
110
|
// Debug the full API response (only in debug mode)
|
|
@@ -154,7 +151,6 @@ export class TddService {
|
|
|
154
151
|
}
|
|
155
152
|
baselineBuild = builds.data[0];
|
|
156
153
|
}
|
|
157
|
-
logger.info(`📥 Found baseline build: ${colors.cyan(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
|
|
158
154
|
|
|
159
155
|
// For specific buildId, we already have screenshots, otherwise get build details
|
|
160
156
|
let buildDetails = baselineBuild;
|
|
@@ -167,7 +163,8 @@ export class TddService {
|
|
|
167
163
|
logger.warn('⚠️ No screenshots found in baseline build');
|
|
168
164
|
return null;
|
|
169
165
|
}
|
|
170
|
-
logger.info(
|
|
166
|
+
logger.info(`Using baseline from build: ${colors.cyan(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
|
|
167
|
+
logger.info(`Checking ${colors.cyan(buildDetails.screenshots.length)} baseline screenshots...`);
|
|
171
168
|
|
|
172
169
|
// Debug screenshots structure (only in debug mode)
|
|
173
170
|
logger.debug(`📊 Screenshots array structure:`, {
|
|
@@ -352,17 +349,37 @@ export class TddService {
|
|
|
352
349
|
const metadataPath = join(this.baselinePath, 'metadata.json');
|
|
353
350
|
writeFileSync(metadataPath, JSON.stringify(this.baselineData, null, 2));
|
|
354
351
|
|
|
352
|
+
// Save baseline build metadata for MCP plugin
|
|
353
|
+
const baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
354
|
+
const buildMetadata = {
|
|
355
|
+
buildId: baselineBuild.id,
|
|
356
|
+
buildName: baselineBuild.name,
|
|
357
|
+
branch: branch,
|
|
358
|
+
environment: environment,
|
|
359
|
+
commitSha: baselineBuild.commit_sha,
|
|
360
|
+
commitMessage: baselineBuild.commit_message,
|
|
361
|
+
approvalStatus: baselineBuild.approval_status,
|
|
362
|
+
completedAt: baselineBuild.completed_at,
|
|
363
|
+
downloadedAt: new Date().toISOString()
|
|
364
|
+
};
|
|
365
|
+
writeFileSync(baselineMetadataPath, JSON.stringify(buildMetadata, null, 2));
|
|
366
|
+
|
|
355
367
|
// Final summary
|
|
356
368
|
const actualDownloads = downloadedCount - skippedCount;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
369
|
+
if (skippedCount > 0) {
|
|
370
|
+
// All skipped (up-to-date)
|
|
371
|
+
if (actualDownloads === 0) {
|
|
372
|
+
logger.info(`✅ All ${skippedCount} baselines up-to-date (matching local SHA)`);
|
|
373
|
+
} else {
|
|
374
|
+
// Mixed: some downloaded, some skipped
|
|
375
|
+
logger.info(`✅ Downloaded ${actualDownloads} new screenshots, ${skippedCount} already up-to-date`);
|
|
376
|
+
}
|
|
364
377
|
} else {
|
|
365
|
-
|
|
378
|
+
// Fresh download
|
|
379
|
+
logger.info(`✅ Downloaded ${downloadedCount}/${buildDetails.screenshots.length} screenshots successfully`);
|
|
380
|
+
}
|
|
381
|
+
if (errorCount > 0) {
|
|
382
|
+
logger.warn(`⚠️ ${errorCount} screenshots failed to download`);
|
|
366
383
|
}
|
|
367
384
|
return this.baselineData;
|
|
368
385
|
} catch (error) {
|