@vizzly-testing/cli 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/cli.js +70 -68
  2. package/dist/commands/doctor.js +30 -34
  3. package/dist/commands/finalize.js +24 -23
  4. package/dist/commands/init.js +30 -28
  5. package/dist/commands/login.js +49 -55
  6. package/dist/commands/logout.js +14 -19
  7. package/dist/commands/project.js +83 -103
  8. package/dist/commands/run.js +77 -89
  9. package/dist/commands/status.js +48 -49
  10. package/dist/commands/tdd-daemon.js +90 -86
  11. package/dist/commands/tdd.js +59 -88
  12. package/dist/commands/upload.js +57 -57
  13. package/dist/commands/whoami.js +40 -45
  14. package/dist/index.js +2 -5
  15. package/dist/plugin-loader.js +15 -17
  16. package/dist/reporter/reporter-bundle.css +1 -1
  17. package/dist/reporter/reporter-bundle.iife.js +74 -41
  18. package/dist/sdk/index.js +36 -45
  19. package/dist/server/handlers/api-handler.js +14 -15
  20. package/dist/server/handlers/tdd-handler.js +34 -37
  21. package/dist/server/http-server.js +75 -869
  22. package/dist/server/middleware/cors.js +22 -0
  23. package/dist/server/middleware/json-parser.js +35 -0
  24. package/dist/server/middleware/response.js +79 -0
  25. package/dist/server/routers/assets.js +91 -0
  26. package/dist/server/routers/auth.js +144 -0
  27. package/dist/server/routers/baseline.js +163 -0
  28. package/dist/server/routers/cloud-proxy.js +146 -0
  29. package/dist/server/routers/config.js +126 -0
  30. package/dist/server/routers/dashboard.js +130 -0
  31. package/dist/server/routers/health.js +61 -0
  32. package/dist/server/routers/projects.js +168 -0
  33. package/dist/server/routers/screenshot.js +86 -0
  34. package/dist/services/auth-service.js +1 -1
  35. package/dist/services/build-manager.js +13 -40
  36. package/dist/services/config-service.js +2 -4
  37. package/dist/services/html-report-generator.js +6 -5
  38. package/dist/services/index.js +64 -0
  39. package/dist/services/project-service.js +121 -40
  40. package/dist/services/screenshot-server.js +9 -9
  41. package/dist/services/server-manager.js +11 -18
  42. package/dist/services/static-report-generator.js +3 -4
  43. package/dist/services/tdd-service.js +246 -103
  44. package/dist/services/test-runner.js +24 -25
  45. package/dist/services/uploader.js +5 -4
  46. package/dist/types/commands/init.d.ts +1 -2
  47. package/dist/types/index.d.ts +2 -3
  48. package/dist/types/plugin-loader.d.ts +1 -2
  49. package/dist/types/reporter/src/api/client.d.ts +178 -0
  50. package/dist/types/reporter/src/components/app-router.d.ts +1 -3
  51. package/dist/types/reporter/src/components/code-block.d.ts +4 -0
  52. package/dist/types/reporter/src/components/comparison/comparison-modes/onion-skin-mode.d.ts +10 -0
  53. package/dist/types/reporter/src/components/comparison/comparison-modes/overlay-mode.d.ts +11 -0
  54. package/dist/types/reporter/src/components/comparison/comparison-modes/shared/base-comparison-mode.d.ts +14 -0
  55. package/dist/types/reporter/src/components/comparison/comparison-modes/shared/image-renderer.d.ts +30 -0
  56. package/dist/types/reporter/src/components/comparison/comparison-modes/toggle-view.d.ts +8 -0
  57. package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
  58. package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +16 -0
  59. package/dist/types/reporter/src/components/design-system/alert.d.ts +9 -0
  60. package/dist/types/reporter/src/components/design-system/badge.d.ts +17 -0
  61. package/dist/types/reporter/src/components/design-system/button.d.ts +19 -0
  62. package/dist/types/reporter/src/components/design-system/card.d.ts +31 -0
  63. package/dist/types/reporter/src/components/design-system/empty-state.d.ts +13 -0
  64. package/dist/types/reporter/src/components/design-system/form-controls.d.ts +44 -0
  65. package/dist/types/reporter/src/components/design-system/health-ring.d.ts +7 -0
  66. package/dist/types/reporter/src/components/design-system/index.d.ts +11 -0
  67. package/dist/types/reporter/src/components/design-system/modal.d.ts +10 -0
  68. package/dist/types/reporter/src/components/design-system/skeleton.d.ts +19 -0
  69. package/dist/types/reporter/src/components/design-system/spinner.d.ts +10 -0
  70. package/dist/types/reporter/src/components/design-system/tabs.d.ts +13 -0
  71. package/dist/types/reporter/src/components/layout/header.d.ts +5 -0
  72. package/dist/types/reporter/src/components/layout/index.d.ts +2 -0
  73. package/dist/types/reporter/src/components/layout/layout.d.ts +6 -0
  74. package/dist/types/reporter/src/components/views/builds-view.d.ts +1 -0
  75. package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +1 -4
  76. package/dist/types/reporter/src/components/views/comparisons-view.d.ts +1 -6
  77. package/dist/types/reporter/src/components/views/stats-view.d.ts +1 -6
  78. package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +1 -0
  79. package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +15 -0
  80. package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +6 -0
  81. package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +6 -0
  82. package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +9 -0
  83. package/dist/types/reporter/src/lib/query-client.d.ts +2 -0
  84. package/dist/types/reporter/src/lib/query-keys.d.ts +13 -0
  85. package/dist/types/sdk/index.d.ts +2 -4
  86. package/dist/types/server/handlers/tdd-handler.d.ts +2 -0
  87. package/dist/types/server/http-server.d.ts +1 -1
  88. package/dist/types/server/middleware/cors.d.ts +11 -0
  89. package/dist/types/server/middleware/json-parser.d.ts +10 -0
  90. package/dist/types/server/middleware/response.d.ts +50 -0
  91. package/dist/types/server/routers/assets.d.ts +6 -0
  92. package/dist/types/server/routers/auth.d.ts +9 -0
  93. package/dist/types/server/routers/baseline.d.ts +13 -0
  94. package/dist/types/server/routers/cloud-proxy.d.ts +11 -0
  95. package/dist/types/server/routers/config.d.ts +9 -0
  96. package/dist/types/server/routers/dashboard.d.ts +6 -0
  97. package/dist/types/server/routers/health.d.ts +11 -0
  98. package/dist/types/server/routers/projects.d.ts +9 -0
  99. package/dist/types/server/routers/screenshot.d.ts +11 -0
  100. package/dist/types/services/build-manager.d.ts +4 -3
  101. package/dist/types/services/config-service.d.ts +2 -3
  102. package/dist/types/services/index.d.ts +7 -0
  103. package/dist/types/services/project-service.d.ts +6 -4
  104. package/dist/types/services/screenshot-server.d.ts +5 -5
  105. package/dist/types/services/server-manager.d.ts +5 -3
  106. package/dist/types/services/tdd-service.d.ts +12 -1
  107. package/dist/types/services/test-runner.d.ts +3 -3
  108. package/dist/types/utils/output.d.ts +84 -0
  109. package/dist/utils/config-loader.js +24 -48
  110. package/dist/utils/global-config.js +2 -17
  111. package/dist/utils/output.js +445 -0
  112. package/dist/utils/security.js +3 -4
  113. package/docs/api-reference.md +0 -1
  114. package/docs/plugins.md +33 -34
  115. package/package.json +3 -2
  116. package/dist/container/index.js +0 -215
  117. package/dist/services/base-service.js +0 -154
  118. package/dist/types/container/index.d.ts +0 -59
  119. package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +0 -3
  120. package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +0 -3
  121. package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +0 -3
  122. package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +0 -3
  123. package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +0 -5
  124. package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +0 -4
  125. package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +0 -8
  126. package/dist/types/reporter/src/components/ui/form-field.d.ts +0 -16
  127. package/dist/types/reporter/src/components/ui/status-badge.d.ts +0 -5
  128. package/dist/types/reporter/src/hooks/use-auth.d.ts +0 -10
  129. package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +0 -5
  130. package/dist/types/reporter/src/hooks/use-config.d.ts +0 -9
  131. package/dist/types/reporter/src/hooks/use-projects.d.ts +0 -10
  132. package/dist/types/reporter/src/hooks/use-report-data.d.ts +0 -7
  133. package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +0 -9
  134. package/dist/types/services/base-service.d.ts +0 -71
  135. package/dist/types/utils/console-ui.d.ts +0 -61
  136. package/dist/types/utils/logger-factory.d.ts +0 -26
  137. package/dist/types/utils/logger.d.ts +0 -79
  138. package/dist/utils/console-ui.js +0 -241
  139. package/dist/utils/logger-factory.js +0 -76
  140. package/dist/utils/logger.js +0 -231
