@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
@@ -0,0 +1,438 @@
1
+ /**
2
+ * Config Core - Pure functions for configuration logic
3
+ *
4
+ * No I/O, no side effects - just data transformations.
5
+ */
6
+
7
+ import { VizzlyError } from '../errors/vizzly-error.js';
8
+
9
+ // ============================================================================
10
+ // Default Configuration
11
+ // ============================================================================
12
+
13
+ /**
14
+ * Default configuration values
15
+ */
16
+ export const CONFIG_DEFAULTS = {
17
+ apiUrl: 'https://app.vizzly.dev',
18
+ server: {
19
+ port: 47392,
20
+ timeout: 30000
21
+ },
22
+ build: {
23
+ name: 'Build {timestamp}',
24
+ environment: 'test'
25
+ },
26
+ upload: {
27
+ screenshotsDir: './screenshots',
28
+ batchSize: 10,
29
+ timeout: 30000
30
+ },
31
+ comparison: {
32
+ threshold: 2.0
33
+ },
34
+ tdd: {
35
+ openReport: false
36
+ },
37
+ plugins: []
38
+ };
39
+
40
+ /**
41
+ * Valid config scopes for reading
42
+ */
43
+ export const READ_SCOPES = ['project', 'global', 'merged'];
44
+
45
+ /**
46
+ * Valid config scopes for writing
47
+ */
48
+ export const WRITE_SCOPES = ['project', 'global'];
49
+
50
+ // ============================================================================
51
+ // Scope Validation
52
+ // ============================================================================
53
+
54
+ /**
55
+ * Validate that a scope is valid for reading
56
+ * @param {string} scope - Scope to validate
57
+ * @returns {{ valid: boolean, error: Error|null }}
58
+ */
59
+ export function validateReadScope(scope) {
60
+ if (!READ_SCOPES.includes(scope)) {
61
+ return {
62
+ valid: false,
63
+ error: new VizzlyError(`Invalid config scope: ${scope}. Must be 'project', 'global', or 'merged'`, 'INVALID_CONFIG_SCOPE')
64
+ };
65
+ }
66
+ return {
67
+ valid: true,
68
+ error: null
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Validate that a scope is valid for writing
74
+ * @param {string} scope - Scope to validate
75
+ * @returns {{ valid: boolean, error: Error|null }}
76
+ */
77
+ export function validateWriteScope(scope) {
78
+ if (!WRITE_SCOPES.includes(scope)) {
79
+ return {
80
+ valid: false,
81
+ error: new VizzlyError(`Invalid config scope for update: ${scope}. Must be 'project' or 'global'`, 'INVALID_CONFIG_SCOPE')
82
+ };
83
+ }
84
+ return {
85
+ valid: true,
86
+ error: null
87
+ };
88
+ }
89
+
90
+ // ============================================================================
91
+ // Deep Merge
92
+ // ============================================================================
93
+
94
+ /**
95
+ * Deep merge two objects
96
+ * @param {Object} target - Target object
97
+ * @param {Object} source - Source object
98
+ * @returns {Object} Merged object (new object, inputs not mutated)
99
+ */
100
+ export function deepMerge(target, source) {
101
+ let output = {
102
+ ...target
103
+ };
104
+ for (let key of Object.keys(source)) {
105
+ if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
106
+ if (target[key] && typeof target[key] === 'object' && !Array.isArray(target[key])) {
107
+ output[key] = deepMerge(target[key], source[key]);
108
+ } else {
109
+ output[key] = source[key];
110
+ }
111
+ } else {
112
+ output[key] = source[key];
113
+ }
114
+ }
115
+ return output;
116
+ }
117
+
118
+ // ============================================================================
119
+ // Config Merging with Source Tracking
120
+ // ============================================================================
121
+
122
+ /**
123
+ * Ensure value is a plain object, return empty object otherwise
124
+ * @param {*} value - Value to check
125
+ * @returns {Object} The value if it's an object, empty object otherwise
126
+ */
127
+ function ensureObject(value) {
128
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
129
+ return value;
130
+ }
131
+ return {};
132
+ }
133
+
134
+ /**
135
+ * Build merged config from layers with source tracking
136
+ * @param {Object} options - Config layers
137
+ * @param {Object} options.projectConfig - Project config (from vizzly.config.js)
138
+ * @param {Object} options.globalConfig - Global config (from ~/.vizzly/config.json)
139
+ * @param {Object} [options.envOverrides] - Environment variable overrides
140
+ * @returns {{ config: Object, sources: Object }}
141
+ */
142
+ export function buildMergedConfig({
143
+ projectConfig = {},
144
+ globalConfig = {},
145
+ envOverrides = {}
146
+ } = {}) {
147
+ // Ensure all inputs are plain objects
148
+ let safeProjectConfig = ensureObject(projectConfig);
149
+ let safeGlobalConfig = ensureObject(globalConfig);
150
+ let safeEnvOverrides = ensureObject(envOverrides);
151
+ let mergedConfig = {};
152
+ let sources = {};
153
+
154
+ // Layer 1: Defaults
155
+ for (let key of Object.keys(CONFIG_DEFAULTS)) {
156
+ mergedConfig[key] = CONFIG_DEFAULTS[key];
157
+ sources[key] = 'default';
158
+ }
159
+
160
+ // Layer 2: Global config (auth, project mappings, user preferences)
161
+ if (safeGlobalConfig.auth) {
162
+ mergedConfig.auth = safeGlobalConfig.auth;
163
+ sources.auth = 'global';
164
+ }
165
+ if (safeGlobalConfig.projects) {
166
+ mergedConfig.projects = safeGlobalConfig.projects;
167
+ sources.projects = 'global';
168
+ }
169
+
170
+ // Layer 3: Project config file
171
+ for (let key of Object.keys(safeProjectConfig)) {
172
+ mergedConfig[key] = safeProjectConfig[key];
173
+ sources[key] = 'project';
174
+ }
175
+
176
+ // Layer 4: Environment variables
177
+ for (let key of Object.keys(safeEnvOverrides)) {
178
+ mergedConfig[key] = safeEnvOverrides[key];
179
+ sources[key] = 'env';
180
+ }
181
+ return {
182
+ config: mergedConfig,
183
+ sources
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Extract environment variable overrides
189
+ * @param {Object} env - Environment variables object (defaults to process.env)
190
+ * @returns {Object} Overrides from environment
191
+ */
192
+ export function extractEnvOverrides(env = process.env) {
193
+ let overrides = {};
194
+ if (env.VIZZLY_TOKEN) {
195
+ overrides.apiKey = env.VIZZLY_TOKEN;
196
+ }
197
+ if (env.VIZZLY_API_URL) {
198
+ overrides.apiUrl = env.VIZZLY_API_URL;
199
+ }
200
+ return overrides;
201
+ }
202
+
203
+ // ============================================================================
204
+ // Config Result Building
205
+ // ============================================================================
206
+
207
+ /**
208
+ * Build a project config result object
209
+ * @param {Object|null} config - Config object or null if not found
210
+ * @param {string|null} filepath - Path to config file or null
211
+ * @returns {{ config: Object, filepath: string|null, isEmpty: boolean }}
212
+ */
213
+ export function buildProjectConfigResult(config, filepath) {
214
+ if (!config) {
215
+ return {
216
+ config: {},
217
+ filepath: null,
218
+ isEmpty: true
219
+ };
220
+ }
221
+ return {
222
+ config,
223
+ filepath,
224
+ isEmpty: Object.keys(config).length === 0
225
+ };
226
+ }
227
+
228
+ /**
229
+ * Build a global config result object
230
+ * @param {Object} config - Global config object
231
+ * @param {string} filepath - Path to global config file
232
+ * @returns {{ config: Object, filepath: string, isEmpty: boolean }}
233
+ */
234
+ export function buildGlobalConfigResult(config, filepath) {
235
+ return {
236
+ config,
237
+ filepath,
238
+ isEmpty: Object.keys(config).length === 0
239
+ };
240
+ }
241
+
242
+ /**
243
+ * Build a merged config result object
244
+ * @param {Object} options - Build options
245
+ * @returns {{ config: Object, sources: Object, projectFilepath: string|null, globalFilepath: string }}
246
+ */
247
+ export function buildMergedConfigResult({
248
+ projectConfig,
249
+ globalConfig,
250
+ envOverrides,
251
+ projectFilepath,
252
+ globalFilepath
253
+ }) {
254
+ let {
255
+ config,
256
+ sources
257
+ } = buildMergedConfig({
258
+ projectConfig,
259
+ globalConfig,
260
+ envOverrides
261
+ });
262
+ return {
263
+ config,
264
+ sources,
265
+ projectFilepath,
266
+ globalFilepath
267
+ };
268
+ }
269
+
270
+ // ============================================================================
271
+ // Config Serialization
272
+ // ============================================================================
273
+
274
+ /**
275
+ * Stringify a value with proper indentation for JavaScript output
276
+ * @param {*} value - Value to stringify
277
+ * @param {number} depth - Current depth for indentation
278
+ * @returns {string} JavaScript representation of value
279
+ */
280
+ export function stringifyWithIndent(value, depth = 0) {
281
+ let indent = ' '.repeat(depth);
282
+ let prevIndent = depth > 0 ? ' '.repeat(depth - 1) : '';
283
+ if (value === null || value === undefined) {
284
+ return String(value);
285
+ }
286
+ if (typeof value === 'string') {
287
+ return `'${value.replace(/'/g, "\\'")}'`;
288
+ }
289
+ if (typeof value === 'number' || typeof value === 'boolean') {
290
+ return String(value);
291
+ }
292
+ if (Array.isArray(value)) {
293
+ if (value.length === 0) return '[]';
294
+ let items = value.map(item => `${indent}${stringifyWithIndent(item, depth + 1)}`);
295
+ return `[\n${items.join(',\n')}\n${prevIndent}]`;
296
+ }
297
+ if (typeof value === 'object') {
298
+ let keys = Object.keys(value);
299
+ if (keys.length === 0) return '{}';
300
+ let items = keys.map(key => {
301
+ let val = stringifyWithIndent(value[key], depth + 1);
302
+ return `${indent}${key}: ${val}`;
303
+ });
304
+ return `{\n${items.join(',\n')}\n${prevIndent}}`;
305
+ }
306
+ return String(value);
307
+ }
308
+
309
+ /**
310
+ * Serialize config to JavaScript module format
311
+ * @param {Object} config - Config object to serialize
312
+ * @returns {string} JavaScript source code
313
+ */
314
+ export function serializeToJavaScript(config) {
315
+ let lines = ['/**', ' * Vizzly Configuration', ' * @see https://docs.vizzly.dev/cli/configuration', ' */', '', "import { defineConfig } from '@vizzly-testing/cli/config';", '', 'export default defineConfig(', stringifyWithIndent(config, 1), ');', ''];
316
+ return lines.join('\n');
317
+ }
318
+
319
+ /**
320
+ * Serialize config to JSON format
321
+ * @param {Object} config - Config object to serialize
322
+ * @returns {string} JSON string with 2-space indentation
323
+ */
324
+ export function serializeToJson(config) {
325
+ return JSON.stringify(config, null, 2);
326
+ }
327
+
328
+ /**
329
+ * Determine the serialization format based on filepath
330
+ * @param {string} filepath - Path to config file
331
+ * @returns {'javascript'|'json'|'package'|'unknown'} Format type
332
+ */
333
+ export function getConfigFormat(filepath) {
334
+ if (filepath.endsWith('.js') || filepath.endsWith('.mjs')) {
335
+ return 'javascript';
336
+ }
337
+ if (filepath.endsWith('.json') && !filepath.endsWith('package.json')) {
338
+ return 'json';
339
+ }
340
+ if (filepath.endsWith('package.json')) {
341
+ return 'package';
342
+ }
343
+ return 'unknown';
344
+ }
345
+
346
+ /**
347
+ * Serialize config for writing to file
348
+ * @param {Object} config - Config object to serialize
349
+ * @param {string} filepath - Target file path
350
+ * @returns {{ content: string|null, format: string, error: Error|null }}
351
+ */
352
+ export function serializeConfig(config, filepath) {
353
+ let format = getConfigFormat(filepath);
354
+ if (format === 'javascript') {
355
+ return {
356
+ content: serializeToJavaScript(config),
357
+ format,
358
+ error: null
359
+ };
360
+ }
361
+ if (format === 'json') {
362
+ return {
363
+ content: serializeToJson(config),
364
+ format,
365
+ error: null
366
+ };
367
+ }
368
+ if (format === 'package') {
369
+ // Can't serialize standalone, need existing package.json
370
+ return {
371
+ content: null,
372
+ format,
373
+ error: null
374
+ };
375
+ }
376
+ return {
377
+ content: null,
378
+ format,
379
+ error: new VizzlyError(`Unsupported config file format: ${filepath}`, 'UNSUPPORTED_CONFIG_FORMAT')
380
+ };
381
+ }
382
+
383
+ // ============================================================================
384
+ // Config Extraction
385
+ // ============================================================================
386
+
387
+ /**
388
+ * Extract config from cosmiconfig result (handles .default exports)
389
+ * @param {Object|null} result - Cosmiconfig result
390
+ * @returns {{ config: Object|null, filepath: string|null }}
391
+ */
392
+ export function extractCosmiconfigResult(result) {
393
+ if (!result || !result.config) {
394
+ return {
395
+ config: null,
396
+ filepath: null
397
+ };
398
+ }
399
+
400
+ // Handle both `export default` and `module.exports`
401
+ let config = result.config.default || result.config;
402
+ return {
403
+ config,
404
+ filepath: result.filepath
405
+ };
406
+ }
407
+
408
+ // ============================================================================
409
+ // Validation Result Building
410
+ // ============================================================================
411
+
412
+ /**
413
+ * Build a validation success result
414
+ * @param {Object} validatedConfig - Validated config
415
+ * @returns {{ valid: true, config: Object, errors: [] }}
416
+ */
417
+ export function buildValidationSuccess(validatedConfig) {
418
+ return {
419
+ valid: true,
420
+ config: validatedConfig,
421
+ errors: []
422
+ };
423
+ }
424
+
425
+ /**
426
+ * Build a validation failure result
427
+ * @param {Error} error - Validation error
428
+ * @returns {{ valid: false, config: null, errors: Array }}
429
+ */
430
+ export function buildValidationFailure(error) {
431
+ return {
432
+ valid: false,
433
+ config: null,
434
+ errors: error.errors || [{
435
+ message: error.message
436
+ }]
437
+ };
438
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Config Module - Public exports
3
+ *
4
+ * Provides functional configuration primitives:
5
+ * - core.js: Pure functions for merging, serialization, validation results
6
+ * - operations.js: Config operations with dependency injection
7
+ */
8
+
9
+ // Core pure functions
10
+ export { buildGlobalConfigResult, buildMergedConfig, buildMergedConfigResult, buildProjectConfigResult, buildValidationFailure, buildValidationSuccess, CONFIG_DEFAULTS, deepMerge, extractCosmiconfigResult, extractEnvOverrides, getConfigFormat, READ_SCOPES, serializeConfig, serializeToJavaScript, serializeToJson, stringifyWithIndent, validateReadScope, validateWriteScope, WRITE_SCOPES } from './core.js';
11
+
12
+ // Config operations (take dependencies as parameters)
13
+ export { getConfig, getConfigSource, getGlobalConfig, getMergedConfig, getProjectConfig, updateConfig, updateGlobalConfig, updateProjectConfig, validateConfig, writeProjectConfigFile } from './operations.js';