@vizzly-testing/cli 0.39.1 → 0.39.2

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 (195) hide show
  1. package/README.md +8 -0
  2. package/bin/vizzly.js +1 -1
  3. package/dist/{cli.js → adapters/cli/cli.js} +41 -35
  4. package/dist/adapters/cli/setup.js +34 -0
  5. package/dist/{client → adapters/client}/index.js +6 -7
  6. package/dist/adapters/http/cloud-proxy.js +29 -0
  7. package/dist/{server → adapters/http}/http-server.js +12 -13
  8. package/dist/{server-manager/operations.js → adapters/http/server-lifecycle.js} +10 -13
  9. package/dist/{server-manager/index.js → adapters/http/server-manager.js} +23 -27
  10. package/dist/{plugin-api.js → adapters/plugins/plugin-api.js} +1 -1
  11. package/dist/{plugin-loader.js → adapters/plugins/plugin-loader.js} +1 -1
  12. package/dist/{services/index.js → adapters/plugins/services.js} +4 -9
  13. package/dist/adapters/sdk/index.d.ts +89 -0
  14. package/dist/{sdk → adapters/sdk}/index.js +22 -19
  15. package/dist/{commands/api.js → domains/api/api.commands.js} +3 -3
  16. package/dist/{auth/client.js → domains/auth/auth-client.js} +4 -3
  17. package/dist/domains/auth/auth-errors.js +50 -0
  18. package/dist/domains/auth/auth-headers.js +41 -0
  19. package/dist/domains/auth/auth.normalizers.js +50 -0
  20. package/dist/{server/routers/auth.js → domains/auth/auth.routes.js} +9 -9
  21. package/dist/domains/auth/auth.rules.js +19 -0
  22. package/dist/domains/auth/auth.serializers.js +32 -0
  23. package/dist/{auth/operations.js → domains/auth/auth.service.js} +115 -17
  24. package/dist/domains/auth/auth.validations.js +21 -0
  25. package/dist/{api/client.js → domains/auth/cloud-client.js} +11 -18
  26. package/dist/{commands/login.js → domains/auth/login.commands.js} +11 -8
  27. package/dist/{commands/logout.js → domains/auth/logout.commands.js} +6 -4
  28. package/dist/domains/auth/token-store.js +81 -0
  29. package/dist/{commands/whoami.js → domains/auth/whoami.commands.js} +6 -3
  30. package/dist/domains/baselines/baseline-download.js +192 -0
  31. package/dist/domains/baselines/baseline-write.js +22 -0
  32. package/dist/{server/routers/baseline.js → domains/baselines/baseline.routes.js} +3 -3
  33. package/dist/{tdd/services/baseline-manager.js → domains/baselines/baseline.service.js} +1 -39
  34. package/dist/{commands/baselines.js → domains/baselines/baselines.commands.js} +3 -3
  35. package/dist/domains/baselines/cloud-baselines.js +9 -0
  36. package/dist/domains/baselines/cloud-baselines.routes.js +42 -0
  37. package/dist/domains/builds/build.normalizers.js +35 -0
  38. package/dist/domains/builds/build.serializers.js +164 -0
  39. package/dist/domains/builds/build.service.js +119 -0
  40. package/dist/domains/builds/build.validations.js +39 -0
  41. package/dist/{commands/builds.js → domains/builds/builds.commands.js} +6 -74
  42. package/dist/domains/builds/cloud-builds.routes.js +31 -0
  43. package/dist/{commands/finalize.js → domains/builds/finalize.commands.js} +5 -17
  44. package/dist/{commands/status.js → domains/builds/status.commands.js} +9 -120
  45. package/dist/domains/comparisons/cloud-comparisons.js +43 -0
  46. package/dist/{tdd/services/comparison-service.js → domains/comparisons/comparison.service.js} +1 -1
  47. package/dist/{commands/comparisons.js → domains/comparisons/comparisons.commands.js} +7 -5
  48. package/dist/domains/config/config-defaults.js +27 -0
  49. package/dist/{utils → domains/config}/config-discovery.js +1 -9
  50. package/dist/{utils → domains/config}/config-loader.js +10 -9
  51. package/dist/{commands/config-cmd.js → domains/config/config.commands.js} +4 -4
  52. package/dist/{types → domains/config}/config.d.ts +3 -3
  53. package/dist/domains/config/config.normalizers.js +126 -0
  54. package/dist/{server/routers/config.js → domains/config/config.routes.js} +13 -13
  55. package/dist/domains/config/config.rules.js +47 -0
  56. package/dist/domains/config/config.serializers.js +202 -0
  57. package/dist/domains/config/config.service.js +625 -0
  58. package/dist/{utils/config-schema.js → domains/config/config.validations.js} +9 -9
  59. package/dist/{utils/environment-config.js → domains/config/environment.js} +1 -8
  60. package/dist/domains/context/compact-context.validations.js +60 -0
  61. package/dist/domains/context/context-presentation.js +368 -0
  62. package/dist/domains/context/context.commands.js +385 -0
  63. package/dist/domains/context/context.serializers.js +157 -0
  64. package/dist/domains/context/context.service.js +76 -0
  65. package/dist/domains/context/context.validations.js +100 -0
  66. package/dist/{context → domains/context}/local-workspace-provider.js +1 -1
  67. package/dist/{commands/doctor.js → domains/diagnostics/doctor.commands.js} +6 -5
  68. package/dist/{server/routers/health.js → domains/diagnostics/health.routes.js} +11 -20
  69. package/dist/{commands/preview.js → domains/previews/preview.commands.js} +9 -7
  70. package/dist/domains/previews/preview.service.js +37 -0
  71. package/dist/domains/projects/cloud-projects.routes.js +28 -0
  72. package/dist/{commands/orgs.js → domains/projects/orgs.commands.js} +6 -8
  73. package/dist/{utils → domains/projects}/project-binding.js +3 -3
  74. package/dist/domains/projects/project-errors.js +61 -0
  75. package/dist/{utils → domains/projects}/project-link-store.js +2 -2
  76. package/dist/domains/projects/project-requests.js +61 -0
  77. package/dist/{commands/project.js → domains/projects/project.commands.js} +9 -41
  78. package/dist/domains/projects/project.normalizers.js +69 -0
  79. package/dist/{project/operations.js → domains/projects/project.service.js} +89 -16
  80. package/dist/domains/projects/project.validations.js +31 -0
  81. package/dist/{commands/projects.js → domains/projects/projects.commands.js} +6 -8
  82. package/dist/{server/routers/projects.js → domains/projects/projects.routes.js} +5 -5
  83. package/dist/domains/reports/dashboard.routes.js +96 -0
  84. package/dist/{server/routers/events.js → domains/reports/events.routes.js} +14 -36
  85. package/dist/domains/reports/report-assets.routes.js +81 -0
  86. package/dist/domains/reports/report-state.js +51 -0
  87. package/dist/domains/reports/report-store.js +85 -0
  88. package/dist/domains/reports/report.serializers.js +99 -0
  89. package/dist/{services/static-report-generator.js → domains/reports/report.service.js} +11 -33
  90. package/dist/{commands/review.js → domains/review/review.commands.js} +4 -4
  91. package/dist/domains/runs/run-environment.js +69 -0
  92. package/dist/{services/test-runner.js → domains/runs/run-events.js} +11 -9
  93. package/dist/{commands/run.js → domains/runs/run.commands.js} +16 -13
  94. package/dist/domains/runs/run.normalizers.js +8 -0
  95. package/dist/domains/runs/run.rules.js +56 -0
  96. package/dist/domains/runs/run.serializers.js +101 -0
  97. package/dist/{test-runner/operations.js → domains/runs/run.service.js} +35 -7
  98. package/dist/domains/runs/run.validations.js +17 -0
  99. package/dist/{utils/session.js → domains/runs/session-store.js} +3 -2
  100. package/dist/{server/handlers/api-handler.js → domains/screenshots/cloud-handler.js} +4 -4
  101. package/dist/domains/screenshots/cloud-screenshots.js +109 -0
  102. package/dist/{utils/file-helpers.js → domains/screenshots/image-files.js} +1 -1
  103. package/dist/domains/screenshots/screenshot-hash.js +53 -0
  104. package/dist/domains/screenshots/screenshot-server-options.js +28 -0
  105. package/dist/{screenshot-server/operations.js → domains/screenshots/screenshot-server.js} +14 -1
  106. package/dist/domains/screenshots/screenshot.normalizers.js +40 -0
  107. package/dist/{server/routers/screenshot.js → domains/screenshots/screenshot.routes.js} +4 -4
  108. package/dist/domains/screenshots/screenshot.serializers.js +146 -0
  109. package/dist/{utils/security.js → domains/screenshots/screenshot.validations.js} +20 -70
  110. package/dist/{init/agents.js → domains/setup/agent-setup.js} +4 -5
  111. package/dist/{commands/init.js → domains/setup/init.commands.js} +10 -41
  112. package/dist/{server/handlers/tdd-handler.js → domains/tdd/screenshot-handler.js} +46 -281
  113. package/dist/{commands/tdd-daemon.js → domains/tdd/tdd-daemon.commands.js} +6 -6
  114. package/dist/{commands/tdd-screenshots.js → domains/tdd/tdd-screenshots.commands.js} +5 -5
  115. package/dist/{commands/tdd.js → domains/tdd/tdd.commands.js} +16 -15
  116. package/dist/{tdd/tdd-service.js → domains/tdd/tdd.service.js} +81 -405
  117. package/dist/{utils → domains/updates}/cli-update.js +4 -6
  118. package/dist/{utils → domains/updates}/update-notifications.js +7 -7
  119. package/dist/domains/uploads/upload-defaults.js +4 -0
  120. package/dist/domains/uploads/upload-files.js +32 -0
  121. package/dist/domains/uploads/upload-settings.js +21 -0
  122. package/dist/domains/uploads/upload-timing.js +18 -0
  123. package/dist/{commands/upload.js → domains/uploads/upload.commands.js} +11 -9
  124. package/dist/domains/uploads/upload.normalizers.js +60 -0
  125. package/dist/domains/uploads/upload.serializers.js +179 -0
  126. package/dist/{uploader/operations.js → domains/uploads/upload.service.js} +85 -28
  127. package/dist/domains/uploads/upload.validations.js +77 -0
  128. package/dist/index.js +12 -12
  129. package/dist/reporter/reporter-bundle.iife.js +86 -86
  130. package/dist/reporter-ssr/ssr-entry.js +2 -2
  131. package/dist/shared/cloud-protocol.js +171 -0
  132. package/dist/{utils → shared}/file-changes.js +1 -1
  133. package/dist/shared/files.js +9 -0
  134. package/dist/{utils → shared}/global-config.js +7 -78
  135. package/dist/shared/log-level.js +7 -0
  136. package/dist/{utils → shared}/output.js +1 -1
  137. package/dist/{utils → shared}/package-info.js +5 -4
  138. package/dist/shared/paths.js +57 -0
  139. package/package.json +23 -22
  140. package/dist/api/core.js +0 -382
  141. package/dist/api/endpoints.js +0 -421
  142. package/dist/api/index.js +0 -19
  143. package/dist/auth/core.js +0 -237
  144. package/dist/auth/index.js +0 -31
  145. package/dist/commands/context.js +0 -1066
  146. package/dist/config/core.js +0 -439
  147. package/dist/config/operations.js +0 -327
  148. package/dist/project/core.js +0 -211
  149. package/dist/screenshot-server/core.js +0 -161
  150. package/dist/screenshot-server/index.js +0 -11
  151. package/dist/server/routers/assets.js +0 -95
  152. package/dist/server/routers/cloud-proxy.js +0 -146
  153. package/dist/server/routers/dashboard.js +0 -172
  154. package/dist/services/auth-service.js +0 -117
  155. package/dist/services/config-service.js +0 -313
  156. package/dist/services/project-service.js +0 -88
  157. package/dist/test-runner/core.js +0 -293
  158. package/dist/test-runner/index.js +0 -13
  159. package/dist/types/sdk.d.ts +0 -186
  160. package/dist/uploader/core.js +0 -432
  161. package/dist/uploader/index.js +0 -88
  162. /package/dist/{types/client.d.ts → adapters/client/index.d.ts} +0 -0
  163. /package/dist/{server → adapters/http}/middleware/cors.js +0 -0
  164. /package/dist/{server → adapters/http}/middleware/json-parser.js +0 -0
  165. /package/dist/{server → adapters/http}/middleware/response.js +0 -0
  166. /package/dist/{server-manager/core.js → adapters/http/server-options.js} +0 -0
  167. /package/dist/{utils/login-poll-wait.js → domains/auth/login-wait.js} +0 -0
  168. /package/dist/{tdd/metadata → domains/baselines}/baseline-metadata.js +0 -0
  169. /package/dist/{services/build-manager.js → domains/builds/build-info.js} +0 -0
  170. /package/dist/{tdd/services/result-service.js → domains/comparisons/comparison-results.js} +0 -0
  171. /package/dist/{init/core.js → domains/config/config-plan.js} +0 -0
  172. /package/dist/{utils/config-helpers.js → domains/config/config.js} +0 -0
  173. /package/dist/{utils/visual-context-normalizers.js → domains/context/context.normalizers.js} +0 -0
  174. /package/dist/{context → domains/context}/provider-resolver.js +0 -0
  175. /package/dist/{utils/context.js → domains/diagnostics/environment-context.js} +0 -0
  176. /package/dist/{utils/report-data.js → domains/reports/report.normalizers.js} +0 -0
  177. /package/dist/{utils/image-input-detector.js → domains/screenshots/image-input.js} +0 -0
  178. /package/dist/{tdd/core/signature.js → domains/screenshots/screenshot-identity.js} +0 -0
  179. /package/dist/{utils → domains/screenshots}/screenshot-options.js +0 -0
  180. /package/dist/{init/prompts.js → domains/setup/setup-prompts.js} +0 -0
  181. /package/dist/{tdd → domains/tdd}/runtime-context.js +0 -0
  182. /package/dist/{tdd → domains/tdd}/server-registry.js +0 -0
  183. /package/dist/{commands/tdd-daemon-process.js → domains/tdd/tdd-daemon-process.commands.js} +0 -0
  184. /package/dist/{utils → domains/updates}/update-check.js +0 -0
  185. /package/dist/{types/index.d.ts → index.d.ts} +0 -0
  186. /package/dist/{utils → shared}/api-url.js +0 -0
  187. /package/dist/{utils → shared}/async-utils.js +0 -0
  188. /package/dist/{utils → shared}/browser.js +0 -0
  189. /package/dist/{utils → shared}/ci-env.js +0 -0
  190. /package/dist/{utils → shared}/colors.js +0 -0
  191. /package/dist/{utils → shared}/fetch-utils.js +0 -0
  192. /package/dist/{utils → shared}/git.js +0 -0
  193. /package/dist/{utils → shared}/patterns.js +0 -0
  194. /package/dist/{utils → shared}/secret-store.js +0 -0
  195. /package/dist/{errors → shared}/vizzly-error.js +0 -0
