@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.
Files changed (68) hide show
  1. package/README.md +4 -4
  2. package/claude-plugin/skills/debug-visual-regression/SKILL.md +2 -2
  3. package/dist/cli.js +84 -58
  4. package/dist/client/index.js +6 -6
  5. package/dist/commands/doctor.js +18 -17
  6. package/dist/commands/finalize.js +7 -7
  7. package/dist/commands/init.js +30 -30
  8. package/dist/commands/login.js +23 -23
  9. package/dist/commands/logout.js +4 -4
  10. package/dist/commands/project.js +36 -36
  11. package/dist/commands/run.js +33 -33
  12. package/dist/commands/status.js +14 -14
  13. package/dist/commands/tdd-daemon.js +43 -43
  14. package/dist/commands/tdd.js +27 -27
  15. package/dist/commands/upload.js +33 -33
  16. package/dist/commands/whoami.js +12 -12
  17. package/dist/index.js +9 -14
  18. package/dist/plugin-loader.js +28 -28
  19. package/dist/reporter/reporter-bundle.css +1 -1
  20. package/dist/reporter/reporter-bundle.iife.js +19 -19
  21. package/dist/sdk/index.js +33 -35
  22. package/dist/server/handlers/api-handler.js +4 -4
  23. package/dist/server/handlers/tdd-handler.js +12 -12
  24. package/dist/server/http-server.js +21 -22
  25. package/dist/server/middleware/json-parser.js +1 -1
  26. package/dist/server/routers/assets.js +14 -14
  27. package/dist/server/routers/auth.js +14 -14
  28. package/dist/server/routers/baseline.js +8 -8
  29. package/dist/server/routers/cloud-proxy.js +15 -15
  30. package/dist/server/routers/config.js +11 -11
  31. package/dist/server/routers/dashboard.js +11 -11
  32. package/dist/server/routers/health.js +4 -4
  33. package/dist/server/routers/projects.js +19 -19
  34. package/dist/server/routers/screenshot.js +9 -9
  35. package/dist/services/api-service.js +16 -16
  36. package/dist/services/auth-service.js +17 -17
  37. package/dist/services/build-manager.js +3 -3
  38. package/dist/services/config-service.js +33 -33
  39. package/dist/services/html-report-generator.js +8 -8
  40. package/dist/services/index.js +11 -11
  41. package/dist/services/project-service.js +19 -19
  42. package/dist/services/report-generator/report.css +3 -3
  43. package/dist/services/report-generator/viewer.js +25 -23
  44. package/dist/services/screenshot-server.js +1 -1
  45. package/dist/services/server-manager.js +5 -5
  46. package/dist/services/static-report-generator.js +14 -14
  47. package/dist/services/tdd-service.js +101 -95
  48. package/dist/services/test-runner.js +14 -4
  49. package/dist/services/uploader.js +10 -8
  50. package/dist/types/config.d.ts +2 -1
  51. package/dist/types/index.d.ts +11 -1
  52. package/dist/types/sdk.d.ts +1 -1
  53. package/dist/utils/browser.js +3 -3
  54. package/dist/utils/build-history.js +12 -12
  55. package/dist/utils/config-loader.js +19 -19
  56. package/dist/utils/config-schema.js +10 -9
  57. package/dist/utils/environment-config.js +11 -0
  58. package/dist/utils/fetch-utils.js +2 -2
  59. package/dist/utils/file-helpers.js +2 -2
  60. package/dist/utils/git.js +3 -6
  61. package/dist/utils/global-config.js +28 -25
  62. package/dist/utils/output.js +136 -28
  63. package/dist/utils/package-info.js +3 -3
  64. package/dist/utils/security.js +12 -12
  65. package/docs/api-reference.md +56 -27
  66. package/docs/doctor-command.md +1 -1
  67. package/docs/tdd-mode.md +3 -3
  68. package/package.json +9 -13
package/dist/sdk/index.js CHANGED
@@ -10,14 +10,14 @@
10
10
  * @description Full SDK for custom integrations and advanced usage
11
11
  */
12
12
 
13
- import { EventEmitter } from 'events';
14
- import { resolveImageBuffer } from '../utils/file-helpers.js';
15
- import { createUploader } from '../services/uploader.js';
16
- import { createTDDService } from '../services/tdd-service.js';
13
+ import { EventEmitter } from 'node:events';
14
+ import { VizzlyError } from '../errors/vizzly-error.js';
17
15
  import { ScreenshotServer } from '../services/screenshot-server.js';
