@vizzly-testing/cli 0.1.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/LICENSE +21 -0
- package/README.md +363 -0
- package/bin/vizzly.js +3 -0
- package/dist/cli.js +104 -0
- package/dist/client/index.js +237 -0
- package/dist/commands/doctor.js +158 -0
- package/dist/commands/init.js +102 -0
- package/dist/commands/run.js +224 -0
- package/dist/commands/status.js +164 -0
- package/dist/commands/tdd.js +212 -0
- package/dist/commands/upload.js +181 -0
- package/dist/container/index.js +184 -0
- package/dist/errors/vizzly-error.js +149 -0
- package/dist/index.js +31 -0
- package/dist/screenshot-wrapper.js +68 -0
- package/dist/sdk/index.js +364 -0
- package/dist/server/index.js +522 -0
- package/dist/services/api-service.js +215 -0
- package/dist/services/base-service.js +154 -0
- package/dist/services/build-manager.js +214 -0
- package/dist/services/screenshot-server.js +96 -0
- package/dist/services/server-manager.js +61 -0
- package/dist/services/service-utils.js +171 -0
- package/dist/services/tdd-service.js +444 -0
- package/dist/services/test-runner.js +210 -0
- package/dist/services/uploader.js +413 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/client/index.d.ts +76 -0
- package/dist/types/commands/doctor.d.ts +11 -0
- package/dist/types/commands/init.d.ts +14 -0
- package/dist/types/commands/run.d.ts +13 -0
- package/dist/types/commands/status.d.ts +13 -0
- package/dist/types/commands/tdd.d.ts +13 -0
- package/dist/types/commands/upload.d.ts +13 -0
- package/dist/types/container/index.d.ts +61 -0
- package/dist/types/errors/vizzly-error.d.ts +75 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.js +153 -0
- package/dist/types/screenshot-wrapper.d.ts +27 -0
- package/dist/types/sdk/index.d.ts +108 -0
- package/dist/types/server/index.d.ts +38 -0
- package/dist/types/services/api-service.d.ts +77 -0
- package/dist/types/services/base-service.d.ts +72 -0
- package/dist/types/services/build-manager.d.ts +68 -0
- package/dist/types/services/screenshot-server.d.ts +10 -0
- package/dist/types/services/server-manager.d.ts +8 -0
- package/dist/types/services/service-utils.d.ts +45 -0
- package/dist/types/services/tdd-service.d.ts +55 -0
- package/dist/types/services/test-runner.d.ts +25 -0
- package/dist/types/services/uploader.d.ts +34 -0
- package/dist/types/types/index.d.ts +373 -0
- package/dist/types/utils/colors.d.ts +12 -0
- package/dist/types/utils/config-helpers.d.ts +6 -0
- package/dist/types/utils/config-loader.d.ts +22 -0
- package/dist/types/utils/console-ui.d.ts +61 -0
- package/dist/types/utils/diagnostics.d.ts +69 -0
- package/dist/types/utils/environment-config.d.ts +54 -0
- package/dist/types/utils/environment.d.ts +36 -0
- package/dist/types/utils/error-messages.d.ts +42 -0
- package/dist/types/utils/fetch-utils.d.ts +1 -0
- package/dist/types/utils/framework-detector.d.ts +5 -0
- package/dist/types/utils/git.d.ts +44 -0
- package/dist/types/utils/help.d.ts +11 -0
- package/dist/types/utils/image-comparison.d.ts +42 -0
- package/dist/types/utils/logger-factory.d.ts +26 -0
- package/dist/types/utils/logger.d.ts +79 -0
- package/dist/types/utils/package-info.d.ts +15 -0
- package/dist/types/utils/package.d.ts +1 -0
- package/dist/types/utils/project-detection.d.ts +19 -0
- package/dist/types/utils/ui-helpers.d.ts +23 -0
- package/dist/utils/colors.js +66 -0
- package/dist/utils/config-helpers.js +8 -0
- package/dist/utils/config-loader.js +120 -0
- package/dist/utils/console-ui.js +226 -0
- package/dist/utils/diagnostics.js +184 -0
- package/dist/utils/environment-config.js +93 -0
- package/dist/utils/environment.js +109 -0
- package/dist/utils/error-messages.js +34 -0
- package/dist/utils/fetch-utils.js +9 -0
- package/dist/utils/framework-detector.js +40 -0
- package/dist/utils/git.js +226 -0
- package/dist/utils/help.js +66 -0
- package/dist/utils/image-comparison.js +172 -0
- package/dist/utils/logger-factory.js +76 -0
- package/dist/utils/logger.js +231 -0
- package/dist/utils/package-info.js +38 -0
- package/dist/utils/package.js +9 -0
- package/dist/utils/project-detection.js +145 -0
- package/dist/utils/ui-helpers.js +86 -0
- package/package.json +103 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module @vizzly-testing/cli/client
|
|
3
|
+
* @description Thin client for test runners - minimal API for taking screenshots
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isVizzlyEnabled, getServerUrl, getBuildId, isTddMode, setVizzlyEnabled } from '../utils/environment-config.js';
|
|
7
|
+
|
|
8
|
+
// Internal client state
|
|
9
|
+
let currentClient = null;
|
|
10
|
+
let isDisabled = false;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if Vizzly is currently disabled
|
|
14
|
+
* @private
|
|
15
|
+
* @returns {boolean} True if disabled via environment variable or auto-disabled due to failure
|
|
16
|
+
*/
|
|
17
|
+
function isVizzlyDisabled() {
|
|
18
|
+
return !isVizzlyEnabled() || isDisabled;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Disable Vizzly SDK for the current session
|
|
23
|
+
* @private
|
|
24
|
+
* @param {string} [reason] - Optional reason for disabling
|
|
25
|
+
*/
|
|
26
|
+
function disableVizzly(reason = 'disabled') {
|
|
27
|
+
isDisabled = true;
|
|
28
|
+
currentClient = null;
|
|
29
|
+
if (reason !== 'disabled') {
|
|
30
|
+
console.warn(`Vizzly SDK disabled due to ${reason}. Screenshots will be skipped for the remainder of this session.`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get the current client instance
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
function getClient() {
|
|
39
|
+
if (isVizzlyDisabled()) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (!currentClient) {
|
|
43
|
+
// Only try to initialize if VIZZLY_ENABLED is explicitly true
|
|
44
|
+
const serverUrl = getServerUrl();
|
|
45
|
+
if (serverUrl && isVizzlyEnabled()) {
|
|
46
|
+
currentClient = createSimpleClient(serverUrl);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return currentClient;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a simple HTTP client for screenshots
|
|
54
|
+
* @private
|
|
55
|
+
*/
|
|
56
|
+
function createSimpleClient(serverUrl) {
|
|
57
|
+
return {
|
|
58
|
+
async screenshot(name, imageBuffer, options = {}) {
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(`${serverUrl}/screenshot`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json'
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
buildId: getBuildId(),
|
|
67
|
+
name,
|
|
68
|
+
image: imageBuffer.toString('base64'),
|
|
69
|
+
properties: options.properties || {},
|
|
70
|
+
threshold: options.threshold || 0,
|
|
71
|
+
variant: options.variant,
|
|
72
|
+
fullPage: options.fullPage || false
|
|
73
|
+
})
|
|
74
|
+
});
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const errorData = await response.json().catch(async () => {
|
|
77
|
+
const errorText = await response.text().catch(() => 'Unknown error');
|
|
78
|
+
return {
|
|
79
|
+
error: errorText
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// In TDD mode, if we get 422 (visual difference), throw with clean message
|
|
84
|
+
if (response.status === 422 && errorData.tddMode && errorData.comparison) {
|
|
85
|
+
const comp = errorData.comparison;
|
|
86
|
+
throw new Error(`Visual difference detected in "${name}"\n` + ` Baseline: ${comp.baseline}\n` + ` Current: ${comp.current}\n` + ` Diff: ${comp.diff}`);
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Screenshot failed: ${response.status} ${response.statusText} - ${errorData.error || 'Unknown error'}`);
|
|
89
|
+
}
|
|
90
|
+
return await response.json();
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// In TDD mode with visual differences, throw the error to fail the test
|
|
93
|
+
if (error.message.includes('Visual difference detected')) {
|
|
94
|
+
// Clean output for TDD mode - don't spam with additional logs
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
console.error(`Failed to save screenshot "${name}":`, error.message);
|
|
98
|
+
console.error(`Vizzly screenshot failed for ${name}: ${error.message}`);
|
|
99
|
+
if (error.message.includes('fetch') || error.code === 'ECONNREFUSED') {
|
|
100
|
+
console.error(`Server URL: ${serverUrl}/screenshot`);
|
|
101
|
+
console.error('This usually means the Vizzly server is not running or not accessible');
|
|
102
|
+
console.error('Check that the server is started and the port is correct');
|
|
103
|
+
} else if (error.message.includes('404') || error.message.includes('Not Found')) {
|
|
104
|
+
console.error(`Server URL: ${serverUrl}/screenshot`);
|
|
105
|
+
console.error('The screenshot endpoint was not found - check server configuration');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Disable the SDK after first failure to prevent spam
|
|
109
|
+
disableVizzly('failure');
|
|
110
|
+
|
|
111
|
+
// Don't throw - just return silently to not break tests (except TDD mode)
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
async flush() {
|
|
116
|
+
// Simple client doesn't need explicit flushing
|
|
117
|
+
return Promise.resolve();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Take a screenshot for visual regression testing
|
|
124
|
+
*
|
|
125
|
+
* @param {string} name - Unique name for the screenshot
|
|
126
|
+
* @param {Buffer} imageBuffer - PNG image data as a Buffer
|
|
127
|
+
* @param {Object} [options] - Optional configuration
|
|
128
|
+
* @param {Record<string, any>} [options.properties] - Additional properties to attach to the screenshot
|
|
129
|
+
* @param {number} [options.threshold=0] - Pixel difference threshold (0-100)
|
|
130
|
+
* @param {string} [options.variant] - Variant name for organizing screenshots
|
|
131
|
+
* @param {boolean} [options.fullPage=false] - Whether this is a full page screenshot
|
|
132
|
+
*
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* // Basic usage
|
|
137
|
+
* import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
138
|
+
*
|
|
139
|
+
* const screenshot = await page.screenshot();
|
|
140
|
+
* await vizzlyScreenshot('homepage', screenshot);
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* // With properties and threshold
|
|
144
|
+
* await vizzlyScreenshot('checkout-form', screenshot, {
|
|
145
|
+
* properties: {
|
|
146
|
+
* browser: 'chrome',
|
|
147
|
+
* viewport: '1920x1080'
|
|
148
|
+
* },
|
|
149
|
+
* threshold: 5
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* @throws {VizzlyError} When screenshot capture fails or client is not initialized
|
|
153
|
+
*/
|
|
154
|
+
export async function vizzlyScreenshot(name, imageBuffer, options = {}) {
|
|
155
|
+
if (isVizzlyDisabled()) {
|
|
156
|
+
return; // Silently skip when disabled
|
|
157
|
+
}
|
|
158
|
+
const client = getClient();
|
|
159
|
+
if (!client) {
|
|
160
|
+
console.warn('Vizzly client not initialized. Screenshots will be skipped.');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
return client.screenshot(name, imageBuffer, options);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Wait for all queued screenshots to be processed
|
|
168
|
+
*
|
|
169
|
+
* @returns {Promise<void>}
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* afterAll(async () => {
|
|
173
|
+
* await vizzlyFlush();
|
|
174
|
+
* });
|
|
175
|
+
*/
|
|
176
|
+
export async function vizzlyFlush() {
|
|
177
|
+
const client = getClient();
|
|
178
|
+
if (client) {
|
|
179
|
+
return client.flush();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Check if the Vizzly client is initialized and ready
|
|
185
|
+
*
|
|
186
|
+
* @returns {boolean} True if client is ready, false otherwise
|
|
187
|
+
*/
|
|
188
|
+
export function isVizzlyReady() {
|
|
189
|
+
return !isVizzlyDisabled() && getClient() !== null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Configure the client with custom settings
|
|
194
|
+
*
|
|
195
|
+
* @param {Object} config - Configuration options
|
|
196
|
+
* @param {string} [config.serverUrl] - Server URL override
|
|
197
|
+
* @param {boolean} [config.enabled] - Enable/disable screenshots
|
|
198
|
+
*/
|
|
199
|
+
export function configure(config = {}) {
|
|
200
|
+
if (config.serverUrl) {
|
|
201
|
+
currentClient = createSimpleClient(config.serverUrl);
|
|
202
|
+
}
|
|
203
|
+
if (typeof config.enabled === 'boolean') {
|
|
204
|
+
setVizzlyEnabled(config.enabled);
|
|
205
|
+
if (!config.enabled) {
|
|
206
|
+
disableVizzly();
|
|
207
|
+
} else {
|
|
208
|
+
isDisabled = false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Enable or disable screenshot capture
|
|
215
|
+
* @param {boolean} enabled - Whether to enable screenshots
|
|
216
|
+
*/
|
|
217
|
+
export function setEnabled(enabled) {
|
|
218
|
+
configure({
|
|
219
|
+
enabled
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Get information about Vizzly client state
|
|
225
|
+
* @returns {Object} Client information
|
|
226
|
+
*/
|
|
227
|
+
export function getVizzlyInfo() {
|
|
228
|
+
const client = getClient();
|
|
229
|
+
return {
|
|
230
|
+
enabled: !isVizzlyDisabled(),
|
|
231
|
+
serverUrl: getServerUrl(),
|
|
232
|
+
ready: !isVizzlyDisabled() && client !== null,
|
|
233
|
+
buildId: getBuildId(),
|
|
234
|
+
tddMode: isTddMode(),
|
|
235
|
+
disabled: isVizzlyDisabled()
|
|
236
|
+
};
|
|
237
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { URL } from 'url';
|
|
2
|
+
import { loadConfig } from '../utils/config-loader.js';
|
|
3
|
+
import { ConsoleUI } from '../utils/console-ui.js';
|
|
4
|
+
import { ApiService } from '../services/api-service.js';
|
|
5
|
+
import { ConfigError } from '../errors/vizzly-error.js';
|
|
6
|
+
import { getApiToken } from '../utils/environment-config.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Doctor command implementation - Run diagnostics to check environment
|
|
10
|
+
* @param {Object} options - Command options
|
|
11
|
+
* @param {Object} globalOptions - Global CLI options
|
|
12
|
+
*/
|
|
13
|
+
export async function doctorCommand(options = {}, globalOptions = {}) {
|
|
14
|
+
// Create UI handler
|
|
15
|
+
const ui = new ConsoleUI({
|
|
16
|
+
json: globalOptions.json,
|
|
17
|
+
verbose: globalOptions.verbose,
|
|
18
|
+
color: !globalOptions.noColor
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Note: ConsoleUI handles cleanup via global process listeners
|
|
22
|
+
|
|
23
|
+
const diagnostics = {
|
|
24
|
+
environment: {
|
|
25
|
+
nodeVersion: null,
|
|
26
|
+
nodeVersionValid: null
|
|
27
|
+
},
|
|
28
|
+
configuration: {
|
|
29
|
+
apiUrl: null,
|
|
30
|
+
apiUrlValid: null,
|
|
31
|
+
threshold: null,
|
|
32
|
+
thresholdValid: null,
|
|
33
|
+
port: null
|
|
34
|
+
},
|
|
35
|
+
connectivity: {
|
|
36
|
+
checked: false,
|
|
37
|
+
ok: null,
|
|
38
|
+
error: null
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
let hasErrors = false;
|
|
42
|
+
try {
|
|
43
|
+
// Determine if we'll attempt remote checks (API connectivity)
|
|
44
|
+
const willCheckConnectivity = Boolean(options.api || getApiToken());
|
|
45
|
+
|
|
46
|
+
// Announce preflight, indicating local-only when no token/connectivity is planned
|
|
47
|
+
ui.info(`Running Vizzly preflight${willCheckConnectivity ? '' : ' (local checks only)'}...`);
|
|
48
|
+
|
|
49
|
+
// Node.js version check (require >= 20)
|
|
50
|
+
const nodeVersion = process.version;
|
|
51
|
+
const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
|
|
52
|
+
diagnostics.environment.nodeVersion = nodeVersion;
|
|
53
|
+
diagnostics.environment.nodeVersionValid = nodeMajor >= 20;
|
|
54
|
+
if (nodeMajor >= 20) {
|
|
55
|
+
ui.success(`Node.js version: ${nodeVersion} (supported)`);
|
|
56
|
+
} else {
|
|
57
|
+
hasErrors = true;
|
|
58
|
+
ui.error('Node.js version must be >= 20', {}, 0);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Load configuration (apply global CLI overrides like --config only)
|
|
62
|
+
const config = await loadConfig(globalOptions.config);
|
|
63
|
+
|
|
64
|
+
// Validate apiUrl
|
|
65
|
+
diagnostics.configuration.apiUrl = config.apiUrl;
|
|
66
|
+
try {
|
|
67
|
+
const url = new URL(config.apiUrl);
|
|
68
|
+
if (!['http:', 'https:'].includes(url.protocol)) {
|
|
69
|
+
throw new ConfigError('URL must use http or https');
|
|
70
|
+
}
|
|
71
|
+
diagnostics.configuration.apiUrlValid = true;
|
|
72
|
+
ui.success(`API URL: ${config.apiUrl}`);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
diagnostics.configuration.apiUrlValid = false;
|
|
75
|
+
hasErrors = true;
|
|
76
|
+
ui.error('Invalid apiUrl in configuration (set VIZZLY_API_URL or config file)', e, 0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Validate threshold (0..1 inclusive)
|
|
80
|
+
const threshold = Number(config?.comparison?.threshold);
|
|
81
|
+
diagnostics.configuration.threshold = threshold;
|
|
82
|
+
const thresholdValid = Number.isFinite(threshold) && threshold >= 0 && threshold <= 1;
|
|
83
|
+
diagnostics.configuration.thresholdValid = thresholdValid;
|
|
84
|
+
if (thresholdValid) {
|
|
85
|
+
ui.success(`Threshold: ${threshold}`);
|
|
86
|
+
} else {
|
|
87
|
+
hasErrors = true;
|
|
88
|
+
ui.error('Invalid threshold (expected number between 0 and 1)', {}, 0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Report effective port without binding
|
|
92
|
+
const port = config?.server?.port ?? 47392;
|
|
93
|
+
diagnostics.configuration.port = port;
|
|
94
|
+
ui.info(`Effective port: ${port}`);
|
|
95
|
+
|
|
96
|
+
// Optional: API connectivity check when --api is provided or VIZZLY_TOKEN is present
|
|
97
|
+
const autoApi = Boolean(getApiToken());
|
|
98
|
+
if (options.api || autoApi) {
|
|
99
|
+
diagnostics.connectivity.checked = true;
|
|
100
|
+
if (!config.apiKey) {
|
|
101
|
+
diagnostics.connectivity.ok = false;
|
|
102
|
+
diagnostics.connectivity.error = 'Missing API token (VIZZLY_TOKEN)';
|
|
103
|
+
hasErrors = true;
|
|
104
|
+
ui.error('Missing API token for connectivity check', {}, 0);
|
|
105
|
+
} else {
|
|
106
|
+
ui.progress('Checking API connectivity...');
|
|
107
|
+
try {
|
|
108
|
+
const api = new ApiService({
|
|
109
|
+
baseUrl: config.apiUrl,
|
|
110
|
+
token: config.apiKey,
|
|
111
|
+
command: 'doctor'
|
|
112
|
+
});
|
|
113
|
+
// Minimal, read-only call
|
|
114
|
+
await api.getBuilds({
|
|
115
|
+
limit: 1
|
|
116
|
+
});
|
|
117
|
+
diagnostics.connectivity.ok = true;
|
|
118
|
+
ui.success('API connectivity OK');
|
|
119
|
+
} catch (err) {
|
|
120
|
+
diagnostics.connectivity.ok = false;
|
|
121
|
+
diagnostics.connectivity.error = err?.message || String(err);
|
|
122
|
+
hasErrors = true;
|
|
123
|
+
ui.error('API connectivity failed', err, 0);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Summary
|
|
129
|
+
if (hasErrors) {
|
|
130
|
+
ui.warning('Preflight completed with issues.');
|
|
131
|
+
} else {
|
|
132
|
+
ui.success('Preflight passed.');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Emit structured data in json/verbose modes
|
|
136
|
+
if (globalOptions.json || globalOptions.verbose) {
|
|
137
|
+
ui.data({
|
|
138
|
+
passed: !hasErrors,
|
|
139
|
+
diagnostics,
|
|
140
|
+
timestamp: new Date().toISOString()
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
} catch (error) {
|
|
144
|
+
hasErrors = true;
|
|
145
|
+
ui.error('Failed to run preflight', error, 0);
|
|
146
|
+
} finally {
|
|
147
|
+
ui.cleanup();
|
|
148
|
+
if (hasErrors) process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Validate doctor options (no specific validation needed)
|
|
154
|
+
* @param {Object} options - Command options
|
|
155
|
+
*/
|
|
156
|
+
export function validateDoctorOptions() {
|
|
157
|
+
return []; // No specific validation for now
|
|
158
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
5
|
+
import { createComponentLogger } from '../utils/logger-factory.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Simple configuration setup for Vizzly CLI
|
|
9
|
+
*/
|
|
10
|
+
export class InitCommand {
|
|
11
|
+
constructor(logger) {
|
|
12
|
+
this.logger = logger || createComponentLogger('INIT', {
|
|
13
|
+
level: 'info'
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async run(options = {}) {
|
|
17
|
+
this.logger.info('šÆ Initializing Vizzly configuration...\n');
|
|
18
|
+
try {
|
|
19
|
+
// Check for existing config
|
|
20
|
+
const configPath = path.join(process.cwd(), 'vizzly.config.js');
|
|
21
|
+
const hasConfig = await this.fileExists(configPath);
|
|
22
|
+
if (hasConfig && !options.force) {
|
|
23
|
+
this.logger.info('ā A vizzly.config.js file already exists. Use --force to overwrite.');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Generate config file with defaults
|
|
28
|
+
await this.generateConfigFile(configPath);
|
|
29
|
+
|
|
30
|
+
// Show next steps
|
|
31
|
+
this.showNextSteps();
|
|
32
|
+
this.logger.info('\nā
Vizzly CLI setup complete!');
|
|
33
|
+
} catch (error) {
|
|
34
|
+
throw new VizzlyError('Failed to initialize Vizzly configuration', 'INIT_FAILED', {
|
|
35
|
+
error: error.message
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async generateConfigFile(configPath) {
|
|
40
|
+
const configContent = `export default {
|
|
41
|
+
// API configuration
|
|
42
|
+
// Set VIZZLY_TOKEN environment variable or uncomment and set here:
|
|
43
|
+
// apiToken: 'your-token-here',
|
|
44
|
+
|
|
45
|
+
// Screenshot configuration
|
|
46
|
+
screenshots: {
|
|
47
|
+
directory: './screenshots',
|
|
48
|
+
formats: ['png']
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// Server configuration
|
|
52
|
+
server: {
|
|
53
|
+
port: 47392,
|
|
54
|
+
screenshotPath: '/screenshot'
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
// Comparison configuration
|
|
58
|
+
comparison: {
|
|
59
|
+
threshold: 0.1,
|
|
60
|
+
ignoreAntialiasing: true
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
// Upload configuration
|
|
64
|
+
upload: {
|
|
65
|
+
concurrency: 5,
|
|
66
|
+
timeout: 30000
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
`;
|
|
70
|
+
await fs.writeFile(configPath, configContent, 'utf8');
|
|
71
|
+
this.logger.info(`š Created vizzly.config.js`);
|
|
72
|
+
}
|
|
73
|
+
showNextSteps() {
|
|
74
|
+
this.logger.info('\nš Next steps:');
|
|
75
|
+
this.logger.info(' 1. Set your API token:');
|
|
76
|
+
this.logger.info(' export VIZZLY_TOKEN="your-api-key"');
|
|
77
|
+
this.logger.info(' 2. Run your tests with Vizzly:');
|
|
78
|
+
this.logger.info(' npx vizzly run "npm test"');
|
|
79
|
+
this.logger.info(' 3. Upload screenshots:');
|
|
80
|
+
this.logger.info(' npx vizzly upload ./screenshots');
|
|
81
|
+
}
|
|
82
|
+
async fileExists(filePath) {
|
|
83
|
+
try {
|
|
84
|
+
await fs.access(filePath);
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Export factory function for CLI
|
|
93
|
+
export function createInitCommand(options) {
|
|
94
|
+
const command = new InitCommand(options.logger);
|
|
95
|
+
return () => command.run(options);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Simple export for direct CLI usage
|
|
99
|
+
export async function init(options = {}) {
|
|
100
|
+
const command = new InitCommand();
|
|
101
|
+
return await command.run(options);
|
|
102
|
+
}
|