@vizzly-testing/cli 0.16.4 → 0.18.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.
- package/README.md +4 -4
- package/claude-plugin/skills/debug-visual-regression/SKILL.md +2 -2
- package/dist/cli.js +84 -58
- package/dist/client/index.js +6 -6
- package/dist/commands/doctor.js +18 -17
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/init.js +30 -30
- package/dist/commands/login.js +23 -23
- package/dist/commands/logout.js +4 -4
- package/dist/commands/project.js +36 -36
- package/dist/commands/run.js +33 -33
- package/dist/commands/status.js +14 -14
- package/dist/commands/tdd-daemon.js +43 -43
- package/dist/commands/tdd.js +27 -27
- package/dist/commands/upload.js +33 -33
- package/dist/commands/whoami.js +12 -12
- package/dist/index.js +9 -14
- package/dist/plugin-loader.js +28 -28
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +19 -19
- package/dist/sdk/index.js +33 -35
- package/dist/server/handlers/api-handler.js +4 -4
- package/dist/server/handlers/tdd-handler.js +12 -12
- package/dist/server/http-server.js +21 -22
- package/dist/server/middleware/json-parser.js +1 -1
- package/dist/server/routers/assets.js +14 -14
- package/dist/server/routers/auth.js +14 -14
- package/dist/server/routers/baseline.js +8 -8
- package/dist/server/routers/cloud-proxy.js +15 -15
- package/dist/server/routers/config.js +11 -11
- package/dist/server/routers/dashboard.js +11 -11
- package/dist/server/routers/health.js +4 -4
- package/dist/server/routers/projects.js +19 -19
- package/dist/server/routers/screenshot.js +9 -9
- package/dist/services/api-service.js +16 -16
- package/dist/services/auth-service.js +17 -17
- package/dist/services/build-manager.js +3 -3
- package/dist/services/config-service.js +33 -33
- package/dist/services/html-report-generator.js +8 -8
- package/dist/services/index.js +11 -11
- package/dist/services/project-service.js +19 -19
- package/dist/services/report-generator/report.css +3 -3
- package/dist/services/report-generator/viewer.js +25 -23
- package/dist/services/screenshot-server.js +1 -1
- package/dist/services/server-manager.js +5 -5
- package/dist/services/static-report-generator.js +14 -14
- package/dist/services/tdd-service.js +101 -95
- package/dist/services/test-runner.js +14 -4
- package/dist/services/uploader.js +10 -8
- package/dist/types/config.d.ts +2 -1
- package/dist/types/index.d.ts +11 -1
- package/dist/types/sdk.d.ts +1 -1
- package/dist/utils/browser.js +3 -3
- package/dist/utils/build-history.js +12 -12
- package/dist/utils/config-loader.js +19 -19
- package/dist/utils/config-schema.js +10 -9
- package/dist/utils/environment-config.js +11 -0
- package/dist/utils/fetch-utils.js +2 -2
- package/dist/utils/file-helpers.js +2 -2
- package/dist/utils/git.js +3 -6
- package/dist/utils/global-config.js +28 -25
- package/dist/utils/output.js +136 -28
- package/dist/utils/package-info.js +3 -3
- package/dist/utils/security.js +12 -12
- package/docs/api-reference.md +56 -27
- package/docs/doctor-command.md +1 -1
- package/docs/tdd-mode.md +3 -3
- package/package.json +9 -13
package/README.md
CHANGED
|
@@ -227,7 +227,7 @@ vizzly run "npm test" --parallel-id "ci-run-123" # For parallel CI builds
|
|
|
227
227
|
|
|
228
228
|
**Processing Options:**
|
|
229
229
|
- `--wait` - Wait for build completion and exit with appropriate code
|
|
230
|
-
- `--threshold <number>` - Comparison threshold (
|
|
230
|
+
- `--threshold <number>` - Comparison threshold (CIEDE2000 Delta E, default: 2.0)
|
|
231
231
|
- `--upload-timeout <ms>` - Upload wait timeout in ms
|
|
232
232
|
- `--upload-all` - Upload all screenshots without SHA deduplication
|
|
233
233
|
|
|
@@ -279,7 +279,7 @@ vizzly dev stop
|
|
|
279
279
|
**Dev Command Options:**
|
|
280
280
|
- `--set-baseline` - Accept current screenshots as new baseline
|
|
281
281
|
- `--baseline-build <id>` - Use specific build as baseline (requires API token)
|
|
282
|
-
- `--threshold <number>` - Comparison threshold (
|
|
282
|
+
- `--threshold <number>` - Comparison threshold (CIEDE2000 Delta E, default: 2.0)
|
|
283
283
|
- `--port <port>` - Server port (default: 47392)
|
|
284
284
|
- `--timeout <ms>` - Server timeout (default: 30000)
|
|
285
285
|
- `--open` - Auto-open dashboard in browser (start command only)
|
|
@@ -357,9 +357,9 @@ export default {
|
|
|
357
357
|
timeout: 30000
|
|
358
358
|
},
|
|
359
359
|
|
|
360
|
-
// Comparison configuration
|
|
360
|
+
// Comparison configuration (CIEDE2000 Delta E: 0=exact, 1=JND, 2=recommended)
|
|
361
361
|
comparison: {
|
|
362
|
-
threshold: 0
|
|
362
|
+
threshold: 2.0,
|
|
363
363
|
ignoreAntialiasing: true
|
|
364
364
|
},
|
|
365
365
|
|
|
@@ -118,7 +118,7 @@ Response:
|
|
|
118
118
|
"name": "homepage",
|
|
119
119
|
"status": "failed",
|
|
120
120
|
"diffPercentage": 2.3,
|
|
121
|
-
"threshold": 0
|
|
121
|
+
"threshold": 2.0,
|
|
122
122
|
"mode": "local",
|
|
123
123
|
"baselinePath": "/Users/you/project/.vizzly/baselines/homepage.png",
|
|
124
124
|
"currentPath": "/Users/you/project/.vizzly/screenshots/homepage.png"
|
|
@@ -170,7 +170,7 @@ Response:
|
|
|
170
170
|
"name": "login-page",
|
|
171
171
|
"status": "failed",
|
|
172
172
|
"diffPercentage": 0.8,
|
|
173
|
-
"threshold": 0
|
|
173
|
+
"threshold": 2.0,
|
|
174
174
|
"mode": "cloud",
|
|
175
175
|
"baselineUrl": "https://app.vizzly.dev/screenshots/abc123/baseline.png",
|
|
176
176
|
"currentUrl": "https://app.vizzly.dev/screenshots/abc123/current.png",
|
package/dist/cli.js
CHANGED
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import 'dotenv/config';
|
|
3
3
|
import { program } from 'commander';
|
|
4
|
-
import { init } from './commands/init.js';
|
|
5
|
-
import { uploadCommand, validateUploadOptions } from './commands/upload.js';
|
|
6
|
-
import { runCommand, validateRunOptions } from './commands/run.js';
|
|
7
|
-
import { tddCommand, validateTddOptions } from './commands/tdd.js';
|
|
8
|
-
import { tddStartCommand, tddStopCommand, tddStatusCommand, runDaemonChild } from './commands/tdd-daemon.js';
|
|
9
|
-
import { statusCommand, validateStatusOptions } from './commands/status.js';
|
|
10
|
-
import { finalizeCommand, validateFinalizeOptions } from './commands/finalize.js';
|
|
11
4
|
import { doctorCommand, validateDoctorOptions } from './commands/doctor.js';
|
|
5
|
+
import { finalizeCommand, validateFinalizeOptions } from './commands/finalize.js';
|
|
6
|
+
import { init } from './commands/init.js';
|
|
12
7
|
import { loginCommand, validateLoginOptions } from './commands/login.js';
|
|
13
8
|
import { logoutCommand, validateLogoutOptions } from './commands/logout.js';
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
9
|
+
import { projectListCommand, projectRemoveCommand, projectSelectCommand, projectTokenCommand, validateProjectOptions } from './commands/project.js';
|
|
10
|
+
import { runCommand, validateRunOptions } from './commands/run.js';
|
|
11
|
+
import { statusCommand, validateStatusOptions } from './commands/status.js';
|
|
12
|
+
import { tddCommand, validateTddOptions } from './commands/tdd.js';
|
|
13
|
+
import { runDaemonChild, tddStartCommand, tddStatusCommand, tddStopCommand } from './commands/tdd-daemon.js';
|
|
14
|
+
import { uploadCommand, validateUploadOptions } from './commands/upload.js';
|
|
15
|
+
import { validateWhoamiOptions, whoamiCommand } from './commands/whoami.js';
|
|
17
16
|
import { loadPlugins } from './plugin-loader.js';
|
|
18
|
-
import { loadConfig } from './utils/config-loader.js';
|
|
19
17
|
import { createServices } from './services/index.js';
|
|
18
|
+
import { loadConfig } from './utils/config-loader.js';
|
|
20
19
|
import * as output from './utils/output.js';
|
|
21
|
-
|
|
20
|
+
import { getPackageVersion } from './utils/package-info.js';
|
|
21
|
+
program.name('vizzly').description('Vizzly CLI for visual regression testing').version(getPackageVersion()).option('-c, --config <path>', 'Config file path').option('--token <token>', 'Vizzly API token').option('-v, --verbose', 'Verbose output (shorthand for --log-level debug)').option('--log-level <level>', 'Log level: debug, info, warn, error (default: info, or VIZZLY_LOG_LEVEL env var)').option('--json', 'Machine-readable output').option('--no-color', 'Disable colored output');
|
|
22
22
|
|
|
23
23
|
// Load plugins before defining commands
|
|
24
24
|
// We need to manually parse to get the config option early
|
|
25
25
|
let configPath = null;
|
|
26
26
|
let verboseMode = false;
|
|
27
|
+
let logLevelArg = null;
|
|
27
28
|
for (let i = 0; i < process.argv.length; i++) {
|
|
28
29
|
if ((process.argv[i] === '-c' || process.argv[i] === '--config') && process.argv[i + 1]) {
|
|
29
30
|
configPath = process.argv[i + 1];
|
|
@@ -31,30 +32,35 @@ for (let i = 0; i < process.argv.length; i++) {
|
|
|
31
32
|
if (process.argv[i] === '-v' || process.argv[i] === '--verbose') {
|
|
32
33
|
verboseMode = true;
|
|
33
34
|
}
|
|
35
|
+
if (process.argv[i] === '--log-level' && process.argv[i + 1]) {
|
|
36
|
+
logLevelArg = process.argv[i + 1];
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
// Configure output early
|
|
41
|
+
// Priority: --log-level > --verbose > VIZZLY_LOG_LEVEL env var > default ('info')
|
|
37
42
|
output.configure({
|
|
43
|
+
logLevel: logLevelArg,
|
|
38
44
|
verbose: verboseMode,
|
|
39
45
|
color: !process.argv.includes('--no-color'),
|
|
40
46
|
json: process.argv.includes('--json')
|
|
41
47
|
});
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
const config = await loadConfig(configPath, {});
|
|
49
|
+
const services = createServices(config);
|
|
44
50
|
let plugins = [];
|
|
45
51
|
try {
|
|
46
52
|
plugins = await loadPlugins(configPath, config);
|
|
47
|
-
for (
|
|
53
|
+
for (const plugin of plugins) {
|
|
48
54
|
try {
|
|
49
55
|
// Add timeout protection for plugin registration (5 seconds)
|
|
50
|
-
|
|
56
|
+
const registerPromise = plugin.register(program, {
|
|
51
57
|
config,
|
|
52
58
|
services,
|
|
53
59
|
output,
|
|
54
60
|
// Backwards compatibility alias for plugins using old API
|
|
55
61
|
logger: output
|
|
56
62
|
});
|
|
57
|
-
|
|
63
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Plugin registration timeout (5s)')), 5000));
|
|
58
64
|
await Promise.race([registerPromise, timeoutPromise]);
|
|
59
65
|
output.debug(`Registered plugin: ${plugin.name}`);
|
|
60
66
|
} catch (error) {
|
|
@@ -65,7 +71,7 @@ try {
|
|
|
65
71
|
output.debug(`Plugin loading failed: ${error.message}`);
|
|
66
72
|
}
|
|
67
73
|
program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').action(async options => {
|
|
68
|
-
|
|
74
|
+
const globalOptions = program.opts();
|
|
69
75
|
await init({
|
|
70
76
|
...globalOptions,
|
|
71
77
|
...options,
|
|
@@ -73,24 +79,26 @@ program.command('init').description('Initialize Vizzly in your project').option(
|
|
|
73
79
|
});
|
|
74
80
|
});
|
|
75
81
|
program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory or file').option('-b, --build-name <name>', 'Build name for grouping').option('-m, --metadata <json>', 'Additional metadata as JSON').option('--batch-size <n>', 'Upload batch size', v => parseInt(v, 10)).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', v => parseInt(v, 10)).option('--branch <branch>', 'Git branch').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (path, options) => {
|
|
76
|
-
|
|
82
|
+
const globalOptions = program.opts();
|
|
77
83
|
|
|
78
84
|
// Validate options
|
|
79
|
-
|
|
85
|
+
const validationErrors = validateUploadOptions(path, options);
|
|
80
86
|
if (validationErrors.length > 0) {
|
|
81
87
|
output.error('Validation errors:');
|
|
82
|
-
|
|
88
|
+
for (let error of validationErrors) {
|
|
89
|
+
output.printErr(` - ${error}`);
|
|
90
|
+
}
|
|
83
91
|
process.exit(1);
|
|
84
92
|
}
|
|
85
93
|
await uploadCommand(path, options, globalOptions);
|
|
86
94
|
});
|
|
87
95
|
|
|
88
96
|
// TDD command with subcommands - Local visual testing with interactive dashboard
|
|
89
|
-
|
|
97
|
+
const tddCmd = program.command('tdd').description('Run tests in TDD mode with local visual comparisons');
|
|
90
98
|
|
|
91
99
|
// TDD Start - Background server
|
|
92
100
|
tddCmd.command('start').description('Start background TDD server with dashboard').option('--port <port>', 'Port for TDD server', '47392').option('--open', 'Open dashboard in browser').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--token <token>', 'API token override').option('--daemon-child', 'Internal: run as daemon child process').action(async options => {
|
|
93
|
-
|
|
101
|
+
const globalOptions = program.opts();
|
|
94
102
|
|
|
95
103
|
// If this is a daemon child process, run the server directly
|
|
96
104
|
if (options.daemonChild) {
|
|
@@ -102,34 +110,36 @@ tddCmd.command('start').description('Start background TDD server with dashboard'
|
|
|
102
110
|
|
|
103
111
|
// TDD Stop - Kill background server
|
|
104
112
|
tddCmd.command('stop').description('Stop background TDD server').action(async options => {
|
|
105
|
-
|
|
113
|
+
const globalOptions = program.opts();
|
|
106
114
|
await tddStopCommand(options, globalOptions);
|
|
107
115
|
});
|
|
108
116
|
|
|
109
117
|
// TDD Status - Check server status
|
|
110
118
|
tddCmd.command('status').description('Check TDD server status').action(async options => {
|
|
111
|
-
|
|
119
|
+
const globalOptions = program.opts();
|
|
112
120
|
await tddStatusCommand(options, globalOptions);
|
|
113
121
|
});
|
|
114
122
|
|
|
115
123
|
// TDD Run - One-off test run with ephemeral server (generates static report)
|
|
116
124
|
tddCmd.command('run <command>').description('Run tests once in TDD mode with local visual comparisons').option('--port <port>', 'Port for TDD server', '47392').option('--branch <branch>', 'Git branch override').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--set-baseline', 'Accept current screenshots as new baseline (overwrites existing)').action(async (command, options) => {
|
|
117
|
-
|
|
125
|
+
const globalOptions = program.opts();
|
|
118
126
|
|
|
119
127
|
// Validate options
|
|
120
|
-
|
|
128
|
+
const validationErrors = validateTddOptions(command, options);
|
|
121
129
|
if (validationErrors.length > 0) {
|
|
122
130
|
output.error('Validation errors:');
|
|
123
|
-
|
|
131
|
+
for (let error of validationErrors) {
|
|
132
|
+
output.printErr(` - ${error}`);
|
|
133
|
+
}
|
|
124
134
|
process.exit(1);
|
|
125
135
|
}
|
|
126
|
-
|
|
136
|
+
const {
|
|
127
137
|
result,
|
|
128
138
|
cleanup
|
|
129
139
|
} = await tddCommand(command, options, globalOptions);
|
|
130
140
|
|
|
131
141
|
// Set up cleanup on process signals
|
|
132
|
-
|
|
142
|
+
const handleCleanup = async () => {
|
|
133
143
|
await cleanup();
|
|
134
144
|
};
|
|
135
145
|
process.once('SIGINT', () => {
|
|
@@ -145,17 +155,19 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
|
|
|
145
155
|
await cleanup();
|
|
146
156
|
});
|
|
147
157
|
program.command('run').description('Run tests with Vizzly integration').argument('<command>', 'Test command to run').option('--port <port>', 'Port for screenshot server', '47392').option('-b, --build-name <name>', 'Custom build name').option('--branch <branch>', 'Git branch override').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--allow-no-token', 'Allow running without API token').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (command, options) => {
|
|
148
|
-
|
|
158
|
+
const globalOptions = program.opts();
|
|
149
159
|
|
|
150
160
|
// Validate options
|
|
151
|
-
|
|
161
|
+
const validationErrors = validateRunOptions(command, options);
|
|
152
162
|
if (validationErrors.length > 0) {
|
|
153
163
|
output.error('Validation errors:');
|
|
154
|
-
|
|
164
|
+
for (let error of validationErrors) {
|
|
165
|
+
output.printErr(` - ${error}`);
|
|
166
|
+
}
|
|
155
167
|
process.exit(1);
|
|
156
168
|
}
|
|
157
169
|
try {
|
|
158
|
-
|
|
170
|
+
const result = await runCommand(command, options, globalOptions);
|
|
159
171
|
if (result && !result.success && result.exitCode > 0) {
|
|
160
172
|
process.exit(result.exitCode);
|
|
161
173
|
}
|
|
@@ -165,99 +177,113 @@ program.command('run').description('Run tests with Vizzly integration').argument
|
|
|
165
177
|
}
|
|
166
178
|
});
|
|
167
179
|
program.command('status').description('Check the status of a build').argument('<build-id>', 'Build ID to check status for').action(async (buildId, options) => {
|
|
168
|
-
|
|
180
|
+
const globalOptions = program.opts();
|
|
169
181
|
|
|
170
182
|
// Validate options
|
|
171
|
-
|
|
183
|
+
const validationErrors = validateStatusOptions(buildId, options);
|
|
172
184
|
if (validationErrors.length > 0) {
|
|
173
185
|
output.error('Validation errors:');
|
|
174
|
-
|
|
186
|
+
for (let error of validationErrors) {
|
|
187
|
+
output.printErr(` - ${error}`);
|
|
188
|
+
}
|
|
175
189
|
process.exit(1);
|
|
176
190
|
}
|
|
177
191
|
await statusCommand(buildId, options, globalOptions);
|
|
178
192
|
});
|
|
179
193
|
program.command('finalize').description('Finalize a parallel build after all shards complete').argument('<parallel-id>', 'Parallel ID to finalize').action(async (parallelId, options) => {
|
|
180
|
-
|
|
194
|
+
const globalOptions = program.opts();
|
|
181
195
|
|
|
182
196
|
// Validate options
|
|
183
|
-
|
|
197
|
+
const validationErrors = validateFinalizeOptions(parallelId, options);
|
|
184
198
|
if (validationErrors.length > 0) {
|
|
185
199
|
output.error('Validation errors:');
|
|
186
|
-
|
|
200
|
+
for (let error of validationErrors) {
|
|
201
|
+
output.printErr(` - ${error}`);
|
|
202
|
+
}
|
|
187
203
|
process.exit(1);
|
|
188
204
|
}
|
|
189
205
|
await finalizeCommand(parallelId, options, globalOptions);
|
|
190
206
|
});
|
|
191
207
|
program.command('doctor').description('Run diagnostics to check your environment and configuration').option('--api', 'Include API connectivity checks').action(async options => {
|
|
192
|
-
|
|
208
|
+
const globalOptions = program.opts();
|
|
193
209
|
|
|
194
210
|
// Validate options
|
|
195
|
-
|
|
211
|
+
const validationErrors = validateDoctorOptions(options);
|
|
196
212
|
if (validationErrors.length > 0) {
|
|
197
213
|
output.error('Validation errors:');
|
|
198
|
-
|
|
214
|
+
for (let error of validationErrors) {
|
|
215
|
+
output.printErr(` - ${error}`);
|
|
216
|
+
}
|
|
199
217
|
process.exit(1);
|
|
200
218
|
}
|
|
201
219
|
await doctorCommand(options, globalOptions);
|
|
202
220
|
});
|
|
203
221
|
program.command('login').description('Authenticate with your Vizzly account').option('--api-url <url>', 'API URL override').action(async options => {
|
|
204
|
-
|
|
222
|
+
const globalOptions = program.opts();
|
|
205
223
|
|
|
206
224
|
// Validate options
|
|
207
|
-
|
|
225
|
+
const validationErrors = validateLoginOptions(options);
|
|
208
226
|
if (validationErrors.length > 0) {
|
|
209
227
|
output.error('Validation errors:');
|
|
210
|
-
|
|
228
|
+
for (let error of validationErrors) {
|
|
229
|
+
output.printErr(` - ${error}`);
|
|
230
|
+
}
|
|
211
231
|
process.exit(1);
|
|
212
232
|
}
|
|
213
233
|
await loginCommand(options, globalOptions);
|
|
214
234
|
});
|
|
215
235
|
program.command('logout').description('Clear stored authentication tokens').option('--api-url <url>', 'API URL override').action(async options => {
|
|
216
|
-
|
|
236
|
+
const globalOptions = program.opts();
|
|
217
237
|
|
|
218
238
|
// Validate options
|
|
219
|
-
|
|
239
|
+
const validationErrors = validateLogoutOptions(options);
|
|
220
240
|
if (validationErrors.length > 0) {
|
|
221
241
|
output.error('Validation errors:');
|
|
222
|
-
|
|
242
|
+
for (let error of validationErrors) {
|
|
243
|
+
output.printErr(` - ${error}`);
|
|
244
|
+
}
|
|
223
245
|
process.exit(1);
|
|
224
246
|
}
|
|
225
247
|
await logoutCommand(options, globalOptions);
|
|
226
248
|
});
|
|
227
249
|
program.command('whoami').description('Show current authentication status and user information').option('--api-url <url>', 'API URL override').action(async options => {
|
|
228
|
-
|
|
250
|
+
const globalOptions = program.opts();
|
|
229
251
|
|
|
230
252
|
// Validate options
|
|
231
|
-
|
|
253
|
+
const validationErrors = validateWhoamiOptions(options);
|
|
232
254
|
if (validationErrors.length > 0) {
|
|
233
255
|
output.error('Validation errors:');
|
|
234
|
-
|
|
256
|
+
for (let error of validationErrors) {
|
|
257
|
+
output.printErr(` - ${error}`);
|
|
258
|
+
}
|
|
235
259
|
process.exit(1);
|
|
236
260
|
}
|
|
237
261
|
await whoamiCommand(options, globalOptions);
|
|
238
262
|
});
|
|
239
263
|
program.command('project:select').description('Configure project for current directory').option('--api-url <url>', 'API URL override').action(async options => {
|
|
240
|
-
|
|
264
|
+
const globalOptions = program.opts();
|
|
241
265
|
|
|
242
266
|
// Validate options
|
|
243
|
-
|
|
267
|
+
const validationErrors = validateProjectOptions(options);
|
|
244
268
|
if (validationErrors.length > 0) {
|
|
245
269
|
output.error('Validation errors:');
|
|
246
|
-
|
|
270
|
+
for (let error of validationErrors) {
|
|
271
|
+
output.printErr(` - ${error}`);
|
|
272
|
+
}
|
|
247
273
|
process.exit(1);
|
|
248
274
|
}
|
|
249
275
|
await projectSelectCommand(options, globalOptions);
|
|
250
276
|
});
|
|
251
277
|
program.command('project:list').description('Show all configured projects').action(async options => {
|
|
252
|
-
|
|
278
|
+
const globalOptions = program.opts();
|
|
253
279
|
await projectListCommand(options, globalOptions);
|
|
254
280
|
});
|
|
255
281
|
program.command('project:token').description('Show project token for current directory').action(async options => {
|
|
256
|
-
|
|
282
|
+
const globalOptions = program.opts();
|
|
257
283
|
await projectTokenCommand(options, globalOptions);
|
|
258
284
|
});
|
|
259
285
|
program.command('project:remove').description('Remove project configuration for current directory').action(async options => {
|
|
260
|
-
|
|
286
|
+
const globalOptions = program.opts();
|
|
261
287
|
await projectRemoveCommand(options, globalOptions);
|
|
262
288
|
});
|
|
263
289
|
program.parse();
|
package/dist/client/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* @description Thin client for test runners - minimal API for taking screenshots
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join, parse } from 'node:path';
|
|
8
|
+
import { getBuildId, getServerUrl, isTddMode, setVizzlyEnabled } from '../utils/environment-config.js';
|
|
9
9
|
|
|
10
10
|
// Internal client state
|
|
11
11
|
let currentClient = null;
|
|
@@ -104,12 +104,12 @@ function getClient() {
|
|
|
104
104
|
function createSimpleClient(serverUrl) {
|
|
105
105
|
return {
|
|
106
106
|
async screenshot(name, imageBuffer, options = {}) {
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const timeoutId = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
|
|
109
109
|
try {
|
|
110
110
|
// If it's a string, assume it's a file path and send directly
|
|
111
111
|
// Otherwise it's a Buffer, so convert to base64
|
|
112
|
-
|
|
112
|
+
const image = typeof imageBuffer === 'string' ? imageBuffer : imageBuffer.toString('base64');
|
|
113
113
|
const response = await fetch(`${serverUrl}/screenshot`, {
|
|
114
114
|
method: 'POST',
|
|
115
115
|
headers: {
|
package/dist/commands/doctor.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { URL } from 'url';
|
|
2
|
-
import { loadConfig } from '../utils/config-loader.js';
|
|
3
|
-
import * as output from '../utils/output.js';
|
|
4
|
-
import { ApiService } from '../services/api-service.js';
|
|
1
|
+
import { URL } from 'node:url';
|
|
5
2
|
import { ConfigError } from '../errors/vizzly-error.js';
|
|
3
|
+
import { ApiService } from '../services/api-service.js';
|
|
4
|
+
import { loadConfig } from '../utils/config-loader.js';
|
|
6
5
|
import { getApiToken } from '../utils/environment-config.js';
|
|
6
|
+
import * as output from '../utils/output.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Doctor command implementation - Run diagnostics to check environment
|
|
@@ -16,7 +16,7 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
|
|
|
16
16
|
verbose: globalOptions.verbose,
|
|
17
17
|
color: !globalOptions.noColor
|
|
18
18
|
});
|
|
19
|
-
|
|
19
|
+
const diagnostics = {
|
|
20
20
|
environment: {
|
|
21
21
|
nodeVersion: null,
|
|
22
22
|
nodeVersionValid: null
|
|
@@ -37,14 +37,14 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
|
|
|
37
37
|
let hasErrors = false;
|
|
38
38
|
try {
|
|
39
39
|
// Determine if we'll attempt remote checks (API connectivity)
|
|
40
|
-
|
|
40
|
+
const willCheckConnectivity = Boolean(options.api || getApiToken());
|
|
41
41
|
|
|
42
42
|
// Announce preflight, indicating local-only when no token/connectivity is planned
|
|
43
43
|
output.info(`Running Vizzly preflight${willCheckConnectivity ? '' : ' (local checks only)'}...`);
|
|
44
44
|
|
|
45
45
|
// Node.js version check (require >= 20)
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
const nodeVersion = process.version;
|
|
47
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
48
48
|
diagnostics.environment.nodeVersion = nodeVersion;
|
|
49
49
|
diagnostics.environment.nodeVersionValid = nodeMajor >= 20;
|
|
50
50
|
if (nodeMajor >= 20) {
|
|
@@ -55,12 +55,12 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Load configuration (apply global CLI overrides like --config only)
|
|
58
|
-
|
|
58
|
+
const config = await loadConfig(globalOptions.config);
|
|
59
59
|
|
|
60
60
|
// Validate apiUrl
|
|
61
61
|
diagnostics.configuration.apiUrl = config.apiUrl;
|
|
62
62
|
try {
|
|
63
|
-
|
|
63
|
+
const url = new URL(config.apiUrl);
|
|
64
64
|
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
65
65
|
throw new ConfigError('URL must use http or https');
|
|
66
66
|
}
|
|
@@ -73,24 +73,25 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
|
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
// Validate threshold (0..1 inclusive)
|
|
76
|
-
|
|
76
|
+
const threshold = Number(config?.comparison?.threshold);
|
|
77
77
|
diagnostics.configuration.threshold = threshold;
|
|
78
|
-
|
|
78
|
+
// CIEDE2000 threshold: 0 = exact, 1 = JND, 2 = recommended, 3+ = permissive
|
|
79
|
+
const thresholdValid = Number.isFinite(threshold) && threshold >= 0;
|
|
79
80
|
diagnostics.configuration.thresholdValid = thresholdValid;
|
|
80
81
|
if (thresholdValid) {
|
|
81
|
-
output.success(`Threshold: ${threshold}`);
|
|
82
|
+
output.success(`Threshold: ${threshold} (CIEDE2000 Delta E)`);
|
|
82
83
|
} else {
|
|
83
84
|
hasErrors = true;
|
|
84
|
-
output.error('Invalid threshold (expected number
|
|
85
|
+
output.error('Invalid threshold (expected non-negative number)');
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
// Report effective port without binding
|
|
88
|
-
|
|
89
|
+
const port = config?.server?.port ?? 47392;
|
|
89
90
|
diagnostics.configuration.port = port;
|
|
90
91
|
output.info(`Effective port: ${port}`);
|
|
91
92
|
|
|
92
93
|
// Optional: API connectivity check when --api is provided or VIZZLY_TOKEN is present
|
|
93
|
-
|
|
94
|
+
const autoApi = Boolean(getApiToken());
|
|
94
95
|
if (options.api || autoApi) {
|
|
95
96
|
diagnostics.connectivity.checked = true;
|
|
96
97
|
if (!config.apiKey) {
|
|
@@ -101,7 +102,7 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
|
|
|
101
102
|
} else {
|
|
102
103
|
output.progress('Checking API connectivity...');
|
|
103
104
|
try {
|
|
104
|
-
|
|
105
|
+
const api = new ApiService({
|
|
105
106
|
baseUrl: config.apiUrl,
|
|
106
107
|
token: config.apiKey,
|
|
107
108
|
command: 'doctor'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { createServices } from '../services/index.js';
|
|
1
2
|
import { loadConfig } from '../utils/config-loader.js';
|
|
2
3
|
import * as output from '../utils/output.js';
|
|
3
|
-
import { createServices } from '../services/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Finalize command implementation
|
|
@@ -16,11 +16,11 @@ export async function finalizeCommand(parallelId, options = {}, globalOptions =
|
|
|
16
16
|
});
|
|
17
17
|
try {
|
|
18
18
|
// Load configuration with CLI overrides
|
|
19
|
-
|
|
19
|
+
const allOptions = {
|
|
20
20
|
...globalOptions,
|
|
21
21
|
...options
|
|
22
22
|
};
|
|
23
|
-
|
|
23
|
+
const config = await loadConfig(globalOptions.config, allOptions);
|
|
24
24
|
|
|
25
25
|
// Validate API token
|
|
26
26
|
if (!config.apiKey) {
|
|
@@ -37,12 +37,12 @@ export async function finalizeCommand(parallelId, options = {}, globalOptions =
|
|
|
37
37
|
|
|
38
38
|
// Create services and get API service
|
|
39
39
|
output.startSpinner('Finalizing parallel build...');
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const services = createServices(config, 'finalize');
|
|
41
|
+
const apiService = services.apiService;
|
|
42
42
|
output.stopSpinner();
|
|
43
43
|
|
|
44
44
|
// Call finalize endpoint
|
|
45
|
-
|
|
45
|
+
const result = await apiService.finalizeParallelBuild(parallelId);
|
|
46
46
|
if (globalOptions.json) {
|
|
47
47
|
output.data(result);
|
|
48
48
|
} else {
|
|
@@ -65,7 +65,7 @@ export async function finalizeCommand(parallelId, options = {}, globalOptions =
|
|
|
65
65
|
* @param {Object} options - Command options
|
|
66
66
|
*/
|
|
67
67
|
export function validateFinalizeOptions(parallelId, _options) {
|
|
68
|
-
|
|
68
|
+
const errors = [];
|
|
69
69
|
if (!parallelId || parallelId.trim() === '') {
|
|
70
70
|
errors.push('Parallel ID is required');
|
|
71
71
|
}
|