@vizzly-testing/cli 0.13.4 → 0.15.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 (143) hide show
  1. package/dist/cli.js +68 -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 +78 -32
  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/fullscreen-viewer.d.ts +13 -0
  59. package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +16 -0
  60. package/dist/types/reporter/src/components/comparison/screenshot-list.d.ts +9 -0
  61. package/dist/types/reporter/src/components/comparison/variant-selector.d.ts +1 -1
  62. package/dist/types/reporter/src/components/design-system/alert.d.ts +9 -0
  63. package/dist/types/reporter/src/components/design-system/badge.d.ts +17 -0
  64. package/dist/types/reporter/src/components/design-system/button.d.ts +19 -0
  65. package/dist/types/reporter/src/components/design-system/card.d.ts +31 -0
  66. package/dist/types/reporter/src/components/design-system/empty-state.d.ts +13 -0
  67. package/dist/types/reporter/src/components/design-system/form-controls.d.ts +44 -0
  68. package/dist/types/reporter/src/components/design-system/health-ring.d.ts +7 -0
  69. package/dist/types/reporter/src/components/design-system/index.d.ts +11 -0
  70. package/dist/types/reporter/src/components/design-system/modal.d.ts +10 -0
  71. package/dist/types/reporter/src/components/design-system/skeleton.d.ts +19 -0
  72. package/dist/types/reporter/src/components/design-system/spinner.d.ts +10 -0
  73. package/dist/types/reporter/src/components/design-system/tabs.d.ts +13 -0
  74. package/dist/types/reporter/src/components/layout/header.d.ts +5 -0
  75. package/dist/types/reporter/src/components/layout/index.d.ts +2 -0
  76. package/dist/types/reporter/src/components/layout/layout.d.ts +6 -0
  77. package/dist/types/reporter/src/components/views/builds-view.d.ts +1 -0
  78. package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +5 -0
  79. package/dist/types/reporter/src/components/views/comparisons-view.d.ts +5 -6
  80. package/dist/types/reporter/src/components/views/stats-view.d.ts +1 -6
  81. package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +1 -0
  82. package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +15 -0
  83. package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +6 -0
  84. package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +6 -0
  85. package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +9 -0
  86. package/dist/types/reporter/src/lib/query-client.d.ts +2 -0
  87. package/dist/types/reporter/src/lib/query-keys.d.ts +13 -0
  88. package/dist/types/sdk/index.d.ts +2 -4
  89. package/dist/types/server/handlers/tdd-handler.d.ts +2 -0
  90. package/dist/types/server/http-server.d.ts +1 -1
  91. package/dist/types/server/middleware/cors.d.ts +11 -0
  92. package/dist/types/server/middleware/json-parser.d.ts +10 -0
  93. package/dist/types/server/middleware/response.d.ts +50 -0
  94. package/dist/types/server/routers/assets.d.ts +6 -0
  95. package/dist/types/server/routers/auth.d.ts +9 -0
  96. package/dist/types/server/routers/baseline.d.ts +13 -0
  97. package/dist/types/server/routers/cloud-proxy.d.ts +11 -0
  98. package/dist/types/server/routers/config.d.ts +9 -0
  99. package/dist/types/server/routers/dashboard.d.ts +6 -0
  100. package/dist/types/server/routers/health.d.ts +11 -0
  101. package/dist/types/server/routers/projects.d.ts +9 -0
  102. package/dist/types/server/routers/screenshot.d.ts +11 -0
  103. package/dist/types/services/build-manager.d.ts +4 -3
  104. package/dist/types/services/config-service.d.ts +2 -3
  105. package/dist/types/services/index.d.ts +7 -0
  106. package/dist/types/services/project-service.d.ts +6 -4
  107. package/dist/types/services/screenshot-server.d.ts +5 -5
  108. package/dist/types/services/server-manager.d.ts +5 -3
  109. package/dist/types/services/tdd-service.d.ts +12 -1
  110. package/dist/types/services/test-runner.d.ts +3 -3
  111. package/dist/types/utils/output.d.ts +84 -0
  112. package/dist/utils/config-loader.js +24 -48
  113. package/dist/utils/global-config.js +2 -17
  114. package/dist/utils/output.js +445 -0
  115. package/dist/utils/security.js +3 -4
  116. package/docs/api-reference.md +0 -1
  117. package/docs/plugins.md +22 -22
  118. package/package.json +3 -2
  119. package/dist/container/index.js +0 -215
  120. package/dist/services/base-service.js +0 -154
  121. package/dist/types/container/index.d.ts +0 -59
  122. package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +0 -3
  123. package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +0 -3
  124. package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +0 -3
  125. package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +0 -3
  126. package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +0 -5
  127. package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +0 -4
  128. package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +0 -8
  129. package/dist/types/reporter/src/components/ui/form-field.d.ts +0 -16
  130. package/dist/types/reporter/src/components/ui/status-badge.d.ts +0 -5
  131. package/dist/types/reporter/src/hooks/use-auth.d.ts +0 -10
  132. package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +0 -5
  133. package/dist/types/reporter/src/hooks/use-config.d.ts +0 -9
  134. package/dist/types/reporter/src/hooks/use-projects.d.ts +0 -10
  135. package/dist/types/reporter/src/hooks/use-report-data.d.ts +0 -7
  136. package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +0 -9
  137. package/dist/types/services/base-service.d.ts +0 -71
  138. package/dist/types/utils/console-ui.d.ts +0 -61
  139. package/dist/types/utils/logger-factory.d.ts +0 -26
  140. package/dist/types/utils/logger.d.ts +0 -79
  141. package/dist/utils/console-ui.js +0 -241
  142. package/dist/utils/logger-factory.js +0 -76
  143. package/dist/utils/logger.js +0 -231