@@ -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
  import { detectBranch, detectCommit } from '../utils/git.js';
5
5
 
6
6
  /**
@@ -11,7 +11,7 @@ import { detectBranch, detectCommit } from '../utils/git.js';
11
11
  * @returns {Promise<{result: Object, cleanup: Function}>} Result and cleanup function
12
12
  */
13
13
  export async function tddCommand(testCommand, options = {}, globalOptions = {}) {
14
- const ui = new ConsoleUI({
14
+ output.configure({
15
15
  json: globalOptions.json,
16
16
  verbose: globalOptions.verbose,
17
17
  color: !globalOptions.noColor
@@ -20,126 +20,99 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
20
20
  let isCleanedUp = false;
21
21
 
22
22
  // Create cleanup function that can be called by the caller
23
- const cleanup = async () => {
23
+ let cleanup = async () => {
24
24
  if (isCleanedUp) return;
25
25
  isCleanedUp = true;
26
- ui.cleanup();
26
+ output.cleanup();
27
27
  if (testRunner?.cancel) {
28
28
  await testRunner.cancel();
29
29
  }
30
30
  };
31
31
  try {
32
32
  // Load configuration with CLI overrides
33
- const allOptions = {
33
+ let allOptions = {
34
34
  ...globalOptions,
35
35
  ...options
36
36
  };
37
- const config = await loadConfig(globalOptions.config, allOptions);
37
+ let config = await loadConfig(globalOptions.config, allOptions);
38
38
 
39
39
  // Dev mode works locally by default - only needs token for baseline download
40
- const needsToken = options.baselineBuild || options.baselineComparison;
40
+ let needsToken = options.baselineBuild || options.baselineComparison;
41
41
  if (!config.apiKey && needsToken) {
42
42
  throw new Error('API token required when using --baseline-build or --baseline-comparison flags');
43
43
  }
44
44
 
45
45
  // Always allow no-token mode for dev mode unless baseline flags are used
46
46
  config.allowNoToken = true;
47
- if (!config.apiKey && !options.daemon) {
48
- ui.info('Running in local-only mode (no API token)');
49
- } else if (!needsToken && !options.daemon) {
50
- ui.info('Running in local mode (API token available but not needed)');
51
- }
52
47
 
53
48
  // Collect git metadata
54
- const branch = await detectBranch(options.branch);
55
- const commit = await detectCommit(options.commit);
49
+ let branch = await detectBranch(options.branch);
50
+ let commit = await detectCommit(options.commit);
51
+
52
+ // Show header (skip in daemon mode)
53
+ if (!options.daemon) {
54
+ let mode = config.apiKey ? 'local' : 'local';
55
+ output.header('tdd', mode);
56
56
 
57
- // Only show config in verbose mode for non-daemon (daemon shows baseline info instead)
58
- if (globalOptions.verbose && !options.daemon) {
59
- ui.info('TDD Configuration loaded', {
60
- testCommand,
57
+ // Show config in verbose mode
58
+ output.debug('config', 'loaded', {
61
59
  port: config.server.port,
62
- timeout: config.server.timeout,
63
60
  branch,
64
- commit: commit?.substring(0, 7),
65
- environment: config.build.environment,
66
- threshold: config.comparison.threshold,
67
- baselineBuildId: config.baselineBuildId,
68
- baselineComparisonId: config.baselineComparisonId
61
+ threshold: config.comparison.threshold
69
62
  });
70
63
  }
71
64
 
72
- // Create service container and get services
73
- ui.startSpinner('Initializing TDD server...');
74
- const configWithVerbose = {
65
+ // Create services
66
+ output.startSpinner('Initializing TDD server...');
67
+ let configWithVerbose = {
75
68
  ...config,
76
69
  verbose: globalOptions.verbose
77
70
  };
78
- const container = await createServiceContainer(configWithVerbose, 'tdd');
79
- testRunner = await container.get('testRunner');
80
- ui.stopSpinner();
71
+ let services = createServices(configWithVerbose, 'tdd');
72
+ testRunner = services.testRunner;
73
+ output.stopSpinner();
81
74
 
82
75
  // Set up event handlers for user feedback
83
76
  testRunner.on('progress', progressData => {
84
- const {
77
+ let {
85
78
  message: progressMessage
86
79
  } = progressData;
87
- ui.progress(progressMessage || 'Running tests...');
80
+ output.progress(progressMessage || 'Running tests...');
88
81
  });
89
- testRunner.on('test-output', output => {
82
+ testRunner.on('test-output', data => {
90
83
  // In non-JSON mode, show test output directly
91
84
  if (!globalOptions.json) {
92
- ui.stopSpinner();
93
- console.log(output.data);
85
+ output.stopSpinner();
86
+ output.print(data.data);
94
87
  }
95
88
  });
96
89
  testRunner.on('server-ready', serverInfo => {
97
90
  // Only show in non-daemon mode (daemon shows its own startup message)
98
91
  if (!options.daemon) {
99
- ui.info(`TDD screenshot server running on port ${serverInfo.port}`);
100
- ui.info(`Dashboard: http://localhost:${serverInfo.port}/dashboard`);
101
- }
102
- // Verbose server details only in non-daemon mode
103
- if (globalOptions.verbose && !options.daemon) {
104
- ui.info('Server started', {
105
- port: serverInfo.port,
106
- pid: serverInfo.pid,
107
- uptime: serverInfo.uptime
108
- });
92
+ output.debug('server', `listening on :${serverInfo.port}`);
109
93
  }
110
94
  });
111
95
  testRunner.on('screenshot-captured', screenshotInfo => {
112
- ui.info(`Vizzly TDD: Screenshot captured - ${screenshotInfo.name}`);
96
+ output.debug('capture', screenshotInfo.name);
113
97
  });
114
98
  testRunner.on('comparison-result', comparisonInfo => {
115
- const {
99
+ let {
116
100
  name,
117
101
  status,
118
102
  pixelDifference
119
103
  } = comparisonInfo;
120
104
  if (status === 'passed') {
121
- ui.info(`✅ ${name}: Visual comparison passed`);
105
+ output.debug('compare', `${name} passed`);
122
106
  } else if (status === 'failed') {
123
- ui.warning(`❌ ${name}: Visual comparison failed (${pixelDifference}% difference)`);
107
+ output.warn(`${name}: ${pixelDifference}% difference`);
124
108
  } else if (status === 'new') {
125
- ui.warning(`🆕 ${name}: New screenshot (no baseline)`);
109
+ output.debug('compare', `${name} (new baseline)`);
126
110
  }
127
111
  });
128
112
  testRunner.on('error', error => {
129
- ui.error('TDD test runner error occurred', error, 0); // Don't exit immediately
113
+ output.error('Test runner error', error);
130
114
  });
131
-
132
- // Show informational messages about baseline behavior (skip in daemon mode)
133
- if (!options.daemon) {
134
- if (options.setBaseline) {
135
- ui.info('🐻 Baseline update mode - will ignore existing baselines and create new ones');
136
- } else if (options.baselineBuild || options.baselineComparison) {
137
- ui.info('📥 Will fetch remote baselines from Vizzly for local comparison');
138
- } else {
139
- ui.info('📁 Will use local baselines or create new ones when screenshots differ');
140
- }
141
- }
142
- const runOptions = {
115
+ let runOptions = {
143
116
  testCommand,
144
117
  port: config.server.port,
145
118
  timeout: config.server.timeout,
@@ -175,30 +148,28 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
175
148
  }
176
149
 
177
150
  // Normal dev mode - run tests
178
- ui.info('Starting test execution...');
179
- const result = await testRunner.run(runOptions);
151
+ output.debug('run', testCommand);
152
+ let runResult = await testRunner.run(runOptions);
180
153
 
181
154
  // Show summary
182
- const {
155
+ let {
183
156
  screenshotsCaptured,
184
157
  comparisons
185
- } = result;
186
- console.log(`🐻 Vizzly TDD: Processed ${screenshotsCaptured} screenshots`);
187
- if (comparisons && comparisons.length > 0) {
188
- const passed = comparisons.filter(c => c.status === 'passed').length;
189
- const failed = comparisons.filter(c => c.status === 'failed').length;
190
- const newScreenshots = comparisons.filter(c => c.status === 'new').length;
191
- console.log(`📊 Results: ${passed} passed, ${failed} failed, ${newScreenshots} new`);
192
- if (failed > 0) {
193
- console.log(`🔍 Check diff images in .vizzly/diffs/ directory`);
194
- }
195
- }
196
- ui.success('TDD test run completed');
158
+ } = runResult;
197
159
 
198
160
  // Determine success based on comparison results
199
- const hasFailures = result.failed || result.comparisons && result.comparisons.some(c => c.status === 'failed');
200
- if (hasFailures) {
201
- ui.error('Visual differences detected in TDD mode', {}, 0);
161
+ let hasFailures = runResult.failed || runResult.comparisons && runResult.comparisons.some(c => c.status === 'failed');
162
+ if (comparisons && comparisons.length > 0) {
163
+ let passed = comparisons.filter(c => c.status === 'passed').length;
164
+ let failed = comparisons.filter(c => c.status === 'failed').length;
165
+ if (hasFailures) {
166
+ output.error(`${failed} visual difference${failed !== 1 ? 's' : ''} detected`);
167
+ output.info(`Check .vizzly/diffs/ for diff images`);
168
+ } else {
169
+ output.result(`${screenshotsCaptured} screenshot${screenshotsCaptured !== 1 ? 's' : ''} · ${passed} passed`);
170
+ }
171
+ } else {
172
+ output.result(`${screenshotsCaptured} screenshot${screenshotsCaptured !== 1 ? 's' : ''}`);
202
173
  }
203
174
 
204
175
  // Return result and cleanup function
@@ -206,12 +177,12 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
206
177
  result: {
207
178
  success: !hasFailures,
208
179
  exitCode: hasFailures ? 1 : 0,
209
- ...result
180
+ ...runResult
210
181
  },
211
182
  cleanup
212
183
  };
213
184
  } catch (error) {
214
- ui.error('TDD test run failed', error);
185
+ output.error('Test failed', error);
215
186
  return {
216
187
  result: {
217
188
  success: false,
@@ -229,24 +200,24 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
229
200
  * @param {Object} options - Command options
230
201
  */
231
202
  export function validateTddOptions(testCommand, options) {
232
- const errors = [];
203
+ let errors = [];
233
204
  if (!testCommand || testCommand.trim() === '') {
234
205
  errors.push('Test command is required');
235
206
  }
236
207
  if (options.port) {
237
- const port = parseInt(options.port, 10);
208
+ let port = parseInt(options.port, 10);
238
209
  if (isNaN(port) || port < 1 || port > 65535) {
239
210
  errors.push('Port must be a valid number between 1 and 65535');
240
211
  }
241
212
  }
242
213
  if (options.timeout) {
243
- const timeout = parseInt(options.timeout, 10);
214
+ let timeout = parseInt(options.timeout, 10);
244
215
  if (isNaN(timeout) || timeout < 1000) {
245
216
  errors.push('Timeout must be at least 1000 milliseconds');
246
217
  }
247
218
  }
248
219
  if (options.threshold !== undefined) {
249
- const threshold = parseFloat(options.threshold);
220
+ let threshold = parseFloat(options.threshold);
250
221
  if (isNaN(threshold) || threshold < 0 || threshold > 1) {
251
222
  errors.push('Threshold must be a number between 0 and 1');
252
223
  }
@@ -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
  import { detectBranch, detectCommit, detectCommitMessage, detectPullRequestNumber, generateBuildNameWithGit } from '../utils/git.js';
5
5
  import { ApiService } from '../services/api-service.js';
6
6
 
@@ -13,23 +13,25 @@ import { ApiService } from '../services/api-service.js';
13
13
  */
14
14
  async function constructBuildUrl(buildId, apiUrl, apiToken) {
15
15
  try {
16
- const apiService = new ApiService({
16
+ let apiService = new ApiService({
17
17
  baseUrl: apiUrl,
18
18
  token: apiToken,
19
19
  command: 'upload'
20
20
  });
21
- const tokenContext = await apiService.getTokenContext();
22
- const baseUrl = apiUrl.replace(/\/api.*$/, '');
21
+ let tokenContext = await apiService.getTokenContext();
22
+ let baseUrl = apiUrl.replace(/\/api.*$/, '');
23
23
  if (tokenContext.organization?.slug && tokenContext.project?.slug) {
24
24
  return `${baseUrl}/${tokenContext.organization.slug}/${tokenContext.project.slug}/builds/${buildId}`;
25
25
  }
26
26
  } catch (error) {
27
27
  // Fall back to simple URL if context fetch fails
28
- console.debug('Failed to fetch token context, using fallback URL:', error.message);
28
+ output.debug('Failed to fetch token context, using fallback URL:', {
29
+ error: error.message
30
+ });
29
31
  }
30
32
 
31
33
  // Fallback URL construction
32
- const baseUrl = apiUrl.replace(/\/api.*$/, '');
34
+ let baseUrl = apiUrl.replace(/\/api.*$/, '');
33
35
  return `${baseUrl}/builds/${buildId}`;
34
36
  }
35
37
 
@@ -40,23 +42,19 @@ async function constructBuildUrl(buildId, apiUrl, apiToken) {
40
42
  * @param {Object} globalOptions - Global CLI options
41
43
  */
42
44
  export async function uploadCommand(screenshotsPath, options = {}, globalOptions = {}) {
43
- // Create UI handler
44
- const ui = new ConsoleUI({
45
+ output.configure({
45
46
  json: globalOptions.json,
46
47
  verbose: globalOptions.verbose,
47
48
  color: !globalOptions.noColor
48
49
  });
49
-
50
- // Note: ConsoleUI handles cleanup via global process listeners
51
-
52
50
  let buildId = null;
53
51
  let config = null;
54
- const uploadStartTime = Date.now();
52
+ let uploadStartTime = Date.now();
55
53
  try {
56
- ui.info('Starting upload process...');
54
+ output.info('Starting upload process...');
57
55
 
58
56
  // Load configuration with CLI overrides
59
- const allOptions = {
57
+ let allOptions = {
60
58
  ...globalOptions,
61
59
  ...options
62
60
  };
@@ -64,19 +62,20 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
64
62
 
65
63
  // Validate API token
66
64
  if (!config.apiKey) {
67
- ui.error('API token required. Use --token or set VIZZLY_TOKEN environment variable');
68
- return; // Won't reach here due to process.exit in error()
65
+ output.error('API token required. Use --token or set VIZZLY_TOKEN environment variable');
66
+ process.exit(1);
69
67
  }
70
68
 
71
69
  // Collect git metadata if not provided
72
- const branch = await detectBranch(options.branch);
73
- const commit = await detectCommit(options.commit);
74
- const message = options.message || (await detectCommitMessage());
75
- const buildName = await generateBuildNameWithGit(options.buildName);
76
- const pullRequestNumber = detectPullRequestNumber();
77
- ui.info(`Uploading screenshots from: ${screenshotsPath}`);
70
+ let branch = await detectBranch(options.branch);
71
+ let commit = await detectCommit(options.commit);
72
+ let message = options.message || (await detectCommitMessage());
73
+ let buildName = await generateBuildNameWithGit(options.buildName);
74
+ let pullRequestNumber = detectPullRequestNumber();
75
+ output.info(`Uploading screenshots from: ${screenshotsPath}`);
78
76
  if (globalOptions.verbose) {
79
- ui.info('Configuration loaded', {
77
+ output.info('Configuration loaded');
78
+ output.debug('Config details', {
80
79
  branch,
81
80
  commit: commit?.substring(0, 7),
82
81
  environment: config.build.environment,
@@ -85,12 +84,12 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
85
84
  }
86
85
 
87
86
  // Get uploader service
88
- ui.startSpinner('Initializing uploader...');
89
- const container = await createServiceContainer(config, 'upload');
90
- const uploader = await container.get('uploader');
87
+ output.startSpinner('Initializing uploader...');
88
+ let services = createServices(config, 'upload');
89
+ let uploader = services.uploader;
91
90
 
92
91
  // Prepare upload options with progress callback
93
- const uploadOptions = {
92
+ let uploadOptions = {
94
93
  screenshotsDir: screenshotsPath,
95
94
  buildName,
96
95
  branch,
@@ -103,7 +102,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
103
102
  pullRequestNumber,
104
103
  parallelId: config.parallelId,
105
104
  onProgress: progressData => {
106
- const {
105
+ let {
107
106
  message: progressMessage,
108
107
  current,
109
108
  total,
@@ -123,76 +122,77 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
123
122
  displayMessage = phase;
124
123
  }
125
124
  }
126
- ui.progress(displayMessage || 'Processing...', current, total);
125
+ output.progress(displayMessage || 'Processing...', current, total);
127
126
  }
128
127
  };
129
128
 
130
129
  // Start upload
131
- ui.progress('Starting upload...');
132
- const result = await uploader.upload(uploadOptions);
130
+ output.progress('Starting upload...');
131
+ let result = await uploader.upload(uploadOptions);
133
132
  buildId = result.buildId; // Ensure we have the buildId
134
133
 
135
134
  // Mark build as completed
136
135
  if (result.buildId) {
137
- ui.progress('Finalizing build...');
136
+ output.progress('Finalizing build...');
138
137
  try {
139
- const apiService = new ApiService({
138
+ let apiService = new ApiService({
140
139
  baseUrl: config.apiUrl,
141
140
  token: config.apiKey,
142
141
  command: 'upload'
143
142
  });
144
- const executionTime = Date.now() - uploadStartTime;
143
+ let executionTime = Date.now() - uploadStartTime;
145
144
  await apiService.finalizeBuild(result.buildId, true, executionTime);
146
145
  } catch (error) {
147
- ui.warning(`Failed to finalize build: ${error.message}`);
146
+ output.warn(`Failed to finalize build: ${error.message}`);
148
147
  }
149
148
  }
150
- ui.success('Upload completed successfully');
149
+ output.success('Upload completed successfully');
151
150
 
152
151
  // Show Vizzly summary
153
152
  if (result.buildId) {
154
- ui.info(`🐻 Vizzly: Uploaded ${result.stats.uploaded} of ${result.stats.total} screenshots to build ${result.buildId}`);
153
+ output.info(`🐻 Vizzly: Uploaded ${result.stats.uploaded} of ${result.stats.total} screenshots to build ${result.buildId}`);
155
154
  // Use API-provided URL or construct proper URL with org/project context
156
- const buildUrl = result.url || (await constructBuildUrl(result.buildId, config.apiUrl, config.apiKey));
157
- ui.info(`🔗 Vizzly: View results at ${buildUrl}`);
155
+ let buildUrl = result.url || (await constructBuildUrl(result.buildId, config.apiUrl, config.apiKey));
156
+ output.info(`🔗 Vizzly: View results at ${buildUrl}`);
158
157
  }
159
158
 
160
159
  // Wait for build completion if requested
161
160
  if (options.wait && result.buildId) {
162
- ui.info('Waiting for build completion...');
163
- ui.startSpinner('Processing comparisons...');
164
- const buildResult = await uploader.waitForBuild(result.buildId);
165
- ui.success('Build processing completed');
161
+ output.info('Waiting for build completion...');
162
+ output.startSpinner('Processing comparisons...');
163
+ let buildResult = await uploader.waitForBuild(result.buildId);
164
+ output.success('Build processing completed');
166
165
 
167
166
  // Show build processing results
168
167
  if (buildResult.failedComparisons > 0) {
169
- ui.warning(`${buildResult.failedComparisons} visual comparisons failed`);
168
+ output.warn(`${buildResult.failedComparisons} visual comparisons failed`);
170
169
  } else {
171
- ui.success(`All ${buildResult.passedComparisons} visual comparisons passed`);
170
+ output.success(`All ${buildResult.passedComparisons} visual comparisons passed`);
172
171
  }
173
172
  // Use API-provided URL or construct proper URL with org/project context
174
- const buildUrl = buildResult.url || (await constructBuildUrl(result.buildId, config.apiUrl, config.apiKey));
175
- ui.info(`🔗 Vizzly: View results at ${buildUrl}`);
173
+ let buildUrl = buildResult.url || (await constructBuildUrl(result.buildId, config.apiUrl, config.apiKey));
174
+ output.info(`🔗 Vizzly: View results at ${buildUrl}`);
176
175
  }
177
- ui.cleanup();
176
+ output.cleanup();
178
177
  } catch (error) {
179
178
  // Mark build as failed if we have a buildId and config
180
179
  if (buildId && config) {
181
180
  try {
182
- const apiService = new ApiService({
181
+ let apiService = new ApiService({
183
182
  baseUrl: config.apiUrl,
184
183
  token: config.apiKey,
185
184
  command: 'upload'
186
185
  });
187
- const executionTime = Date.now() - uploadStartTime;
186
+ let executionTime = Date.now() - uploadStartTime;
188
187
  await apiService.finalizeBuild(buildId, false, executionTime);
189
188
  } catch {
190
189
  // Silent fail on cleanup
191
190
  }
192
191
  }
193
192
  // Use user-friendly error message if available
194
- const errorMessage = error?.getUserMessage ? error.getUserMessage() : error.message;
195
- ui.error(errorMessage || 'Upload failed', error);
193
+ let errorMessage = error?.getUserMessage ? error.getUserMessage() : error.message;
194
+ output.error(errorMessage || 'Upload failed', error);
195
+ process.exit(1);
196
196
  }
197
197
  }
198
198
 
@@ -202,7 +202,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
202
202
  * @param {Object} options - Command options
203
203
  */
204
204
  export function validateUploadOptions(screenshotsPath, options) {
205
- const errors = [];
205
+ let errors = [];
206
206
  if (!screenshotsPath) {
207
207
  errors.push('Screenshots path is required');
208
208
  }
@@ -214,19 +214,19 @@ export function validateUploadOptions(screenshotsPath, options) {
214
214
  }
215
215
  }
216
216
  if (options.threshold !== undefined) {
217
- const threshold = parseFloat(options.threshold);
217
+ let threshold = parseFloat(options.threshold);
218
218
  if (isNaN(threshold) || threshold < 0 || threshold > 1) {
219
219
  errors.push('Threshold must be a number between 0 and 1');
220
220
  }
221
221
  }
222
222
  if (options.batchSize !== undefined) {
223
- const n = parseInt(options.batchSize, 10);
223
+ let n = parseInt(options.batchSize, 10);
224
224
  if (!Number.isFinite(n) || n <= 0) {
225
225
  errors.push('Batch size must be a positive integer');
226
226
  }
227
227
  }
228
228
  if (options.uploadTimeout !== undefined) {
229
- const n = parseInt(options.uploadTimeout, 10);
229
+ let n = parseInt(options.uploadTimeout, 10);
230
230
  if (!Number.isFinite(n) || n <= 0) {
231
231
  errors.push('Upload timeout must be a positive integer (milliseconds)');
232
232
  }