@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,313 +0,0 @@
1
- /**
2
- * Config Service
3
- * Manages configuration for the TDD dashboard settings page
4
- *
5
- * Provides read/write access to:
6
- * - Merged config (read-only combination of all sources)
7
- * - Project config (vizzly.config.js in working directory)
8
- * - Global config (~/.vizzly/config.json)
9
- */
10
-
11
- import { existsSync } from 'node:fs';
12
- import { writeFile } from 'node:fs/promises';
13
- import { join } from 'node:path';
14
- import { cosmiconfigSync } from 'cosmiconfig';
15
- import { CONFIG_DEFAULTS } from '../config/core.js';
16
- import { loadGlobalConfig, saveGlobalConfig } from '../utils/global-config.js';
17
- import * as output from '../utils/output.js';
18
-
19
- /**
20
- * Create a config service instance
21
- * @param {Object} options
22
- * @param {string} options.workingDir - Working directory for project config
23
- * @returns {Object} Config service with getConfig, updateConfig, validateConfig methods
24
- */
25
- export function createConfigService({
26
- workingDir
27
- }) {
28
- let projectConfigPath = null;
29
- let projectConfigFormat = 'js'; // 'js' or 'json'
30
-
31
- // Find project config file
32
- let explorer = cosmiconfigSync('vizzly');
33
- let searchResult = explorer.search(workingDir);
34
- if (searchResult?.filepath) {
35
- projectConfigPath = searchResult.filepath;
36
- projectConfigFormat = searchResult.filepath.endsWith('.json') ? 'json' : 'js';
37
- }
38
-
39
- /**
40
- * Get configuration by type
41
- * @param {'merged'|'project'|'global'} type
42
- * @returns {Promise<Object>}
43
- */
44
- async function getConfig(type) {
45
- if (type === 'merged') {
46
- return getMergedConfig();
47
- } else if (type === 'project') {
48
- return getProjectConfig();
49
- } else if (type === 'global') {
50
- return getGlobalConfigData();
51
- }
52
- throw new Error(`Unknown config type: ${type}`);
53
- }
54
-
55
- /**
56
- * Get merged configuration with source tracking
57
- */
58
- async function getMergedConfig() {
59
- let config = cloneConfigValue(CONFIG_DEFAULTS);
60
- let sources = {};
61
-
62
- // Layer 1: Global config
63
- let globalConfig = await loadGlobalConfig();
64
- if (globalConfig.settings) {
65
- mergeWithTracking(config, globalConfig.settings, sources, 'global');
66
- }
67
-
68
- // Layer 2: Project config
69
- if (projectConfigPath && existsSync(projectConfigPath)) {
70
- try {
71
- let result = explorer.load(projectConfigPath);
72
- let projectConfig = result?.config?.default || result?.config || {};
73
- mergeWithTracking(config, projectConfig, sources, 'project');
74
- } catch (error) {
75
- output.debug('config-service', `Error loading project config: ${error.message}`);
76
- }
77
- }
78
-
79
- // Layer 3: Environment variables
80
- if (process.env.VIZZLY_THRESHOLD) {
81
- config.comparison.threshold = Number(process.env.VIZZLY_THRESHOLD);
82
- sources.comparison = 'env';
83
- }
84
- if (process.env.VIZZLY_MIN_CLUSTER_SIZE) {
85
- config.comparison.minClusterSize = Number(process.env.VIZZLY_MIN_CLUSTER_SIZE);
86
- sources.comparison = 'env';
87
- }
88
- if (process.env.VIZZLY_PORT) {
89
- config.server.port = Number(process.env.VIZZLY_PORT);
90
- sources.server = 'env';
91
- }
92
- return {
93
- config,
94
- sources
95
- };
96
- }
97
-
98
- /**
99
- * Get project-level configuration only
100
- */
101
- async function getProjectConfig() {
102
- if (!projectConfigPath || !existsSync(projectConfigPath)) {
103
- return {
104
- config: {},
105
- path: null
106
- };
107
- }
108
- try {
109
- let result = explorer.load(projectConfigPath);
110
- let config = result?.config?.default || result?.config || {};
111
- return {
112
- config,
113
- path: projectConfigPath
114
- };
115
- } catch (error) {
116
- output.debug('config-service', `Error loading project config: ${error.message}`);
117
- return {
118
- config: {},
119
- path: projectConfigPath,
120
- error: error.message
121
- };
122
- }
123
- }
124
-
125
- /**
126
- * Get global configuration only
127
- */
128
- async function getGlobalConfigData() {
129
- let globalConfig = await loadGlobalConfig();
130
- return {
131
- config: globalConfig.settings || {},
132
- path: join(process.env.VIZZLY_HOME || join(process.env.HOME || '', '.vizzly'), 'config.json')
133
- };
134
- }
135
-
136
- /**
137
- * Update configuration by type
138
- * @param {'project'|'global'} type
139
- * @param {Object} updates - Config updates to apply
140
- * @returns {Promise<Object>}
141
- */
142
- async function updateConfig(type, updates) {
143
- if (type === 'project') {
144
- return updateProjectConfig(updates);
145
- } else if (type === 'global') {
146
- return updateGlobalConfig(updates);
147
- }
148
- throw new Error(`Cannot update config type: ${type}`);
149
- }
150
-
151
- /**
152
- * Update project configuration (vizzly.config.js)
153
- */
154
- async function updateProjectConfig(updates) {
155
- // If no project config exists, create one
156
- if (!projectConfigPath) {
157
- projectConfigPath = join(workingDir, 'vizzly.config.js');
158
- projectConfigFormat = 'js';
159
- }
160
-
161
- // Read existing config
162
- let existingConfig = {};
163
- if (existsSync(projectConfigPath)) {
164
- try {
165
- let result = explorer.load(projectConfigPath);
166
- existingConfig = result?.config?.default || result?.config || {};
167
- } catch {
168
- // Start fresh if corrupted
169
- }
170
- }
171
-
172
- // Merge updates
173
- let newConfig = mergeDeep(existingConfig, updates);
174
-
175
- // Write based on format
176
- if (projectConfigFormat === 'json') {
177
- await writeFile(projectConfigPath, JSON.stringify(newConfig, null, 2));
178
- } else {
179
- // Write as ES module
180
- let content = `import { defineConfig } from '@vizzly-testing/cli/config';
181
-
182
- export default defineConfig(${JSON.stringify(newConfig, null, 2)});
183
- `;
184
- await writeFile(projectConfigPath, content);
185
- }
186
-
187
- // Clear cosmiconfig cache so next read gets fresh data
188
- explorer.clearCaches();
189
- return {
190
- success: true,
191
- path: projectConfigPath
192
- };
193
- }
194
-
195
- /**
196
- * Update global configuration (~/.vizzly/config.json)
197
- */
198
- async function updateGlobalConfig(updates) {
199
- let globalConfig = await loadGlobalConfig();
200
- if (!globalConfig.settings) {
201
- globalConfig.settings = {};
202
- }
203
- globalConfig.settings = mergeDeep(globalConfig.settings, updates);
204
- await saveGlobalConfig(globalConfig);
205
- return {
206
- success: true
207
- };
208
- }
209
-
210
- /**
211
- * Validate configuration
212
- * @param {Object} config - Config to validate
213
- * @returns {Promise<Object>}
214
- */
215
- async function validateConfig(config) {
216
- let errors = [];
217
- let warnings = [];
218
-
219
- // Validate threshold
220
- if (config.comparison?.threshold !== undefined) {
221
- let threshold = config.comparison.threshold;
222
- if (typeof threshold !== 'number' || !Number.isFinite(threshold) || threshold < 0) {
223
- errors.push('comparison.threshold must be a non-negative number');
224
- } else if (threshold > 100) {
225
- warnings.push('comparison.threshold above 100 may cause all comparisons to pass');
226
- }
227
- }
228
-
229
- // Validate minClusterSize
230
- if (config.comparison?.minClusterSize !== undefined) {
231
- let minClusterSize = config.comparison.minClusterSize;
232
- if (!Number.isInteger(minClusterSize) || minClusterSize < 1) {
233
- errors.push('comparison.minClusterSize must be a positive integer (1 or greater)');
234
- } else if (minClusterSize > 100) {
235
- warnings.push('comparison.minClusterSize above 100 may filter out most differences');
236
- }
237
- }
238
-
239
- // Validate port
240
- if (config.server?.port !== undefined) {
241
- let port = config.server.port;
242
- if (!Number.isInteger(port) || port < 1 || port > 65535) {
243
- errors.push('server.port must be an integer between 1 and 65535');
244
- } else if (port < 1024) {
245
- warnings.push('server.port below 1024 may require elevated privileges');
246
- }
247
- }
248
-
249
- // Validate timeout
250
- if (config.server?.timeout !== undefined) {
251
- let timeout = config.server.timeout;
252
- if (!Number.isInteger(timeout) || timeout < 0) {
253
- errors.push('server.timeout must be a non-negative integer');
254
- }
255
- }
256
- return {
257
- valid: errors.length === 0,
258
- errors,
259
- warnings
260
- };
261
- }
262
- return {
263
- getConfig,
264
- updateConfig,
265
- validateConfig
266
- };
267
- }
268
-
269
- /**
270
- * Deep merge two objects
271
- */
272
- function mergeDeep(target, source) {
273
- let result = cloneConfigValue(target);
274
- for (let key in source) {
275
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
276
- result[key] = mergeDeep(result[key] || {}, source[key]);
277
- } else if (Array.isArray(source[key])) {
278
- result[key] = [...source[key]];
279
- } else {
280
- result[key] = source[key];
281
- }
282
- }
283
- return result;
284
- }
285
- function cloneConfigValue(value) {
286
- if (Array.isArray(value)) {
287
- return value.map(cloneConfigValue);
288
- }
289
- if (value && typeof value === 'object') {
290
- let clone = {};
291
- for (let key in value) {
292
- clone[key] = cloneConfigValue(value[key]);
293
- }
294
- return clone;
295
- }
296
- return value;
297
- }
298
-
299
- /**
300
- * Merge config with source tracking
301
- */
302
- function mergeWithTracking(target, source, sources, sourceName, sectionName) {
303
- for (let key in source) {
304
- let sourceSection = sectionName || key;
305
- if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
306
- if (!target[key]) target[key] = {};
307
- mergeWithTracking(target[key], source[key], sources, sourceName, sourceSection);
308
- } else {
309
- target[key] = source[key];
310
- sources[sourceSection] = sourceName;
311
- }
312
- }
313
- }
@@ -1,88 +0,0 @@
1
- /**
2
- * Project Service
3
- * Wraps project operations for use by the HTTP server
4
- *
5
- * Provides the interface expected by src/server/routers/projects.js:
6
- * - listProjects() - Returns [] if not authenticated
7
- * - getRecentBuilds(projectSlug, organizationSlug, options) - Returns [] if not authenticated
8
- *
9
- * Error handling:
10
- * - API methods (listProjects, getRecentBuilds) return empty arrays when not authenticated
11
- */
12
-
13
- import { createAuthClient } from '../auth/client.js';
14
- import * as projectOps from '../project/operations.js';
15
- import { getApiUrl } from '../utils/environment-config.js';
16
- import { getAuthTokens } from '../utils/global-config.js';
17
-
18
- /**
19
- * Create a project service instance
20
- * @param {Object} [options]
21
- * @param {string} [options.apiUrl] - API base URL (defaults to VIZZLY_API_URL or https://app.vizzly.dev)
22
- * @param {Object} [options.httpClient] - Injectable HTTP client (for testing)
23
- * @param {Function} [options.getAuthTokens] - Injectable token getter (for testing)
24
- * @returns {Object} Project service
25
- */
26
- export function createProjectService(options = {}) {
27
- let apiUrl = options.apiUrl || getApiUrl();
28
-
29
- // Create HTTP client once at service creation (not per-request)
30
- // Allow injection for testing
31
- let httpClient = options.httpClient || createAuthClient({
32
- baseUrl: apiUrl
33
- });
34
-
35
- // Allow injection of getAuthTokens for testing
36
- let tokenGetter = options.getAuthTokens || getAuthTokens;
37
-
38
- /**
39
- * Create an OAuth client with current access token
40
- * @returns {Promise<Object|null>} OAuth client or null if not authenticated
41
- */
42
- async function createOAuthClient() {
43
- let auth = await tokenGetter();
44
- if (!auth?.accessToken) {
45
- return null;
46
- }
47
-
48
- // Wrap authenticatedRequest to auto-inject the access token
49
- return {
50
- authenticatedRequest: (endpoint, fetchOptions = {}) => httpClient.authenticatedRequest(endpoint, auth.accessToken, fetchOptions)
51
- };
52
- }
53
- return {
54
- /**
55
- * List all projects from API
56
- * Returns empty array if not authenticated (projectOps handles null oauthClient)
57
- * @returns {Promise<Array>} Array of projects, empty if not authenticated
58
- */
59
- async listProjects() {
60
- let oauthClient = await createOAuthClient();
61
- // projectOps.listProjects handles null oauthClient by returning []
62
- return projectOps.listProjects({
63
- oauthClient,
64
- apiClient: null
65
- });
66
- },
67
- /**
68
- * Get recent builds for a project
69
- * Returns empty array if not authenticated (projectOps handles null oauthClient)
70
- * @param {string} projectSlug - Project slug
71
- * @param {string} organizationSlug - Organization slug
72
- * @param {Object} options - Query options
73
- * @returns {Promise<Array>} Array of builds, empty if not authenticated
74
- */
75
- async getRecentBuilds(projectSlug, organizationSlug, options = {}) {
76
- let oauthClient = await createOAuthClient();
77
- // projectOps.getRecentBuilds handles null oauthClient by returning []
78
- return projectOps.getRecentBuilds({
79
- oauthClient,
80
- apiClient: null,
81
- projectSlug,
82
- organizationSlug,
83
- limit: options.limit,
84
- branch: options.branch
85
- });
86
- }
87
- };
88
- }
@@ -1,293 +0,0 @@
1
- /**
2
- * Test Runner Core - Pure functions for test execution logic
3
- *
4
- * No I/O, no side effects - just data transformations.
5
- */
6
-
7
- // ============================================================================
8
- // Environment Building
9
- // ============================================================================
10
-
11
- /**
12
- * Build environment variables for test process execution
13
- * @param {Object} options - Options
14
- * @param {number} options.port - Server port
15
- * @param {string} options.buildId - Build ID
16
- * @param {boolean} [options.setBaseline] - Whether to set baseline
17
- * @param {boolean} [options.failOnDiff] - Whether visual diffs should fail tests
18
- * @param {Object} [options.baseEnv] - Base environment (defaults to process.env)
19
- * @returns {Object} Environment variables object
20
- */
21
- export function buildTestEnv({
22
- port,
23
- buildId,
24
- setBaseline = false,
25
- failOnDiff = false,
26
- baseEnv = process.env
27
- }) {
28
- return {
29
- ...baseEnv,
30
- VIZZLY_SERVER_URL: `http://localhost:${port}`,
31
- VIZZLY_BUILD_ID: buildId,
32
- VIZZLY_ENABLED: 'true',
33
- VIZZLY_SET_BASELINE: setBaseline ? 'true' : 'false',
34
- VIZZLY_FAIL_ON_DIFF: failOnDiff ? 'true' : 'false'
35
- };
36
- }
37
-
38
- /**
39
- * Build environment for disabled Vizzly (allowNoToken mode)
40
- * @param {Object} [baseEnv] - Base environment (defaults to process.env)
41
- * @returns {Object} Environment variables object
42
- */
43
- export function buildDisabledEnv(baseEnv = process.env) {
44
- return {
45
- ...baseEnv,
46
- VIZZLY_ENABLED: 'false'
47
- };
48
- }
49
-
50
- // ============================================================================
51
- // Build Payload Building
52
- // ============================================================================
53
-
54
- /**
55
- * Build API build payload from options
56
- * @param {Object} options - Run options
57
- * @param {Object} [comparisonConfig] - Comparison configuration (threshold, minClusterSize)
58
- * @returns {Object} Build payload for API
59
- */
60
- export function buildApiBuildPayload(options, comparisonConfig = null) {
61
- let payload = {
62
- name: options.buildName || `Test Run ${new Date().toISOString()}`,
63
- branch: options.branch || 'main',
64
- environment: options.environment || 'test',
65
- commit_sha: options.commit,
66
- commit_message: options.message,
67
- commit_author_name: options.commitAuthorName,
68
- commit_author_email: options.commitAuthorEmail,
69
- github_pull_request_number: options.pullRequestNumber,
70
- parallel_id: options.parallelId
71
- };
72
-
73
- // Only include metadata if we have meaningful config to send
74
- if (comparisonConfig?.threshold != null || comparisonConfig?.minClusterSize != null) {
75
- payload.metadata = {
76
- comparison: {
77
- threshold: comparisonConfig.threshold,
78
- minClusterSize: comparisonConfig.minClusterSize
79
- }
80
- };
81
- }
82
- return payload;
83
- }
84
-
85
- // ============================================================================
86
- // Mode Determination
87
- // ============================================================================
88
-
89
- /**
90
- * Determine if we should skip Vizzly integration entirely
91
- * @param {Object} options - Options
92
- * @param {boolean} options.allowNoToken - Whether to allow running without token
93
- * @param {boolean} options.hasApiKey - Whether an API key is available
94
- * @param {boolean} options.tdd - Whether in TDD mode
95
- * @returns {boolean} True if Vizzly should be disabled
96
- */
97
- export function shouldDisableVizzly({
98
- allowNoToken,
99
- hasApiKey,
100
- tdd
101
- }) {
102
- return allowNoToken && !hasApiKey && !tdd;
103
- }
104
-
105
- /**
106
- * Determine build mode
107
- * @param {boolean} tdd - Whether TDD mode is enabled
108
- * @returns {'tdd'|'api'} Build mode
109
- */
110
- export function determineBuildMode(tdd) {
111
- return tdd ? 'tdd' : 'api';
112
- }
113
-
114
- // ============================================================================
115
- // Result Building
116
- // ============================================================================
117
-
118
- /**
119
- * Build result object for disabled run (no Vizzly integration)
120
- * @returns {Object} Result object
121
- */
122
- export function buildDisabledRunResult(reason = null) {
123
- return {
124
- testsPassed: 1,
125
- testsFailed: 0,
126
- screenshotsCaptured: 0,
127
- ...(reason ? {
128
- capture: {
129
- setup: 'disabled',
130
- reason,
131
- accepted: 0,
132
- rejected: 0,
133
- errors: 0
134
- }
135
- } : {})
136
- };
137
- }
138
-
139
- /**
140
- * Build final run result object
141
- * @param {Object} options - Options
142
- * @param {string|null} options.buildId - Build ID
143
- * @param {string|null} options.buildUrl - Build URL
144
- * @param {boolean} options.testSuccess - Whether tests passed
145
- * @param {number} options.screenshotCount - Number of screenshots
146
- * @param {Object|null} options.tddResults - TDD results (comparisons, etc.)
147
- * @param {Object|null} [options.uploadStats] - Cumulative cloud upload outcomes,
148
- * exposed as `uploads` independently of test and local comparison results.
149
- * @returns {Object} Final result object
150
- */
151
- export function buildRunResult({
152
- buildId,
153
- buildUrl,
154
- testSuccess,
155
- screenshotCount,
156
- tddResults,
157
- uploadStats
158
- }) {
159
- return {
160
- buildId,
161
- url: buildUrl,
162
- testsPassed: testSuccess ? 1 : 0,
163
- testsFailed: testSuccess ? 0 : 1,
164
- screenshotsCaptured: screenshotCount,
165
- ...(uploadStats ? {
166
- uploads: uploadStats
167
- } : {}),
168
- comparisons: tddResults?.comparisons || null,
169
- failed: (tddResults?.failed || 0) > 0,
170
- ...(tddResults ? {
171
- summary: {
172
- total: tddResults.total ?? screenshotCount,
173
- passed: tddResults.passed || 0,
174
- failed: tddResults.failed || 0,
175
- new: tddResults.new || 0,
176
- errors: tddResults.errors || 0
177
- },
178
- capture: {
179
- setup: 'ready',
180
- reason: tddResults.error || null,
181
- accepted: tddResults.capture?.accepted ?? screenshotCount,
182
- rejected: tddResults.capture?.rejected || 0,
183
- errors: tddResults.errors || 0
184
- }
185
- } : {})
186
- };
187
- }
188
-
189
- // ============================================================================
190
- // Validation
191
- // ============================================================================
192
-
193
- /**
194
- * Validate test command is provided
195
- * @param {string|undefined} testCommand - Test command to validate
196
- * @returns {{ valid: boolean, error: string|null }}
197
- */
198
- export function validateTestCommand(testCommand) {
199
- if (!testCommand) {
200
- return {
201
- valid: false,
202
- error: 'No test command provided'
203
- };
204
- }
205
- return {
206
- valid: true,
207
- error: null
208
- };
209
- }
210
-
211
- /**
212
- * Validate daemon mode requirements
213
- * @param {Object} options - Options
214
- * @param {boolean} options.tdd - Whether TDD mode is enabled
215
- * @param {boolean} options.daemon - Whether daemon mode is enabled
216
- * @returns {{ valid: boolean, error: string|null }}
217
- */
218
- export function validateDaemonMode({
219
- tdd,
220
- daemon
221
- }) {
222
- if (!tdd || !daemon) {
223
- return {
224
- valid: false,
225
- error: 'Initialize method is only for TDD daemon mode'
226
- };
227
- }
228
- return {
229
- valid: true,
230
- error: null
231
- };
232
- }
233
-
234
- // ============================================================================
235
- // Client Options Building
236
- // ============================================================================
237
-
238
- /**
239
- * Build API client options from config
240
- * @param {Object} config - Configuration object
241
- * @returns {Object|null} Client options or null if no API key
242
- */
243
- export function buildClientOptions(config) {
244
- if (!config?.apiKey) {
245
- return null;
246
- }
247
- return {
248
- baseUrl: config.apiUrl,
249
- token: config.apiKey,
250
- command: 'run'
251
- };
252
- }
253
-
254
- /**
255
- * Check if API key is available
256
- * @param {Object} config - Configuration object
257
- * @returns {boolean} Whether API key exists
258
- */
259
- export function hasApiKey(config) {
260
- return Boolean(config?.apiKey);
261
- }
262
-
263
- // ============================================================================
264
- // Spawn Options Building
265
- // ============================================================================
266
-
267
- /**
268
- * Build spawn options for test command execution
269
- * @param {Object} env - Environment variables
270
- * @param {Object} [options] - Spawn display options
271
- * @param {boolean} [options.json] - Keep stdout reserved for CLI JSON output
272
- * @returns {Object} Spawn options
273
- */
274
- export function buildSpawnOptions(env, options = {}) {
275
- return {
276
- env,
277
- stdio: options.json ? ['inherit', 'pipe', 'pipe'] : 'inherit',
278
- shell: true
279
- };
280
- }
281
-
282
- // ============================================================================
283
- // Set Baseline Normalization
284
- // ============================================================================
285
-
286
- /**
287
- * Normalize setBaseline option (handles both camelCase and kebab-case)
288
- * @param {Object} options - Options object
289
- * @returns {boolean} Whether to set baseline
290
- */
291
- export function normalizeSetBaseline(options) {
292
- return Boolean(options?.setBaseline || options?.['set-baseline']);
293
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * Test Runner Module - Public exports
3
- *
4
- * Provides functional test runner primitives:
5
- * - core.js: Pure functions for building env, payloads, results
6
- * - operations.js: Test execution operations with dependency injection
7
- */
8
-
9
- // Core pure functions
10
- export { buildApiBuildPayload, buildClientOptions, buildDisabledEnv, buildDisabledRunResult, buildRunResult, buildSpawnOptions, buildTestEnv, determineBuildMode, hasApiKey, normalizeSetBaseline, shouldDisableVizzly, validateDaemonMode, validateTestCommand } from './core.js';
11
-
12
- // Test runner operations (take dependencies as parameters)
13
- export { cancelTests, createBuild, executeTestCommand, fetchBuildUrl, finalizeBuild, initializeDaemon, runTests } from './operations.js';