package/dist/cli.js CHANGED
@@ -16,8 +16,8 @@ import { projectSelectCommand, projectListCommand, projectTokenCommand, projectR
16
16
  import { getPackageVersion } from './utils/package-info.js';
17
17
  import { loadPlugins } from './plugin-loader.js';
18
18
  import { loadConfig } from './utils/config-loader.js';
19
- import { createComponentLogger } from './utils/logger-factory.js';
20
- import { createServiceContainer } from './container/index.js';
19
+ import { createServices } from './services/index.js';
20
+ import * as output from './utils/output.js';
21
21
  program.name('vizzly').description('Vizzly CLI for visual regression testing').version(getPackageVersion()).option('-c, --config <path>', 'Config file path').option('--token <token>', 'Vizzly API token').option('-v, --verbose', 'Verbose output').option('--json', 'Machine-readable output').option('--no-color', 'Disable colored output');
22
22
 
23
23
  // Load plugins before defining commands
@@ -32,35 +32,38 @@ for (let i = 0; i < process.argv.length; i++) {
32
32
  verboseMode = true;
33
33
  }
34
34
  }
35
- let config = await loadConfig(configPath, {});
36
- let logger = createComponentLogger('CLI', {
37
- level: config.logLevel || (verboseMode ? 'debug' : 'warn'),
38
- verbose: verboseMode || false
35
+
36
+ // Configure output early
37
+ output.configure({
38
+ verbose: verboseMode,
39
+ color: !process.argv.includes('--no-color'),
40
+ json: process.argv.includes('--json')
39
41
  });
40
- let container = await createServiceContainer(config);
42
+ let config = await loadConfig(configPath, {});
43
+ let services = createServices(config);
41
44
  let plugins = [];
42
45
  try {
43
- plugins = await loadPlugins(configPath, config, logger);
46
+ plugins = await loadPlugins(configPath, config);
44
47
  for (let plugin of plugins) {
45
48
  try {
46
49
  // Add timeout protection for plugin registration (5 seconds)
47
50
  let registerPromise = plugin.register(program, {
48
51
  config,
49
- logger,
50
- services: container
52
+ services,
53
+ output
51
54
  });
52
55
  let timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Plugin registration timeout (5s)')), 5000));
53
56
  await Promise.race([registerPromise, timeoutPromise]);
54
- logger.debug(`Registered plugin: ${plugin.name}`);
57
+ output.debug(`Registered plugin: ${plugin.name}`);
55
58
  } catch (error) {
56
- logger.warn(`Failed to register plugin ${plugin.name}: ${error.message}`);
59
+ output.warn(`Failed to register plugin ${plugin.name}: ${error.message}`);
57
60
  }
58
61
  }
59
62
  } catch (error) {
60
- logger.debug(`Plugin loading failed: ${error.message}`);
63
+ output.debug(`Plugin loading failed: ${error.message}`);
61
64
  }
62
65
  program.command('init').description('Initialize Vizzly in your project').option('--force', 'Overwrite existing configuration').action(async options => {
63
- const globalOptions = program.opts();
66
+ let globalOptions = program.opts();
64
67
  await init({
65
68
  ...globalOptions,
66
69
  ...options,
@@ -68,24 +71,24 @@ program.command('init').description('Initialize Vizzly in your project').option(
68
71
  });
69
72
  });
70
73
  program.command('upload').description('Upload screenshots to Vizzly').argument('<path>', 'Path to screenshots directory or file').option('-b, --build-name <name>', 'Build name for grouping').option('-m, --metadata <json>', 'Additional metadata as JSON').option('--batch-size <n>', 'Upload batch size', v => parseInt(v, 10)).option('--upload-timeout <ms>', 'Upload timeout in milliseconds', v => parseInt(v, 10)).option('--branch <branch>', 'Git branch').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (path, options) => {
71
- const globalOptions = program.opts();
74
+ let globalOptions = program.opts();
72
75
 
73
76
  // Validate options
74
- const validationErrors = validateUploadOptions(path, options);
77
+ let validationErrors = validateUploadOptions(path, options);
75
78
  if (validationErrors.length > 0) {
76
- console.error('Validation errors:');
77
- validationErrors.forEach(error => console.error(` - ${error}`));
79
+ output.error('Validation errors:');
80
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
78
81
  process.exit(1);
79
82
  }
80
83
  await uploadCommand(path, options, globalOptions);
81
84
  });
82
85
 
83
86
  // TDD command with subcommands - Local visual testing with interactive dashboard
84
- const tddCmd = program.command('tdd').description('Run tests in TDD mode with local visual comparisons');
87
+ let tddCmd = program.command('tdd').description('Run tests in TDD mode with local visual comparisons');
85
88
 
86
89
  // TDD Start - Background server
87
90
  tddCmd.command('start').description('Start background TDD server with dashboard').option('--port <port>', 'Port for TDD server', '47392').option('--open', 'Open dashboard in browser').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--token <token>', 'API token override').option('--daemon-child', 'Internal: run as daemon child process').action(async options => {
88
- const globalOptions = program.opts();
91
+ let globalOptions = program.opts();
89
92
 
90
93
  // If this is a daemon child process, run the server directly
91
94
  if (options.daemonChild) {
@@ -97,34 +100,34 @@ tddCmd.command('start').description('Start background TDD server with dashboard'
97
100
 
98
101
  // TDD Stop - Kill background server
99
102
  tddCmd.command('stop').description('Stop background TDD server').action(async options => {
100
- const globalOptions = program.opts();
103
+ let globalOptions = program.opts();
101
104
  await tddStopCommand(options, globalOptions);
102
105
  });
103
106
 
104
107
  // TDD Status - Check server status
105
108
  tddCmd.command('status').description('Check TDD server status').action(async options => {
106
- const globalOptions = program.opts();
109
+ let globalOptions = program.opts();
107
110
  await tddStatusCommand(options, globalOptions);
108
111
  });
109
112
 
110
113
  // TDD Run - One-off test run with ephemeral server (generates static report)
111
114
  tddCmd.command('run <command>').description('Run tests once in TDD mode with local visual comparisons').option('--port <port>', 'Port for TDD server', '47392').option('--branch <branch>', 'Git branch override').option('--environment <env>', 'Environment name', 'test').option('--threshold <number>', 'Comparison threshold', parseFloat).option('--token <token>', 'API token override').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--baseline-build <id>', 'Use specific build as baseline').option('--baseline-comparison <id>', 'Use specific comparison as baseline').option('--set-baseline', 'Accept current screenshots as new baseline (overwrites existing)').action(async (command, options) => {
112
- const globalOptions = program.opts();
115
+ let globalOptions = program.opts();
113
116
 
114
117
  // Validate options
115
- const validationErrors = validateTddOptions(command, options);
118
+ let validationErrors = validateTddOptions(command, options);
116
119
  if (validationErrors.length > 0) {
117
- console.error('Validation errors:');
118
- validationErrors.forEach(error => console.error(` - ${error}`));
120
+ output.error('Validation errors:');
121
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
119
122
  process.exit(1);
120
123
  }
121
- const {
124
+ let {
122
125
  result,
123
126
  cleanup
124
127
  } = await tddCommand(command, options, globalOptions);
125
128
 
126
129
  // Set up cleanup on process signals
127
- const handleCleanup = async () => {
130
+ let handleCleanup = async () => {
128
131
  await cleanup();
129
132
  };
130
133
  process.once('SIGINT', () => {
@@ -140,122 +143,119 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
140
143
  await cleanup();
141
144
  });
142
145
  program.command('run').description('Run tests with Vizzly integration').argument('<command>', 'Test command to run').option('--port <port>', 'Port for screenshot server', '47392').option('-b, --build-name <name>', 'Custom build name').option('--branch <branch>', 'Git branch override').option('--commit <sha>', 'Git commit SHA').option('--message <msg>', 'Commit message').option('--environment <env>', 'Environment name', 'test').option('--token <token>', 'API token override').option('--wait', 'Wait for build completion').option('--timeout <ms>', 'Server timeout in milliseconds', '30000').option('--allow-no-token', 'Allow running without API token').option('--upload-all', 'Upload all screenshots without SHA deduplication').option('--parallel-id <id>', 'Unique identifier for parallel test execution').action(async (command, options) => {
143
- const globalOptions = program.opts();
146
+ let globalOptions = program.opts();
144
147
 
145
148
  // Validate options
146
- const validationErrors = validateRunOptions(command, options);
149
+ let validationErrors = validateRunOptions(command, options);
147
150
  if (validationErrors.length > 0) {
148
- console.error('Validation errors:');
149
- validationErrors.forEach(error => console.error(` - ${error}`));
151
+ output.error('Validation errors:');
152
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
150
153
  process.exit(1);
151
154
  }
152
155
  try {
153
- const result = await runCommand(command, options, globalOptions);
156
+ let result = await runCommand(command, options, globalOptions);
154
157
  if (result && !result.success && result.exitCode > 0) {
155
158
  process.exit(result.exitCode);
156
159
  }
157
160
  } catch (error) {
158
- console.error('Command failed:', error.message);
159
- if (globalOptions.verbose) {
160
- console.error('Stack trace:', error.stack);
161
- }
161
+ output.error('Command failed', error);
162
162
  process.exit(1);
163
163
  }
164
164
  });
165
165
  program.command('status').description('Check the status of a build').argument('<build-id>', 'Build ID to check status for').action(async (buildId, options) => {
166
- const globalOptions = program.opts();
166
+ let globalOptions = program.opts();
167
167
 
168
168
  // Validate options
169
- const validationErrors = validateStatusOptions(buildId, options);
169
+ let validationErrors = validateStatusOptions(buildId, options);
170
170
  if (validationErrors.length > 0) {
171
- console.error('Validation errors:');
172
- validationErrors.forEach(error => console.error(` - ${error}`));
171
+ output.error('Validation errors:');
172
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
173
173
  process.exit(1);
174
174
  }
175
175
  await statusCommand(buildId, options, globalOptions);
176
176
  });
177
177
  program.command('finalize').description('Finalize a parallel build after all shards complete').argument('<parallel-id>', 'Parallel ID to finalize').action(async (parallelId, options) => {
178
- const globalOptions = program.opts();
178
+ let globalOptions = program.opts();
179
179
 
180
180
  // Validate options
181
- const validationErrors = validateFinalizeOptions(parallelId, options);
181
+ let validationErrors = validateFinalizeOptions(parallelId, options);
182
182
  if (validationErrors.length > 0) {
183
- console.error('Validation errors:');
184
- validationErrors.forEach(error => console.error(` - ${error}`));
183
+ output.error('Validation errors:');
184
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
185
185
  process.exit(1);
186
186
  }
187
187
  await finalizeCommand(parallelId, options, globalOptions);
188
188
  });
189
189
  program.command('doctor').description('Run diagnostics to check your environment and configuration').option('--api', 'Include API connectivity checks').action(async options => {
190
- const globalOptions = program.opts();
190
+ let globalOptions = program.opts();
191
191
 
192
192
  // Validate options
193
- const validationErrors = validateDoctorOptions(options);
193
+ let validationErrors = validateDoctorOptions(options);
194
194
  if (validationErrors.length > 0) {
195
- console.error('Validation errors:');
196
- validationErrors.forEach(error => console.error(` - ${error}`));
195
+ output.error('Validation errors:');
196
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
197
197
  process.exit(1);
198
198
  }
199
199
  await doctorCommand(options, globalOptions);
200
200
  });
201
201
  program.command('login').description('Authenticate with your Vizzly account').option('--api-url <url>', 'API URL override').action(async options => {
202
- const globalOptions = program.opts();
202
+ let globalOptions = program.opts();
203
203
 
204
204
  // Validate options
205
- const validationErrors = validateLoginOptions(options);
205
+ let validationErrors = validateLoginOptions(options);
206
206
  if (validationErrors.length > 0) {
207
- console.error('Validation errors:');
208
- validationErrors.forEach(error => console.error(` - ${error}`));
207
+ output.error('Validation errors:');
208
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
209
209
  process.exit(1);
210
210
  }
211
211
  await loginCommand(options, globalOptions);
212
212
  });
213
213
  program.command('logout').description('Clear stored authentication tokens').option('--api-url <url>', 'API URL override').action(async options => {
214
- const globalOptions = program.opts();
214
+ let globalOptions = program.opts();
215
215
 
216
216
  // Validate options
217
- const validationErrors = validateLogoutOptions(options);
217
+ let validationErrors = validateLogoutOptions(options);
218
218
  if (validationErrors.length > 0) {
219
- console.error('Validation errors:');
220
- validationErrors.forEach(error => console.error(` - ${error}`));
219
+ output.error('Validation errors:');
220
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
221
221
  process.exit(1);
222
222
  }
223
223
  await logoutCommand(options, globalOptions);
224
224
  });
225
225
  program.command('whoami').description('Show current authentication status and user information').option('--api-url <url>', 'API URL override').action(async options => {
226
- const globalOptions = program.opts();
226
+ let globalOptions = program.opts();
227
227
 
228
228
  // Validate options
229
- const validationErrors = validateWhoamiOptions(options);
229
+ let validationErrors = validateWhoamiOptions(options);
230
230
  if (validationErrors.length > 0) {
231
- console.error('Validation errors:');
232
- validationErrors.forEach(error => console.error(` - ${error}`));
231
+ output.error('Validation errors:');
232
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
233
233
  process.exit(1);
234
234
  }
235
235
  await whoamiCommand(options, globalOptions);
236
236
  });
237
237
  program.command('project:select').description('Configure project for current directory').option('--api-url <url>', 'API URL override').action(async options => {
238
- const globalOptions = program.opts();
238
+ let globalOptions = program.opts();
239
239
 
240
240
  // Validate options
241
- const validationErrors = validateProjectOptions(options);
241
+ let validationErrors = validateProjectOptions(options);
242
242
  if (validationErrors.length > 0) {
243
- console.error('Validation errors:');
244
- validationErrors.forEach(error => console.error(` - ${error}`));
243
+ output.error('Validation errors:');
244
+ validationErrors.forEach(error => output.printErr(` - ${error}`));
245
245
  process.exit(1);
246
246
  }
247
247
  await projectSelectCommand(options, globalOptions);
248
248
  });
249
249
  program.command('project:list').description('Show all configured projects').action(async options => {
250
- const globalOptions = program.opts();
250
+ let globalOptions = program.opts();
251
251
  await projectListCommand(options, globalOptions);
252
252
  });
253
253
  program.command('project:token').description('Show project token for current directory').action(async options => {
254
- const globalOptions = program.opts();
254
+ let globalOptions = program.opts();
255
255
  await projectTokenCommand(options, globalOptions);
256
256
  });
257
257
  program.command('project:remove').description('Remove project configuration for current directory').action(async options => {
258
- const globalOptions = program.opts();
258
+ let globalOptions = program.opts();
259
259
  await projectRemoveCommand(options, globalOptions);
260
260
  });
261
261
  program.parse();
@@ -1,6 +1,6 @@
1
1
  import { URL } from 'url';
2
2
  import { loadConfig } from '../utils/config-loader.js';
3
- import { ConsoleUI } from '../utils/console-ui.js';
3
+ import * as output from '../utils/output.js';
4
4
  import { ApiService } from '../services/api-service.js';
5
5
  import { ConfigError } from '../errors/vizzly-error.js';
6
6
  import { getApiToken } from '../utils/environment-config.js';
@@ -11,16 +11,12 @@ import { getApiToken } from '../utils/environment-config.js';
11
11
  * @param {Object} globalOptions - Global CLI options
12
12
  */
13
13
  export async function doctorCommand(options = {}, globalOptions = {}) {
14
- // Create UI handler
15
- const ui = new ConsoleUI({
14
+ output.configure({
16
15
  json: globalOptions.json,
17
16
  verbose: globalOptions.verbose,
18
17
  color: !globalOptions.noColor
19
18
  });
20
-
21
- // Note: ConsoleUI handles cleanup via global process listeners
22
-
23
- const diagnostics = {
19
+ let diagnostics = {
24
20
  environment: {
25
21
  nodeVersion: null,
26
22
  nodeVersionValid: null
@@ -41,71 +37,71 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
41
37
  let hasErrors = false;
42
38
  try {
43
39
  // Determine if we'll attempt remote checks (API connectivity)
44
- const willCheckConnectivity = Boolean(options.api || getApiToken());
40
+ let willCheckConnectivity = Boolean(options.api || getApiToken());
45
41
 
46
42
  // Announce preflight, indicating local-only when no token/connectivity is planned
47
- ui.info(`Running Vizzly preflight${willCheckConnectivity ? '' : ' (local checks only)'}...`);
43
+ output.info(`Running Vizzly preflight${willCheckConnectivity ? '' : ' (local checks only)'}...`);
48
44
 
49
45
  // Node.js version check (require >= 20)
50
- const nodeVersion = process.version;
51
- const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
46
+ let nodeVersion = process.version;
47
+ let nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
52
48
  diagnostics.environment.nodeVersion = nodeVersion;
53
49
  diagnostics.environment.nodeVersionValid = nodeMajor >= 20;
54
50
  if (nodeMajor >= 20) {
55
- ui.success(`Node.js version: ${nodeVersion} (supported)`);
51
+ output.success(`Node.js version: ${nodeVersion} (supported)`);
56
52
  } else {
57
53
  hasErrors = true;
58
- ui.error('Node.js version must be >= 20', {}, 0);
54
+ output.error('Node.js version must be >= 20');
59
55
  }
60
56
 
61
57
  // Load configuration (apply global CLI overrides like --config only)
62
- const config = await loadConfig(globalOptions.config);
58
+ let config = await loadConfig(globalOptions.config);
63
59
 
64
60
  // Validate apiUrl
65
61
  diagnostics.configuration.apiUrl = config.apiUrl;
66
62
  try {
67
- const url = new URL(config.apiUrl);
63
+ let url = new URL(config.apiUrl);
68
64
  if (!['http:', 'https:'].includes(url.protocol)) {
69
65
  throw new ConfigError('URL must use http or https');
70
66
  }
71
67
  diagnostics.configuration.apiUrlValid = true;
72
- ui.success(`API URL: ${config.apiUrl}`);
68
+ output.success(`API URL: ${config.apiUrl}`);
73
69
  } catch (e) {
74
70
  diagnostics.configuration.apiUrlValid = false;
75
71
  hasErrors = true;
76
- ui.error('Invalid apiUrl in configuration (set VIZZLY_API_URL or config file)', e, 0);
72
+ output.error('Invalid apiUrl in configuration (set VIZZLY_API_URL or config file)', e);
77
73
  }
78
74
 
79
75
  // Validate threshold (0..1 inclusive)
80
- const threshold = Number(config?.comparison?.threshold);
76
+ let threshold = Number(config?.comparison?.threshold);
81
77
  diagnostics.configuration.threshold = threshold;
82
- const thresholdValid = Number.isFinite(threshold) && threshold >= 0 && threshold <= 1;
78
+ let thresholdValid = Number.isFinite(threshold) && threshold >= 0 && threshold <= 1;
83
79
  diagnostics.configuration.thresholdValid = thresholdValid;
84
80
  if (thresholdValid) {
85
- ui.success(`Threshold: ${threshold}`);
81
+ output.success(`Threshold: ${threshold}`);
86
82
  } else {
87
83
  hasErrors = true;
88
- ui.error('Invalid threshold (expected number between 0 and 1)', {}, 0);
84
+ output.error('Invalid threshold (expected number between 0 and 1)');
89
85
  }
90
86
 
91
87
  // Report effective port without binding
92
- const port = config?.server?.port ?? 47392;
88
+ let port = config?.server?.port ?? 47392;
93
89
  diagnostics.configuration.port = port;
94
- ui.info(`Effective port: ${port}`);
90
+ output.info(`Effective port: ${port}`);
95
91
 
96
92
  // Optional: API connectivity check when --api is provided or VIZZLY_TOKEN is present
97
- const autoApi = Boolean(getApiToken());
93
+ let autoApi = Boolean(getApiToken());
98
94
  if (options.api || autoApi) {
99
95
  diagnostics.connectivity.checked = true;
100
96
  if (!config.apiKey) {
101
97
  diagnostics.connectivity.ok = false;
102
98
  diagnostics.connectivity.error = 'Missing API token (VIZZLY_TOKEN)';
103
99
  hasErrors = true;
104
- ui.error('Missing API token for connectivity check', {}, 0);
100
+ output.error('Missing API token for connectivity check');
105
101
  } else {
106
- ui.progress('Checking API connectivity...');
102
+ output.progress('Checking API connectivity...');
107
103
  try {
108
- const api = new ApiService({
104
+ let api = new ApiService({
109
105
  baseUrl: config.apiUrl,
110
106
  token: config.apiKey,
111
107
  command: 'doctor'
@@ -115,26 +111,26 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
115
111
  limit: 1
116
112
  });
117
113
  diagnostics.connectivity.ok = true;
118
- ui.success('API connectivity OK');
114
+ output.success('API connectivity OK');
119
115
  } catch (err) {
120
116
  diagnostics.connectivity.ok = false;
121
117
  diagnostics.connectivity.error = err?.message || String(err);
122
118
  hasErrors = true;
123
- ui.error('API connectivity failed', err, 0);
119
+ output.error('API connectivity failed', err);
124
120
  }
125
121
  }
126
122
  }
127
123
 
128
124
  // Summary
129
125
  if (hasErrors) {
130
- ui.warning('Preflight completed with issues.');
126
+ output.warn('Preflight completed with issues.');
131
127
  } else {
132
- ui.success('Preflight passed.');
128
+ output.success('Preflight passed.');
133
129
  }
134
130
 
135
131
  // Emit structured data in json/verbose modes
136
132
  if (globalOptions.json || globalOptions.verbose) {
137
- ui.data({
133
+ output.data({
138
134
  passed: !hasErrors,
139
135
  diagnostics,
140
136
  timestamp: new Date().toISOString()
@@ -142,9 +138,9 @@ export async function doctorCommand(options = {}, globalOptions = {}) {
142
138
  }
143
139
  } catch (error) {
144
140
  hasErrors = true;
145
- ui.error('Failed to run preflight', error, 0);
141
+ output.error('Failed to run preflight', error);
146
142
  } finally {
147
- ui.cleanup();
143
+ output.cleanup();
148
144
  if (hasErrors) process.exit(1);
149
145
  }
150
146
  }
@@ -1,6 +1,6 @@
1
1
  import { loadConfig } from '../utils/config-loader.js';
2
- import { ConsoleUI } from '../utils/console-ui.js';
3
- import { createServiceContainer } from '../container/index.js';
2
+ import * as output from '../utils/output.js';
3
+ import { createServices } from '../services/index.js';
4
4
 
5
5
  /**
6
6
  * Finalize command implementation
@@ -9,52 +9,53 @@ import { createServiceContainer } from '../container/index.js';
9
9
  * @param {Object} globalOptions - Global CLI options
10
10
  */
11
11
  export async function finalizeCommand(parallelId, options = {}, globalOptions = {}) {
12
- // Create UI handler
13
- const ui = new ConsoleUI({
12
+ output.configure({
14
13
  json: globalOptions.json,
15
14
  verbose: globalOptions.verbose,
16
15
  color: !globalOptions.noColor
17
16
  });
18
17
  try {
19
18
  // Load configuration with CLI overrides
20
- const allOptions = {
19
+ let allOptions = {
21
20
  ...globalOptions,
22
21
  ...options
23
22
  };
24
- const config = await loadConfig(globalOptions.config, allOptions);
23
+ let config = await loadConfig(globalOptions.config, allOptions);
25
24
 
26
25
  // Validate API token
27
26
  if (!config.apiKey) {
28
- ui.error('API token required. Use --token or set VIZZLY_TOKEN environment variable');
29
- return;
27
+ output.error('API token required. Use --token or set VIZZLY_TOKEN environment variable');
28
+ process.exit(1);
30
29
  }
31
30
  if (globalOptions.verbose) {
32
- ui.info('Configuration loaded', {
31
+ output.info('Configuration loaded');
32
+ output.debug('Config details', {
33
33
  parallelId,
34
34
  apiUrl: config.apiUrl
35
35
  });
36
36
  }
37
37
 
38
- // Create service container and get API service
39
- ui.startSpinner('Finalizing parallel build...');
40
- const container = await createServiceContainer(config, 'finalize');
41
- const apiService = await container.get('apiService');
42
- ui.stopSpinner();
38
+ // Create services and get API service
39
+ output.startSpinner('Finalizing parallel build...');
40
+ let services = createServices(config, 'finalize');
41
+ let apiService = services.apiService;
42
+ output.stopSpinner();
43
43
 
44
44
  // Call finalize endpoint
45
- const result = await apiService.finalizeParallelBuild(parallelId);
45
+ let result = await apiService.finalizeParallelBuild(parallelId);
46
46
  if (globalOptions.json) {
47
- console.log(JSON.stringify(result, null, 2));
47
+ output.data(result);
48
48
  } else {
49
- ui.success(`Parallel build ${result.build.id} finalized successfully`);
50
- ui.info(`Status: ${result.build.status}`);
51
- ui.info(`Parallel ID: ${result.build.parallel_id}`);
49
+ output.success(`Parallel build ${result.build.id} finalized successfully`);
50
+ output.info(`Status: ${result.build.status}`);
51
+ output.info(`Parallel ID: ${result.build.parallel_id}`);
52
52
  }
53
53
  } catch (error) {
54
- ui.stopSpinner();
55
- ui.error('Failed to finalize parallel build', error);
54
+ output.stopSpinner();
55
+ output.error('Failed to finalize parallel build', error);
56
+ process.exit(1);
56
57
  } finally {
57
- ui.cleanup();
58
+ output.cleanup();
58
59
  }
59
60
  }
60
61
 
@@ -64,7 +65,7 @@ export async function finalizeCommand(parallelId, options = {}, globalOptions =
64
65
  * @param {Object} options - Command options
65
66
  */
66
67
  export function validateFinalizeOptions(parallelId, _options) {
67
- const errors = [];
68
+ let errors = [];
68
69
  if (!parallelId || parallelId.trim() === '') {
69
70
  errors.push('Parallel ID is required');
70
71
  }