16
+ import { createTDDService } from '../services/tdd-service.js';
17
+ import { createUploader } from '../services/uploader.js';
18
18
  import { loadConfig } from '../utils/config-loader.js';
19
+ import { resolveImageBuffer } from '../utils/file-helpers.js';
19
20
  import * as output from '../utils/output.js';
20
- import { VizzlyError } from '../errors/vizzly-error.js';
21
21
 
22
22
  /**
23
23
  * Create a new Vizzly instance with custom configuration
@@ -59,15 +59,15 @@ export function createVizzly(config = {}, options = {}) {
59
59
  });
60
60
 
61
61
  // Merge with loaded config
62
- let resolvedConfig = {
62
+ const resolvedConfig = {
63
63
  ...config
64
64
  };
65
65
 
66
66
  /**
67
67
  * Initialize SDK with config loading
68
68
  */
69
- let init = async () => {
70
- let fileConfig = await loadConfig();
69
+ const init = async () => {
70
+ const fileConfig = await loadConfig();
71
71
  Object.assign(resolvedConfig, fileConfig, config); // CLI config takes precedence
72
72
  return resolvedConfig;
73
73
  };
@@ -75,7 +75,7 @@ export function createVizzly(config = {}, options = {}) {
75
75
  /**
76
76
  * Create uploader service
77
77
  */
78
- let createUploaderService = (uploaderOptions = {}) => {
78
+ const createUploaderService = (uploaderOptions = {}) => {
79
79
  return createUploader({
80
80
  apiKey: resolvedConfig.apiKey,
81
81
  apiUrl: resolvedConfig.apiUrl
@@ -88,7 +88,7 @@ export function createVizzly(config = {}, options = {}) {
88
88
  /**
89
89
  * Create TDD service
90
90
  */
91
- let createTDDServiceInstance = (tddOptions = {}) => {
91
+ const createTDDServiceInstance = (tddOptions = {}) => {
92
92
  return createTDDService(resolvedConfig, {
93
93
  ...options,
94
94
  ...tddOptions
@@ -98,16 +98,16 @@ export function createVizzly(config = {}, options = {}) {
98
98
  /**
99
99
  * Upload screenshots (convenience method)
100
100
  */
101
- let upload = async uploadOptions => {
102
- let uploader = createUploaderService();
101
+ const upload = async uploadOptions => {
102
+ const uploader = createUploaderService();
103
103
  return uploader.upload(uploadOptions);
104
104
  };
105
105
 
106
106
  /**
107
107
  * Start TDD mode (convenience method)
108
108
  */
109
- let startTDD = async (tddOptions = {}) => {
110
- let tddService = createTDDServiceInstance();
109
+ const startTDD = async (tddOptions = {}) => {
110
+ const tddService = createTDDServiceInstance();
111
111
  return tddService.start(tddOptions);
112
112
  };
113
113
  return {
@@ -195,7 +195,7 @@ export class VizzlySDK extends EventEmitter {
195
195
  }
196
196
 
197
197
  // Create a simple build manager for screenshot collection
198
- let buildManager = {
198
+ const buildManager = {
199
199
  screenshots: new Map(),
200
200
  currentBuildId: null,
201
201
  async addScreenshot(buildId, screenshot) {
@@ -210,8 +210,8 @@ export class VizzlySDK extends EventEmitter {
210
210
  };
211
211
  this.server = new ScreenshotServer(this.config, buildManager);
212
212
  await this.server.start();
213
- let port = this.config.server?.port || 3000;
214
- let serverInfo = {
213
+ const port = this.config.server?.port || 3000;
214
+ const serverInfo = {
215
215
  port,
216
216
  url: `http://localhost:${port}`
217
217
  };
@@ -235,15 +235,15 @@ export class VizzlySDK extends EventEmitter {
235
235
  }
236
236
 
237
237
  // Resolve Buffer or file path using shared utility
238
- let buffer = resolveImageBuffer(imageBuffer, 'screenshot');
238
+ const buffer = resolveImageBuffer(imageBuffer, 'screenshot');
239
239
 
240
240
  // Generate or use provided build ID
241
- let buildId = options.buildId || this.currentBuildId || 'default';
241
+ const buildId = options.buildId || this.currentBuildId || 'default';
242
242
  this.currentBuildId = buildId;
243
243
 
244
244
  // Convert Buffer to base64 for JSON transport
245
- let imageBase64 = buffer.toString('base64');
246
- let screenshotData = {
245
+ const imageBase64 = buffer.toString('base64');
246
+ const screenshotData = {
247
247
  buildId,
248
248
  name,
249
249
  image: imageBase64,
@@ -251,9 +251,9 @@ export class VizzlySDK extends EventEmitter {
251
251
  };
252
252
 
253
253
  // POST to the local screenshot server
254
- let serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
254
+ const serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
255
255
  try {
256
- let response = await fetch(`${serverUrl}/screenshot`, {
256
+ const response = await fetch(`${serverUrl}/screenshot`, {
257
257
  method: 'POST',
258
258
  headers: {
259
259
  'Content-Type': 'application/json'
@@ -261,7 +261,7 @@ export class VizzlySDK extends EventEmitter {
261
261
  body: JSON.stringify(screenshotData)
262
262
  });
263
263
  if (!response.ok) {
264
- let errorData = await response.json().catch(() => ({
264
+ const errorData = await response.json().catch(() => ({
265
265
  error: 'Unknown error'
266
266
  }));
267
267
  throw new VizzlyError(`Screenshot capture failed: ${errorData.error}`, 'SCREENSHOT_FAILED', {
@@ -302,8 +302,8 @@ export class VizzlySDK extends EventEmitter {
302
302
  }
303
303
 
304
304
  // Get the screenshots directory from config or default
305
- let screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
306
- let uploadOptions = {
305
+ const screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
306
+ const uploadOptions = {
307
307
  screenshotsDir,
308
308
  buildName: options.buildName || this.config.buildName,
309
309
  branch: options.branch || this.config.branch,
@@ -319,7 +319,7 @@ export class VizzlySDK extends EventEmitter {
319
319
  }
320
320
  };
321
321
  try {
322
- let result = await this.services.uploader.upload(uploadOptions);
322
+ const result = await this.services.uploader.upload(uploadOptions);
323
323
  this.emit('upload:completed', result);
324
324
  return result;
325
325
  } catch (error) {
@@ -343,9 +343,9 @@ export class VizzlySDK extends EventEmitter {
343
343
  }
344
344
 
345
345
  // Resolve Buffer or file path using shared utility
346
- let buffer = resolveImageBuffer(imageBuffer, 'compare');
346
+ const buffer = resolveImageBuffer(imageBuffer, 'compare');
347
347
  try {
348
- let result = await this.services.tddService.compareScreenshot(name, buffer);
348
+ const result = await this.services.tddService.compareScreenshot(name, buffer);
349
349
  this.emit('comparison:completed', result);
350
350
  return result;
351
351
  } catch (error) {
@@ -357,11 +357,9 @@ export class VizzlySDK extends EventEmitter {
357
357
  }
358
358
  }
359
359
  }
360
-
361
- // Re-export key utilities and errors
362
- export { loadConfig } from '../utils/config-loader.js';
363
- export * as output from '../utils/output.js';
364
-
360
+ export { createTDDService } from '../services/tdd-service.js';
365
361
  // Export service creators for advanced usage
366
362
  export { createUploader } from '../services/uploader.js';
367
- export { createTDDService } from '../services/tdd-service.js';
363
+ // Re-export key utilities and errors
364
+ export { loadConfig } from '../utils/config-loader.js';
365
+ export * as output from '../utils/output.js';
@@ -1,8 +1,8 @@
1
- import { Buffer } from 'buffer';
2
- import { existsSync, readFileSync } from 'fs';
3
- import { resolve } from 'path';
4
- import * as output from '../../utils/output.js';
1
+ import { Buffer } from 'node:buffer';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { resolve } from 'node:path';
5
4
  import { detectImageInputType } from '../../utils/image-input-detector.js';
5
+ import * as output from '../../utils/output.js';
6
6
 
7
7
  /**
8
8
  * API Handler - Non-blocking screenshot upload
@@ -1,11 +1,11 @@
1
- import { Buffer } from 'buffer';
2
- import { writeFileSync, readFileSync, existsSync } from 'fs';
3
- import { join, resolve } from 'path';
1
+ import { Buffer } from 'node:buffer';
2
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
4
  import { getDimensionsSync } from '@vizzly-testing/honeydiff';
5
- import * as output from '../../utils/output.js';
6
5
  import { TddService } from '../../services/tdd-service.js';
7
- import { sanitizeScreenshotName, validateScreenshotProperties } from '../../utils/security.js';
8
6
  import { detectImageInputType } from '../../utils/image-input-detector.js';
7
+ import * as output from '../../utils/output.js';
8
+ import { sanitizeScreenshotName, validateScreenshotProperties } from '../../utils/security.js';
9
9
 
10
10
  /**
11
11
  * Group comparisons by screenshot name with variant structure
@@ -141,7 +141,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
141
141
  if (existingIndex >= 0) {
142
142
  // Preserve initialStatus from the original comparison
143
143
  // This keeps sort order stable when status changes (e.g., after approval)
144
- let initialStatus = reportData.comparisons[existingIndex].initialStatus;
144
+ const initialStatus = reportData.comparisons[existingIndex].initialStatus;
145
145
  reportData.comparisons[existingIndex] = {
146
146
  ...newComparison,
147
147
  initialStatus: initialStatus || newComparison.status
@@ -200,7 +200,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
200
200
  output.debug('tdd', `using baseline: ${baseline.buildName}`);
201
201
  }
202
202
  };
203
- const handleScreenshot = async (buildId, name, image, properties = {}) => {
203
+ const handleScreenshot = async (_buildId, name, image, properties = {}) => {
204
204
  // Validate and sanitize screenshot name
205
205
  let sanitizedName;
206
206
  try {
@@ -217,7 +217,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
217
217
  }
218
218
 
219
219
  // Unwrap double-nested properties if needed (client SDK wraps options in properties field)
220
- // This happens when test helper passes { properties: {...}, threshold: 0.1 }
220
+ // This happens when test helper passes { properties: {...}, threshold: 2.0 }
221
221
  // and client SDK wraps it as { properties: options }
222
222
  let unwrappedProperties = properties;
223
223
  if (properties.properties && typeof properties.properties === 'object') {
@@ -504,7 +504,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
504
504
  try {
505
505
  const {
506
506
  unlinkSync
507
- } = await import('fs');
507
+ } = await import('node:fs');
508
508
  unlinkSync(baselinePath);
509
509
  deletedBaselines++;
510
510
  // Silent deletion
@@ -521,7 +521,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
521
521
  try {
522
522
  const {
523
523
  unlinkSync
524
- } = await import('fs');
524
+ } = await import('node:fs');
525
525
  unlinkSync(currentPath);
526
526
  deletedCurrents++;
527
527
  // Silent deletion
@@ -538,7 +538,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
538
538
  try {
539
539
  const {
540
540
  unlinkSync
541
- } = await import('fs');
541
+ } = await import('node:fs');
542
542
  unlinkSync(diffPath);
543
543
  deletedDiffs++;
544
544
  output.debug(`Deleted diff for ${comparison.name}`);
@@ -555,7 +555,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
555
555
  try {
556
556
  const {
557
557
  unlinkSync
558
- } = await import('fs');
558
+ } = await import('node:fs');
559
559
  unlinkSync(metadataPath);
560
560
  output.debug('Deleted baseline metadata');
561
561
  } catch (error) {
@@ -3,29 +3,28 @@
3
3
  * Thin dispatcher that routes requests to modular routers
4
4
  */
5
5
 
6
- import { createServer } from 'http';
6
+ import { createServer } from 'node:http';
7
7
  import * as output from '../utils/output.js';
8
8
 
9
9
  // Middleware
10
10
  import { corsMiddleware } from './middleware/cors.js';
11
11
  import { sendError } from './middleware/response.js';
12
-
13
- // Routers
14
- import { createHealthRouter } from './routers/health.js';
15
12
  import { createAssetsRouter } from './routers/assets.js';
16
- import { createDashboardRouter } from './routers/dashboard.js';
17
- import { createScreenshotRouter } from './routers/screenshot.js';
13
+ import { createAuthRouter } from './routers/auth.js';
18
14
  import { createBaselineRouter } from './routers/baseline.js';
15
+ import { createCloudProxyRouter } from './routers/cloud-proxy.js';
19
16
  import { createConfigRouter } from './routers/config.js';
20
- import { createAuthRouter } from './routers/auth.js';
17
+ import { createDashboardRouter } from './routers/dashboard.js';
18
+ // Routers
19
+ import { createHealthRouter } from './routers/health.js';
21
20
  import { createProjectsRouter } from './routers/projects.js';
22
- import { createCloudProxyRouter } from './routers/cloud-proxy.js';
23
- export let createHttpServer = (port, screenshotHandler, services = {}) => {
21
+ import { createScreenshotRouter } from './routers/screenshot.js';
22
+ export const createHttpServer = (port, screenshotHandler, services = {}) => {
24
23
  let server = null;
25
- let defaultBuildId = services.buildId || null;
24
+ const defaultBuildId = services.buildId || null;
26
25
 
27
26
  // Extract services
28
- let {
27
+ const {
29
28
  configService,
30
29
  authService,
31
30
  projectService,
@@ -33,7 +32,7 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
33
32
  } = services;
34
33
 
35
34
  // Create router context
36
- let routerContext = {
35
+ const routerContext = {
37
36
  port,
38
37
  screenshotHandler,
39
38
  defaultBuildId,
@@ -45,9 +44,9 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
45
44
  };
46
45
 
47
46
  // Initialize routers
48
- let routers = [createHealthRouter(routerContext), createAssetsRouter(routerContext), createScreenshotRouter(routerContext), createBaselineRouter(routerContext), createConfigRouter(routerContext), createAuthRouter(routerContext), createProjectsRouter(routerContext), createCloudProxyRouter(routerContext), createDashboardRouter(routerContext) // Catch-all for SPA routes - must be last
47
+ const routers = [createHealthRouter(routerContext), createAssetsRouter(routerContext), createScreenshotRouter(routerContext), createBaselineRouter(routerContext), createConfigRouter(routerContext), createAuthRouter(routerContext), createProjectsRouter(routerContext), createCloudProxyRouter(routerContext), createDashboardRouter(routerContext) // Catch-all for SPA routes - must be last
49
48
  ];
50
- let handleRequest = async (req, res) => {
49
+ const handleRequest = async (req, res) => {
51
50
  // Apply CORS middleware
52
51
  if (corsMiddleware(req, res)) {
53
52
  return;
@@ -57,13 +56,13 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
57
56
  res.setHeader('Content-Type', 'application/json');
58
57
 
59
58
  // Parse URL
60
- let parsedUrl = new URL(req.url, `http://${req.headers.host}`);
61
- let pathname = parsedUrl.pathname;
59
+ const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
60
+ const pathname = parsedUrl.pathname;
62
61
 
63
62
  // Try each router in order
64
- for (let router of routers) {
63
+ for (const router of routers) {
65
64
  try {
66
- let handled = await router(req, res, pathname, parsedUrl);
65
+ const handled = await router(req, res, pathname, parsedUrl);
67
66
  if (handled) {
68
67
  return;
69
68
  }
@@ -79,7 +78,7 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
79
78
  // No router handled the request
80
79
  sendError(res, 404, 'Not found');
81
80
  };
82
- let start = () => {
81
+ const start = () => {
83
82
  return new Promise((resolve, reject) => {
84
83
  server = createServer(async (req, res) => {
85
84
  try {
@@ -112,7 +111,7 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
112
111
  });
113
112
  });
114
113
  };
115
- let stop = () => {
114
+ const stop = () => {
116
115
  if (server) {
117
116
  return new Promise(resolve => {
118
117
  server.close(() => {
@@ -129,10 +128,10 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
129
128
  * Finish build - flush any pending background operations
130
129
  * Call this before finalizing a build to ensure all uploads complete
131
130
  */
132
- let finishBuild = async _buildId => {
131
+ const finishBuild = async _buildId => {
133
132
  // Flush screenshot handler if it has a flush method (API mode)
134
133
  if (screenshotHandler?.flush) {
135
- let stats = await screenshotHandler.flush();
134
+ const stats = await screenshotHandler.flush();
136
135
  if (stats.uploaded > 0 || stats.failed > 0) {
137
136
  output.debug('upload', 'flushed', {
138
137
  uploaded: stats.uploaded,
@@ -24,7 +24,7 @@ export function parseJsonBody(req) {
24
24
  return;
25
25
  }
26
26
  try {
27
- let data = JSON.parse(body);
27
+ const data = JSON.parse(body);
28
28
  resolve(data);
29
29
  } catch {
30
30
  reject(new Error('Invalid JSON'));
@@ -3,14 +3,14 @@
3
3
  * Serves static assets (bundle files, images)
4
4
  */
5
5
 
6
- import { readFileSync, existsSync } from 'fs';
7
- import { join, dirname } from 'path';
8
- import { fileURLToPath } from 'url';
9
- import { sendFile, sendError, sendNotFound } from '../middleware/response.js';
6
+ import { existsSync, readFileSync } from 'node:fs';
7
+ import { dirname, join } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
10
9
  import * as output from '../../utils/output.js';
11
- let __filename = fileURLToPath(import.meta.url);
12
- let __dirname = dirname(__filename);
13
- let PROJECT_ROOT = join(__dirname, '..', '..', '..');
10
+ import { sendError, sendFile, sendNotFound } from '../middleware/response.js';
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+ const PROJECT_ROOT = join(__dirname, '..', '..', '..');
14
14
 
15
15
  /**
16
16
  * Create assets router
@@ -25,10 +25,10 @@ export function createAssetsRouter() {
25
25
 
26
26
  // Serve React bundle JS
27
27
  if (pathname === '/reporter-bundle.js') {
28
- let bundlePath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.iife.js');
28
+ const bundlePath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.iife.js');
29
29
  if (existsSync(bundlePath)) {
30
30
  try {
31
- let bundle = readFileSync(bundlePath, 'utf8');
31
+ const bundle = readFileSync(bundlePath, 'utf8');
32
32
  sendFile(res, bundle, 'application/javascript');
33
33
  return true;
34
34
  } catch (error) {
@@ -46,10 +46,10 @@ export function createAssetsRouter() {
46
46
 
47
47
  // Serve React bundle CSS
48
48
  if (pathname === '/reporter-bundle.css') {
49
- let cssPath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.css');
49
+ const cssPath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.css');
50
50
  if (existsSync(cssPath)) {
51
51
  try {
52
- let css = readFileSync(cssPath, 'utf8');
52
+ const css = readFileSync(cssPath, 'utf8');
53
53
  sendFile(res, css, 'text/css');
54
54
  return true;
55
55
  } catch (error) {
@@ -67,11 +67,11 @@ export function createAssetsRouter() {
67
67
 
68
68
  // Serve images from .vizzly directory
69
69
  if (pathname.startsWith('/images/')) {
70
- let imagePath = pathname.replace('/images/', '');
71
- let fullImagePath = join(process.cwd(), '.vizzly', imagePath);
70
+ const imagePath = pathname.replace('/images/', '');
71
+ const fullImagePath = join(process.cwd(), '.vizzly', imagePath);
72
72
  if (existsSync(fullImagePath)) {
73
73
  try {
74
- let imageData = readFileSync(fullImagePath);
74
+ const imageData = readFileSync(fullImagePath);
75
75
  sendFile(res, imageData, 'image/png');
76
76
  return true;
77
77
  } catch (error) {
@@ -3,9 +3,9 @@
3
3
  * Handles authentication endpoints (device flow login, logout, status)
4
4
  */
5
5
 
6
- import { parseJsonBody } from '../middleware/json-parser.js';
7
- import { sendSuccess, sendError, sendServiceUnavailable } from '../middleware/response.js';
8
6
  import * as output from '../../utils/output.js';
7
+ import { parseJsonBody } from '../middleware/json-parser.js';
8
+ import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
9
9
 
10
10
  /**
11
11
  * Create auth router
@@ -26,10 +26,10 @@ export function createAuthRouter({
26
26
  // Get auth status and user info
27
27
  if (req.method === 'GET' && pathname === '/api/auth/status') {
28
28
  try {
29
- let isAuthenticated = await authService.isAuthenticated();
29
+ const isAuthenticated = await authService.isAuthenticated();
30
30
  let user = null;
31
31
  if (isAuthenticated) {
32
- let whoami = await authService.whoami();
32
+ const whoami = await authService.whoami();
33
33
  user = whoami.user;
34
34
  }
35
35
  sendSuccess(res, {
@@ -50,10 +50,10 @@ export function createAuthRouter({
50
50
  // Initiate device flow login
51
51
  if (req.method === 'POST' && pathname === '/api/auth/login') {
52
52
  try {
53
- let deviceFlow = await authService.initiateDeviceFlow();
53
+ const deviceFlow = await authService.initiateDeviceFlow();
54
54
 
55
55
  // Transform snake_case to camelCase for frontend
56
- let response = {
56
+ const response = {
57
57
  deviceCode: deviceFlow.device_code,
58
58
  userCode: deviceFlow.user_code,
59
59
  verificationUri: deviceFlow.verification_uri,
@@ -73,8 +73,8 @@ export function createAuthRouter({
73
73
  // Poll device authorization status
74
74
  if (req.method === 'POST' && pathname === '/api/auth/poll') {
75
75
  try {
76
- let body = await parseJsonBody(req);
77
- let {
76
+ const body = await parseJsonBody(req);
77
+ const {
78
78
  deviceCode
79
79
  } = body;
80
80
  if (!deviceCode) {
@@ -86,7 +86,7 @@ export function createAuthRouter({
86
86
  result = await authService.pollDeviceAuthorization(deviceCode);
87
87
  } catch (error) {
88
88
  // Handle "Authorization pending" as a valid response
89
- if (error.message && error.message.includes('Authorization pending')) {
89
+ if (error.message?.includes('Authorization pending')) {
90
90
  sendSuccess(res, {
91
91
  status: 'pending'
92
92
  });
@@ -96,11 +96,11 @@ export function createAuthRouter({
96
96
  }
97
97
 
98
98
  // Check if authorization is complete by looking for tokens
99
- if (result.tokens && result.tokens.accessToken) {
100
- let tokensData = result.tokens;
101
- let tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
102
- let tokenExpiresAt = tokenExpiresIn ? new Date(Date.now() + tokenExpiresIn * 1000).toISOString() : result.expires_at || result.expiresAt;
103
- let tokens = {
99
+ if (result.tokens?.accessToken) {
100
+ const tokensData = result.tokens;
101
+ const tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
102
+ const tokenExpiresAt = tokenExpiresIn ? new Date(Date.now() + tokenExpiresIn * 1000).toISOString() : result.expires_at || result.expiresAt;
103
+ const tokens = {
104
104
  accessToken: tokensData.accessToken || tokensData.access_token,
105
105
  refreshToken: tokensData.refreshToken || tokensData.refresh_token,
106
106
  expiresAt: tokenExpiresAt,
@@ -3,9 +3,9 @@
3
3
  * Handles baseline management (accept, accept-all, reset)
4
4
  */
5
5
 
6
- import { parseJsonBody } from '../middleware/json-parser.js';
7
- import { sendSuccess, sendError, sendServiceUnavailable } from '../middleware/response.js';
8
6
  import * as output from '../../utils/output.js';
7
+ import { parseJsonBody } from '../middleware/json-parser.js';
8
+ import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
9
9
 
10
10
  /**
11
11
  * Create baseline router
@@ -28,7 +28,7 @@ export function createBaselineRouter({
28
28
  return true;
29
29
  }
30
30
  try {
31
- let {
31
+ const {
32
32
  id
33
33
  } = await parseJsonBody(req);
34
34
  if (!id) {
@@ -55,7 +55,7 @@ export function createBaselineRouter({
55
55
  return true;
56
56
  }
57
57
  try {
58
- let result = await screenshotHandler.acceptAllBaselines();
58
+ const result = await screenshotHandler.acceptAllBaselines();
59
59
  sendSuccess(res, {
60
60
  success: true,
61
61
  message: `Accepted ${result.count} baselines`,
@@ -96,8 +96,8 @@ export function createBaselineRouter({
96
96
  return true;
97
97
  }
98
98
  try {
99
- let body = await parseJsonBody(req);
100
- let {
99
+ const body = await parseJsonBody(req);
100
+ const {
101
101
  buildId,
102
102
  organizationSlug,
103
103
  projectSlug
@@ -111,7 +111,7 @@ export function createBaselineRouter({
111
111
  // If organizationSlug and projectSlug are provided, use OAuth-based download
112
112
  if (organizationSlug && projectSlug && authService) {
113
113
  try {
114
- let result = await tddService.downloadBaselinesWithAuth(buildId, organizationSlug, projectSlug, authService);
114
+ const result = await tddService.downloadBaselinesWithAuth(buildId, organizationSlug, projectSlug, authService);
115
115
  sendSuccess(res, {
116
116
  success: true,
117
117
  message: `Baselines downloaded from build ${buildId}`,
@@ -138,7 +138,7 @@ export function createBaselineRouter({
138
138
  }
139
139
 
140
140
  // Fall back to API token-based download (when no OAuth info or OAuth auth failed)
141
- let result = await tddService.downloadBaselines('test',
141
+ const result = await tddService.downloadBaselines('test',
142
142
  // environment
143
143
  null,
144
144
  // branch (not needed when buildId is specified)
@@ -9,9 +9,9 @@
9
9
  * - Returns proxied response to React app
10
10
  */
11
11
 
12
- import { parseJsonBody } from '../middleware/json-parser.js';
13
- import { sendSuccess, sendError, sendServiceUnavailable } from '../middleware/response.js';
14
12
  import * as output from '../../utils/output.js';
13
+ import { parseJsonBody } from '../middleware/json-parser.js';
14
+ import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
15
15
 
16
16
  /**
17
17
  * Create cloud proxy router
@@ -53,7 +53,7 @@ export function createCloudProxyRouter({
53
53
  // Route: GET /api/cloud/projects - List user's projects
54
54
  if (req.method === 'GET' && pathname === '/api/cloud/projects') {
55
55
  try {
56
- let response = await proxyRequest('/api/cli/projects', {
56
+ const response = await proxyRequest('/api/cli/projects', {
57
57
  method: 'GET'
58
58
  });
59
59
  sendSuccess(res, {
@@ -76,19 +76,19 @@ export function createCloudProxyRouter({
76
76
  }
77
77
 
78
78
  // Route: GET /api/cloud/organizations/:org/projects/:project/builds
79
- let buildsMatch = pathname.match(/^\/api\/cloud\/organizations\/([^/]+)\/projects\/([^/]+)\/builds$/);
79
+ const buildsMatch = pathname.match(/^\/api\/cloud\/organizations\/([^/]+)\/projects\/([^/]+)\/builds$/);
80
80
  if (req.method === 'GET' && buildsMatch) {
81
81
  try {
82
- let organizationSlug = decodeURIComponent(buildsMatch[1]);
83
- let projectSlug = decodeURIComponent(buildsMatch[2]);
84
- let limit = parsedUrl.searchParams.get('limit') || '20';
85
- let branch = parsedUrl.searchParams.get('branch');
86
- let queryParams = new URLSearchParams();
82
+ const organizationSlug = decodeURIComponent(buildsMatch[1]);
83
+ const projectSlug = decodeURIComponent(buildsMatch[2]);
84
+ const limit = parsedUrl.searchParams.get('limit') || '20';
85
+ const branch = parsedUrl.searchParams.get('branch');
86
+ const queryParams = new URLSearchParams();
87
87
  if (limit) queryParams.append('limit', limit);
88
88
  if (branch) queryParams.append('branch', branch);
89
- let query = queryParams.toString();
90
- let endpoint = `/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/builds${query ? `?${query}` : ''}`;
91
- let response = await proxyRequest(endpoint, {
89
+ const query = queryParams.toString();
90
+ const endpoint = `/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/builds${query ? `?${query}` : ''}`;
91
+ const response = await proxyRequest(endpoint, {
92
92
  method: 'GET'
93
93
  });
94
94
  sendSuccess(res, {
@@ -105,8 +105,8 @@ export function createCloudProxyRouter({
105
105
  // Route: POST /api/cloud/baselines/download - Download baselines from build
106
106
  if (req.method === 'POST' && pathname === '/api/cloud/baselines/download') {
107
107
  try {
108
- let body = await parseJsonBody(req);
109
- let {
108
+ const body = await parseJsonBody(req);
109
+ const {
110
110
  buildId,
111
111
  screenshotNames
112
112
  } = body;
@@ -116,7 +116,7 @@ export function createCloudProxyRouter({
116
116
  }
117
117
 
118
118
  // Download baselines from the specified build
119
- let response = await proxyRequest('/api/cli/baselines/download', {
119
+ const response = await proxyRequest('/api/cli/baselines/download', {
120
120
  method: 'POST',
121
121
  headers: {
122
122
  'Content-Type': 'application/json'