@vizzly-testing/cli 0.14.0 → 0.15.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.
- package/dist/cli.js +70 -68
- package/dist/commands/doctor.js +30 -34
- package/dist/commands/finalize.js +24 -23
- package/dist/commands/init.js +30 -28
- package/dist/commands/login.js +49 -55
- package/dist/commands/logout.js +14 -19
- package/dist/commands/project.js +83 -103
- package/dist/commands/run.js +77 -89
- package/dist/commands/status.js +48 -49
- package/dist/commands/tdd-daemon.js +90 -86
- package/dist/commands/tdd.js +59 -88
- package/dist/commands/upload.js +57 -57
- package/dist/commands/whoami.js +40 -45
- package/dist/index.js +2 -5
- package/dist/plugin-loader.js +15 -17
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +74 -41
- package/dist/sdk/index.js +36 -45
- package/dist/server/handlers/api-handler.js +14 -15
- package/dist/server/handlers/tdd-handler.js +34 -37
- package/dist/server/http-server.js +75 -869
- package/dist/server/middleware/cors.js +22 -0
- package/dist/server/middleware/json-parser.js +35 -0
- package/dist/server/middleware/response.js +79 -0
- package/dist/server/routers/assets.js +91 -0
- package/dist/server/routers/auth.js +144 -0
- package/dist/server/routers/baseline.js +163 -0
- package/dist/server/routers/cloud-proxy.js +146 -0
- package/dist/server/routers/config.js +126 -0
- package/dist/server/routers/dashboard.js +130 -0
- package/dist/server/routers/health.js +61 -0
- package/dist/server/routers/projects.js +168 -0
- package/dist/server/routers/screenshot.js +86 -0
- package/dist/services/auth-service.js +1 -1
- package/dist/services/build-manager.js +13 -40
- package/dist/services/config-service.js +2 -4
- package/dist/services/html-report-generator.js +6 -5
- package/dist/services/index.js +64 -0
- package/dist/services/project-service.js +121 -40
- package/dist/services/screenshot-server.js +9 -9
- package/dist/services/server-manager.js +11 -18
- package/dist/services/static-report-generator.js +3 -4
- package/dist/services/tdd-service.js +246 -103
- package/dist/services/test-runner.js +24 -25
- package/dist/services/uploader.js +5 -4
- package/dist/types/commands/init.d.ts +1 -2
- package/dist/types/index.d.ts +2 -3
- package/dist/types/plugin-loader.d.ts +1 -2
- package/dist/types/reporter/src/api/client.d.ts +178 -0
- package/dist/types/reporter/src/components/app-router.d.ts +1 -3
- package/dist/types/reporter/src/components/code-block.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/onion-skin-mode.d.ts +10 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/overlay-mode.d.ts +11 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/base-comparison-mode.d.ts +14 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/image-renderer.d.ts +30 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/toggle-view.d.ts +8 -0
- package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +16 -0
- package/dist/types/reporter/src/components/design-system/alert.d.ts +9 -0
- package/dist/types/reporter/src/components/design-system/badge.d.ts +17 -0
- package/dist/types/reporter/src/components/design-system/button.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/card.d.ts +31 -0
- package/dist/types/reporter/src/components/design-system/empty-state.d.ts +13 -0
- package/dist/types/reporter/src/components/design-system/form-controls.d.ts +44 -0
- package/dist/types/reporter/src/components/design-system/health-ring.d.ts +7 -0
- package/dist/types/reporter/src/components/design-system/index.d.ts +11 -0
- package/dist/types/reporter/src/components/design-system/modal.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/skeleton.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/spinner.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/tabs.d.ts +13 -0
- package/dist/types/reporter/src/components/layout/header.d.ts +5 -0
- package/dist/types/reporter/src/components/layout/index.d.ts +2 -0
- package/dist/types/reporter/src/components/layout/layout.d.ts +6 -0
- package/dist/types/reporter/src/components/views/builds-view.d.ts +1 -0
- package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +1 -4
- package/dist/types/reporter/src/components/views/comparisons-view.d.ts +1 -6
- package/dist/types/reporter/src/components/views/stats-view.d.ts +1 -6
- package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +1 -0
- package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +15 -0
- package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +9 -0
- package/dist/types/reporter/src/lib/query-client.d.ts +2 -0
- package/dist/types/reporter/src/lib/query-keys.d.ts +13 -0
- package/dist/types/sdk/index.d.ts +2 -4
- package/dist/types/server/handlers/tdd-handler.d.ts +2 -0
- package/dist/types/server/http-server.d.ts +1 -1
- package/dist/types/server/middleware/cors.d.ts +11 -0
- package/dist/types/server/middleware/json-parser.d.ts +10 -0
- package/dist/types/server/middleware/response.d.ts +50 -0
- package/dist/types/server/routers/assets.d.ts +6 -0
- package/dist/types/server/routers/auth.d.ts +9 -0
- package/dist/types/server/routers/baseline.d.ts +13 -0
- package/dist/types/server/routers/cloud-proxy.d.ts +11 -0
- package/dist/types/server/routers/config.d.ts +9 -0
- package/dist/types/server/routers/dashboard.d.ts +6 -0
- package/dist/types/server/routers/health.d.ts +11 -0
- package/dist/types/server/routers/projects.d.ts +9 -0
- package/dist/types/server/routers/screenshot.d.ts +11 -0
- package/dist/types/services/build-manager.d.ts +4 -3
- package/dist/types/services/config-service.d.ts +2 -3
- package/dist/types/services/index.d.ts +7 -0
- package/dist/types/services/project-service.d.ts +6 -4
- package/dist/types/services/screenshot-server.d.ts +5 -5
- package/dist/types/services/server-manager.d.ts +5 -3
- package/dist/types/services/tdd-service.d.ts +12 -1
- package/dist/types/services/test-runner.d.ts +3 -3
- package/dist/types/utils/output.d.ts +84 -0
- package/dist/utils/config-loader.js +24 -48
- package/dist/utils/global-config.js +2 -17
- package/dist/utils/output.js +445 -0
- package/dist/utils/security.js +3 -4
- package/docs/api-reference.md +0 -1
- package/docs/plugins.md +33 -34
- package/package.json +3 -2
- package/dist/container/index.js +0 -215
- package/dist/services/base-service.js +0 -154
- package/dist/types/container/index.d.ts +0 -59
- package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +0 -5
- package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +0 -4
- package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +0 -8
- package/dist/types/reporter/src/components/ui/form-field.d.ts +0 -16
- package/dist/types/reporter/src/components/ui/status-badge.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-auth.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-config.d.ts +0 -9
- package/dist/types/reporter/src/hooks/use-projects.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-report-data.d.ts +0 -7
- package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +0 -9
- package/dist/types/services/base-service.d.ts +0 -71
- package/dist/types/utils/console-ui.d.ts +0 -61
- package/dist/types/utils/logger-factory.d.ts +0 -26
- package/dist/types/utils/logger.d.ts +0 -79
- package/dist/utils/console-ui.js +0 -241
- package/dist/utils/logger-factory.js +0 -76
- package/dist/utils/logger.js +0 -231
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified CLI output module
|
|
3
|
+
*
|
|
4
|
+
* Handles all console output with proper stream separation:
|
|
5
|
+
* - stdout: program output only (things you can pipe)
|
|
6
|
+
* - stderr: everything else (spinners, progress, errors, debug)
|
|
7
|
+
*
|
|
8
|
+
* Replaces both ConsoleUI and Logger with a single, simple API.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createColors } from './colors.js';
|
|
12
|
+
import { writeFileSync, appendFileSync, mkdirSync } from 'fs';
|
|
13
|
+
import { dirname } from 'path';
|
|
14
|
+
|
|
15
|
+
// Module state
|
|
16
|
+
let config = {
|
|
17
|
+
json: false,
|
|
18
|
+
verbose: false,
|
|
19
|
+
color: true,
|
|
20
|
+
silent: false,
|
|
21
|
+
logFile: null
|
|
22
|
+
};
|
|
23
|
+
let colors = createColors({
|
|
24
|
+
useColor: config.color
|
|
25
|
+
});
|
|
26
|
+
let spinnerInterval = null;
|
|
27
|
+
let spinnerMessage = '';
|
|
28
|
+
let lastSpinnerLine = '';
|
|
29
|
+
let startTime = Date.now();
|
|
30
|
+
|
|
31
|
+
// Track if we've shown the header
|
|
32
|
+
let headerShown = false;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configure output settings
|
|
36
|
+
* Call this once at CLI startup with global options
|
|
37
|
+
*/
|
|
38
|
+
export function configure(options = {}) {
|
|
39
|
+
if (options.json !== undefined) config.json = options.json;
|
|
40
|
+
if (options.verbose !== undefined) config.verbose = options.verbose;
|
|
41
|
+
if (options.color !== undefined) config.color = options.color;
|
|
42
|
+
if (options.silent !== undefined) config.silent = options.silent;
|
|
43
|
+
if (options.logFile !== undefined) config.logFile = options.logFile;
|
|
44
|
+
colors = createColors({
|
|
45
|
+
useColor: config.color
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Reset state
|
|
49
|
+
startTime = Date.now();
|
|
50
|
+
headerShown = false;
|
|
51
|
+
|
|
52
|
+
// Initialize log file if specified
|
|
53
|
+
if (config.logFile) {
|
|
54
|
+
initLogFile();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Show command header (e.g., "vizzly · tdd · local")
|
|
60
|
+
* Only shows once per command execution
|
|
61
|
+
*/
|
|
62
|
+
export function header(command, mode = null) {
|
|
63
|
+
if (config.json || config.silent || headerShown) return;
|
|
64
|
+
headerShown = true;
|
|
65
|
+
let parts = ['vizzly', command];
|
|
66
|
+
if (mode) parts.push(mode);
|
|
67
|
+
console.error('');
|
|
68
|
+
console.error(colors.dim(parts.join(' · ')));
|
|
69
|
+
console.error('');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get current colors instance (for custom formatting)
|
|
74
|
+
*/
|
|
75
|
+
export function getColors() {
|
|
76
|
+
return colors;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// User-facing output (what the user asked for)
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Show a success message
|
|
85
|
+
*/
|
|
86
|
+
export function success(message, data = {}) {
|
|
87
|
+
stopSpinner();
|
|
88
|
+
if (config.silent) return;
|
|
89
|
+
if (config.json) {
|
|
90
|
+
console.log(JSON.stringify({
|
|
91
|
+
status: 'success',
|
|
92
|
+
message,
|
|
93
|
+
...data
|
|
94
|
+
}));
|
|
95
|
+
} else {
|
|
96
|
+
console.error('');
|
|
97
|
+
console.error(colors.green('✓'), message);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Show final result summary (e.g., "✓ 5 screenshots · 234ms")
|
|
103
|
+
*/
|
|
104
|
+
export function result(message) {
|
|
105
|
+
stopSpinner();
|
|
106
|
+
if (config.silent) return;
|
|
107
|
+
let elapsed = getElapsedTime();
|
|
108
|
+
if (config.json) {
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
status: 'complete',
|
|
111
|
+
message,
|
|
112
|
+
elapsed
|
|
113
|
+
}));
|
|
114
|
+
} else {
|
|
115
|
+
console.error('');
|
|
116
|
+
console.error(colors.green('✓'), `${message} ${colors.dim(`· ${elapsed}`)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Show an info message
|
|
122
|
+
*/
|
|
123
|
+
export function info(message, data = {}) {
|
|
124
|
+
if (config.silent) return;
|
|
125
|
+
if (config.json) {
|
|
126
|
+
console.log(JSON.stringify({
|
|
127
|
+
status: 'info',
|
|
128
|
+
message,
|
|
129
|
+
...data
|
|
130
|
+
}));
|
|
131
|
+
} else {
|
|
132
|
+
console.log(colors.cyan('ℹ'), message);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Show a warning message (goes to stderr)
|
|
138
|
+
*/
|
|
139
|
+
export function warn(message, data = {}) {
|
|
140
|
+
stopSpinner();
|
|
141
|
+
if (config.silent) return;
|
|
142
|
+
if (config.json) {
|
|
143
|
+
console.error(JSON.stringify({
|
|
144
|
+
status: 'warning',
|
|
145
|
+
message,
|
|
146
|
+
...data
|
|
147
|
+
}));
|
|
148
|
+
} else {
|
|
149
|
+
console.error(colors.yellow('⚠'), message);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Show an error message (goes to stderr)
|
|
155
|
+
* Does NOT exit - caller decides whether to exit
|
|
156
|
+
*/
|
|
157
|
+
export function error(message, err = null, data = {}) {
|
|
158
|
+
stopSpinner();
|
|
159
|
+
if (config.json) {
|
|
160
|
+
let errorData = {
|
|
161
|
+
status: 'error',
|
|
162
|
+
message,
|
|
163
|
+
...data
|
|
164
|
+
};
|
|
165
|
+
if (err instanceof Error) {
|
|
166
|
+
errorData.error = {
|
|
167
|
+
name: err.name,
|
|
168
|
+
message: err.getUserMessage ? err.getUserMessage() : err.message,
|
|
169
|
+
code: err.code
|
|
170
|
+
};
|
|
171
|
+
if (config.verbose) {
|
|
172
|
+
errorData.error.stack = err.stack;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
console.error(JSON.stringify(errorData));
|
|
176
|
+
} else {
|
|
177
|
+
console.error(colors.red('✖'), message);
|
|
178
|
+
|
|
179
|
+
// Show error details
|
|
180
|
+
if (err instanceof Error) {
|
|
181
|
+
let errMessage = err.getUserMessage ? err.getUserMessage() : err.message;
|
|
182
|
+
if (errMessage && errMessage !== message) {
|
|
183
|
+
console.error(colors.dim(errMessage));
|
|
184
|
+
}
|
|
185
|
+
if (config.verbose && err.stack) {
|
|
186
|
+
console.error(colors.dim(err.stack));
|
|
187
|
+
}
|
|
188
|
+
} else if (typeof err === 'string' && err) {
|
|
189
|
+
console.error(colors.dim(err));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Write to log file
|
|
194
|
+
writeLog('error', message, {
|
|
195
|
+
error: err?.message,
|
|
196
|
+
...data
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Print a blank line for spacing
|
|
202
|
+
*/
|
|
203
|
+
export function blank() {
|
|
204
|
+
if (!config.json && !config.silent) {
|
|
205
|
+
console.log('');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Print raw text without any formatting
|
|
211
|
+
*/
|
|
212
|
+
export function print(text) {
|
|
213
|
+
if (!config.silent) {
|
|
214
|
+
console.log(text);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Print raw text to stderr
|
|
220
|
+
*/
|
|
221
|
+
export function printErr(text) {
|
|
222
|
+
if (!config.silent) {
|
|
223
|
+
console.error(text);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Output structured data
|
|
229
|
+
*/
|
|
230
|
+
export function data(obj) {
|
|
231
|
+
if (config.json) {
|
|
232
|
+
console.log(JSON.stringify({
|
|
233
|
+
status: 'data',
|
|
234
|
+
data: obj
|
|
235
|
+
}));
|
|
236
|
+
} else {
|
|
237
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// Spinner / Progress (stderr so it doesn't pollute piped output)
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Start a spinner with message
|
|
247
|
+
*/
|
|
248
|
+
export function startSpinner(message) {
|
|
249
|
+
if (config.json || config.silent || !process.stderr.isTTY) return;
|
|
250
|
+
stopSpinner();
|
|
251
|
+
spinnerMessage = message;
|
|
252
|
+
let frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
253
|
+
let i = 0;
|
|
254
|
+
spinnerInterval = setInterval(() => {
|
|
255
|
+
let frame = frames[i++ % frames.length];
|
|
256
|
+
let line = `${colors.cyan(frame)} ${spinnerMessage}`;
|
|
257
|
+
|
|
258
|
+
// Clear previous line and write new one
|
|
259
|
+
process.stderr.write('\r' + ' '.repeat(lastSpinnerLine.length) + '\r');
|
|
260
|
+
process.stderr.write(line);
|
|
261
|
+
lastSpinnerLine = line;
|
|
262
|
+
}, 80);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Update spinner message
|
|
267
|
+
*/
|
|
268
|
+
export function updateSpinner(message, current = 0, total = 0) {
|
|
269
|
+
if (config.json || config.silent || !process.stderr.isTTY) return;
|
|
270
|
+
let progressText = total > 0 ? ` (${current}/${total})` : '';
|
|
271
|
+
spinnerMessage = `${message}${progressText}`;
|
|
272
|
+
if (!spinnerInterval) {
|
|
273
|
+
startSpinner(spinnerMessage);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Stop the spinner
|
|
279
|
+
*/
|
|
280
|
+
export function stopSpinner() {
|
|
281
|
+
if (spinnerInterval) {
|
|
282
|
+
clearInterval(spinnerInterval);
|
|
283
|
+
spinnerInterval = null;
|
|
284
|
+
|
|
285
|
+
// Clear the spinner line
|
|
286
|
+
if (process.stderr.isTTY) {
|
|
287
|
+
process.stderr.write('\r' + ' '.repeat(lastSpinnerLine.length) + '\r');
|
|
288
|
+
}
|
|
289
|
+
lastSpinnerLine = '';
|
|
290
|
+
spinnerMessage = '';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Show progress update
|
|
296
|
+
*/
|
|
297
|
+
export function progress(message, current = 0, total = 0) {
|
|
298
|
+
if (config.silent) return;
|
|
299
|
+
if (config.json) {
|
|
300
|
+
console.log(JSON.stringify({
|
|
301
|
+
status: 'progress',
|
|
302
|
+
message,
|
|
303
|
+
progress: {
|
|
304
|
+
current,
|
|
305
|
+
total
|
|
306
|
+
}
|
|
307
|
+
}));
|
|
308
|
+
} else {
|
|
309
|
+
updateSpinner(message, current, total);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// ============================================================================
|
|
314
|
+
// Debug logging (only when verbose, goes to stderr and/or file)
|
|
315
|
+
// ============================================================================
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Format elapsed time since CLI start
|
|
319
|
+
*/
|
|
320
|
+
function getElapsedTime() {
|
|
321
|
+
let elapsed = Date.now() - startTime;
|
|
322
|
+
if (elapsed < 1000) {
|
|
323
|
+
return `${elapsed}ms`;
|
|
324
|
+
}
|
|
325
|
+
return `${(elapsed / 1000).toFixed(1)}s`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Format a data object for human-readable output
|
|
330
|
+
* Only shows meaningful values, skips nulls/undefined/empty
|
|
331
|
+
*/
|
|
332
|
+
function formatData(data) {
|
|
333
|
+
if (!data || typeof data !== 'object') return '';
|
|
334
|
+
let entries = Object.entries(data).filter(([, v]) => {
|
|
335
|
+
if (v === null || v === undefined) return false;
|
|
336
|
+
if (typeof v === 'string' && v === '') return false;
|
|
337
|
+
if (Array.isArray(v) && v.length === 0) return false;
|
|
338
|
+
return true;
|
|
339
|
+
});
|
|
340
|
+
if (entries.length === 0) return '';
|
|
341
|
+
|
|
342
|
+
// For simple key-value pairs, show inline
|
|
343
|
+
if (entries.length <= 4 && entries.every(([, v]) => typeof v !== 'object')) {
|
|
344
|
+
return entries.map(([k, v]) => `${k}=${v}`).join(' ');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// For complex objects, show on multiple lines
|
|
348
|
+
return entries.map(([k, v]) => {
|
|
349
|
+
if (typeof v === 'object') {
|
|
350
|
+
return `${k}: ${JSON.stringify(v)}`;
|
|
351
|
+
}
|
|
352
|
+
return `${k}: ${v}`;
|
|
353
|
+
}).join('\n');
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Log debug message with component prefix (only shown in verbose mode)
|
|
358
|
+
*
|
|
359
|
+
* @param {string} component - Component name (e.g., 'server', 'config', 'build')
|
|
360
|
+
* @param {string} message - Debug message
|
|
361
|
+
* @param {Object} data - Optional data object to display inline
|
|
362
|
+
*/
|
|
363
|
+
export function debug(component, message, data = {}) {
|
|
364
|
+
if (!config.verbose) return;
|
|
365
|
+
|
|
366
|
+
// Handle legacy calls: debug('message') or debug('message', {data})
|
|
367
|
+
if (typeof message === 'object' || message === undefined) {
|
|
368
|
+
data = message || {};
|
|
369
|
+
message = component;
|
|
370
|
+
component = null;
|
|
371
|
+
}
|
|
372
|
+
let elapsed = getElapsedTime();
|
|
373
|
+
if (config.json) {
|
|
374
|
+
console.error(JSON.stringify({
|
|
375
|
+
status: 'debug',
|
|
376
|
+
time: elapsed,
|
|
377
|
+
component,
|
|
378
|
+
message,
|
|
379
|
+
...data
|
|
380
|
+
}));
|
|
381
|
+
} else {
|
|
382
|
+
let formattedData = formatData(data);
|
|
383
|
+
let dataStr = formattedData ? ` ${colors.dim(formattedData)}` : '';
|
|
384
|
+
if (component) {
|
|
385
|
+
// Component-based format: " server listening on :47392"
|
|
386
|
+
let paddedComponent = component.padEnd(8);
|
|
387
|
+
console.error(` ${colors.cyan(paddedComponent)} ${message}${dataStr}`);
|
|
388
|
+
} else {
|
|
389
|
+
// Simple format for legacy calls
|
|
390
|
+
console.error(` ${colors.dim('•')} ${colors.dim(message)}${dataStr}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
writeLog('debug', message, {
|
|
394
|
+
component,
|
|
395
|
+
...data
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Log file support
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
function initLogFile() {
|
|
404
|
+
if (!config.logFile) return;
|
|
405
|
+
try {
|
|
406
|
+
mkdirSync(dirname(config.logFile), {
|
|
407
|
+
recursive: true
|
|
408
|
+
});
|
|
409
|
+
let header = {
|
|
410
|
+
timestamp: new Date().toISOString(),
|
|
411
|
+
session_start: true,
|
|
412
|
+
pid: process.pid,
|
|
413
|
+
node_version: process.version,
|
|
414
|
+
platform: process.platform
|
|
415
|
+
};
|
|
416
|
+
writeFileSync(config.logFile, JSON.stringify(header) + '\n');
|
|
417
|
+
} catch {
|
|
418
|
+
// Silently fail - don't crash CLI for logging issues
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function writeLog(level, message, data = {}) {
|
|
422
|
+
if (!config.logFile) return;
|
|
423
|
+
try {
|
|
424
|
+
let entry = {
|
|
425
|
+
timestamp: new Date().toISOString(),
|
|
426
|
+
level,
|
|
427
|
+
message,
|
|
428
|
+
...data
|
|
429
|
+
};
|
|
430
|
+
appendFileSync(config.logFile, JSON.stringify(entry) + '\n');
|
|
431
|
+
} catch {
|
|
432
|
+
// Silently fail
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ============================================================================
|
|
437
|
+
// Cleanup
|
|
438
|
+
// ============================================================================
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Clean up (stop spinner, flush logs)
|
|
442
|
+
*/
|
|
443
|
+
export function cleanup() {
|
|
444
|
+
stopSpinner();
|
|
445
|
+
}
|
package/dist/utils/security.js
CHANGED
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { resolve, normalize, isAbsolute, join } from 'path';
|
|
7
|
-
import
|
|
8
|
-
const logger = createServiceLogger('SECURITY');
|
|
7
|
+
import * as output from './output.js';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Sanitizes a screenshot name to prevent path traversal and ensure safe file naming
|
|
@@ -75,7 +74,7 @@ export function validatePathSecurity(targetPath, workingDir) {
|
|
|
75
74
|
|
|
76
75
|
// Ensure the target path starts with the working directory
|
|
77
76
|
if (!resolvedTargetPath.startsWith(resolvedWorkingDir)) {
|
|
78
|
-
|
|
77
|
+
output.warn(`Path traversal attempt blocked: ${targetPath} (resolved: ${resolvedTargetPath}) is outside working directory: ${resolvedWorkingDir}`);
|
|
79
78
|
throw new Error('Path is outside the allowed working directory');
|
|
80
79
|
}
|
|
81
80
|
return resolvedTargetPath;
|
|
@@ -128,7 +127,7 @@ export function validateScreenshotProperties(properties = {}) {
|
|
|
128
127
|
validated.browser = sanitizeScreenshotName(browserName, 50);
|
|
129
128
|
} catch (error) {
|
|
130
129
|
// Skip invalid browser names, don't include them
|
|
131
|
-
|
|
130
|
+
output.warn(`Invalid browser name '${properties.browser}': ${error.message}`);
|
|
132
131
|
}
|
|
133
132
|
}
|
|
134
133
|
if (properties.viewport && typeof properties.viewport === 'object') {
|
package/docs/api-reference.md
CHANGED
|
@@ -676,7 +676,6 @@ Configuration loaded via cosmiconfig in this order:
|
|
|
676
676
|
|
|
677
677
|
**Core Configuration:**
|
|
678
678
|
- `VIZZLY_API_URL` - API base URL override (default: `https://app.vizzly.dev`)
|
|
679
|
-
- `VIZZLY_LOG_LEVEL` - Logger level (`debug`, `info`, `warn`, `error`)
|
|
680
679
|
|
|
681
680
|
**Parallel Builds:**
|
|
682
681
|
- `VIZZLY_PARALLEL_ID` - Unique identifier for parallel test execution
|