@@ -1,439 +0,0 @@
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
- minClusterSize: 2
34
- },
35
- tdd: {
36
- openReport: false
37
- },
38
- plugins: []
39
- };
40
-
41
- /**
42
- * Valid config scopes for reading
43
- */
44
- export const READ_SCOPES = ['project', 'global', 'merged'];
45
-
46
- /**
47
- * Valid config scopes for writing
48
- */
49
- export const WRITE_SCOPES = ['project', 'global'];
50
-
51
- // ============================================================================
52
- // Scope Validation
53
- // ============================================================================
54
-
55
- /**
56
- * Validate that a scope is valid for reading
57
- * @param {string} scope - Scope to validate
58
- * @returns {{ valid: boolean, error: Error|null }}
59
- */
60
- export function validateReadScope(scope) {
61
- if (!READ_SCOPES.includes(scope)) {
62
- return {
63
- valid: false,
64
- error: new VizzlyError(`Invalid config scope: ${scope}. Must be 'project', 'global', or 'merged'`, 'INVALID_CONFIG_SCOPE')
65
- };
66
- }
67
- return {
68
- valid: true,
69
- error: null
70
- };
71
- }
72
-
73
- /**
74
- * Validate that a scope is valid for writing
75
- * @param {string} scope - Scope to validate
76
- * @returns {{ valid: boolean, error: Error|null }}
77
- */
78
- export function validateWriteScope(scope) {
79
- if (!WRITE_SCOPES.includes(scope)) {
80
- return {
81
- valid: false,
82
- error: new VizzlyError(`Invalid config scope for update: ${scope}. Must be 'project' or 'global'`, 'INVALID_CONFIG_SCOPE')
83
- };
84
- }
85
- return {
86
- valid: true,
87
- error: null
88
- };
89
- }
90
-
91
- // ============================================================================
92
- // Deep Merge
93
- // ============================================================================
94
-
95
- function isPlainConfigObject(value) {
96
- return value && typeof value === 'object' && !Array.isArray(value);
97
- }
98
- function cloneConfigValue(value) {
99
- if (Array.isArray(value)) {
100
- return value.map(cloneConfigValue);
101
- }
102
- if (isPlainConfigObject(value)) {
103
- let cloned = {};
104
- for (let key of Object.keys(value)) {
105
- cloned[key] = cloneConfigValue(value[key]);
106
- }
107
- return cloned;
108
- }
109
- return value;
110
- }
111
-
112
- /**
113
- * Deep merge two objects
114
- * @param {Object} target - Target object
115
- * @param {Object} source - Source object
116
- * @returns {Object} Merged object (new object, inputs not mutated)
117
- */
118
- export function deepMerge(target, source) {
119
- let output = cloneConfigValue(target);
120
- for (let key of Object.keys(source)) {
121
- if (isPlainConfigObject(source[key])) {
122
- if (isPlainConfigObject(target[key])) {
123
- output[key] = deepMerge(target[key], source[key]);
124
- } else {
125
- output[key] = cloneConfigValue(source[key]);
126
- }
127
- } else {
128
- output[key] = cloneConfigValue(source[key]);
129
- }
130
- }
131
- return output;
132
- }
133
-
134
- // ============================================================================
135
- // Config Merging with Source Tracking
136
- // ============================================================================
137
-
138
- /**
139
- * Ensure value is a plain object, return empty object otherwise
140
- * @param {*} value - Value to check
141
- * @returns {Object} The value if it's an object, empty object otherwise
142
- */
143
- function ensureObject(value) {
144
- if (isPlainConfigObject(value)) {
145
- return value;
146
- }
147
- return {};
148
- }
149
- function applyConfigLayer(config, sources, layer, sourceName) {
150
- let merged = deepMerge(config, layer);
151
- for (let key of Object.keys(layer)) {
152
- sources[key] = sourceName;
153
- }
154
- return merged;
155
- }
156
-
157
- /**
158
- * Build merged config from layers with source tracking
159
- * @param {Object} options - Config layers
160
- * @param {Object} options.projectConfig - Project config (from vizzly.config.js)
161
- * @param {Object} options.globalConfig - Global config (from ~/.vizzly/config.json)
162
- * @param {Object} [options.envOverrides] - Environment variable overrides
163
- * @returns {{ config: Object, sources: Object }}
164
- */
165
- export function buildMergedConfig({
166
- projectConfig = {},
167
- globalConfig = {},
168
- envOverrides = {}
169
- } = {}) {
170
- // Ensure all inputs are plain objects
171
- let safeProjectConfig = ensureObject(projectConfig);
172
- let safeGlobalConfig = ensureObject(globalConfig);
173
- let safeEnvOverrides = ensureObject(envOverrides);
174
- let mergedConfig = cloneConfigValue(CONFIG_DEFAULTS);
175
- let sources = {};
176
- for (let key of Object.keys(CONFIG_DEFAULTS)) {
177
- sources[key] = 'default';
178
- }
179
- mergedConfig = applyConfigLayer(mergedConfig, sources, safeGlobalConfig, 'global');
180
- mergedConfig = applyConfigLayer(mergedConfig, sources, safeProjectConfig, 'project');
181
- mergedConfig = applyConfigLayer(mergedConfig, sources, safeEnvOverrides, 'env');
182
- return {
183
- config: mergedConfig,
184
- sources
185
- };
186
- }
187
-
188
- /**
189
- * Extract environment variable overrides
190
- * @param {Object} env - Environment variables object (defaults to process.env)
191
- * @returns {Object} Overrides from environment
192
- */
193
- export function extractEnvOverrides(env = process.env) {
194
- let overrides = {};
195
- if (env.VIZZLY_TOKEN) {
196
- overrides.apiKey = env.VIZZLY_TOKEN;
197
- }
198
- if (env.VIZZLY_API_URL) {
199
- overrides.apiUrl = env.VIZZLY_API_URL;
200
- }
201
- return overrides;
202
- }
203
-
204
- // ============================================================================
205
- // Config Result Building
206
- // ============================================================================
207
-
208
- /**
209
- * Build a project config result object
210
- * @param {Object|null} config - Config object or null if not found
211
- * @param {string|null} filepath - Path to config file or null
212
- * @returns {{ config: Object, filepath: string|null, isEmpty: boolean }}
213
- */
214
- export function buildProjectConfigResult(config, filepath) {
215
- if (!config) {
216
- return {
217
- config: {},
218
- filepath: null,
219
- isEmpty: true
220
- };
221
- }
222
- return {
223
- config,
224
- filepath,
225
- isEmpty: Object.keys(config).length === 0
226
- };
227
- }
228
-
229
- /**
230
- * Build a global config result object
231
- * @param {Object} config - Global config object
232
- * @param {string} filepath - Path to global config file
233
- * @returns {{ config: Object, filepath: string, isEmpty: boolean }}
234
- */
235
- export function buildGlobalConfigResult(config, filepath) {
236
- return {
237
- config,
238
- filepath,
239
- isEmpty: Object.keys(config).length === 0
240
- };
241
- }
242
-
243
- /**
244
- * Build a merged config result object
245
- * @param {Object} options - Build options
246
- * @returns {{ config: Object, sources: Object, projectFilepath: string|null, globalFilepath: string }}
247
- */
248
- export function buildMergedConfigResult({
249
- projectConfig,
250
- globalConfig,
251
- envOverrides,
252
- projectFilepath,
253
- globalFilepath
254
- }) {
255
- let {
256
- config,
257
- sources
258
- } = buildMergedConfig({
259
- projectConfig,
260
- globalConfig,
261
- envOverrides
262
- });
263
- return {
264
- config,
265
- sources,
266
- projectFilepath,
267
- globalFilepath
268
- };
269
- }
270
-
271
- // ============================================================================
272
- // Config Serialization
273
- // ============================================================================
274
-
275
- /**
276
- * Stringify a value with proper indentation for JavaScript output
277
- * @param {*} value - Value to stringify
278
- * @param {number} depth - Current depth for indentation
279
- * @returns {string} JavaScript representation of value
280
- */
281
- export function stringifyWithIndent(value, depth = 0) {
282
- let indent = ' '.repeat(depth);
283
- let prevIndent = depth > 0 ? ' '.repeat(depth - 1) : '';
284
- if (value === null || value === undefined) {
285
- return String(value);
286
- }
287
- if (typeof value === 'string') {
288
- return `'${value.replace(/'/g, "\\'")}'`;
289
- }
290
- if (typeof value === 'number' || typeof value === 'boolean') {
291
- return String(value);
292
- }
293
- if (Array.isArray(value)) {
294
- if (value.length === 0) return '[]';
295
- let items = value.map(item => `${indent}${stringifyWithIndent(item, depth + 1)}`);
296
- return `[\n${items.join(',\n')}\n${prevIndent}]`;
297
- }
298
- if (typeof value === 'object') {
299
- let keys = Object.keys(value);
300
- if (keys.length === 0) return '{}';
301
- let items = keys.map(key => {
302
- let val = stringifyWithIndent(value[key], depth + 1);
303
- return `${indent}${key}: ${val}`;
304
- });
305
- return `{\n${items.join(',\n')}\n${prevIndent}}`;
306
- }
307
- return String(value);
308
- }
309
-
310
- /**
311
- * Serialize config to JavaScript module format
312
- * @param {Object} config - Config object to serialize
313
- * @returns {string} JavaScript source code
314
- */
315
- export function serializeToJavaScript(config) {
316
- let lines = ['/**', ' * Vizzly Configuration', ' * @see https://docs.vizzly.dev/cli/configuration', ' */', '', "import { defineConfig } from '@vizzly-testing/cli/config';", '', 'export default defineConfig(', stringifyWithIndent(config, 1), ');', ''];
317
- return lines.join('\n');
318
- }
319
-
320
- /**
321
- * Serialize config to JSON format
322
- * @param {Object} config - Config object to serialize
323
- * @returns {string} JSON string with 2-space indentation
324
- */
325
- export function serializeToJson(config) {
326
- return JSON.stringify(config, null, 2);
327
- }
328
-
329
- /**
330
- * Determine the serialization format based on filepath
331
- * @param {string} filepath - Path to config file
332
- * @returns {'javascript'|'json'|'package'|'unknown'} Format type
333
- */
334
- export function getConfigFormat(filepath) {
335
- if (filepath.endsWith('.js') || filepath.endsWith('.mjs')) {
336
- return 'javascript';
337
- }
338
- if (filepath.endsWith('.json') && !filepath.endsWith('package.json')) {
339
- return 'json';
340
- }
341
- if (filepath.endsWith('package.json')) {
342
- return 'package';
343
- }
344
- return 'unknown';
345
- }
346
-
347
- /**
348
- * Serialize config for writing to file
349
- * @param {Object} config - Config object to serialize
350
- * @param {string} filepath - Target file path
351
- * @returns {{ content: string|null, format: string, error: Error|null }}
352
- */
353
- export function serializeConfig(config, filepath) {
354
- let format = getConfigFormat(filepath);
355
- if (format === 'javascript') {
356
- return {
357
- content: serializeToJavaScript(config),
358
- format,
359
- error: null
360
- };
361
- }
362
- if (format === 'json') {
363
- return {
364
- content: serializeToJson(config),
365
- format,
366
- error: null
367
- };
368
- }
369
- if (format === 'package') {
370
- // Can't serialize standalone, need existing package.json
371
- return {
372
- content: null,
373
- format,
374
- error: null
375
- };
376
- }
377
- return {
378
- content: null,
379
- format,
380
- error: new VizzlyError(`Unsupported config file format: ${filepath}`, 'UNSUPPORTED_CONFIG_FORMAT')
381
- };
382
- }
383
-
384
- // ============================================================================
385
- // Config Extraction
386
- // ============================================================================
387
-
388
- /**
389
- * Extract config from cosmiconfig result (handles .default exports)
390
- * @param {Object|null} result - Cosmiconfig result
391
- * @returns {{ config: Object|null, filepath: string|null }}
392
- */
393
- export function extractCosmiconfigResult(result) {
394
- if (!result?.config) {
395
- return {
396
- config: null,
397
- filepath: null
398
- };
399
- }
400
-
401
- // Handle both `export default` and `module.exports`
402
- let config = result.config.default || result.config;
403
- return {
404
- config,
405
- filepath: result.filepath
406
- };
407
- }
408
-
409
- // ============================================================================
410
- // Validation Result Building
411
- // ============================================================================
412
-
413
- /**
414
- * Build a validation success result
415
- * @param {Object} validatedConfig - Validated config
416
- * @returns {{ valid: true, config: Object, errors: [] }}
417
- */
418
- export function buildValidationSuccess(validatedConfig) {
419
- return {
420
- valid: true,
421
- config: validatedConfig,
422
- errors: []
423
- };
424
- }
425
-
426
- /**
427
- * Build a validation failure result
428
- * @param {Error} error - Validation error
429
- * @returns {{ valid: false, config: null, errors: Array }}
430
- */
431
- export function buildValidationFailure(error) {
432
- return {
433
- valid: false,
434
- config: null,
435
- errors: error.errors || [{
436
- message: error.message
437
- }]
438
- };
439
- }