@vizzly-testing/cli 0.19.2 → 0.20.1-beta.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 (76) hide show
  1. package/dist/api/client.js +134 -0
  2. package/dist/api/core.js +341 -0
  3. package/dist/api/endpoints.js +314 -0
  4. package/dist/api/index.js +19 -0
  5. package/dist/auth/client.js +91 -0
  6. package/dist/auth/core.js +176 -0
  7. package/dist/auth/index.js +30 -0
  8. package/dist/auth/operations.js +148 -0
  9. package/dist/cli.js +1 -1
  10. package/dist/client/index.js +0 -1
  11. package/dist/commands/doctor.js +3 -3
  12. package/dist/commands/finalize.js +41 -15
  13. package/dist/commands/login.js +7 -6
  14. package/dist/commands/logout.js +4 -4
  15. package/dist/commands/project.js +5 -4
  16. package/dist/commands/run.js +158 -90
  17. package/dist/commands/status.js +22 -18
  18. package/dist/commands/tdd.js +105 -78
  19. package/dist/commands/upload.js +61 -26
  20. package/dist/commands/whoami.js +4 -4
  21. package/dist/config/core.js +438 -0
  22. package/dist/config/index.js +13 -0
  23. package/dist/config/operations.js +327 -0
  24. package/dist/index.js +1 -1
  25. package/dist/project/core.js +295 -0
  26. package/dist/project/index.js +13 -0
  27. package/dist/project/operations.js +393 -0
  28. package/dist/report-generator/core.js +315 -0
  29. package/dist/report-generator/index.js +8 -0
  30. package/dist/report-generator/operations.js +196 -0
  31. package/dist/reporter/reporter-bundle.iife.js +16 -16
  32. package/dist/screenshot-server/core.js +157 -0
  33. package/dist/screenshot-server/index.js +11 -0
  34. package/dist/screenshot-server/operations.js +183 -0
  35. package/dist/sdk/index.js +3 -2
  36. package/dist/server/handlers/api-handler.js +14 -5
  37. package/dist/server/handlers/tdd-handler.js +80 -48
  38. package/dist/server-manager/core.js +183 -0
  39. package/dist/server-manager/index.js +81 -0
  40. package/dist/server-manager/operations.js +208 -0
  41. package/dist/services/build-manager.js +2 -69
  42. package/dist/services/index.js +21 -48
  43. package/dist/services/screenshot-server.js +40 -74
  44. package/dist/services/server-manager.js +45 -80
  45. package/dist/services/static-report-generator.js +21 -163
  46. package/dist/services/test-runner.js +90 -249
  47. package/dist/services/uploader.js +56 -358
  48. package/dist/tdd/core/hotspot-coverage.js +112 -0
  49. package/dist/tdd/core/signature.js +101 -0
  50. package/dist/tdd/index.js +19 -0
  51. package/dist/tdd/metadata/baseline-metadata.js +103 -0
  52. package/dist/tdd/metadata/hotspot-metadata.js +93 -0
  53. package/dist/tdd/services/baseline-downloader.js +151 -0
  54. package/dist/tdd/services/baseline-manager.js +166 -0
  55. package/dist/tdd/services/comparison-service.js +230 -0
  56. package/dist/tdd/services/hotspot-service.js +71 -0
  57. package/dist/tdd/services/result-service.js +123 -0
  58. package/dist/tdd/tdd-service.js +1081 -0
  59. package/dist/test-runner/core.js +255 -0
  60. package/dist/test-runner/index.js +13 -0
  61. package/dist/test-runner/operations.js +483 -0
  62. package/dist/types/client.d.ts +4 -2
  63. package/dist/types/index.d.ts +5 -0
  64. package/dist/uploader/core.js +396 -0
  65. package/dist/uploader/index.js +11 -0
  66. package/dist/uploader/operations.js +412 -0
  67. package/dist/utils/config-schema.js +8 -3
  68. package/package.json +7 -12
  69. package/dist/services/api-service.js +0 -412
  70. package/dist/services/auth-service.js +0 -226
  71. package/dist/services/config-service.js +0 -369
  72. package/dist/services/html-report-generator.js +0 -455
  73. package/dist/services/project-service.js +0 -326
  74. package/dist/services/report-generator/report.css +0 -411
  75. package/dist/services/report-generator/viewer.js +0 -102
  76. package/dist/services/tdd-service.js +0 -1429
