@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
@@ -3,12 +3,12 @@
3
3
  * Manages reading and writing Vizzly configuration files
4
4
  */
5
5
 
6
+ import { readFile, writeFile } from 'node:fs/promises';
7
+ import { join } from 'node:path';
6
8
  import { cosmiconfigSync } from 'cosmiconfig';
7
- import { writeFile, readFile } from 'fs/promises';
8
- import { join } from 'path';
9
9
  import { VizzlyError } from '../errors/vizzly-error.js';
10
10
  import { validateVizzlyConfigWithDefaults } from '../utils/config-schema.js';
11
- import { loadGlobalConfig, saveGlobalConfig, getGlobalConfigPath } from '../utils/global-config.js';
11
+ import { getGlobalConfigPath, loadGlobalConfig, saveGlobalConfig } from '../utils/global-config.js';
12
12
 
13
13
  /**
14
14
  * ConfigService for reading and writing configuration
@@ -44,7 +44,7 @@ export class ConfigService {
44
44
  * @returns {Promise<Object>}
45
45
  */
46
46
  async _getProjectConfig() {
47
- let result = this.explorer.search(this.projectRoot);
47
+ const result = this.explorer.search(this.projectRoot);
48
48
  if (!result || !result.config) {
49
49
  return {
50
50
  config: {},
@@ -52,7 +52,7 @@ export class ConfigService {
52
52
  isEmpty: true
53
53
  };
54
54
  }
55
- let config = result.config.default || result.config;
55
+ const config = result.config.default || result.config;
56
56
  return {
57
57
  config,
58
58
  filepath: result.filepath,
@@ -66,7 +66,7 @@ export class ConfigService {
66
66
  * @returns {Promise<Object>}
67
67
  */
68
68
  async _getGlobalConfig() {
69
- let globalConfig = await loadGlobalConfig();
69
+ const globalConfig = await loadGlobalConfig();
70
70
  return {
71
71
  config: globalConfig,
72
72
  filepath: getGlobalConfigPath(),
@@ -80,15 +80,15 @@ export class ConfigService {
80
80
  * @returns {Promise<Object>}
81
81
  */
82
82
  async _getMergedConfig() {
83
- let projectConfigData = await this._getProjectConfig();
84
- let globalConfigData = await this._getGlobalConfig();
83
+ const projectConfigData = await this._getProjectConfig();
84
+ const globalConfigData = await this._getGlobalConfig();
85
85
 
86
86
  // Build config with source tracking
87
- let mergedConfig = {};
88
- let sources = {};
87
+ const mergedConfig = {};
88
+ const sources = {};
89
89
 
90
90
  // Layer 1: Defaults
91
- let defaults = {
91
+ const defaults = {
92
92
  apiUrl: 'https://app.vizzly.dev',
93
93
  server: {
94
94
  port: 47392,
@@ -104,7 +104,7 @@ export class ConfigService {
104
104
  timeout: 30000
105
105
  },
106
106
  comparison: {
107
- threshold: 0.1
107
+ threshold: 2.0
108
108
  },
109
109
  tdd: {
110
110
  openReport: false
@@ -133,7 +133,7 @@ export class ConfigService {
133
133
  });
134
134
 
135
135
  // Layer 4: Environment variables (tracked separately)
136
- let envOverrides = {};
136
+ const envOverrides = {};
137
137
  if (process.env.VIZZLY_TOKEN) {
138
138
  envOverrides.apiKey = process.env.VIZZLY_TOKEN;
139
139
  sources.apiKey = 'env';
@@ -176,12 +176,12 @@ export class ConfigService {
176
176
  * @returns {Promise<Object>} Updated config
177
177
  */
178
178
  async _updateProjectConfig(updates) {
179
- let result = this.explorer.search(this.projectRoot);
179
+ const result = this.explorer.search(this.projectRoot);
180
180
 
181
181
  // Determine config file path
182
182
  let configPath;
183
183
  let currentConfig = {};
184
- if (result && result.filepath) {
184
+ if (result?.filepath) {
185
185
  configPath = result.filepath;
186
186
  currentConfig = result.config.default || result.config;
187
187
  } else {
@@ -190,7 +190,7 @@ export class ConfigService {
190
190
  }
191
191
 
192
192
  // Merge updates with current config
193
- let newConfig = this._deepMerge(currentConfig, updates);
193
+ const newConfig = this._deepMerge(currentConfig, updates);
194
194
 
195
195
  // Validate before writing
196
196
  try {
@@ -219,8 +219,8 @@ export class ConfigService {
219
219
  * @returns {Promise<Object>} Updated config
220
220
  */
221
221
  async _updateGlobalConfig(updates) {
222
- let currentConfig = await loadGlobalConfig();
223
- let newConfig = this._deepMerge(currentConfig, updates);
222
+ const currentConfig = await loadGlobalConfig();
223
+ const newConfig = this._deepMerge(currentConfig, updates);
224
224
  await saveGlobalConfig(newConfig);
225
225
  return {
226
226
  config: newConfig,
@@ -238,22 +238,22 @@ export class ConfigService {
238
238
  async _writeProjectConfigFile(filepath, config) {
239
239
  // For .js files, export as ES module
240
240
  if (filepath.endsWith('.js') || filepath.endsWith('.mjs')) {
241
- let content = this._serializeToJavaScript(config);
241
+ const content = this._serializeToJavaScript(config);
242
242
  await writeFile(filepath, content, 'utf-8');
243
243
  return;
244
244
  }
245
245
 
246
246
  // For .json files
247
247
  if (filepath.endsWith('.json')) {
248
- let content = JSON.stringify(config, null, 2);
248
+ const content = JSON.stringify(config, null, 2);
249
249
  await writeFile(filepath, content, 'utf-8');
250
250
  return;
251
251
  }
252
252
 
253
253
  // For package.json, merge into existing
254
254
  if (filepath.endsWith('package.json')) {
255
- let pkgContent = await readFile(filepath, 'utf-8');
256
- let pkg = JSON.parse(pkgContent);
255
+ const pkgContent = await readFile(filepath, 'utf-8');
256
+ const pkg = JSON.parse(pkgContent);
257
257
  pkg.vizzly = config;
258
258
  await writeFile(filepath, JSON.stringify(pkg, null, 2), 'utf-8');
259
259
  return;
@@ -268,7 +268,7 @@ export class ConfigService {
268
268
  * @returns {string} JavaScript source code
269
269
  */
270
270
  _serializeToJavaScript(config) {
271
- let lines = ['/**', ' * Vizzly Configuration', ' * @see https://docs.vizzly.dev/cli/configuration', ' */', '', "import { defineConfig } from '@vizzly-testing/cli/config';", '', 'export default defineConfig(', this._stringifyWithIndent(config, 1), ');', ''];
271
+ const lines = ['/**', ' * Vizzly Configuration', ' * @see https://docs.vizzly.dev/cli/configuration', ' */', '', "import { defineConfig } from '@vizzly-testing/cli/config';", '', 'export default defineConfig(', this._stringifyWithIndent(config, 1), ');', ''];
272
272
  return lines.join('\n');
273
273
  }
274
274
 
@@ -280,8 +280,8 @@ export class ConfigService {
280
280
  * @returns {string}
281
281
  */
282
282
  _stringifyWithIndent(value, depth = 0) {
283
- let indent = ' '.repeat(depth);
284
- let prevIndent = ' '.repeat(depth - 1);
283
+ const indent = ' '.repeat(depth);
284
+ const prevIndent = ' '.repeat(depth - 1);
285
285
  if (value === null || value === undefined) {
286
286
  return String(value);
287
287
  }
@@ -293,14 +293,14 @@ export class ConfigService {
293
293
  }
294
294
  if (Array.isArray(value)) {
295
295
  if (value.length === 0) return '[]';
296
- let items = value.map(item => `${indent}${this._stringifyWithIndent(item, depth + 1)}`);
296
+ const items = value.map(item => `${indent}${this._stringifyWithIndent(item, depth + 1)}`);
297
297
  return `[\n${items.join(',\n')}\n${prevIndent}]`;
298
298
  }
299
299
  if (typeof value === 'object') {
300
- let keys = Object.keys(value);
300
+ const keys = Object.keys(value);
301
301
  if (keys.length === 0) return '{}';
302
- let items = keys.map(key => {
303
- let val = this._stringifyWithIndent(value[key], depth + 1);
302
+ const items = keys.map(key => {
303
+ const val = this._stringifyWithIndent(value[key], depth + 1);
304
304
  return `${indent}${key}: ${val}`;
305
305
  });
306
306
  return `{\n${items.join(',\n')}\n${prevIndent}}`;
@@ -315,7 +315,7 @@ export class ConfigService {
315
315
  */
316
316
  async validateConfig(config) {
317
317
  try {
318
- let validated = validateVizzlyConfigWithDefaults(config);
318
+ const validated = validateVizzlyConfigWithDefaults(config);
319
319
  return {
320
320
  valid: true,
321
321
  config: validated,
@@ -338,7 +338,7 @@ export class ConfigService {
338
338
  * @returns {Promise<string>} Source ('default', 'global', 'project', 'env', 'cli')
339
339
  */
340
340
  async getConfigSource(key) {
341
- let merged = await this._getMergedConfig();
341
+ const merged = await this._getMergedConfig();
342
342
  return merged.sources[key] || 'unknown';
343
343
  }
344
344
 
@@ -350,10 +350,10 @@ export class ConfigService {
350
350
  * @returns {Object} Merged object
351
351
  */
352
352
  _deepMerge(target, source) {
353
- let output = {
353
+ const output = {
354
354
  ...target
355
355
  };
356
- for (let key in source) {
356
+ for (const key in source) {
357
357
  if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
358
358
  if (target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
359
359
  output[key] = this._deepMerge(target[key], source[key]);
@@ -3,10 +3,10 @@
3
3
  * Creates an interactive report with overlay, toggle, and onion skin modes
4
4
  */
5
5
 
6
- import { writeFile, mkdir } from 'fs/promises';
7
- import { existsSync } from 'fs';
8
- import { join, relative, dirname } from 'path';
9
- import { fileURLToPath } from 'url';
6
+ import { existsSync } from 'node:fs';
7
+ import { mkdir, writeFile } from 'node:fs/promises';
8
+ import { dirname, join, relative } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
10
  import * as output from '../utils/output.js';
11
11
  export class HtmlReportGenerator {
12
12
  constructor(workingDir, config) {
@@ -16,8 +16,8 @@ export class HtmlReportGenerator {
16
16
  this.reportPath = join(this.reportDir, 'index.html');
17
17
 
18
18
  // Get path to the CSS file that ships with the package
19
- let __filename = fileURLToPath(import.meta.url);
20
- let __dirname = dirname(__filename);
19
+ const __filename = fileURLToPath(import.meta.url);
20
+ const __dirname = dirname(__filename);
21
21
  this.cssPath = join(__dirname, 'report-generator', 'report.css');
22
22
  }
23
23
 
@@ -37,7 +37,7 @@ export class HtmlReportGenerator {
37
37
  * @returns {Object} Sanitized build info
38
38
  */
39
39
  sanitizeBuildInfo(buildInfo = {}) {
40
- let sanitized = {};
40
+ const sanitized = {};
41
41
  if (buildInfo.baseline && typeof buildInfo.baseline === 'object') {
42
42
  sanitized.baseline = {
43
43
  buildId: this.sanitizeHtml(buildInfo.baseline.buildId || ''),
@@ -383,7 +383,7 @@ function rejectChanges(screenshotName) {
383
383
  <p>Missing comparison images</p>
384
384
  </div>`;
385
385
  }
386
- let safeName = this.sanitizeHtml(comparison.name);
386
+ const safeName = this.sanitizeHtml(comparison.name);
387
387
  return `
388
388
  <div class="comparison" data-comparison="${safeName}">
389
389
  <div class="comparison-header">
@@ -5,13 +5,13 @@
5
5
 
6
6
  import { ApiService } from './api-service.js';
7
7
  import { AuthService } from './auth-service.js';
8
+ import { BuildManager } from './build-manager.js';
8
9
  import { ConfigService } from './config-service.js';
9
10
  import { ProjectService } from './project-service.js';
10
- import { createUploader } from './uploader.js';
11
- import { BuildManager } from './build-manager.js';
12
11
  import { ServerManager } from './server-manager.js';
13
12
  import { createTDDService } from './tdd-service.js';
14
13
  import { TestRunner } from './test-runner.js';
14
+ import { createUploader } from './uploader.js';
15
15
 
16
16
  /**
17
17
  * Create all services with their dependencies
@@ -20,36 +20,36 @@ import { TestRunner } from './test-runner.js';
20
20
  * @returns {Object} Services object
21
21
  */
22
22
  export function createServices(config, command = 'run') {
23
- let apiService = new ApiService({
23
+ const apiService = new ApiService({
24
24
  ...config,
25
25
  allowNoToken: true
26
26
  });
27
- let authService = new AuthService({
27
+ const authService = new AuthService({
28
28
  baseUrl: config.apiUrl
29
29
  });
30
- let configService = new ConfigService(config, {
30
+ const configService = new ConfigService(config, {
31
31
  projectRoot: process.cwd()
32
32
  });
33
- let projectService = new ProjectService(config, {
33
+ const projectService = new ProjectService(config, {
34
34
  apiService,
35
35
  authService
36
36
  });
37
- let uploader = createUploader({
37
+ const uploader = createUploader({
38
38
  ...config,
39
39
  command
40
40
  });
41
- let buildManager = new BuildManager(config);
42
- let tddService = createTDDService(config, {
41
+ const buildManager = new BuildManager(config);
42
+ const tddService = createTDDService(config, {
43
43
  authService
44
44
  });
45
- let serverManager = new ServerManager(config, {
45
+ const serverManager = new ServerManager(config, {
46
46
  services: {
47
47
  configService,
48
48
  authService,
49
49
  projectService
50
50
  }
51
51
  });
52
- let testRunner = new TestRunner(config, buildManager, serverManager, tddService);
52
+ const testRunner = new TestRunner(config, buildManager, serverManager, tddService);
53
53
  return {
54
54
  apiService,
55
55
  authService,
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { VizzlyError } from '../errors/vizzly-error.js';
7
- import { getProjectMappings, saveProjectMapping, deleteProjectMapping, getProjectMapping } from '../utils/global-config.js';
7
+ import { deleteProjectMapping, getProjectMapping, getProjectMappings, saveProjectMapping } from '../utils/global-config.js';
8
8
 
9
9
  /**
10
10
  * ProjectService for managing project mappings and operations
@@ -21,7 +21,7 @@ export class ProjectService {
21
21
  * @returns {Promise<Array>} Array of project mappings
22
22
  */
23
23
  async listMappings() {
24
- let mappings = await getProjectMappings();
24
+ const mappings = await getProjectMappings();
25
25
 
26
26
  // Convert object to array with directory path included
27
27
  return Object.entries(mappings).map(([directory, data]) => ({
@@ -89,7 +89,7 @@ export class ProjectService {
89
89
  * @returns {Promise<Object>} Updated mapping
90
90
  */
91
91
  async switchProject(projectSlug, organizationSlug, token) {
92
- let currentDir = process.cwd();
92
+ const currentDir = process.cwd();
93
93
  return this.createMapping(currentDir, {
94
94
  projectSlug,
95
95
  organizationSlug,
@@ -107,19 +107,19 @@ export class ProjectService {
107
107
  if (this.authService) {
108
108
  try {
109
109
  // First get the user's organizations via whoami
110
- let whoami = await this.authService.authenticatedRequest('/api/auth/cli/whoami', {
110
+ const whoami = await this.authService.authenticatedRequest('/api/auth/cli/whoami', {
111
111
  method: 'GET'
112
112
  });
113
- let organizations = whoami.organizations || [];
113
+ const organizations = whoami.organizations || [];
114
114
  if (organizations.length === 0) {
115
115
  return [];
116
116
  }
117
117
 
118
118
  // Fetch projects for each organization
119
- let allProjects = [];
120
- for (let org of organizations) {
119
+ const allProjects = [];
120
+ for (const org of organizations) {
121
121
  try {
122
- let response = await this.authService.authenticatedRequest('/api/project', {
122
+ const response = await this.authService.authenticatedRequest('/api/project', {
123
123
  method: 'GET',
124
124
  headers: {
125
125
  'X-Organization': org.slug
@@ -127,7 +127,7 @@ export class ProjectService {
127
127
  });
128
128
 
129
129
  // Add organization info to each project
130
- let projects = (response.projects || []).map(project => ({
130
+ const projects = (response.projects || []).map(project => ({
131
131
  ...project,
132
132
  organizationSlug: org.slug,
133
133
  organizationName: org.name
@@ -146,7 +146,7 @@ export class ProjectService {
146
146
  // Fall back to API token-based request (tokens are org-scoped, so no org header needed)
147
147
  if (this.apiService) {
148
148
  try {
149
- let response = await this.apiService.request('/api/project', {
149
+ const response = await this.apiService.request('/api/project', {
150
150
  method: 'GET'
151
151
  });
152
152
  return response.projects || [];
@@ -169,7 +169,7 @@ export class ProjectService {
169
169
  // Try OAuth-based request first
170
170
  if (this.authService) {
171
171
  try {
172
- let response = await this.authService.authenticatedRequest(`/api/project/${projectSlug}`, {
172
+ const response = await this.authService.authenticatedRequest(`/api/project/${projectSlug}`, {
173
173
  method: 'GET',
174
174
  headers: {
175
175
  'X-Organization': organizationSlug
@@ -184,7 +184,7 @@ export class ProjectService {
184
184
  // Fall back to API token
185
185
  if (this.apiService) {
186
186
  try {
187
- let response = await this.apiService.request(`/api/project/${projectSlug}`, {
187
+ const response = await this.apiService.request(`/api/project/${projectSlug}`, {
188
188
  method: 'GET',
189
189
  headers: {
190
190
  'X-Organization': organizationSlug
@@ -211,16 +211,16 @@ export class ProjectService {
211
211
  * @returns {Promise<Array>} Array of builds
212
212
  */
213
213
  async getRecentBuilds(projectSlug, organizationSlug, options = {}) {
214
- let queryParams = new globalThis.URLSearchParams();
214
+ const queryParams = new globalThis.URLSearchParams();
215
215
  if (options.limit) queryParams.append('limit', String(options.limit));
216
216
  if (options.branch) queryParams.append('branch', options.branch);
217
- let query = queryParams.toString();
218
- let url = `/api/build/${projectSlug}${query ? `?${query}` : ''}`;
217
+ const query = queryParams.toString();
218
+ const url = `/api/build/${projectSlug}${query ? `?${query}` : ''}`;
219
219
 
220
220
  // Try OAuth-based request first (user login via device flow)
221
221
  if (this.authService) {
222
222
  try {
223
- let response = await this.authService.authenticatedRequest(url, {
223
+ const response = await this.authService.authenticatedRequest(url, {
224
224
  method: 'GET',
225
225
  headers: {
226
226
  'X-Organization': organizationSlug
@@ -235,7 +235,7 @@ export class ProjectService {
235
235
  // Fall back to API token-based request
236
236
  if (this.apiService) {
237
237
  try {
238
- let response = await this.apiService.request(url, {
238
+ const response = await this.apiService.request(url, {
239
239
  method: 'GET',
240
240
  headers: {
241
241
  'X-Organization': organizationSlug
@@ -265,7 +265,7 @@ export class ProjectService {
265
265
  throw new VizzlyError('API service not available', 'NO_API_SERVICE');
266
266
  }
267
267
  try {
268
- let response = await this.apiService.request(`/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/tokens`, {
268
+ const response = await this.apiService.request(`/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/tokens`, {
269
269
  method: 'POST',
270
270
  headers: {
271
271
  'Content-Type': 'application/json'
@@ -291,7 +291,7 @@ export class ProjectService {
291
291
  throw new VizzlyError('API service not available', 'NO_API_SERVICE');
292
292
  }
293
293
  try {
294
- let response = await this.apiService.request(`/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/tokens`, {
294
+ const response = await this.apiService.request(`/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/tokens`, {
295
295
  method: 'GET'
296
296
  });
297
297
  return response.tokens || [];
@@ -6,7 +6,7 @@
6
6
 
7
7
  body {
8
8
  font-family:
9
- -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
9
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
10
10
  line-height: 1.6;
11
11
  color: #e2e8f0;
12
12
  background: #0f172a;
@@ -296,7 +296,7 @@ body {
296
296
  }
297
297
 
298
298
  .onion-divider::before {
299
- content: '';
299
+ content: "";
300
300
  position: absolute;
301
301
  top: 50%;
302
302
  left: 50%;
@@ -309,7 +309,7 @@ body {
309
309
  }
310
310
 
311
311
  .onion-divider::after {
312
- content: '';
312
+ content: "";
313
313
  position: absolute;
314
314
  top: 50%;
315
315
  left: 50%;
@@ -1,16 +1,18 @@
1
- document.addEventListener('DOMContentLoaded', function () {
1
+ document.addEventListener('DOMContentLoaded', () => {
2
2
  // Handle view mode switching
3
3
  document.querySelectorAll('.view-mode-btn').forEach(btn => {
4
4
  btn.addEventListener('click', function () {
5
- let comparison = this.closest('.comparison');
6
- let mode = this.dataset.mode;
5
+ const comparison = this.closest('.comparison');
6
+ const mode = this.dataset.mode;
7
7
 
8
8
  // Update active button
9
- comparison.querySelectorAll('.view-mode-btn').forEach(b => b.classList.remove('active'));
9
+ for (let b of comparison.querySelectorAll('.view-mode-btn')) {
10
+ b.classList.remove('active');
11
+ }
10
12
  this.classList.add('active');
11
13
 
12
14
  // Update viewer mode
13
- let viewer = comparison.querySelector('.comparison-viewer');
15
+ const viewer = comparison.querySelector('.comparison-viewer');
14
16
  viewer.dataset.mode = mode;
15
17
 
16
18
  // Hide all mode containers
@@ -19,7 +21,7 @@ document.addEventListener('DOMContentLoaded', function () {
19
21
  });
20
22
 
21
23
  // Show appropriate mode container
22
- let activeContainer = viewer.querySelector('.' + mode + '-mode');
24
+ const activeContainer = viewer.querySelector(`.${mode}-mode`);
23
25
  if (activeContainer) {
24
26
  activeContainer.style.display = 'block';
25
27
  }
@@ -30,42 +32,42 @@ document.addEventListener('DOMContentLoaded', function () {
30
32
  document.querySelectorAll('.onion-container').forEach(container => {
31
33
  let isDragging = false;
32
34
  function updateOnionSkin(x) {
33
- let rect = container.getBoundingClientRect();
34
- let percentage = Math.max(0, Math.min(100, (x - rect.left) / rect.width * 100));
35
- let currentImg = container.querySelector('.onion-current');
36
- let divider = container.querySelector('.onion-divider');
35
+ const rect = container.getBoundingClientRect();
36
+ const percentage = Math.max(0, Math.min(100, (x - rect.left) / rect.width * 100));
37
+ const currentImg = container.querySelector('.onion-current');
38
+ const divider = container.querySelector('.onion-divider');
37
39
  if (currentImg && divider) {
38
- currentImg.style.clipPath = 'inset(0 ' + (100 - percentage) + '% 0 0)';
39
- divider.style.left = percentage + '%';
40
+ currentImg.style.clipPath = `inset(0 ${100 - percentage}% 0 0)`;
41
+ divider.style.left = `${percentage}%`;
40
42
  }
41
43
  }
42
- container.addEventListener('mousedown', function (e) {
44
+ container.addEventListener('mousedown', e => {
43
45
  isDragging = true;
44
46
  updateOnionSkin(e.clientX);
45
47
  e.preventDefault();
46
48
  });
47
- container.addEventListener('mousemove', function (e) {
49
+ container.addEventListener('mousemove', e => {
48
50
  if (isDragging) {
49
51
  updateOnionSkin(e.clientX);
50
52
  }
51
53
  });
52
- document.addEventListener('mouseup', function () {
54
+ document.addEventListener('mouseup', () => {
53
55
  isDragging = false;
54
56
  });
55
57
 
56
58
  // Touch events for mobile
57
- container.addEventListener('touchstart', function (e) {
59
+ container.addEventListener('touchstart', e => {
58
60
  isDragging = true;
59
61
  updateOnionSkin(e.touches[0].clientX);
60
62
  e.preventDefault();
61
63
  });
62
- container.addEventListener('touchmove', function (e) {
64
+ container.addEventListener('touchmove', e => {
63
65
  if (isDragging) {
64
66
  updateOnionSkin(e.touches[0].clientX);
65
67
  e.preventDefault();
66
68
  }
67
69
  });
68
- document.addEventListener('touchend', function () {
70
+ document.addEventListener('touchend', () => {
69
71
  isDragging = false;
70
72
  });
71
73
  });
@@ -73,10 +75,10 @@ document.addEventListener('DOMContentLoaded', function () {
73
75
  // Handle overlay mode clicking
74
76
  document.querySelectorAll('.overlay-container').forEach(container => {
75
77
  container.addEventListener('click', function () {
76
- let diffImage = this.querySelector('.diff-image');
78
+ const diffImage = this.querySelector('.diff-image');
77
79
  if (diffImage) {
78
80
  // Toggle diff visibility
79
- let isVisible = diffImage.style.opacity === '1';
81
+ const isVisible = diffImage.style.opacity === '1';
80
82
  diffImage.style.opacity = isVisible ? '0' : '1';
81
83
  }
82
84
  });
@@ -85,9 +87,9 @@ document.addEventListener('DOMContentLoaded', function () {
85
87
  // Handle toggle mode clicking
86
88
  document.querySelectorAll('.toggle-container img').forEach(img => {
87
89
  let isBaseline = true;
88
- let comparison = img.closest('.comparison');
89
- let baselineSrc = comparison.querySelector('.baseline-image').src;
90
- let currentSrc = comparison.querySelector('.current-image').src;
90
+ const comparison = img.closest('.comparison');
91
+ const baselineSrc = comparison.querySelector('.baseline-image').src;
92
+ const currentSrc = comparison.querySelector('.current-image').src;
91
93
  img.addEventListener('click', function () {
92
94
  isBaseline = !isBaseline;
93
95
  this.src = isBaseline ? baselineSrc : currentSrc;
@@ -3,7 +3,7 @@
3
3
  * Listens for and processes screenshots from the test runner
4
4
  */
5
5
 
6
- import { createServer } from 'http';
6
+ import { createServer } from 'node:http';
7
7
  import { VizzlyError } from '../errors/vizzly-error.js';
8
8
  import * as output from '../utils/output.js';
9
9
  export class ScreenshotServer {
@@ -3,11 +3,11 @@
3
3
  * Manages the HTTP server with functional handlers
4
4
  */
5
5
 
6
- import { createHttpServer } from '../server/http-server.js';
7
- import { createTddHandler } from '../server/handlers/tdd-handler.js';
6
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
7
+ import { join } from 'node:path';
8
8
  import { createApiHandler } from '../server/handlers/api-handler.js';
9
- import { writeFileSync, mkdirSync, unlinkSync, existsSync } from 'fs';
10
- import { join } from 'path';
9
+ import { createTddHandler } from '../server/handlers/tdd-handler.js';
10
+ import { createHttpServer } from '../server/http-server.js';
11
11
  export class ServerManager {
12
12
  constructor(config, options = {}) {
13
13
  this.config = config;
@@ -88,7 +88,7 @@ export class ServerManager {
88
88
 
89
89
  // Clean up server.json so the client SDK doesn't try to connect to a dead server
90
90
  try {
91
- let serverFile = join(process.cwd(), '.vizzly', 'server.json');
91
+ const serverFile = join(process.cwd(), '.vizzly', 'server.json');
92
92
  if (existsSync(serverFile)) {
93
93
  unlinkSync(serverFile);
94
94
  }