@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
package/dist/api/core.js DELETED
@@ -1,382 +0,0 @@
1
- /**
2
- * API Core - Pure functions for building requests and parsing responses
3
- *
4
- * These functions have no side effects and are trivially testable.
5
- * They handle header construction, payload building, error parsing, and SHA computation.
6
- */
7
-
8
- import crypto from 'node:crypto';
9
- import { URLSearchParams } from 'node:url';
10
- import { createScreenshotProperties, getScreenshotOptionsPayload } from '../utils/screenshot-options.js';
11
-
12
- // ============================================================================
13
- // Header Building
14
- // ============================================================================
15
-
16
- /**
17
- * Build Authorization header for Bearer token auth
18
- * @param {string|null} token - API token
19
- * @returns {Object} Headers object with Authorization if token provided
20
- */
21
- export function buildAuthHeader(token) {
22
- if (!token) return {};
23
- return {
24
- Authorization: `Bearer ${token}`
25
- };
26
- }
27
-
28
- /**
29
- * Build User-Agent string from components
30
- * @param {string} version - CLI version
31
- * @param {string} command - Command being executed (run, upload, tdd, etc.)
32
- * @param {string|null} sdkUserAgent - Optional SDK user agent to append
33
- * @param {string} nodeVersion - Node.js version to append
34
- * @returns {string} Complete User-Agent string
35
- */
36
- export function buildUserAgent(version, command, sdkUserAgent = null, nodeVersion = process.versions.node) {
37
- let baseUserAgent = `vizzly-cli/${version} (${command})`;
38
- let userAgentParts = [baseUserAgent];
39
- if (sdkUserAgent) {
40
- userAgentParts.push(sdkUserAgent);
41
- }
42
- if (nodeVersion) userAgentParts.push(`node/${nodeVersion}`);
43
- return userAgentParts.join(' ');
44
- }
45
-
46
- /**
47
- * Build complete request headers
48
- * @param {Object} options - Header options
49
- * @param {string|null} options.token - API token
50
- * @param {string} options.userAgent - User-Agent string
51
- * @param {string|null} options.contentType - Content-Type header
52
- * @param {Object} options.extra - Additional headers to merge
53
- * @returns {Object} Complete headers object
54
- */
55
- export function buildRequestHeaders({
56
- token,
57
- userAgent,
58
- contentType = null,
59
- extra = {}
60
- }) {
61
- let headers = {
62
- 'User-Agent': userAgent,
63
- ...buildAuthHeader(token),
64
- ...extra
65
- };
66
- if (contentType) {
67
- headers['Content-Type'] = contentType;
68
- }
69
- return headers;
70
- }
71
-
72
- // ============================================================================
73
- // Payload Construction
74
- // ============================================================================
75
-
76
- /**
77
- * Build payload for screenshot upload or SHA resolution
78
- * @param {string} name - Screenshot name
79
- * @param {Buffer|null} buffer - Image data, or null when resolving by SHA
80
- * @param {Object} metadata - User screenshot properties
81
- * @param {string|null} sha256 - Pre-computed SHA256 hash (optional)
82
- * @returns {Object} Screenshot upload payload
83
- */
84
- export function buildScreenshotPayload(name, buffer, metadata = {}, sha256 = null, screenshotOptions = {}) {
85
- let payload = {
86
- name,
87
- properties: createScreenshotProperties({
88
- properties: metadata
89
- })
90
- };
91
- Object.assign(payload, getScreenshotOptionsPayload(screenshotOptions));
92
- if (buffer) {
93
- payload.image_data = buffer.toString('base64');
94
- }
95
- if (sha256) {
96
- payload.sha256 = sha256;
97
- }
98
- return payload;
99
- }
100
-
101
- /**
102
- * Build payload for server-side screenshot resolution without sending image bytes
103
- * @param {string} name - Screenshot name
104
- * @param {Object} metadata - User screenshot properties
105
- * @param {string} sha256 - Pre-computed SHA256 hash
106
- * @returns {Object} Screenshot resolution payload
107
- */
108
- export function buildScreenshotResolvePayload(name, metadata = {}, sha256, screenshotOptions = {}) {
109
- return buildScreenshotPayload(name, null, metadata, sha256, screenshotOptions);
110
- }
111
-
112
- /**
113
- * Build payload for build creation
114
- * @param {Object} options - Build options
115
- * @returns {Object} Build creation payload
116
- */
117
- export function buildBuildPayload(options) {
118
- let payload = {
119
- name: options.name || options.buildName,
120
- branch: options.branch,
121
- environment: options.environment
122
- };
123
- if (options.commit || options.commit_sha) {
124
- payload.commit_sha = options.commit || options.commit_sha;
125
- }
126
- if (options.message || options.commit_message) {
127
- payload.commit_message = options.message || options.commit_message;
128
- }
129
- if (options.commit_author_name) {
130
- payload.commit_author_name = options.commit_author_name;
131
- }
132
- if (options.commit_author_email) {
133
- payload.commit_author_email = options.commit_author_email;
134
- }
135
- if (options.pullRequestNumber || options.github_pull_request_number) {
136
- payload.github_pull_request_number = options.pullRequestNumber || options.github_pull_request_number;
137
- }
138
- if (options.parallelId || options.parallel_id) {
139
- payload.parallel_id = options.parallelId || options.parallel_id;
140
- }
141
- if (options.threshold != null) {
142
- payload.threshold = options.threshold;
143
- }
144
- if (options.metadata) {
145
- payload.metadata = options.metadata;
146
- }
147
- return payload;
148
- }
149
-
150
- /**
151
- * Build URL query parameters from filter object
152
- * @param {Object} filters - Filter key-value pairs
153
- * @returns {string} URL-encoded query string (without leading ?)
154
- */
155
- export function buildQueryParams(filters) {
156
- let params = new URLSearchParams();
157
- for (let [key, value] of Object.entries(filters)) {
158
- if (value != null && value !== '') {
159
- params.append(key, String(value));
160
- }
161
- }
162
- return params.toString();
163
- }
164
-
165
- /**
166
- * Build payload for SHA existence check (signature-based format)
167
- * @param {Array<Object>} screenshots - Screenshots with sha256 and metadata
168
- * @param {string} buildId - Build ID for screenshot record creation
169
- * @returns {Object} SHA check request payload
170
- */
171
- export function buildShaCheckPayload(screenshots, buildId) {
172
- // Check if using new signature-based format or legacy SHA-only format
173
- if (screenshots.length > 0 && typeof screenshots[0] === 'object' && screenshots[0].sha256) {
174
- return {
175
- buildId,
176
- screenshots
177
- };
178
- }
179
-
180
- // Legacy format: array of SHA strings
181
- return {
182
- shas: screenshots,
183
- buildId
184
- };
185
- }
186
-
187
- /**
188
- * Build screenshot object for SHA checking
189
- * @param {string} sha256 - SHA256 hash of image
190
- * @param {string} name - Screenshot name
191
- * @param {Object} metadata - Screenshot metadata
192
- * @returns {Object} Screenshot check object
193
- */
194
- export function buildScreenshotCheckObject(sha256, name, metadata = {}) {
195
- let meta = metadata || {};
196
- let properties = createScreenshotProperties({
197
- properties: meta
198
- });
199
- return {
200
- sha256,
201
- name,
202
- properties
203
- };
204
- }
205
-
206
- // ============================================================================
207
- // Response/Error Parsing
208
- // ============================================================================
209
-
210
- /**
211
- * Check if HTTP status indicates an auth error
212
- * @param {number} status - HTTP status code
213
- * @returns {boolean} True if auth error
214
- */
215
- export function isAuthError(status) {
216
- return status === 401;
217
- }
218
-
219
- /**
220
- * Check if HTTP status indicates rate limiting
221
- * @param {number} status - HTTP status code
222
- * @returns {boolean} True if rate limited
223
- */
224
- export function isRateLimited(status) {
225
- return status === 429;
226
- }
227
-
228
- /**
229
- * Determine if request should retry with token refresh
230
- * @param {number} status - HTTP status code
231
- * @param {boolean} isRetry - Whether this is already a retry
232
- * @param {boolean} hasRefreshToken - Whether refresh token is available
233
- * @returns {boolean} True if should attempt refresh
234
- */
235
- export function shouldRetryWithRefresh(status, isRetry, hasRefreshToken) {
236
- return status === 401 && !isRetry && hasRefreshToken;
237
- }
238
-
239
- /**
240
- * Parse error information from API response
241
- * @param {number} status - HTTP status code
242
- * @param {string} body - Response body text
243
- * @param {string} url - Request URL
244
- * @returns {Object} Parsed error info with message and code
245
- */
246
- export function parseApiError(status, body, url) {
247
- let message = `API request failed: ${status}`;
248
- let responseDetails = null;
249
- let responseCode = null;
250
- let responseMessage = body;
251
- if (body) {
252
- try {
253
- let parsedBody = JSON.parse(body);
254
- responseDetails = parsedBody.details ?? null;
255
- responseCode = parsedBody.code ?? parsedBody.details?.code ?? null;
256
- responseMessage = parsedBody.error ?? parsedBody.message ?? body;
257
- } catch {
258
- // Plain-text API errors remain valid and are surfaced unchanged.
259
- }
260
- }
261
- if (responseMessage) {
262
- message += ` - ${responseMessage}`;
263
- }
264
- message += ` (URL: ${url})`;
265
- let code = 'API_ERROR';
266
- if (status === 401) code = 'AUTH_ERROR';
267
- if (status === 403) code = 'FORBIDDEN';
268
- if (status === 404) code = 'NOT_FOUND';
269
- if (status === 429) code = 'RATE_LIMITED';
270
- if (status >= 500) code = 'SERVER_ERROR';
271
- return {
272
- message,
273
- code: responseCode || code,
274
- status,
275
- details: responseDetails
276
- };
277
- }
278
-
279
- /**
280
- * Extract error message from response body (JSON or text)
281
- * @param {Response} response - Fetch Response object
282
- * @returns {Promise<string>} Error message
283
- */
284
- export async function extractErrorBody(response) {
285
- try {
286
- if (typeof response.text === 'function') {
287
- return await response.text();
288
- }
289
- return response.statusText || '';
290
- } catch {
291
- return '';
292
- }
293
- }
294
-
295
- // ============================================================================
296
- // SHA/Hash Computation
297
- // ============================================================================
298
-
299
- /**
300
- * Compute SHA256 hash of buffer
301
- * @param {Buffer} buffer - Data to hash
302
- * @returns {string} Hex-encoded SHA256 hash
303
- */
304
- export function computeSha256(buffer) {
305
- return crypto.createHash('sha256').update(buffer).digest('hex');
306
- }
307
-
308
- // ============================================================================
309
- // Deduplication Helpers
310
- // ============================================================================
311
-
312
- /**
313
- * Partition screenshots by SHA existence
314
- * @param {Array<Object>} screenshots - Screenshots with sha256 property
315
- * @param {Set<string>|Array<string>} existingShas - SHAs that already exist
316
- * @returns {Object} { toUpload, existing } partitioned arrays
317
- */
318
- export function partitionByShaExistence(screenshots, existingShas) {
319
- let existingSet = existingShas instanceof Set ? existingShas : new Set(existingShas);
320
- let toUpload = [];
321
- let existing = [];
322
- for (let screenshot of screenshots) {
323
- if (existingSet.has(screenshot.sha256)) {
324
- existing.push(screenshot);
325
- } else {
326
- toUpload.push(screenshot);
327
- }
328
- }
329
- return {
330
- toUpload,
331
- existing
332
- };
333
- }
334
-
335
- /**
336
- * Check if SHA check result indicates file exists
337
- * @param {Object} checkResult - Result from checkShas endpoint
338
- * @param {string} sha256 - SHA to check
339
- * @returns {boolean} True if file exists
340
- */
341
- export function shaExists(checkResult, sha256) {
342
- return checkResult?.existing?.includes(sha256) ?? false;
343
- }
344
-
345
- /**
346
- * Find screenshot record from SHA check result
347
- * @param {Object} checkResult - Result from checkShas endpoint
348
- * @param {string} sha256 - SHA to find
349
- * @returns {Object|null} Screenshot record or null
350
- */
351
- export function findScreenshotBySha(checkResult, sha256) {
352
- return checkResult?.screenshots?.find(s => s.sha256 === sha256) ?? null;
353
- }
354
-
355
- // ============================================================================
356
- // URL Building
357
- // ============================================================================
358
-
359
- /**
360
- * Build full API URL from base and endpoint
361
- * @param {string} baseUrl - Base API URL
362
- * @param {string} endpoint - API endpoint (should start with /)
363
- * @returns {string} Full URL
364
- */
365
- export function buildApiUrl(baseUrl, endpoint) {
366
- // Remove trailing slash from base, ensure endpoint starts with /
367
- let base = baseUrl.replace(/\/$/, '');
368
- let path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
369
- return `${base}${path}`;
370
- }
371
-
372
- /**
373
- * Build endpoint URL with optional query params
374
- * @param {string} endpoint - Base endpoint
375
- * @param {Object} params - Query parameters
376
- * @returns {string} Endpoint with query string
377
- */
378
- export function buildEndpointWithParams(endpoint, params = {}) {
379
- let query = buildQueryParams(params);
380
- if (!query) return endpoint;
381
- return `${endpoint}?${query}`;
382
- }