@vizzly-testing/cli 0.14.0 → 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.
- package/dist/cli.js +68 -68
- package/dist/commands/doctor.js +30 -34
- package/dist/commands/finalize.js +24 -23
- package/dist/commands/init.js +30 -28
- package/dist/commands/login.js +49 -55
- package/dist/commands/logout.js +14 -19
- package/dist/commands/project.js +83 -103
- package/dist/commands/run.js +77 -89
- package/dist/commands/status.js +48 -49
- package/dist/commands/tdd-daemon.js +90 -86
- package/dist/commands/tdd.js +59 -88
- package/dist/commands/upload.js +57 -57
- package/dist/commands/whoami.js +40 -45
- package/dist/index.js +2 -5
- package/dist/plugin-loader.js +15 -17
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +74 -41
- package/dist/sdk/index.js +36 -45
- package/dist/server/handlers/api-handler.js +14 -15
- package/dist/server/handlers/tdd-handler.js +34 -37
- package/dist/server/http-server.js +75 -869
- package/dist/server/middleware/cors.js +22 -0
- package/dist/server/middleware/json-parser.js +35 -0
- package/dist/server/middleware/response.js +79 -0
- package/dist/server/routers/assets.js +91 -0
- package/dist/server/routers/auth.js +144 -0
- package/dist/server/routers/baseline.js +163 -0
- package/dist/server/routers/cloud-proxy.js +146 -0
- package/dist/server/routers/config.js +126 -0
- package/dist/server/routers/dashboard.js +130 -0
- package/dist/server/routers/health.js +61 -0
- package/dist/server/routers/projects.js +168 -0
- package/dist/server/routers/screenshot.js +86 -0
- package/dist/services/auth-service.js +1 -1
- package/dist/services/build-manager.js +13 -40
- package/dist/services/config-service.js +2 -4
- package/dist/services/html-report-generator.js +6 -5
- package/dist/services/index.js +64 -0
- package/dist/services/project-service.js +121 -40
- package/dist/services/screenshot-server.js +9 -9
- package/dist/services/server-manager.js +11 -18
- package/dist/services/static-report-generator.js +3 -4
- package/dist/services/tdd-service.js +246 -103
- package/dist/services/test-runner.js +24 -25
- package/dist/services/uploader.js +5 -4
- package/dist/types/commands/init.d.ts +1 -2
- package/dist/types/index.d.ts +2 -3
- package/dist/types/plugin-loader.d.ts +1 -2
- package/dist/types/reporter/src/api/client.d.ts +178 -0
- package/dist/types/reporter/src/components/app-router.d.ts +1 -3
- package/dist/types/reporter/src/components/code-block.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/onion-skin-mode.d.ts +10 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/overlay-mode.d.ts +11 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/base-comparison-mode.d.ts +14 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/shared/image-renderer.d.ts +30 -0
- package/dist/types/reporter/src/components/comparison/comparison-modes/toggle-view.d.ts +8 -0
- package/dist/types/reporter/src/components/comparison/comparison-viewer.d.ts +4 -0
- package/dist/types/reporter/src/components/comparison/screenshot-display.d.ts +16 -0
- package/dist/types/reporter/src/components/design-system/alert.d.ts +9 -0
- package/dist/types/reporter/src/components/design-system/badge.d.ts +17 -0
- package/dist/types/reporter/src/components/design-system/button.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/card.d.ts +31 -0
- package/dist/types/reporter/src/components/design-system/empty-state.d.ts +13 -0
- package/dist/types/reporter/src/components/design-system/form-controls.d.ts +44 -0
- package/dist/types/reporter/src/components/design-system/health-ring.d.ts +7 -0
- package/dist/types/reporter/src/components/design-system/index.d.ts +11 -0
- package/dist/types/reporter/src/components/design-system/modal.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/skeleton.d.ts +19 -0
- package/dist/types/reporter/src/components/design-system/spinner.d.ts +10 -0
- package/dist/types/reporter/src/components/design-system/tabs.d.ts +13 -0
- package/dist/types/reporter/src/components/layout/header.d.ts +5 -0
- package/dist/types/reporter/src/components/layout/index.d.ts +2 -0
- package/dist/types/reporter/src/components/layout/layout.d.ts +6 -0
- package/dist/types/reporter/src/components/views/builds-view.d.ts +1 -0
- package/dist/types/reporter/src/components/views/comparison-detail-view.d.ts +1 -4
- package/dist/types/reporter/src/components/views/comparisons-view.d.ts +1 -6
- package/dist/types/reporter/src/components/views/stats-view.d.ts +1 -6
- package/dist/types/reporter/src/components/waiting-for-screenshots.d.ts +1 -0
- package/dist/types/reporter/src/hooks/queries/use-auth-queries.d.ts +15 -0
- package/dist/types/reporter/src/hooks/queries/use-cloud-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-config-queries.d.ts +6 -0
- package/dist/types/reporter/src/hooks/queries/use-tdd-queries.d.ts +9 -0
- package/dist/types/reporter/src/lib/query-client.d.ts +2 -0
- package/dist/types/reporter/src/lib/query-keys.d.ts +13 -0
- package/dist/types/sdk/index.d.ts +2 -4
- package/dist/types/server/handlers/tdd-handler.d.ts +2 -0
- package/dist/types/server/http-server.d.ts +1 -1
- package/dist/types/server/middleware/cors.d.ts +11 -0
- package/dist/types/server/middleware/json-parser.d.ts +10 -0
- package/dist/types/server/middleware/response.d.ts +50 -0
- package/dist/types/server/routers/assets.d.ts +6 -0
- package/dist/types/server/routers/auth.d.ts +9 -0
- package/dist/types/server/routers/baseline.d.ts +13 -0
- package/dist/types/server/routers/cloud-proxy.d.ts +11 -0
- package/dist/types/server/routers/config.d.ts +9 -0
- package/dist/types/server/routers/dashboard.d.ts +6 -0
- package/dist/types/server/routers/health.d.ts +11 -0
- package/dist/types/server/routers/projects.d.ts +9 -0
- package/dist/types/server/routers/screenshot.d.ts +11 -0
- package/dist/types/services/build-manager.d.ts +4 -3
- package/dist/types/services/config-service.d.ts +2 -3
- package/dist/types/services/index.d.ts +7 -0
- package/dist/types/services/project-service.d.ts +6 -4
- package/dist/types/services/screenshot-server.d.ts +5 -5
- package/dist/types/services/server-manager.d.ts +5 -3
- package/dist/types/services/tdd-service.d.ts +12 -1
- package/dist/types/services/test-runner.d.ts +3 -3
- package/dist/types/utils/output.d.ts +84 -0
- package/dist/utils/config-loader.js +24 -48
- package/dist/utils/global-config.js +2 -17
- package/dist/utils/output.js +445 -0
- package/dist/utils/security.js +3 -4
- package/docs/api-reference.md +0 -1
- package/docs/plugins.md +22 -22
- package/package.json +3 -2
- package/dist/container/index.js +0 -215
- package/dist/services/base-service.js +0 -154
- package/dist/types/container/index.d.ts +0 -59
- package/dist/types/reporter/src/components/comparison/viewer-modes/onion-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/overlay-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/side-by-side-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/comparison/viewer-modes/toggle-viewer.d.ts +0 -3
- package/dist/types/reporter/src/components/dashboard/dashboard-header.d.ts +0 -5
- package/dist/types/reporter/src/components/dashboard/dashboard-stats.d.ts +0 -4
- package/dist/types/reporter/src/components/dashboard/empty-state.d.ts +0 -8
- package/dist/types/reporter/src/components/ui/form-field.d.ts +0 -16
- package/dist/types/reporter/src/components/ui/status-badge.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-auth.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-baseline-actions.d.ts +0 -5
- package/dist/types/reporter/src/hooks/use-config.d.ts +0 -9
- package/dist/types/reporter/src/hooks/use-projects.d.ts +0 -10
- package/dist/types/reporter/src/hooks/use-report-data.d.ts +0 -7
- package/dist/types/reporter/src/hooks/use-vizzly-api.d.ts +0 -9
- package/dist/types/services/base-service.d.ts +0 -71
- package/dist/types/utils/console-ui.d.ts +0 -61
- package/dist/types/utils/logger-factory.d.ts +0 -26
- package/dist/types/utils/logger.d.ts +0 -79
- package/dist/utils/console-ui.js +0 -241
- package/dist/utils/logger-factory.js +0 -76
- package/dist/utils/logger.js +0 -231
package/dist/commands/tdd.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadConfig } from '../utils/config-loader.js';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
let cleanup = async () => {
|
|
24
24
|
if (isCleanedUp) return;
|
|
25
25
|
isCleanedUp = true;
|
|
26
|
-
|
|
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
|
-
|
|
33
|
+
let allOptions = {
|
|
34
34
|
...globalOptions,
|
|
35
35
|
...options
|
|
36
36
|
};
|
|
37
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
65
|
+
// Create services
|
|
66
|
+
output.startSpinner('Initializing TDD server...');
|
|
67
|
+
let configWithVerbose = {
|
|
75
68
|
...config,
|
|
76
69
|
verbose: globalOptions.verbose
|
|
77
70
|
};
|
|
78
|
-
|
|
79
|
-
testRunner =
|
|
80
|
-
|
|
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
|
-
|
|
77
|
+
let {
|
|
85
78
|
message: progressMessage
|
|
86
79
|
} = progressData;
|
|
87
|
-
|
|
80
|
+
output.progress(progressMessage || 'Running tests...');
|
|
88
81
|
});
|
|
89
|
-
testRunner.on('test-output',
|
|
82
|
+
testRunner.on('test-output', data => {
|
|
90
83
|
// In non-JSON mode, show test output directly
|
|
91
84
|
if (!globalOptions.json) {
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
+
output.debug('capture', screenshotInfo.name);
|
|
113
97
|
});
|
|
114
98
|
testRunner.on('comparison-result', comparisonInfo => {
|
|
115
|
-
|
|
99
|
+
let {
|
|
116
100
|
name,
|
|
117
101
|
status,
|
|
118
102
|
pixelDifference
|
|
119
103
|
} = comparisonInfo;
|
|
120
104
|
if (status === 'passed') {
|
|
121
|
-
|
|
105
|
+
output.debug('compare', `${name} passed`);
|
|
122
106
|
} else if (status === 'failed') {
|
|
123
|
-
|
|
107
|
+
output.warn(`${name}: ${pixelDifference}% difference`);
|
|
124
108
|
} else if (status === 'new') {
|
|
125
|
-
|
|
109
|
+
output.debug('compare', `${name} (new baseline)`);
|
|
126
110
|
}
|
|
127
111
|
});
|
|
128
112
|
testRunner.on('error', error => {
|
|
129
|
-
|
|
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
|
-
|
|
179
|
-
|
|
151
|
+
output.debug('run', testCommand);
|
|
152
|
+
let runResult = await testRunner.run(runOptions);
|
|
180
153
|
|
|
181
154
|
// Show summary
|
|
182
|
-
|
|
155
|
+
let {
|
|
183
156
|
screenshotsCaptured,
|
|
184
157
|
comparisons
|
|
185
|
-
} =
|
|
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
|
-
|
|
200
|
-
if (
|
|
201
|
-
|
|
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
|
-
...
|
|
180
|
+
...runResult
|
|
210
181
|
},
|
|
211
182
|
cleanup
|
|
212
183
|
};
|
|
213
184
|
} catch (error) {
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/commands/upload.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadConfig } from '../utils/config-loader.js';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
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
|
-
|
|
16
|
+
let apiService = new ApiService({
|
|
17
17
|
baseUrl: apiUrl,
|
|
18
18
|
token: apiToken,
|
|
19
19
|
command: 'upload'
|
|
20
20
|
});
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
+
let uploadStartTime = Date.now();
|
|
55
53
|
try {
|
|
56
|
-
|
|
54
|
+
output.info('Starting upload process...');
|
|
57
55
|
|
|
58
56
|
// Load configuration with CLI overrides
|
|
59
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
125
|
+
output.progress(displayMessage || 'Processing...', current, total);
|
|
127
126
|
}
|
|
128
127
|
};
|
|
129
128
|
|
|
130
129
|
// Start upload
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
136
|
+
output.progress('Finalizing build...');
|
|
138
137
|
try {
|
|
139
|
-
|
|
138
|
+
let apiService = new ApiService({
|
|
140
139
|
baseUrl: config.apiUrl,
|
|
141
140
|
token: config.apiKey,
|
|
142
141
|
command: 'upload'
|
|
143
142
|
});
|
|
144
|
-
|
|
143
|
+
let executionTime = Date.now() - uploadStartTime;
|
|
145
144
|
await apiService.finalizeBuild(result.buildId, true, executionTime);
|
|
146
145
|
} catch (error) {
|
|
147
|
-
|
|
146
|
+
output.warn(`Failed to finalize build: ${error.message}`);
|
|
148
147
|
}
|
|
149
148
|
}
|
|
150
|
-
|
|
149
|
+
output.success('Upload completed successfully');
|
|
151
150
|
|
|
152
151
|
// Show Vizzly summary
|
|
153
152
|
if (result.buildId) {
|
|
154
|
-
|
|
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
|
-
|
|
157
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
168
|
+
output.warn(`${buildResult.failedComparisons} visual comparisons failed`);
|
|
170
169
|
} else {
|
|
171
|
-
|
|
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
|
-
|
|
175
|
-
|
|
173
|
+
let buildUrl = buildResult.url || (await constructBuildUrl(result.buildId, config.apiUrl, config.apiKey));
|
|
174
|
+
output.info(`🔗 Vizzly: View results at ${buildUrl}`);
|
|
176
175
|
}
|
|
177
|
-
|
|
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
|
-
|
|
181
|
+
let apiService = new ApiService({
|
|
183
182
|
baseUrl: config.apiUrl,
|
|
184
183
|
token: config.apiKey,
|
|
185
184
|
command: 'upload'
|
|
186
185
|
});
|
|
187
|
-
|
|
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
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|