@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,421 +0,0 @@
1
- /**
2
- * API Endpoints - Functions for each API operation
3
- *
4
- * Each function takes a client as the first parameter and returns the API result.
5
- * This keeps the functions pure (no hidden state) and easily testable.
6
- */
7
-
8
- import { VizzlyError } from '../errors/vizzly-error.js';
9
- import * as output from '../utils/output.js';
10
- import { buildBuildPayload, buildEndpointWithParams, buildQueryParams, buildScreenshotPayload, buildScreenshotResolvePayload, buildShaCheckPayload, computeSha256 } from './core.js';
11
-
12
- // ============================================================================
13
- // Build Endpoints
14
- // ============================================================================
15
-
16
- /**
17
- * Get build information
18
- * @param {Object} client - API client
19
- * @param {string} buildId - Build ID
20
- * @param {string|null} include - Optional include parameter (e.g., 'screenshots')
21
- * @returns {Promise<Object>} Build data
22
- */
23
- export async function getBuild(client, buildId, include = null) {
24
- let endpoint = `/api/sdk/builds/${buildId}`;
25
- if (include) {
26
- endpoint = buildEndpointWithParams(endpoint, {
27
- include
28
- });
29
- }
30
- return client.request(endpoint);
31
- }
32
-
33
- /**
34
- * Get the server-owned build processing and review status.
35
- *
36
- * This endpoint keeps processing, comparison, and review counts separate so
37
- * callers do not have to reconstruct lifecycle state from legacy build fields.
38
- *
39
- * @param {Object} client - API client
40
- * @param {string} buildId - Build ID
41
- * @returns {Promise<Object>} Build status response
42
- */
43
- export async function getBuildStatus(client, buildId) {
44
- return client.request(`/api/sdk/builds/${buildId}/status`);
45
- }
46
-
47
- /**
48
- * Get builds for a project
49
- * @param {Object} client - API client
50
- * @param {Object} filters - Filter options
51
- * @returns {Promise<Array>} List of builds
52
- */
53
- export async function getBuilds(client, filters = {}) {
54
- let query = buildQueryParams(filters);
55
- let endpoint = `/api/sdk/builds${query ? `?${query}` : ''}`;
56
- return client.request(endpoint);
57
- }
58
-
59
- /**
60
- * Create a new build
61
- * @param {Object} client - API client
62
- * @param {Object} metadata - Build metadata
63
- * @returns {Promise<Object>} Created build data
64
- */
65
- export async function createBuild(client, metadata) {
66
- let payload = buildBuildPayload(metadata);
67
- return client.request('/api/sdk/builds', {
68
- method: 'POST',
69
- headers: {
70
- 'Content-Type': 'application/json'
71
- },
72
- body: JSON.stringify({
73
- build: payload
74
- })
75
- });
76
- }
77
-
78
- /**
79
- * Update build status
80
- * @param {Object} client - API client
81
- * @param {string} buildId - Build ID
82
- * @param {string} status - Build status (pending|running|completed|failed)
83
- * @param {number|null} [executionTimeMs=null] - Execution time in milliseconds
84
- * @param {string} [failureReason] - Explanation sent only for a failed build
85
- * @returns {Promise<Object>} Updated build data
86
- */
87
- export async function updateBuildStatus(client, buildId, status, executionTimeMs = null, failureReason) {
88
- let body = {
89
- status
90
- };
91
- if (status === 'failed' && failureReason) body.failureReason = failureReason;
92
- if (executionTimeMs != null) {
93
- body.executionTimeMs = executionTimeMs;
94
- }
95
- return client.request(`/api/sdk/builds/${buildId}/status`, {
96
- method: 'PUT',
97
- headers: {
98
- 'Content-Type': 'application/json'
99
- },
100
- body: JSON.stringify(body)
101
- });
102
- }
103
-
104
- /**
105
- * Mark a build completed or failed, with an optional failure explanation.
106
- * @param {Object} client - API client
107
- * @param {string} buildId - Build ID
108
- * @param {boolean} [success=true] - Whether tests and uploads succeeded
109
- * @param {number|null} [executionTimeMs=null] - Execution time in milliseconds
110
- * @param {string} [failureReason] - Explanation sent only for a failed build
111
- * @returns {Promise<Object>} Finalized build data
112
- */
113
- export async function finalizeBuild(client, buildId, success = true, executionTimeMs = null, failureReason) {
114
- let status = success ? 'completed' : 'failed';
115
- return updateBuildStatus(client, buildId, status, executionTimeMs, failureReason);
116
- }
117
-
118
- /**
119
- * Get TDD baselines for a build
120
- * @param {Object} client - API client
121
- * @param {string} buildId - Build ID
122
- * @returns {Promise<Object>} { build, screenshots, signatureProperties }
123
- */
124
- export async function getTddBaselines(client, buildId) {
125
- return client.request(`/api/sdk/builds/${buildId}/tdd-baselines`);
126
- }
127
-
128
- // ============================================================================
129
- // Screenshot Endpoints
130
- // ============================================================================
131
-
132
- /**
133
- * Check if SHAs already exist on the server
134
- * @param {Object} client - API client
135
- * @param {Array} screenshots - Screenshots to check (objects with sha256, or string SHAs)
136
- * @param {string} buildId - Build ID for screenshot record creation
137
- * @returns {Promise<Object>} { existing, missing, screenshots }
138
- */
139
- export async function checkShas(client, screenshots, buildId) {
140
- try {
141
- let payload = buildShaCheckPayload(screenshots, buildId);
142
- return await client.request('/api/sdk/check-shas', {
143
- method: 'POST',
144
- headers: {
145
- 'Content-Type': 'application/json'
146
- },
147
- body: JSON.stringify(payload)
148
- });
149
- } catch (error) {
150
- // Continue without deduplication on error
151
- output.debug('sha-check', 'failed, continuing without deduplication', {
152
- error: error.message
153
- });
154
-
155
- // Extract SHAs for fallback response
156
- let shaList = Array.isArray(screenshots) && screenshots.length > 0 && typeof screenshots[0] === 'object' ? screenshots.map(s => s.sha256) : screenshots;
157
- return {
158
- existing: [],
159
- missing: shaList,
160
- screenshots: []
161
- };
162
- }
163
- }
164
- function canUploadBytesAfterShaResolveError(error) {
165
- let status = error?.context?.status;
166
-
167
- // Network failures, older servers, and server errors should degrade to the
168
- // slower byte upload path. Auth and ownership errors must still surface.
169
- if (!status) return true;
170
- if (status >= 500) return true;
171
- if (status === 400 && error.message?.includes('image_data')) return true;
172
- return false;
173
- }
174
-
175
- /**
176
- * Upload a screenshot with SHA deduplication
177
- * @param {Object} client - API client
178
- * @param {string} buildId - Build ID
179
- * @param {string} name - Screenshot name
180
- * @param {Buffer} buffer - Screenshot data
181
- * @param {Object} metadata - Additional metadata
182
- * @param {boolean} skipDedup - Skip SHA deduplication (uploadAll mode)
183
- * @returns {Promise<Object>} Upload result
184
- */
185
- export async function uploadScreenshot(client, buildId, name, buffer, metadata = {}, skipDedup = false, screenshotOptions = {}) {
186
- // Skip SHA deduplication if requested
187
- if (skipDedup) {
188
- let payload = buildScreenshotPayload(name, buffer, metadata, null, screenshotOptions);
189
- return client.request(`/api/sdk/builds/${buildId}/screenshots`, {
190
- method: 'POST',
191
- headers: {
192
- 'Content-Type': 'application/json'
193
- },
194
- body: JSON.stringify(payload)
195
- });
196
- }
197
-
198
- // Normal flow with server-side SHA resolution.
199
- let sha256 = computeSha256(buffer);
200
- let resolvePayload = buildScreenshotResolvePayload(name, metadata, sha256, screenshotOptions);
201
- try {
202
- let resolveResult = await client.request(`/api/sdk/builds/${buildId}/screenshots`, {
203
- method: 'POST',
204
- headers: {
205
- 'Content-Type': 'application/json'
206
- },
207
- body: JSON.stringify(resolvePayload)
208
- });
209
- if (resolveResult?.upload_required === false) {
210
- return {
211
- message: 'Screenshot already exists, skipped upload',
212
- sha256,
213
- skipped: true,
214
- screenshot: resolveResult,
215
- fromExisting: true
216
- };
217
- }
218
- } catch (error) {
219
- if (!canUploadBytesAfterShaResolveError(error)) {
220
- throw error;
221
- }
222
- output.debug('sha-resolve', 'failed, uploading screenshot bytes', {
223
- error: error.message
224
- });
225
- }
226
-
227
- // File doesn't exist, or resolve failed; proceed with upload.
228
- let payload = buildScreenshotPayload(name, buffer, metadata, sha256, screenshotOptions);
229
- return client.request(`/api/sdk/builds/${buildId}/screenshots`, {
230
- method: 'POST',
231
- headers: {
232
- 'Content-Type': 'application/json'
233
- },
234
- body: JSON.stringify(payload)
235
- });
236
- }
237
-
238
- // ============================================================================
239
- // Comparison Endpoints
240
- // ============================================================================
241
-
242
- /**
243
- * Get comparison information
244
- * @param {Object} client - API client
245
- * @param {string} comparisonId - Comparison ID
246
- * @returns {Promise<Object>} Comparison data
247
- */
248
- export async function getComparison(client, comparisonId) {
249
- let response = await client.request(`/api/sdk/comparisons/${comparisonId}`);
250
- return response.comparison;
251
- }
252
-
253
- /**
254
- * Get a build context bundle at the detail level the caller can consume.
255
- *
256
- * Passing detail selection to the API keeps compact callers from downloading
257
- * raw Honeydiff geometry by default while preserving the unfiltered endpoint
258
- * for raw and full context consumers.
259
- *
260
- * @param {Object} client - API client
261
- * @param {string} buildId - Build ID
262
- * @param {Object} options - Optional query params
263
- * @returns {Promise<Object>} Build context bundle
264
- */
265
- export async function getBuildContext(client, buildId, options = {}) {
266
- let endpoint = buildEndpointWithParams(`/api/sdk/context/builds/${buildId}`, options);
267
- return client.request(endpoint);
268
- }
269
-
270
- /**
271
- * Get comparison context bundle with similarity and history
272
- * @param {Object} client - API client
273
- * @param {string} comparisonId - Comparison ID
274
- * @param {Object} options - Optional query params
275
- * @returns {Promise<Object>} Comparison context bundle
276
- */
277
- export async function getComparisonContext(client, comparisonId, options = {}) {
278
- let endpoint = buildEndpointWithParams(`/api/sdk/context/comparisons/${comparisonId}`, options);
279
- return client.request(endpoint);
280
- }
281
-
282
- /**
283
- * Get screenshot context bundle for a screenshot name
284
- * @param {Object} client - API client
285
- * @param {string} screenshotName - Screenshot name
286
- * @param {Object} options - Optional query params
287
- * @returns {Promise<Object>} Screenshot context bundle
288
- */
289
- export async function getScreenshotContext(client, screenshotName, options = {}) {
290
- let encodedName = encodeURIComponent(screenshotName);
291
- let endpoint = buildEndpointWithParams(`/api/sdk/context/screenshots/${encodedName}`, options);
292
- return client.request(endpoint);
293
- }
294
-
295
- /**
296
- * Get project-scoped similar comparisons for a fingerprint hash
297
- * @param {Object} client - API client
298
- * @param {string} fingerprintHash - Honeydiff fingerprint hash
299
- * @param {Object} options - Optional query params
300
- * @returns {Promise<Object>} Fingerprint context bundle
301
- */
302
- export async function getSimilarFingerprintContext(client, fingerprintHash, options = {}) {
303
- let encodedFingerprint = encodeURIComponent(fingerprintHash);
304
- let endpoint = buildEndpointWithParams(`/api/sdk/context/fingerprints/${encodedFingerprint}/similar`, options);
305
- return client.request(endpoint);
306
- }
307
-
308
- /**
309
- * Get pending review queue context for a project
310
- * @param {Object} client - API client
311
- * @param {Object} options - Optional query params
312
- * @returns {Promise<Object>} Review queue context bundle
313
- */
314
- export async function getReviewQueueContext(client, options = {}) {
315
- let endpoint = buildEndpointWithParams('/api/sdk/context/review-queue', options);
316
- return client.request(endpoint);
317
- }
318
-
319
- /**
320
- * Search for comparisons by name
321
- * @param {Object} client - API client
322
- * @param {string} name - Screenshot name to search for
323
- * @param {Object} filters - Optional filters (branch, project, organization, limit, offset)
324
- * @returns {Promise<Object>} Search results with comparisons and pagination
325
- */
326
- export async function searchComparisons(client, name, filters = {}) {
327
- if (!name || typeof name !== 'string') {
328
- throw new VizzlyError('name is required and must be a non-empty string');
329
- }
330
- let {
331
- branch,
332
- project,
333
- organization,
334
- limit = 50,
335
- offset = 0
336
- } = filters;
337
- let params = {
338
- name,
339
- limit: String(limit),
340
- offset: String(offset)
341
- };
342
- if (branch) params.branch = branch;
343
- if (project) params.project = project;
344
- if (organization) params.organization = organization;
345
- let endpoint = buildEndpointWithParams('/api/sdk/comparisons/search', params);
346
- return client.request(endpoint);
347
- }
348
-
349
- // ============================================================================
350
- // Auth/Token Endpoints
351
- // ============================================================================
352
-
353
- /**
354
- * Get token context (organization and project info)
355
- * @param {Object} client - API client
356
- * @returns {Promise<Object>} Token context data
357
- */
358
- export async function getTokenContext(client) {
359
- return client.request('/api/sdk/token/context');
360
- }
361
-
362
- // ============================================================================
363
- // Parallel Build Endpoints
364
- // ============================================================================
365
-
366
- /**
367
- * Finalize a parallel build
368
- * @param {Object} client - API client
369
- * @param {string} parallelId - Parallel ID to finalize
370
- * @returns {Promise<Object>} Finalization result
371
- */
372
- export async function finalizeParallelBuild(client, parallelId) {
373
- return client.request(`/api/sdk/parallel/${parallelId}/finalize`, {
374
- method: 'POST',
375
- headers: {
376
- 'Content-Type': 'application/json'
377
- }
378
- });
379
- }
380
-
381
- // ============================================================================
382
- // Preview Endpoints
383
- // ============================================================================
384
-
385
- /**
386
- * Upload preview ZIP file for a build
387
- * @param {Object} client - API client
388
- * @param {string} buildId - Build ID
389
- * @param {Buffer} zipBuffer - ZIP file contents
390
- * @returns {Promise<Object>} Upload result with preview URL
391
- */
392
- export async function uploadPreviewZip(client, buildId, zipBuffer) {
393
- // Use native FormData (Node 18+) with Blob for proper fetch compatibility
394
- let formData = new FormData();
395
- let blob = new Blob([zipBuffer], {
396
- type: 'application/zip'
397
- });
398
- formData.append('file', blob, 'preview.zip');
399
- return client.request(`/api/sdk/builds/${buildId}/preview/upload-zip`, {
400
- method: 'POST',
401
- body: formData
402
- // Let fetch set the Content-Type with boundary automatically
403
- });
404
- }
405
-
406
- /**
407
- * Get preview info for a build
408
- * @param {Object} client - API client
409
- * @param {string} buildId - Build ID
410
- * @returns {Promise<Object>} Preview info or null if not found
411
- */
412
- export async function getPreviewInfo(client, buildId) {
413
- try {
414
- return await client.request(`/api/sdk/builds/${buildId}/preview`);
415
- } catch (error) {
416
- if (error.status === 404) {
417
- return null;
418
- }
419
- throw error;
420
- }
421
- }
package/dist/api/index.js DELETED
@@ -1,19 +0,0 @@
1
- /**
2
- * Vizzly API Module
3
- *
4
- * Functional API for interacting with the Vizzly platform.
5
- *
6
- * Usage:
7
- * import { createApiClient, getBuild, createBuild } from '../api/index.js';
8
- *
9
- * let client = createApiClient({ token: 'xxx', command: 'run' });
10
- * let build = await getBuild(client, buildId);
11
- */
12
-
13
- // Client factory
14
- export { createApiClient, DEFAULT_API_URL } from './client.js';
15
- // Core pure functions
16
- export { buildApiUrl, buildAuthHeader, buildBuildPayload, buildEndpointWithParams, buildQueryParams, buildRequestHeaders, buildScreenshotCheckObject, buildScreenshotPayload, buildScreenshotResolvePayload, buildShaCheckPayload, buildUserAgent, computeSha256, extractErrorBody, findScreenshotBySha, isAuthError, isRateLimited, parseApiError, partitionByShaExistence, shaExists, shouldRetryWithRefresh } from './core.js';
17
-
18
- // Endpoint functions
19
- export { checkShas, createBuild, finalizeBuild, finalizeParallelBuild, getBuild, getBuildContext, getBuildStatus, getBuilds, getComparison, getComparisonContext, getPreviewInfo, getReviewQueueContext, getScreenshotContext, getSimilarFingerprintContext, getTddBaselines, getTokenContext, searchComparisons, updateBuildStatus, uploadPreviewZip, uploadScreenshot } from './endpoints.js';
package/dist/auth/core.js DELETED
@@ -1,237 +0,0 @@
1
- /**
2
- * Auth Core - Pure functions for authentication logic
3
- *
4
- * No I/O, no side effects - just data transformations.
5
- */
6
-
7
- import { AuthError, VizzlyError } from '../errors/vizzly-error.js';
8
-
9
- // ============================================================================
10
- // Header Building
11
- // ============================================================================
12
-
13
- /**
14
- * Build Authorization header from access token
15
- * @param {string|null} accessToken - Access token
16
- * @returns {Object} Headers object with Authorization if token exists
17
- */
18
- export function buildAuthHeader(accessToken) {
19
- if (!accessToken) return {};
20
- return {
21
- Authorization: `Bearer ${accessToken}`
22
- };
23
- }
24
-
25
- /**
26
- * Build User-Agent header for auth requests
27
- * @param {string} version - CLI version
28
- * @returns {string} User-Agent string
29
- */
30
- export function buildAuthUserAgent(version) {
31
- return `vizzly-cli/${version} (auth)`;
32
- }
33
-
34
- /**
35
- * Build complete headers for a request
36
- * @param {Object} options - Header options
37
- * @returns {Object} Complete headers object
38
- */
39
- export function buildRequestHeaders({
40
- userAgent,
41
- accessToken,
42
- contentType,
43
- extra = {}
44
- }) {
45
- return {
46
- 'User-Agent': userAgent,
47
- ...(accessToken ? buildAuthHeader(accessToken) : {}),
48
- ...(contentType ? {
49
- 'Content-Type': contentType
50
- } : {}),
51
- ...extra
52
- };
53
- }
54
-
55
- // ============================================================================
56
- // Error Parsing
57
- // ============================================================================
58
-
59
- /**
60
- * Parse error from API response
61
- * @param {number} status - HTTP status code
62
- * @param {Object|string} body - Response body (parsed JSON or text)
63
- * @param {string} endpoint - API endpoint for context
64
- * @returns {Error} Appropriate error type
65
- */
66
- export function parseAuthError(status, body, _endpoint) {
67
- let errorText = '';
68
- if (typeof body === 'object' && body !== null) {
69
- errorText = body.error || body.message || '';
70
- } else if (typeof body === 'string') {
71
- errorText = body;
72
- }
73
- let retryAfter = Number(body?.retryAfter);
74
- let context = {
75
- status,
76
- serverCode: body?.code,
77
- retryAfter: retryAfter > 0 ? retryAfter : undefined
78
- };
79
- if (status === 401) {
80
- return new AuthError(errorText || 'Invalid credentials. Please check your email/username and password.', context);
81
- }
82
- if (status === 429) {
83
- return new VizzlyError('Too many login attempts. Please try again later.', 'RATE_LIMIT_ERROR', context);
84
- }
85
- return new VizzlyError(`Authentication request failed: ${status}${errorText ? ` - ${errorText}` : ''}`, 'AUTH_REQUEST_ERROR', context);
86
- }
87
-
88
- /**
89
- * Parse error for authenticated requests (different error messages)
90
- * @param {number} status - HTTP status code
91
- * @param {Object|string} body - Response body
92
- * @param {string} endpoint - API endpoint
93
- * @returns {Error} Appropriate error type
94
- */
95
- export function parseAuthenticatedError(status, body, endpoint) {
96
- let errorText = '';
97
- if (typeof body === 'object' && body !== null) {
98
- errorText = body.error || body.message || '';
99
- } else if (typeof body === 'string') {
100
- errorText = body;
101
- }
102
- if (status === 401) {
103
- return new AuthError('Authentication token is invalid or expired. Please run "vizzly login" again.');
104
- }
105
- return new VizzlyError(`API request failed: ${status}${errorText ? ` - ${errorText}` : ''} (${endpoint})`, 'API_REQUEST_ERROR');
106
- }
107
-
108
- // ============================================================================
109
- // Payload Building
110
- // ============================================================================
111
-
112
- /**
113
- * Build device poll request payload
114
- * @param {string} deviceCode - Device code from initiate
115
- * @returns {Object} Request payload
116
- */
117
- export function buildDevicePollPayload(deviceCode) {
118
- return {
119
- device_code: deviceCode
120
- };
121
- }
122
-
123
- /**
124
- * Build refresh token request payload
125
- * @param {string} refreshToken - Refresh token
126
- * @returns {Object} Request payload
127
- */
128
- export function buildRefreshPayload(refreshToken) {
129
- return {
130
- refreshToken
131
- };
132
- }
133
-
134
- /**
135
- * Build logout request payload
136
- * @param {string} refreshToken - Refresh token to revoke
137
- * @returns {Object} Request payload
138
- */
139
- export function buildLogoutPayload(refreshToken) {
140
- return {
141
- refreshToken
142
- };
143
- }
144
-
145
- // ============================================================================
146
- // Token Handling
147
- // ============================================================================
148
-
149
- /**
150
- * Build token data for storage from API response
151
- * @param {Object} response - API response with tokens
152
- * @param {Object|null} existingUser - Existing user data to preserve
153
- * @returns {Object} Token data for storage
154
- */
155
- export function buildTokenData(response, existingUser = null) {
156
- return {
157
- accessToken: response.accessToken,
158
- refreshToken: response.refreshToken,
159
- expiresAt: response.expiresAt,
160
- user: response.user || existingUser
161
- };
162
- }
163
-
164
- /**
165
- * Validate that tokens exist and have required fields
166
- * @param {Object|null} auth - Auth tokens object
167
- * @param {string} requiredField - Field that must exist ('accessToken' or 'refreshToken')
168
- * @returns {{ valid: boolean, error: Error|null }}
169
- */
170
- export function validateTokens(auth, requiredField = 'accessToken') {
171
- if (!auth?.[requiredField]) {
172
- let message = requiredField === 'refreshToken' ? 'No refresh token found. Please run "vizzly login" first.' : 'No authentication token found. Please run "vizzly login" first.';
173
- return {
174
- valid: false,
175
- error: new AuthError(message)
176
- };
177
- }
178
- return {
179
- valid: true,
180
- error: null
181
- };
182
- }
183
- export function normalizeDeviceFlowResponse(deviceFlow) {
184
- let verificationUri = deviceFlow.verification_uri || deviceFlow.verificationUri;
185
- let userCode = deviceFlow.user_code || deviceFlow.userCode;
186
- let deviceCode = deviceFlow.device_code || deviceFlow.deviceCode;
187
- if (!verificationUri || !userCode || !deviceCode) {
188
- throw new Error('Invalid device flow response from server');
189
- }
190
- return {
191
- verificationUri,
192
- userCode,
193
- deviceCode,
194
- urlWithCode: deviceFlow.verification_uri_complete || deviceFlow.verificationUriComplete || `${verificationUri}?code=${encodeURIComponent(userCode)}`
195
- };
196
- }
197
- export function resolveAuthorizedTokenData(pollResponse) {
198
- let accessToken = pollResponse.tokens?.accessToken || pollResponse.tokens?.access_token;
199
- if (accessToken) {
200
- return pollResponse;
201
- }
202
- if (pollResponse.status === 'pending') {
203
- throw new Error('Authorization not complete yet. Please complete the authorization in your browser and try running "vizzly login" again.');
204
- }
205
- if (pollResponse.status === 'expired') {
206
- throw new Error('Device code expired. Please try logging in again.');
207
- }
208
- if (pollResponse.status === 'invalid') {
209
- throw new Error('Device code is invalid or expired. Please try logging in again.');
210
- }
211
- if (pollResponse.status === 'denied') {
212
- throw new Error('Authorization denied. Please try logging in again.');
213
- }
214
- throw new Error('Unexpected response from authorization server. Please try logging in again.');
215
- }
216
- export function buildTokens(tokenData, now = Date.now()) {
217
- let tokensData = tokenData.tokens || tokenData;
218
- let tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
219
- let tokenExpiresAt = tokenExpiresIn ? new Date(now + tokenExpiresIn * 1000).toISOString() : tokenData.expires_at || tokenData.expiresAt;
220
- return {
221
- accessToken: tokensData.accessToken || tokensData.access_token,
222
- refreshToken: tokensData.refreshToken || tokensData.refresh_token,
223
- expiresAt: tokenExpiresAt,
224
- user: tokenData.user,
225
- organizations: tokenData.organizations
226
- };
227
- }
228
-
229
- /** Map protocol codes without depending on the server's human-readable text. */
230
- export function deviceAuthorizationStatus(error) {
231
- return {
232
- AUTHORIZATION_PENDING: 'pending',
233
- ACCESS_DENIED: 'denied',
234
- EXPIRED_TOKEN: 'expired',
235
- INVALID_DEVICE_CODE: 'invalid'
236
- }[error.context?.serverCode];
237
- }