@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.
- package/dist/api/client.js +134 -0
- package/dist/api/core.js +341 -0
- package/dist/api/endpoints.js +314 -0
- package/dist/api/index.js +19 -0
- package/dist/auth/client.js +91 -0
- package/dist/auth/core.js +176 -0
- package/dist/auth/index.js +30 -0
- package/dist/auth/operations.js +148 -0
- package/dist/cli.js +1 -1
- package/dist/client/index.js +0 -1
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/finalize.js +41 -15
- package/dist/commands/login.js +7 -6
- package/dist/commands/logout.js +4 -4
- package/dist/commands/project.js +5 -4
- package/dist/commands/run.js +158 -90
- package/dist/commands/status.js +22 -18
- package/dist/commands/tdd.js +105 -78
- package/dist/commands/upload.js +61 -26
- package/dist/commands/whoami.js +4 -4
- package/dist/config/core.js +438 -0
- package/dist/config/index.js +13 -0
- package/dist/config/operations.js +327 -0
- package/dist/index.js +1 -1
- package/dist/project/core.js +295 -0
- package/dist/project/index.js +13 -0
- package/dist/project/operations.js +393 -0
- package/dist/report-generator/core.js +315 -0
- package/dist/report-generator/index.js +8 -0
- package/dist/report-generator/operations.js +196 -0
- package/dist/reporter/reporter-bundle.iife.js +16 -16
- package/dist/screenshot-server/core.js +157 -0
- package/dist/screenshot-server/index.js +11 -0
- package/dist/screenshot-server/operations.js +183 -0
- package/dist/sdk/index.js +3 -2
- package/dist/server/handlers/api-handler.js +14 -5
- package/dist/server/handlers/tdd-handler.js +80 -48
- package/dist/server-manager/core.js +183 -0
- package/dist/server-manager/index.js +81 -0
- package/dist/server-manager/operations.js +208 -0
- package/dist/services/build-manager.js +2 -69
- package/dist/services/index.js +21 -48
- package/dist/services/screenshot-server.js +40 -74
- package/dist/services/server-manager.js +45 -80
- package/dist/services/static-report-generator.js +21 -163
- package/dist/services/test-runner.js +90 -249
- package/dist/services/uploader.js +56 -358
- package/dist/tdd/core/hotspot-coverage.js +112 -0
- package/dist/tdd/core/signature.js +101 -0
- package/dist/tdd/index.js +19 -0
- package/dist/tdd/metadata/baseline-metadata.js +103 -0
- package/dist/tdd/metadata/hotspot-metadata.js +93 -0
- package/dist/tdd/services/baseline-downloader.js +151 -0
- package/dist/tdd/services/baseline-manager.js +166 -0
- package/dist/tdd/services/comparison-service.js +230 -0
- package/dist/tdd/services/hotspot-service.js +71 -0
- package/dist/tdd/services/result-service.js +123 -0
- package/dist/tdd/tdd-service.js +1081 -0
- package/dist/test-runner/core.js +255 -0
- package/dist/test-runner/index.js +13 -0
- package/dist/test-runner/operations.js +483 -0
- package/dist/types/client.d.ts +4 -2
- package/dist/types/index.d.ts +5 -0
- package/dist/uploader/core.js +396 -0
- package/dist/uploader/index.js +11 -0
- package/dist/uploader/operations.js +412 -0
- package/dist/utils/config-schema.js +8 -3
- package/package.json +7 -12
- package/dist/services/api-service.js +0 -412
- package/dist/services/auth-service.js +0 -226
- package/dist/services/config-service.js +0 -369
- package/dist/services/html-report-generator.js +0 -455
- package/dist/services/project-service.js +0 -326
- package/dist/services/report-generator/report.css +0 -411
- package/dist/services/report-generator/viewer.js +0 -102
- package/dist/services/tdd-service.js +0 -1429
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Configuration Service
|
|
3
|
-
* Manages reading and writing Vizzly configuration files
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFile, writeFile } from 'node:fs/promises';
|
|
7
|
-
import { join } from 'node:path';
|
|
8
|
-
import { cosmiconfigSync } from 'cosmiconfig';
|
|
9
|
-
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
10
|
-
import { validateVizzlyConfigWithDefaults } from '../utils/config-schema.js';
|
|
11
|
-
import { getGlobalConfigPath, loadGlobalConfig, saveGlobalConfig } from '../utils/global-config.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* ConfigService for reading and writing configuration
|
|
15
|
-
*/
|
|
16
|
-
export class ConfigService {
|
|
17
|
-
constructor(config, options = {}) {
|
|
18
|
-
this.config = config;
|
|
19
|
-
this.projectRoot = options.projectRoot || process.cwd();
|
|
20
|
-
this.explorer = cosmiconfigSync('vizzly');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Get configuration with source information
|
|
25
|
-
* @param {string} scope - 'project', 'global', or 'merged'
|
|
26
|
-
* @returns {Promise<Object>} Config object with metadata
|
|
27
|
-
*/
|
|
28
|
-
async getConfig(scope = 'merged') {
|
|
29
|
-
if (scope === 'project') {
|
|
30
|
-
return this._getProjectConfig();
|
|
31
|
-
}
|
|
32
|
-
if (scope === 'global') {
|
|
33
|
-
return this._getGlobalConfig();
|
|
34
|
-
}
|
|
35
|
-
if (scope === 'merged') {
|
|
36
|
-
return this._getMergedConfig();
|
|
37
|
-
}
|
|
38
|
-
throw new VizzlyError(`Invalid config scope: ${scope}. Must be 'project', 'global', or 'merged'`, 'INVALID_CONFIG_SCOPE');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get project-level config from vizzly.config.js or similar
|
|
43
|
-
* @private
|
|
44
|
-
* @returns {Promise<Object>}
|
|
45
|
-
*/
|
|
46
|
-
async _getProjectConfig() {
|
|
47
|
-
const result = this.explorer.search(this.projectRoot);
|
|
48
|
-
if (!result || !result.config) {
|
|
49
|
-
return {
|
|
50
|
-
config: {},
|
|
51
|
-
filepath: null,
|
|
52
|
-
isEmpty: true
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
const config = result.config.default || result.config;
|
|
56
|
-
return {
|
|
57
|
-
config,
|
|
58
|
-
filepath: result.filepath,
|
|
59
|
-
isEmpty: Object.keys(config).length === 0
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Get global config from ~/.vizzly/config.json
|
|
65
|
-
* @private
|
|
66
|
-
* @returns {Promise<Object>}
|
|
67
|
-
*/
|
|
68
|
-
async _getGlobalConfig() {
|
|
69
|
-
const globalConfig = await loadGlobalConfig();
|
|
70
|
-
return {
|
|
71
|
-
config: globalConfig,
|
|
72
|
-
filepath: getGlobalConfigPath(),
|
|
73
|
-
isEmpty: Object.keys(globalConfig).length === 0
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Get merged config showing source for each setting
|
|
79
|
-
* @private
|
|
80
|
-
* @returns {Promise<Object>}
|
|
81
|
-
*/
|
|
82
|
-
async _getMergedConfig() {
|
|
83
|
-
const projectConfigData = await this._getProjectConfig();
|
|
84
|
-
const globalConfigData = await this._getGlobalConfig();
|
|
85
|
-
|
|
86
|
-
// Build config with source tracking
|
|
87
|
-
const mergedConfig = {};
|
|
88
|
-
const sources = {};
|
|
89
|
-
|
|
90
|
-
// Layer 1: Defaults
|
|
91
|
-
const defaults = {
|
|
92
|
-
apiUrl: 'https://app.vizzly.dev',
|
|
93
|
-
server: {
|
|
94
|
-
port: 47392,
|
|
95
|
-
timeout: 30000
|
|
96
|
-
},
|
|
97
|
-
build: {
|
|
98
|
-
name: 'Build {timestamp}',
|
|
99
|
-
environment: 'test'
|
|
100
|
-
},
|
|
101
|
-
upload: {
|
|
102
|
-
screenshotsDir: './screenshots',
|
|
103
|
-
batchSize: 10,
|
|
104
|
-
timeout: 30000
|
|
105
|
-
},
|
|
106
|
-
comparison: {
|
|
107
|
-
threshold: 2.0
|
|
108
|
-
},
|
|
109
|
-
tdd: {
|
|
110
|
-
openReport: false
|
|
111
|
-
},
|
|
112
|
-
plugins: []
|
|
113
|
-
};
|
|
114
|
-
Object.keys(defaults).forEach(key => {
|
|
115
|
-
mergedConfig[key] = defaults[key];
|
|
116
|
-
sources[key] = 'default';
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
// Layer 2: Global config (auth, project mappings, user preferences)
|
|
120
|
-
if (globalConfigData.config.auth) {
|
|
121
|
-
mergedConfig.auth = globalConfigData.config.auth;
|
|
122
|
-
sources.auth = 'global';
|
|
123
|
-
}
|
|
124
|
-
if (globalConfigData.config.projects) {
|
|
125
|
-
mergedConfig.projects = globalConfigData.config.projects;
|
|
126
|
-
sources.projects = 'global';
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Layer 3: Project config file
|
|
130
|
-
Object.keys(projectConfigData.config).forEach(key => {
|
|
131
|
-
mergedConfig[key] = projectConfigData.config[key];
|
|
132
|
-
sources[key] = 'project';
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
// Layer 4: Environment variables (tracked separately)
|
|
136
|
-
const envOverrides = {};
|
|
137
|
-
if (process.env.VIZZLY_TOKEN) {
|
|
138
|
-
envOverrides.apiKey = process.env.VIZZLY_TOKEN;
|
|
139
|
-
sources.apiKey = 'env';
|
|
140
|
-
}
|
|
141
|
-
if (process.env.VIZZLY_API_URL) {
|
|
142
|
-
envOverrides.apiUrl = process.env.VIZZLY_API_URL;
|
|
143
|
-
sources.apiUrl = 'env';
|
|
144
|
-
}
|
|
145
|
-
return {
|
|
146
|
-
config: {
|
|
147
|
-
...mergedConfig,
|
|
148
|
-
...envOverrides
|
|
149
|
-
},
|
|
150
|
-
sources,
|
|
151
|
-
projectFilepath: projectConfigData.filepath,
|
|
152
|
-
globalFilepath: globalConfigData.filepath
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Update configuration
|
|
158
|
-
* @param {string} scope - 'project' or 'global'
|
|
159
|
-
* @param {Object} updates - Configuration updates to apply
|
|
160
|
-
* @returns {Promise<Object>} Updated config
|
|
161
|
-
*/
|
|
162
|
-
async updateConfig(scope, updates) {
|
|
163
|
-
if (scope === 'project') {
|
|
164
|
-
return this._updateProjectConfig(updates);
|
|
165
|
-
}
|
|
166
|
-
if (scope === 'global') {
|
|
167
|
-
return this._updateGlobalConfig(updates);
|
|
168
|
-
}
|
|
169
|
-
throw new VizzlyError(`Invalid config scope for update: ${scope}. Must be 'project' or 'global'`, 'INVALID_CONFIG_SCOPE');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Update project-level config
|
|
174
|
-
* @private
|
|
175
|
-
* @param {Object} updates - Config updates
|
|
176
|
-
* @returns {Promise<Object>} Updated config
|
|
177
|
-
*/
|
|
178
|
-
async _updateProjectConfig(updates) {
|
|
179
|
-
const result = this.explorer.search(this.projectRoot);
|
|
180
|
-
|
|
181
|
-
// Determine config file path
|
|
182
|
-
let configPath;
|
|
183
|
-
let currentConfig = {};
|
|
184
|
-
if (result?.filepath) {
|
|
185
|
-
configPath = result.filepath;
|
|
186
|
-
currentConfig = result.config.default || result.config;
|
|
187
|
-
} else {
|
|
188
|
-
// Create new config file - prefer vizzly.config.js
|
|
189
|
-
configPath = join(this.projectRoot, 'vizzly.config.js');
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Merge updates with current config
|
|
193
|
-
const newConfig = this._deepMerge(currentConfig, updates);
|
|
194
|
-
|
|
195
|
-
// Validate before writing
|
|
196
|
-
try {
|
|
197
|
-
validateVizzlyConfigWithDefaults(newConfig);
|
|
198
|
-
} catch (error) {
|
|
199
|
-
throw new VizzlyError(`Invalid configuration: ${error.message}`, 'CONFIG_VALIDATION_ERROR', {
|
|
200
|
-
errors: error.errors
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Write config file
|
|
205
|
-
await this._writeProjectConfigFile(configPath, newConfig);
|
|
206
|
-
|
|
207
|
-
// Clear cosmiconfig cache
|
|
208
|
-
this.explorer.clearCaches();
|
|
209
|
-
return {
|
|
210
|
-
config: newConfig,
|
|
211
|
-
filepath: configPath
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Update global config
|
|
217
|
-
* @private
|
|
218
|
-
* @param {Object} updates - Config updates
|
|
219
|
-
* @returns {Promise<Object>} Updated config
|
|
220
|
-
*/
|
|
221
|
-
async _updateGlobalConfig(updates) {
|
|
222
|
-
const currentConfig = await loadGlobalConfig();
|
|
223
|
-
const newConfig = this._deepMerge(currentConfig, updates);
|
|
224
|
-
await saveGlobalConfig(newConfig);
|
|
225
|
-
return {
|
|
226
|
-
config: newConfig,
|
|
227
|
-
filepath: getGlobalConfigPath()
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Write project config file (JavaScript format)
|
|
233
|
-
* @private
|
|
234
|
-
* @param {string} filepath - Path to write to
|
|
235
|
-
* @param {Object} config - Config object
|
|
236
|
-
* @returns {Promise<void>}
|
|
237
|
-
*/
|
|
238
|
-
async _writeProjectConfigFile(filepath, config) {
|
|
239
|
-
// For .js files, export as ES module
|
|
240
|
-
if (filepath.endsWith('.js') || filepath.endsWith('.mjs')) {
|
|
241
|
-
const content = this._serializeToJavaScript(config);
|
|
242
|
-
await writeFile(filepath, content, 'utf-8');
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// For .json files
|
|
247
|
-
if (filepath.endsWith('.json')) {
|
|
248
|
-
const content = JSON.stringify(config, null, 2);
|
|
249
|
-
await writeFile(filepath, content, 'utf-8');
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// For package.json, merge into existing
|
|
254
|
-
if (filepath.endsWith('package.json')) {
|
|
255
|
-
const pkgContent = await readFile(filepath, 'utf-8');
|
|
256
|
-
const pkg = JSON.parse(pkgContent);
|
|
257
|
-
pkg.vizzly = config;
|
|
258
|
-
await writeFile(filepath, JSON.stringify(pkg, null, 2), 'utf-8');
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
throw new VizzlyError(`Unsupported config file format: ${filepath}`, 'UNSUPPORTED_CONFIG_FORMAT');
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Serialize config object to JavaScript module
|
|
266
|
-
* @private
|
|
267
|
-
* @param {Object} config - Config object
|
|
268
|
-
* @returns {string} JavaScript source code
|
|
269
|
-
*/
|
|
270
|
-
_serializeToJavaScript(config) {
|
|
271
|
-
const lines = ['/**', ' * Vizzly Configuration', ' * @see https://docs.vizzly.dev/cli/configuration', ' */', '', "import { defineConfig } from '@vizzly-testing/cli/config';", '', 'export default defineConfig(', this._stringifyWithIndent(config, 1), ');', ''];
|
|
272
|
-
return lines.join('\n');
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Stringify object with proper indentation (2 spaces)
|
|
277
|
-
* @private
|
|
278
|
-
* @param {*} value - Value to stringify
|
|
279
|
-
* @param {number} depth - Current depth
|
|
280
|
-
* @returns {string}
|
|
281
|
-
*/
|
|
282
|
-
_stringifyWithIndent(value, depth = 0) {
|
|
283
|
-
const indent = ' '.repeat(depth);
|
|
284
|
-
const prevIndent = ' '.repeat(depth - 1);
|
|
285
|
-
if (value === null || value === undefined) {
|
|
286
|
-
return String(value);
|
|
287
|
-
}
|
|
288
|
-
if (typeof value === 'string') {
|
|
289
|
-
return `'${value.replace(/'/g, "\\'")}'`;
|
|
290
|
-
}
|
|
291
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
292
|
-
return String(value);
|
|
293
|
-
}
|
|
294
|
-
if (Array.isArray(value)) {
|
|
295
|
-
if (value.length === 0) return '[]';
|
|
296
|
-
const items = value.map(item => `${indent}${this._stringifyWithIndent(item, depth + 1)}`);
|
|
297
|
-
return `[\n${items.join(',\n')}\n${prevIndent}]`;
|
|
298
|
-
}
|
|
299
|
-
if (typeof value === 'object') {
|
|
300
|
-
const keys = Object.keys(value);
|
|
301
|
-
if (keys.length === 0) return '{}';
|
|
302
|
-
const items = keys.map(key => {
|
|
303
|
-
const val = this._stringifyWithIndent(value[key], depth + 1);
|
|
304
|
-
return `${indent}${key}: ${val}`;
|
|
305
|
-
});
|
|
306
|
-
return `{\n${items.join(',\n')}\n${prevIndent}}`;
|
|
307
|
-
}
|
|
308
|
-
return String(value);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Validate configuration object
|
|
313
|
-
* @param {Object} config - Config to validate
|
|
314
|
-
* @returns {Promise<Object>} Validation result
|
|
315
|
-
*/
|
|
316
|
-
async validateConfig(config) {
|
|
317
|
-
try {
|
|
318
|
-
const validated = validateVizzlyConfigWithDefaults(config);
|
|
319
|
-
return {
|
|
320
|
-
valid: true,
|
|
321
|
-
config: validated,
|
|
322
|
-
errors: []
|
|
323
|
-
};
|
|
324
|
-
} catch (error) {
|
|
325
|
-
return {
|
|
326
|
-
valid: false,
|
|
327
|
-
config: null,
|
|
328
|
-
errors: error.errors || [{
|
|
329
|
-
message: error.message
|
|
330
|
-
}]
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Get the source of a specific config key
|
|
337
|
-
* @param {string} key - Config key
|
|
338
|
-
* @returns {Promise<string>} Source ('default', 'global', 'project', 'env', 'cli')
|
|
339
|
-
*/
|
|
340
|
-
async getConfigSource(key) {
|
|
341
|
-
const merged = await this._getMergedConfig();
|
|
342
|
-
return merged.sources[key] || 'unknown';
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Deep merge two objects
|
|
347
|
-
* @private
|
|
348
|
-
* @param {Object} target - Target object
|
|
349
|
-
* @param {Object} source - Source object
|
|
350
|
-
* @returns {Object} Merged object
|
|
351
|
-
*/
|
|
352
|
-
_deepMerge(target, source) {
|
|
353
|
-
const output = {
|
|
354
|
-
...target
|
|
355
|
-
};
|
|
356
|
-
for (const key in source) {
|
|
357
|
-
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
358
|
-
if (target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
|
|
359
|
-
output[key] = this._deepMerge(target[key], source[key]);
|
|
360
|
-
} else {
|
|
361
|
-
output[key] = source[key];
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
output[key] = source[key];
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
return output;
|
|
368
|
-
}
|
|
369
|
-
}
|