@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.
Files changed (140) hide show
  1. package/dist/cli.js +70 -68
  2. package/dist/commands/doctor.js +30 -34
  3. package/dist/commands/finalize.js +24 -23
  4. package/dist/commands/init.js +30 -28
  5. package/dist/commands/login.js +49 -55
  6. package/dist/commands/logout.js +14 -19
  7. package/dist/commands/project.js +83 -103
  8. package/dist/commands/run.js +77 -89
  9. package/dist/commands/status.js +48 -49
  10. package/dist/commands/tdd-daemon.js +90 -86
  11. package/dist/commands/tdd.js +59 -88
  12. package/dist/commands/upload.js +57 -57
  13. package/dist/commands/whoami.js +40 -45
  14. package/dist/index.js +2 -5
  15. package/dist/plugin-loader.js +15 -17
  16. package/dist/reporter/reporter-bundle.css +1 -1
  17. package/dist/reporter/reporter-bundle.iife.js +74 -41
  18. package/dist/sdk/index.js +36 -45
  19. package/dist/server/handlers/api-handler.js +14 -15
  20. package/dist/server/handlers/tdd-handler.js +34 -37
  21. package/dist/server/http-server.js +75 -869
  22. package/dist/server/middleware/cors.js +22 -0
  23. package/dist/server/middleware/json-parser.js +35 -0
  24. package/dist/server/middleware/response.js +79 -0
  25. package/dist/server/routers/assets.js +91 -0
  26. package/dist/server/routers/auth.js +144 -0
  27. package/dist/server/routers/baseline.js +163 -0
  28. package/dist/server/routers/cloud-proxy.js +146 -0
  29. package/dist/server/routers/config.js +126 -0
  30. package/dist/server/routers/dashboard.js +130 -0
  31. package/dist/server/routers/health.js +61 -0
  32. package/dist/server/routers/projects.js +168 -0
  33. package/dist/server/routers/screenshot.js +86 -0
  34. package/dist/services/auth-service.js +1 -1
  35. package/dist/services/build-manager.js +13 -40
  36. package/dist/services/config-service.js +2 -4
  37. package/dist/services/html-report-generator.js +6 -5
  38. package/dist/services/index.js +64 -0
  39. package/dist/services/project-service.js +121 -40
  40. package/dist/services/screenshot-server.js +9 -9
  41. package/dist/services/server-manager.js +11 -18
  42. package/dist/services/static-report-generator.js +3 -4
  43. package/dist/services/tdd-service.js +246 -103
  44. package/dist/services/test-runner.js +24 -25
  45. package/dist/services/uploader.js +5 -4
  46. package/dist/types/commands/init.d.ts +1 -2
  47. package/dist/types/index.d.ts +2 -3
  48. package/dist/types/plugin-loader.d.ts +1 -2
  49. package/dist/types/reporter/src/api/client.d.ts +178 -0
  50. package/dist/types/reporter/src/components/app-router.d.ts +1 -3
  51. package/dist/types/reporter/src/components/code-block.d.ts +4 -0
  52. package/dist/types/reporter/src/components/comparison/comparison-modes/onion-skin-mode.d.ts +10 -0
  53. package/dist/types/reporter/src/components/comparison/comparison-modes/overlay-mode.d.ts +11 -0
  54. package/dist/types/reporter/src/components/comparison/comparison-modes/shared/base-comparison-mode.d.ts +14 -0
  55. package/dist/types/reporter/src/components/comparison/comparison-modes/shared/image-renderer.d.ts +30 -0
  56. package/dist/types/reporter/src/components/comparison/comparison-modes/toggle-view.d.ts +8 -0
  57. package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
  58. package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +16 -0
  59. package/dist/types/reporter/src/components/design-system/alert.d.ts +9 -0
  60. package/dist/types/reporter/src/components/design-system/badge.d.ts +17 -0
  61. package/dist/types/reporter/src/components/design-system/button.d.ts +19 -0
  62. package/dist/types/reporter/src/components/design-system/card.d.ts +31 -0
  63. package/dist/types/reporter/src/components/design-system/empty-state.d.ts +13 -0
  64. package/dist/types/reporter/src/components/design-system/form-controls.d.ts +44 -0
  65. package/dist/types/reporter/src/components/design-system/health-ring.d.ts +7 -0
  66. package/dist/types/reporter/src/components/design-system/index.d.ts +11 -0
  67. package/dist/types/reporter/src/components/design-system/modal.d.ts +10 -0
  68. package/dist/types/reporter/src/components/design-system/skeleton.d.ts +19 -0
  69. package/dist/types/reporter/src/components/design-system/spinner.d.ts +10 -0
  70. package/dist/types/reporter/src/components/design-system/tabs.d.ts +13 -0
  71. package/dist/types/reporter/src/components/layout/header.d.ts +5 -0
  72. package/dist/types/reporter/src/components/layout/index.d.ts +2 -0
  73. package/dist/types/reporter/src/components/layout/layout.d.ts +6 -0
  74. package/dist/types/reporter/src/components/views/builds-view.d.ts +1 -0
  75. package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +1 -4
  76. package/dist/types/reporter/src/components/views/comparisons-view.d.ts +1 -6
  77. package/dist/types/reporter/src/components/views/stats-view.d.ts +1 -6
  78. package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +1 -0
  79. package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +15 -0
  80. package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +6 -0
  81. package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +6 -0
  82. package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +9 -0
  83. package/dist/types/reporter/src/lib/query-client.d.ts +2 -0
  84. package/dist/types/reporter/src/lib/query-keys.d.ts +13 -0
  85. package/dist/types/sdk/index.d.ts +2 -4
  86. package/dist/types/server/handlers/tdd-handler.d.ts +2 -0
  87. package/dist/types/server/http-server.d.ts +1 -1
  88. package/dist/types/server/middleware/cors.d.ts +11 -0
  89. package/dist/types/server/middleware/json-parser.d.ts +10 -0
  90. package/dist/types/server/middleware/response.d.ts +50 -0
  91. package/dist/types/server/routers/assets.d.ts +6 -0
  92. package/dist/types/server/routers/auth.d.ts +9 -0
  93. package/dist/types/server/routers/baseline.d.ts +13 -0
  94. package/dist/types/server/routers/cloud-proxy.d.ts +11 -0
  95. package/dist/types/server/routers/config.d.ts +9 -0
  96. package/dist/types/server/routers/dashboard.d.ts +6 -0
  97. package/dist/types/server/routers/health.d.ts +11 -0
  98. package/dist/types/server/routers/projects.d.ts +9 -0
  99. package/dist/types/server/routers/screenshot.d.ts +11 -0
  100. package/dist/types/services/build-manager.d.ts +4 -3
  101. package/dist/types/services/config-service.d.ts +2 -3
  102. package/dist/types/services/index.d.ts +7 -0
  103. package/dist/types/services/project-service.d.ts +6 -4
  104. package/dist/types/services/screenshot-server.d.ts +5 -5
  105. package/dist/types/services/server-manager.d.ts +5 -3
  106. package/dist/types/services/tdd-service.d.ts +12 -1
  107. package/dist/types/services/test-runner.d.ts +3 -3
  108. package/dist/types/utils/output.d.ts +84 -0
  109. package/dist/utils/config-loader.js +24 -48
  110. package/dist/utils/global-config.js +2 -17
  111. package/dist/utils/output.js +445 -0
  112. package/dist/utils/security.js +3 -4
  113. package/docs/api-reference.md +0 -1
  114. package/docs/plugins.md +33 -34
  115. package/package.json +3 -2
  116. package/dist/container/index.js +0 -215
  117. package/dist/services/base-service.js +0 -154
  118. package/dist/types/container/index.d.ts +0 -59
  119. package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +0 -3
  120. package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +0 -3
  121. package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +0 -3
  122. package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +0 -3
  123. package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +0 -5
  124. package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +0 -4
  125. package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +0 -8
  126. package/dist/types/reporter/src/components/ui/form-field.d.ts +0 -16
  127. package/dist/types/reporter/src/components/ui/status-badge.d.ts +0 -5
  128. package/dist/types/reporter/src/hooks/use-auth.d.ts +0 -10
  129. package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +0 -5
  130. package/dist/types/reporter/src/hooks/use-config.d.ts +0 -9
  131. package/dist/types/reporter/src/hooks/use-projects.d.ts +0 -10
  132. package/dist/types/reporter/src/hooks/use-report-data.d.ts +0 -7
  133. package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +0 -9
  134. package/dist/types/services/base-service.d.ts +0 -71
  135. package/dist/types/utils/console-ui.d.ts +0 -61
  136. package/dist/types/utils/logger-factory.d.ts +0 -26
  137. package/dist/types/utils/logger.d.ts +0 -79
  138. package/dist/utils/console-ui.js +0 -241
  139. package/dist/utils/logger-factory.js +0 -76
  140. 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
+ }
@@ -4,8 +4,7 @@
4
4
  */
5
5
 
6
6
  import { resolve, normalize, isAbsolute, join } from 'path';
7
- import { createServiceLogger } from './logger-factory.js';
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
- logger.warn(`Path traversal attempt blocked: ${targetPath} (resolved: ${resolvedTargetPath}) is outside working directory: ${resolvedWorkingDir}`);
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
- logger.warn(`Invalid browser name '${properties.browser}': ${error.message}`);
130
+ output.warn(`Invalid browser name '${properties.browser}': ${error.message}`);
132
131
  }
133
132
  }
134
133
  if (properties.viewport && typeof properties.viewport === 'object') {
@@ -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