@@ -1,43 +1,73 @@
1
- import { createServices } from '../services/index.js';
2
- import { loadConfig } from '../utils/config-loader.js';
3
- import { detectBranch, detectCommit } from '../utils/git.js';
4
- import * as output from '../utils/output.js';
1
+ /**
2
+ * TDD command implementation
3
+ * Uses functional operations directly - no class wrappers needed
4
+ */
5
+
6
+ import { spawn as defaultSpawn } from 'node:child_process';
7
+ import { createBuild as defaultCreateApiBuild, createApiClient as defaultCreateApiClient, finalizeBuild as defaultFinalizeApiBuild, getBuild as defaultGetBuild } from '../api/index.js';
8
+ import { VizzlyError } from '../errors/vizzly-error.js';
9
+ import { createServerManager as defaultCreateServerManager } from '../server-manager/index.js';
10
+ import { createBuildObject as defaultCreateBuildObject } from '../services/build-manager.js';
11
+ import { initializeDaemon as defaultInitializeDaemon, runTests as defaultRunTests } from '../test-runner/index.js';
12
+ import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
13
+ import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit } from '../utils/git.js';
14
+ import * as defaultOutput from '../utils/output.js';
5
15
 
6
16
  /**
7
17
  * TDD command implementation
8
18
  * @param {string} testCommand - Test command to execute
9
19
  * @param {Object} options - Command options
10
20
  * @param {Object} globalOptions - Global CLI options
21
+ * @param {Object} deps - Dependencies for testing
11
22
  * @returns {Promise<{result: Object, cleanup: Function}>} Result and cleanup function
12
23
  */
