@vizzly-testing/cli 0.20.0 → 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 (72) 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/commands/doctor.js +3 -3
  11. package/dist/commands/finalize.js +41 -15
  12. package/dist/commands/login.js +7 -6
  13. package/dist/commands/logout.js +4 -4
  14. package/dist/commands/project.js +5 -4
  15. package/dist/commands/run.js +158 -90
  16. package/dist/commands/status.js +22 -18
  17. package/dist/commands/tdd.js +105 -78
  18. package/dist/commands/upload.js +61 -26
  19. package/dist/commands/whoami.js +4 -4
  20. package/dist/config/core.js +438 -0
  21. package/dist/config/index.js +13 -0
  22. package/dist/config/operations.js +327 -0
  23. package/dist/index.js +1 -1
  24. package/dist/project/core.js +295 -0
  25. package/dist/project/index.js +13 -0
  26. package/dist/project/operations.js +393 -0
  27. package/dist/report-generator/core.js +315 -0
  28. package/dist/report-generator/index.js +8 -0
  29. package/dist/report-generator/operations.js +196 -0
  30. package/dist/reporter/reporter-bundle.iife.js +16 -16
  31. package/dist/screenshot-server/core.js +157 -0
  32. package/dist/screenshot-server/index.js +11 -0
  33. package/dist/screenshot-server/operations.js +183 -0
  34. package/dist/sdk/index.js +3 -2
  35. package/dist/server/handlers/api-handler.js +14 -5
  36. package/dist/server/handlers/tdd-handler.js +80 -48
  37. package/dist/server-manager/core.js +183 -0
  38. package/dist/server-manager/index.js +81 -0
  39. package/dist/server-manager/operations.js +208 -0
  40. package/dist/services/build-manager.js +2 -69
  41. package/dist/services/index.js +21 -48
  42. package/dist/services/screenshot-server.js +40 -74
  43. package/dist/services/server-manager.js +45 -80
  44. package/dist/services/static-report-generator.js +21 -163
  45. package/dist/services/test-runner.js +90 -250
  46. package/dist/services/uploader.js +56 -358
  47. package/dist/tdd/core/hotspot-coverage.js +112 -0
  48. package/dist/tdd/core/signature.js +101 -0
  49. package/dist/tdd/index.js +19 -0
  50. package/dist/tdd/metadata/baseline-metadata.js +103 -0
  51. package/dist/tdd/metadata/hotspot-metadata.js +93 -0
  52. package/dist/tdd/services/baseline-downloader.js +151 -0
  53. package/dist/tdd/services/baseline-manager.js +166 -0
  54. package/dist/tdd/services/comparison-service.js +230 -0
  55. package/dist/tdd/services/hotspot-service.js +71 -0
  56. package/dist/tdd/services/result-service.js +123 -0
  57. package/dist/tdd/tdd-service.js +1081 -0
  58. package/dist/test-runner/core.js +255 -0
  59. package/dist/test-runner/index.js +13 -0
  60. package/dist/test-runner/operations.js +483 -0
  61. package/dist/uploader/core.js +396 -0
  62. package/dist/uploader/index.js +11 -0
  63. package/dist/uploader/operations.js +412 -0
  64. package/package.json +7 -12
  65. package/dist/services/api-service.js +0 -412
  66. package/dist/services/auth-service.js +0 -226
  67. package/dist/services/config-service.js +0 -369
  68. package/dist/services/html-report-generator.js +0 -455
  69. package/dist/services/project-service.js +0 -326
  70. package/dist/services/report-generator/report.css +0 -411
  71. package/dist/services/report-generator/viewer.js +0 -102
  72. package/dist/services/tdd-service.js +0 -1437
@@ -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
- }