13
- export async function tddCommand(testCommand, options = {}, globalOptions = {}) {
24
+ export async function tddCommand(testCommand, options = {}, globalOptions = {}, deps = {}) {
25
+ let {
26
+ loadConfig = defaultLoadConfig,
27
+ createApiClient = defaultCreateApiClient,
28
+ createApiBuild = defaultCreateApiBuild,
29
+ finalizeApiBuild = defaultFinalizeApiBuild,
30
+ getBuild = defaultGetBuild,
31
+ createServerManager = defaultCreateServerManager,
32
+ createBuildObject = defaultCreateBuildObject,
33
+ initializeDaemon = defaultInitializeDaemon,
34
+ runTests = defaultRunTests,
35
+ detectBranch = defaultDetectBranch,
36
+ detectCommit = defaultDetectCommit,
37
+ spawn = defaultSpawn,
38
+ output = defaultOutput
39
+ } = deps;
14
40
  output.configure({
15
41
  json: globalOptions.json,
16
42
  verbose: globalOptions.verbose,
17
43
  color: !globalOptions.noColor
18
44
  });
19
- let testRunner = null;
45
+ let serverManager = null;
46
+ let testProcess = null;
20
47
  let isCleanedUp = false;
21
48
 
22
49
  // Create cleanup function that can be called by the caller
23
- const cleanup = async () => {
50
+ let cleanup = async () => {
24
51
  if (isCleanedUp) return;
25
52
  isCleanedUp = true;
26
53
  output.cleanup();
27
- if (testRunner?.cancel) {
28
- await testRunner.cancel();
54
+ if (testProcess && !testProcess.killed) {
55
+ testProcess.kill('SIGKILL');
56
+ }
57
+ if (serverManager) {
58
+ await serverManager.stop();
29
59
  }
30
60
  };
31
61
  try {
32
62
  // Load configuration with CLI overrides
33
- const allOptions = {
63
+ let allOptions = {
34
64
  ...globalOptions,
35
65
  ...options
36
66
  };
37
- const config = await loadConfig(globalOptions.config, allOptions);
67
+ let config = await loadConfig(globalOptions.config, allOptions);
38
68
 
39
69
  // Dev mode works locally by default - only needs token for baseline download
40
- const needsToken = options.baselineBuild || options.baselineComparison;
70
+ let needsToken = options.baselineBuild || options.baselineComparison;
41
71
  if (!config.apiKey && needsToken) {
42
72
  throw new Error('API token required when using --baseline-build or --baseline-comparison flags');
43
73
  }
@@ -46,12 +76,12 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
46
76
  config.allowNoToken = true;
47
77
 
48
78
  // Collect git metadata
49
- const branch = await detectBranch(options.branch);
50
- const commit = await detectCommit(options.commit);
79
+ let branch = await detectBranch(options.branch);
80
+ let commit = await detectCommit(options.commit);
51
81
 
52
82
  // Show header (skip in daemon mode)
53
83
  if (!options.daemon) {
54
- const mode = config.apiKey ? 'local' : 'local';
84
+ let mode = config.apiKey ? 'local' : 'local';
55
85
  output.header('tdd', mode);
56
86
 
57
87
  // Show config in verbose mode
@@ -62,81 +92,53 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
62
92
  });
63
93
  }
64
94
 
65
- // Create services
95
+ // Create functional dependencies
66
96
  output.startSpinner('Initializing TDD server...');
67
- const configWithVerbose = {
97
+ let configWithVerbose = {
68
98
  ...config,
69
99
  verbose: globalOptions.verbose
70
100
  };
71
- const services = createServices(configWithVerbose, 'tdd');
72
- testRunner = services.testRunner;
73
- output.stopSpinner();
74
101
 
75
- // Set up event handlers for user feedback
76
- testRunner.on('progress', progressData => {
77
- const {
78
- message: progressMessage
79
- } = progressData;
80
- output.progress(progressMessage || 'Running tests...');
81
- });
82
- testRunner.on('test-output', data => {
83
- // In non-JSON mode, show test output directly
84
- if (!globalOptions.json) {
85
- output.stopSpinner();
86
- output.print(data.data);
87
- }
88
- });
89
- testRunner.on('server-ready', serverInfo => {
90
- // Only show in non-daemon mode (daemon shows its own startup message)
91
- if (!options.daemon) {
92
- output.debug('server', `listening on :${serverInfo.port}`);
93
- }
94
- });
95
- testRunner.on('screenshot-captured', screenshotInfo => {
96
- output.debug('capture', screenshotInfo.name);
97
- });
98
- testRunner.on('comparison-result', comparisonInfo => {
99
- const {
100
- name,
101
- status,
102
- pixelDifference
103
- } = comparisonInfo;
104
- if (status === 'passed') {
105
- output.debug('compare', `${name} passed`);
106
- } else if (status === 'failed') {
107
- output.warn(`${name}: ${pixelDifference}% difference`);
108
- } else if (status === 'new') {
109
- output.debug('compare', `${name} (new baseline)`);
102
+ // Create server manager (functional object)
103
+ serverManager = createServerManager(configWithVerbose, {});
104
+
105
+ // Create build manager (functional object that provides the interface runTests expects)
106
+ let buildManager = {
107
+ async createBuild(buildOptions) {
108
+ return createBuildObject(buildOptions);
110
109
  }
111
- });
112
- testRunner.on('error', error => {
113
- output.error('Test runner error', error);
114
- });
115
- const runOptions = {
110
+ };
111
+ output.stopSpinner();
112
+ let runOptions = {
116
113
  testCommand,
117
114
  port: config.server.port,
118
115
  timeout: config.server.timeout,
119
116
  tdd: true,
120
117
  daemon: options.daemon || false,
121
- // Daemon mode flag
122
118
  setBaseline: options.setBaseline || false,
123
- // Pass through baseline update mode
124
119
  branch,
125
120
  commit,
126
121
  environment: config.build.environment,
127
122
  threshold: config.comparison.threshold,
128
123
  allowNoToken: config.allowNoToken || false,
129
- // Pass through the allow-no-token setting
130
124
  baselineBuildId: config.baselineBuildId,
131
125
  baselineComparisonId: config.baselineComparisonId,
132
- wait: false // No build to wait for in dev mode
126
+ wait: false
133
127
  };
134
128
 
135
129
  // In daemon mode, just start the server without running tests
136
130
  if (options.daemon) {
137
- await testRunner.initialize(runOptions);
138
-
139
- // Return immediately so daemon can set up its lifecycle
131
+ await initializeDaemon({
132
+ initOptions: runOptions,
133
+ deps: {
134
+ serverManager,
135
+ createError: (msg, code) => new VizzlyError(msg, code),
136
+ output,
137
+ onServerReady: data => {
138
+ output.debug('server', `listening on :${data.port}`);
139
+ }
140
+ }
141
+ });
140
142
  return {
141
143
  result: {
142
144
  success: true,
@@ -149,19 +151,46 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
149
151
 
150
152
  // Normal dev mode - run tests
151
153
  output.debug('run', testCommand);
152
- const runResult = await testRunner.run(runOptions);
154
+ let runResult = await runTests({
155
+ runOptions,
156
+ config: configWithVerbose,
157
+ deps: {
158
+ serverManager,
159
+ buildManager,
160
+ spawn: (command, spawnOptions) => {
161
+ let proc = spawn(command, spawnOptions);
162
+ testProcess = proc;
163
+ return proc;
164
+ },
165
+ createApiClient,
166
+ createApiBuild,
167
+ getBuild,
168
+ finalizeApiBuild,
169
+ createError: (msg, code) => new VizzlyError(msg, code),
170
+ output,
171
+ onBuildCreated: data => {
172
+ output.debug('build', `created ${data.buildId?.substring(0, 8)}`);
173
+ },
174
+ onServerReady: data => {
175
+ output.debug('server', `listening on :${data.port}`);
176
+ },
177
+ onFinalizeFailed: data => {
178
+ output.warn(`Failed to finalize build: ${data.error}`);
179
+ }
180
+ }
181
+ });
153
182
 
154
183
  // Show summary
155
- const {
184
+ let {
156
185
  screenshotsCaptured,
157
186
  comparisons
158
187
  } = runResult;
159
188
 
160
189
  // Determine success based on comparison results
161
- const hasFailures = runResult.failed || runResult.comparisons?.some(c => c.status === 'failed');
190
+ let hasFailures = runResult.failed || runResult.comparisons?.some(c => c.status === 'failed');
162
191
  if (comparisons && comparisons.length > 0) {
163
- const passed = comparisons.filter(c => c.status === 'passed').length;
164
- const failed = comparisons.filter(c => c.status === 'failed').length;
192
+ let passed = comparisons.filter(c => c.status === 'passed').length;
193
+ let failed = comparisons.filter(c => c.status === 'failed').length;
165
194
  if (hasFailures) {
166
195
  output.error(`${failed} visual difference${failed !== 1 ? 's' : ''} detected`);
167
196
  output.info(`Check .vizzly/diffs/ for diff images`);
@@ -171,8 +200,6 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
171
200
  } else {
172
201
  output.result(`${screenshotsCaptured} screenshot${screenshotsCaptured !== 1 ? 's' : ''}`);
173
202
  }
174
-
175
- // Return result and cleanup function
176
203
  return {
177
204
  result: {
178
205
  success: !hasFailures,
@@ -200,24 +227,24 @@ export async function tddCommand(testCommand, options = {}, globalOptions = {})
200
227
  * @param {Object} options - Command options
201
228
  */
202
229
  export function validateTddOptions(testCommand, options) {
203
- const errors = [];
230
+ let errors = [];
204
231
  if (!testCommand || testCommand.trim() === '') {
205
232
  errors.push('Test command is required');
206
233
  }
207
234
  if (options.port) {
208
- const port = parseInt(options.port, 10);
235
+ let port = parseInt(options.port, 10);
209
236
  if (Number.isNaN(port) || port < 1 || port > 65535) {
210
237
  errors.push('Port must be a valid number between 1 and 65535');
211
238
  }
212
239
  }
213
240
  if (options.timeout) {
214
- const timeout = parseInt(options.timeout, 10);
241
+ let timeout = parseInt(options.timeout, 10);
215
242
  if (Number.isNaN(timeout) || timeout < 1000) {
216
243
  errors.push('Timeout must be at least 1000 milliseconds');
217
244
  }
218
245
  }
219
246
  if (options.threshold !== undefined) {
220
- const threshold = parseFloat(options.threshold);
247
+ let threshold = parseFloat(options.threshold);
221
248
  if (Number.isNaN(threshold) || threshold < 0) {
222
249
  errors.push('Threshold must be a non-negative number (CIEDE2000 Delta E)');
223
250
  }
@@ -1,25 +1,31 @@
1
- import { ApiService } from '../services/api-service.js';
2
- import { createServices } from '../services/index.js';
3
- import { loadConfig } from '../utils/config-loader.js';
4
- import { detectBranch, detectCommit, detectCommitMessage, detectPullRequestNumber, generateBuildNameWithGit } from '../utils/git.js';
5
- import * as output from '../utils/output.js';
1
+ import { createApiClient as defaultCreateApiClient, finalizeBuild as defaultFinalizeBuild, getTokenContext as defaultGetTokenContext } from '../api/index.js';
2
+ import { createUploader as defaultCreateUploader } from '../services/uploader.js';
3
+ import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
4
+ import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit, detectCommitMessage as defaultDetectCommitMessage, detectPullRequestNumber as defaultDetectPullRequestNumber, generateBuildNameWithGit as defaultGenerateBuildNameWithGit } from '../utils/git.js';
5
+ import * as defaultOutput from '../utils/output.js';
6
6
 
7
7
  /**
8
8
  * Construct proper build URL with org/project context
9
9
  * @param {string} buildId - Build ID
10
10
  * @param {string} apiUrl - API base URL
11
11
  * @param {string} apiToken - API token
12
+ * @param {Object} deps - Dependencies
12
13
  * @returns {Promise<string>} Proper build URL
13
14
  */
14
- async function constructBuildUrl(buildId, apiUrl, apiToken) {
15
+ export async function constructBuildUrl(buildId, apiUrl, apiToken, deps = {}) {
16
+ let {
17
+ createApiClient = defaultCreateApiClient,
18
+ getTokenContext = defaultGetTokenContext,
19
+ output = defaultOutput
20
+ } = deps;
15
21
  try {
16
- const apiService = new ApiService({
22
+ let client = createApiClient({
17
23
  baseUrl: apiUrl,
18
24
  token: apiToken,
19
25
  command: 'upload'
20
26
  });
21
- const tokenContext = await apiService.getTokenContext();
22
- const baseUrl = apiUrl.replace(/\/api.*$/, '');
27
+ let tokenContext = await getTokenContext(client);
28
+ let baseUrl = apiUrl.replace(/\/api.*$/, '');
23
29
  if (tokenContext.organization?.slug && tokenContext.project?.slug) {
24
30
  return `${baseUrl}/${tokenContext.organization.slug}/${tokenContext.project.slug}/builds/${buildId}`;
25
31
  }
@@ -31,7 +37,7 @@ async function constructBuildUrl(buildId, apiUrl, apiToken) {
31
37
  }
32
38
 
33
39
  // Fallback URL construction
34
- const baseUrl = apiUrl.replace(/\/api.*$/, '');
40
+ let baseUrl = apiUrl.replace(/\/api.*$/, '');
35
41
  return `${baseUrl}/builds/${buildId}`;
36
42
  }
37
43
 
@@ -40,8 +46,23 @@ async function constructBuildUrl(buildId, apiUrl, apiToken) {
40
46
  * @param {string} screenshotsPath - Path to screenshots
41
47
  * @param {Object} options - Command options
42
48
  * @param {Object} globalOptions - Global CLI options
49
+ * @param {Object} deps - Dependencies for testing
43
50
  */
44
- export async function uploadCommand(screenshotsPath, options = {}, globalOptions = {}) {
51
+ export async function uploadCommand(screenshotsPath, options = {}, globalOptions = {}, deps = {}) {
52
+ let {
53
+ loadConfig = defaultLoadConfig,
54
+ createApiClient = defaultCreateApiClient,
55
+ finalizeBuild = defaultFinalizeBuild,
56
+ createUploader = defaultCreateUploader,
57
+ detectBranch = defaultDetectBranch,
58
+ detectCommit = defaultDetectCommit,
59
+ detectCommitMessage = defaultDetectCommitMessage,
60
+ detectPullRequestNumber = defaultDetectPullRequestNumber,
61
+ generateBuildNameWithGit = defaultGenerateBuildNameWithGit,
62
+ output = defaultOutput,
63
+ exit = code => process.exit(code),
64
+ buildUrlConstructor = constructBuildUrl
65
+ } = deps;
45
66
  output.configure({
46
67
  json: globalOptions.json,
47
68
  verbose: globalOptions.verbose,
@@ -63,7 +84,11 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
63
84
  // Validate API token
64
85
  if (!config.apiKey) {
65
86
  output.error('API token required. Use --token or set VIZZLY_TOKEN environment variable');
66
- process.exit(1);
87
+ exit(1);
88
+ return {
89
+ success: false,
90
+ reason: 'no-api-key'
91
+ };
67
92
  }
68
93
 
69
94
  // Collect git metadata if not provided
@@ -83,10 +108,12 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
83
108
  });
84
109
  }
85
110
 
86
- // Get uploader service
111
+ // Create uploader
87
112
  output.startSpinner('Initializing uploader...');
88
- const services = createServices(config, 'upload');
89
- const uploader = services.uploader;
113
+ let uploader = createUploader({
114
+ ...config,
115
+ command: 'upload'
116
+ });
90
117
 
91
118
  // Prepare upload options with progress callback
92
119
  const uploadOptions = {
@@ -135,13 +162,13 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
135
162
  if (result.buildId) {
136
163
  output.progress('Finalizing build...');
137
164
  try {
138
- const apiService = new ApiService({
165
+ let client = createApiClient({
139
166
  baseUrl: config.apiUrl,
140
167
  token: config.apiKey,
141
168
  command: 'upload'
142
169
  });
143
- const executionTime = Date.now() - uploadStartTime;
144
- await apiService.finalizeBuild(result.buildId, true, executionTime);
170
+ let executionTime = Date.now() - uploadStartTime;
171
+ await finalizeBuild(client, result.buildId, true, executionTime);
145
172
  } catch (error) {
146
173
  output.warn(`Failed to finalize build: ${error.message}`);
147
174
  }
@@ -152,7 +179,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
152
179
  if (result.buildId) {
153
180
  output.info(`🐻 Vizzly: Uploaded ${result.stats.uploaded} of ${result.stats.total} screenshots to build ${result.buildId}`);
154
181
  // Use API-provided URL or construct proper URL with org/project context
155
- const buildUrl = result.url || (await constructBuildUrl(result.buildId, config.apiUrl, config.apiKey));
182
+ let buildUrl = result.url || (await buildUrlConstructor(result.buildId, config.apiUrl, config.apiKey, deps));
156
183
  output.info(`🔗 Vizzly: View results at ${buildUrl}`);
157
184
  }
158
185
 
@@ -170,29 +197,37 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
170
197
  output.success(`All ${buildResult.passedComparisons} visual comparisons passed`);
171
198
  }
172
199
  // Use API-provided URL or construct proper URL with org/project context
173
- const buildUrl = buildResult.url || (await constructBuildUrl(result.buildId, config.apiUrl, config.apiKey));
174
- output.info(`🔗 Vizzly: View results at ${buildUrl}`);
200
+ let waitBuildUrl = buildResult.url || (await buildUrlConstructor(result.buildId, config.apiUrl, config.apiKey, deps));
201
+ output.info(`🔗 Vizzly: View results at ${waitBuildUrl}`);
175
202
  }
176
203
  output.cleanup();
204
+ return {
205
+ success: true,
206
+ result
207
+ };
177
208
  } catch (error) {
178
209
  // Mark build as failed if we have a buildId and config
179
210
  if (buildId && config) {
180
211
  try {
181
- const apiService = new ApiService({
212
+ let client = createApiClient({
182
213
  baseUrl: config.apiUrl,
183
214
  token: config.apiKey,
184
215
  command: 'upload'
185
216
  });
186
- const executionTime = Date.now() - uploadStartTime;
187
- await apiService.finalizeBuild(buildId, false, executionTime);
217
+ let executionTime = Date.now() - uploadStartTime;
218
+ await finalizeBuild(client, buildId, false, executionTime);
188
219
  } catch {
189
220
  // Silent fail on cleanup
190
221
  }
191
222
  }
192
223
  // Use user-friendly error message if available
193
- const errorMessage = error?.getUserMessage ? error.getUserMessage() : error.message;
224
+ let errorMessage = error?.getUserMessage ? error.getUserMessage() : error.message;
194
225
  output.error(errorMessage || 'Upload failed', error);
195
- process.exit(1);
226
+ exit(1);
227
+ return {
228
+ success: false,
229
+ error
230
+ };
196
231
  }
197
232
  }
198
233
 
@@ -3,9 +3,8 @@
3
3
  * Shows current user and authentication status
4
4
  */
5
5
 
6
- import { AuthService } from '../services/auth-service.js';
6
+ import { createAuthClient, createTokenStore, getAuthTokens, whoami } from '../auth/index.js';
7
7
  import { getApiUrl } from '../utils/environment-config.js';
8
- import { getAuthTokens } from '../utils/global-config.js';
9
8
  import * as output from '../utils/output.js';
10
9
 
11
10
  /**
@@ -38,10 +37,11 @@ export async function whoamiCommand(options = {}, globalOptions = {}) {
38
37
 
39
38
  // Get current user info
40
39
  output.startSpinner('Fetching user information...');
41
- const authService = new AuthService({
40
+ let client = createAuthClient({
42
41
  baseUrl: options.apiUrl || getApiUrl()
43
42
  });
44
- const response = await authService.whoami();
43
+ let tokenStore = createTokenStore();
44
+ let response = await whoami(client, tokenStore);
45
45
  output.stopSpinner();
46
46
 
47
47
  // Output in JSON mode