@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
@@ -0,0 +1,32 @@
1
+ import crypto from 'node:crypto';
2
+
3
+ /**
4
+ * Compute SHA256 hash of a buffer
5
+ * @param {Buffer} buffer - File buffer
6
+ * @returns {string} Hex-encoded SHA256 hash
7
+ */
8
+ export function computeSha256(buffer) {
9
+ return crypto.createHash('sha256').update(buffer).digest('hex');
10
+ }
11
+
12
+ /**
13
+ * Partition files into those that need upload and those that exist
14
+ * @param {Array} fileMetadata - All file metadata
15
+ * @param {Set} existingShas - Set of SHAs that already exist
16
+ * @returns {{ toUpload: Array, existing: Array }}
17
+ */
18
+ export function partitionFilesByExistence(fileMetadata, existingShas) {
19
+ return {
20
+ toUpload: fileMetadata.filter(f => !existingShas.has(f.sha256)),
21
+ existing: fileMetadata.filter(f => existingShas.has(f.sha256))
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Build glob pattern for screenshots
27
+ * @param {string} directory - Base directory
28
+ * @returns {string} Glob pattern
29
+ */
30
+ export function buildScreenshotPattern(directory) {
31
+ return `${directory}/**/*.png`;
32
+ }
@@ -0,0 +1,21 @@
1
+ import { DEFAULT_BATCH_SIZE, DEFAULT_TIMEOUT } from './upload-defaults.js';
2
+
3
+ /**
4
+ * Resolve batch size from options and config
5
+ * @param {Object} options - Runtime options
6
+ * @param {Object} uploadConfig - Upload configuration
7
+ * @returns {number} Resolved batch size
8
+ */
9
+ export function resolveBatchSize(options, uploadConfig) {
10
+ return Number(options?.batchSize ?? uploadConfig?.batchSize ?? DEFAULT_BATCH_SIZE);
11
+ }
12
+
13
+ /**
14
+ * Resolve timeout from options and config
15
+ * @param {Object} options - Runtime options
16
+ * @param {Object} uploadConfig - Upload configuration
17
+ * @returns {number} Resolved timeout in ms
18
+ */
19
+ export function resolveTimeout(options, uploadConfig) {
20
+ return Number(options?.timeout ?? uploadConfig?.timeout ?? DEFAULT_TIMEOUT);
21
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Check if timeout has been exceeded
3
+ * @param {number} startTime - Start timestamp
4
+ * @param {number} timeout - Timeout in ms
5
+ * @returns {boolean} True if timed out
6
+ */
7
+ export function isTimedOut(startTime, timeout, now = Date.now()) {
8
+ return now - startTime >= timeout;
9
+ }
10
+
11
+ /**
12
+ * Get elapsed time since start
13
+ * @param {number} startTime - Start timestamp
14
+ * @returns {number} Elapsed time in ms
15
+ */
16
+ export function getElapsedTime(startTime, now = Date.now()) {
17
+ return now - startTime;
18
+ }
@@ -1,11 +1,13 @@
1
- import { createApiClient as defaultCreateApiClient, finalizeBuild as defaultFinalizeBuild, getTokenContext as defaultGetTokenContext } from '../api/index.js';
2
- import { CONFIG_DEFAULTS } from '../config/core.js';
3
- import { createUploader as defaultCreateUploader } from '../uploader/index.js';
4
- import { getAppBaseUrl } from '../utils/api-url.js';
5
- import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
6
- import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit, detectCommitAuthor as defaultDetectCommitAuthor, detectCommitMessage as defaultDetectCommitMessage, detectPullRequestNumber as defaultDetectPullRequestNumber, generateBuildNameWithGit as defaultGenerateBuildNameWithGit } from '../utils/git.js';
7
- import * as defaultOutput from '../utils/output.js';
8
- import { writeSession as defaultWriteSession } from '../utils/session.js';
1
+ import { getAppBaseUrl } from '../../shared/api-url.js';
2
+ import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit, detectCommitAuthor as defaultDetectCommitAuthor, detectCommitMessage as defaultDetectCommitMessage, detectPullRequestNumber as defaultDetectPullRequestNumber, generateBuildNameWithGit as defaultGenerateBuildNameWithGit } from '../../shared/git.js';
3
+ import * as defaultOutput from '../../shared/output.js';
4
+ import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
5
+ import { finalizeBuild as defaultFinalizeBuild } from '../builds/build.service.js';
6
+ import { CONFIG_DEFAULTS } from '../config/config-defaults.js';
7
+ import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
8
+ import { getTokenContext as defaultGetTokenContext } from '../context/context.service.js';
9
+ import { writeSession as defaultWriteSession } from '../runs/session-store.js';
10
+ import { createUploader as defaultCreateUploader } from './upload.service.js';
9
11
 
10
12
  /**
11
13
  * Construct proper build URL with org/project context
@@ -380,7 +382,7 @@ export async function uploadCommand(screenshotsPath, options = {}, globalOptions
380
382
  * @param {Object} options - Command options
381
383
  */
382
384
  export function validateUploadOptions(screenshotsPath, options) {
383
- const errors = [];
385
+ let errors = [];
384
386
  if (!screenshotsPath) {
385
387
  errors.push('Screenshots path is required');
386
388
  }
@@ -0,0 +1,60 @@
1
+ import { basename } from 'node:path';
2
+ import { computeSha256 } from './upload-files.js';
3
+ let KNOWN_BROWSERS = ['chrome', 'firefox', 'safari', 'edge', 'webkit'];
4
+
5
+ /**
6
+ * Extract browser name from filename
7
+ * @param {string} filename - The screenshot filename
8
+ * @returns {string|null} Browser name or null if not found
9
+ */
10
+ export function extractBrowserFromFilename(filename) {
11
+ let lowerFilename = filename.toLowerCase();
12
+ for (let browser of KNOWN_BROWSERS) {
13
+ if (lowerFilename.includes(browser)) {
14
+ return browser;
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+
20
+ /**
21
+ * Build file metadata object
22
+ * @param {string} filePath - Path to file
23
+ * @param {Buffer} buffer - File contents
24
+ * @returns {Object} File metadata
25
+ */
26
+ export function buildFileMetadata(filePath, buffer) {
27
+ return {
28
+ path: filePath,
29
+ filename: basename(filePath),
30
+ buffer,
31
+ sha256: computeSha256(buffer)
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Convert file metadata to screenshot check format
37
+ * @param {Object} file - File metadata
38
+ * @returns {Object} Screenshot format for SHA check
39
+ */
40
+ export function fileToScreenshotFormat(file) {
41
+ let browser = extractBrowserFromFilename(file.filename);
42
+ return {
43
+ sha256: file.sha256,
44
+ name: file.filename.replace(/\.png$/, ''),
45
+ filename: file.filename,
46
+ properties: browser ? {
47
+ browser
48
+ } : {}
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Extract status code from error message
54
+ * @param {string} errorMessage - Error message
55
+ * @returns {string} Status code or 'unknown'
56
+ */
57
+ export function extractStatusCodeFromError(errorMessage) {
58
+ let match = String(errorMessage || '').match(/API request failed: (\d+)/);
59
+ return match ? match[1] : 'unknown';
60
+ }
@@ -0,0 +1,179 @@
1
+ function buildUploadMetadata(options) {
2
+ let metadata = {
3
+ ...(options.metadata || {})
4
+ };
5
+ let comparison = {
6
+ ...(metadata.comparison || {})
7
+ };
8
+ if (options.threshold !== undefined) {
9
+ comparison.threshold = options.threshold;
10
+ }
11
+ if (options.minClusterSize !== undefined) {
12
+ comparison.minClusterSize = options.minClusterSize;
13
+ }
14
+ if (Object.keys(comparison).length > 0) {
15
+ metadata.comparison = comparison;
16
+ }
17
+ return Object.keys(metadata).length > 0 ? metadata : undefined;
18
+ }
19
+
20
+ /**
21
+ * Build API build info payload
22
+ * @param {Object} options - Upload options
23
+ * @param {string} [defaultBranch] - Default branch to use
24
+ * @returns {Object} Build info payload
25
+ */
26
+ export function buildBuildInfo(options, defaultBranch = 'main') {
27
+ let buildInfo = {
28
+ name: options.buildName || `Upload ${new Date().toISOString()}`,
29
+ branch: options.branch || defaultBranch || 'main',
30
+ commit_sha: options.commit,
31
+ commit_message: options.message,
32
+ commit_author_name: options.commitAuthorName,
33
+ commit_author_email: options.commitAuthorEmail,
34
+ environment: options.environment || 'production',
35
+ threshold: options.threshold,
36
+ github_pull_request_number: options.pullRequestNumber,
37
+ parallel_id: options.parallelId
38
+ };
39
+ let metadata = buildUploadMetadata(options);
40
+ if (metadata) {
41
+ buildInfo.metadata = metadata;
42
+ }
43
+ return buildInfo;
44
+ }
45
+
46
+ /**
47
+ * Build scanning phase progress
48
+ * @param {number} total - Total files found
49
+ * @returns {Object} Progress object
50
+ */
51
+ export function buildScanningProgress(total) {
52
+ return {
53
+ phase: 'scanning',
54
+ message: `Found ${total} screenshots`,
55
+ total
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Build processing phase progress
61
+ * @param {number} current - Current file number
62
+ * @param {number} total - Total files
63
+ * @returns {Object} Progress object
64
+ */
65
+ export function buildProcessingProgress(current, total) {
66
+ return {
67
+ phase: 'processing',
68
+ message: 'Processing files',
69
+ current,
70
+ total
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Build deduplication phase progress
76
+ * @param {number} toUpload - Files to upload
77
+ * @param {number} existing - Existing files
78
+ * @param {number} total - Total files
79
+ * @returns {Object} Progress object
80
+ */
81
+ export function buildDeduplicationProgress(toUpload, existing, total) {
82
+ return {
83
+ phase: 'deduplication',
84
+ message: `Checking for duplicates (${toUpload} to upload, ${existing} existing)`,
85
+ toUpload,
86
+ existing,
87
+ total
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Build uploading phase progress
93
+ * @param {number} current - Current upload number
94
+ * @param {number} total - Total to upload
95
+ * @returns {Object} Progress object
96
+ */
97
+ export function buildUploadingProgress(current, total) {
98
+ return {
99
+ phase: 'uploading',
100
+ message: 'Uploading screenshots',
101
+ current,
102
+ total
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Build completed phase progress
108
+ * @param {string} buildId - Build ID
109
+ * @param {string|null} url - Build URL
110
+ * @returns {Object} Progress object
111
+ */
112
+ export function buildCompletedProgress(buildId, url) {
113
+ return {
114
+ phase: 'completed',
115
+ message: 'Upload completed',
116
+ buildId,
117
+ url
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Build successful upload result
123
+ * @param {Object} options - Options
124
+ * @param {string} options.buildId - Build ID
125
+ * @param {string|null} options.url - Build URL
126
+ * @param {number} options.total - Total files
127
+ * @param {number} options.uploaded - Files uploaded
128
+ * @param {number} options.skipped - Files skipped
129
+ * @returns {Object} Upload result
130
+ */
131
+ export function buildUploadResult({
132
+ buildId,
133
+ url,
134
+ total,
135
+ uploaded,
136
+ skipped
137
+ }) {
138
+ return {
139
+ success: true,
140
+ buildId,
141
+ url,
142
+ stats: {
143
+ total,
144
+ uploaded,
145
+ skipped
146
+ }
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Build wait result from build response
152
+ * @param {Object} build - Build object from API
153
+ * @returns {Object} Wait result
154
+ */
155
+ export function buildWaitResult(build) {
156
+ let totalComparisons = build.total_comparisons;
157
+ let passedComparisons = build.identical_comparisons;
158
+ let failedComparisons = build.changed_comparisons;
159
+ let newComparisons = build.new_comparisons;
160
+ let identicalComparisons = build.identical_comparisons;
161
+ let hasComparisonCounts = build.total_comparisons !== undefined || build.new_comparisons !== undefined || build.changed_comparisons !== undefined || build.identical_comparisons !== undefined;
162
+ let result = {
163
+ status: build.status,
164
+ build,
165
+ passedComparisons,
166
+ failedComparisons,
167
+ newComparisons,
168
+ identicalComparisons,
169
+ visual_review: build.visual_review || null,
170
+ totalComparisons
171
+ };
172
+ if (hasComparisonCounts) {
173
+ result.comparisons = totalComparisons;
174
+ }
175
+ if (build.url) {
176
+ result.url = build.url;
177
+ }
178
+ return result;
179
+ }
@@ -1,10 +1,91 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { glob } from 'glob';
3
+ import { getDefaultBranch } from '../../shared/git.js';
4
+ import * as output from '../../shared/output.js';
5
+ import { TimeoutError, UploadError, ValidationError } from '../../shared/vizzly-error.js';
6
+ import { createApiClient } from '../auth/cloud-client.js';
7
+ import { createBuild } from '../builds/build.service.js';
8
+ import { checkShas } from '../screenshots/cloud-screenshots.js';
9
+ import { buildFileMetadata, extractStatusCodeFromError, fileToScreenshotFormat } from './upload.normalizers.js';
10
+ import { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildProcessingProgress, buildScanningProgress, buildUploadingProgress, buildUploadResult, buildWaitResult } from './upload.serializers.js';
11
+ import { validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './upload.validations.js';
12
+ import { DEFAULT_BUILD_POLL_INTERVAL, DEFAULT_SHA_CHECK_BATCH_SIZE } from './upload-defaults.js';
13
+ import { buildScreenshotPattern, partitionFilesByExistence } from './upload-files.js';
14
+ import { resolveBatchSize, resolveTimeout } from './upload-settings.js';
15
+ import { getElapsedTime, isTimedOut } from './upload-timing.js';
16
+
1
17
  /**
2
- * Uploader Operations - I/O operations with dependency injection
3
- *
4
- * Each operation takes its dependencies as parameters for testability.
18
+ * Screenshot upload operations and the public uploader factory.
19
+ * Dependencies are explicit so callers can choose external integrations.
5
20
  */
6
21
 
7
- import { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildFileMetadata, buildProcessingProgress, buildScanningProgress, buildScreenshotPattern, buildUploadingProgress, buildUploadResult, buildWaitResult, DEFAULT_BUILD_POLL_INTERVAL, DEFAULT_SHA_CHECK_BATCH_SIZE, extractStatusCodeFromError, fileToScreenshotFormat, getElapsedTime, isTimedOut, partitionFilesByExistence, validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './core.js';
22
+ export function createUploader({
23
+ apiKey,
24
+ apiUrl,
25
+ userAgent,
26
+ command,
27
+ upload: uploadConfig = {}
28
+ } = {}, options = {}) {
29
+ let signal = options.signal || new AbortController().signal;
30
+ let client = createApiClient({
31
+ baseUrl: apiUrl,
32
+ token: apiKey,
33
+ command: command || 'upload',
34
+ sdkUserAgent: userAgent,
35
+ allowNoToken: true
36
+ });
37
+ let batchSize = resolveBatchSize(options, uploadConfig);
38
+ let timeout = resolveTimeout(options, uploadConfig);
39
+ let deps = options.deps || {
40
+ client,
41
+ createBuild,
42
+ getDefaultBranch,
43
+ glob,
44
+ readFile,
45
+ stat,
46
+ checkShas,
47
+ createError: (message, code, context) => {
48
+ let error = new UploadError(message, context);
49
+ error.code = code;
50
+ return error;
51
+ },
52
+ createValidationError: (message, context) => new ValidationError(message, context),
53
+ createUploadError: (message, context) => new UploadError(message, context),
54
+ createTimeoutError: (message, context) => new TimeoutError(message, context),
55
+ output
56
+ };
57
+ async function uploadScreenshots(uploadOptions) {
58
+ return upload({
59
+ uploadOptions,
60
+ config: {
61
+ apiKey,
62
+ apiUrl
63
+ },
64
+ signal,
65
+ batchSize,
66
+ deps: {
67
+ ...deps,
68
+ client: deps.client || client
69
+ }
70
+ });
71
+ }
72
+ async function waitForUploadedBuild(buildId, waitTimeout = timeout) {
73
+ return waitForBuild({
74
+ buildId,
75
+ timeout: waitTimeout,
76
+ signal,
77
+ client: deps.client || client,
78
+ deps: {
79
+ createError: deps.createError,
80
+ createTimeoutError: deps.createTimeoutError
81
+ }
82
+ });
83
+ }
84
+ return {
85
+ upload: uploadScreenshots,
86
+ waitForBuild: waitForUploadedBuild
87
+ };
88
+ }
8
89
  function delay(ms, signal) {
9
90
  if (ms <= 0) {
10
91
  return Promise.resolve();
@@ -33,10 +114,6 @@ function delay(ms, signal) {
33
114
  });
34
115
  }
35
116
 
36
- // ============================================================================
37
- // File Discovery
38
- // ============================================================================
39
-
40
117
  /**
41
118
  * Find all PNG screenshots in a directory
42
119
  * @param {Object} options - Options
@@ -58,10 +135,6 @@ export async function findScreenshots({
58
135
  });
59
136
  }
60
137
 
61
- // ============================================================================
62
- // File Processing
63
- // ============================================================================
64
-
65
138
  /**
66
139
  * Process files to extract metadata and compute hashes
67
140
  * @param {Object} options - Options
@@ -100,10 +173,6 @@ export async function processFiles({
100
173
  return results;
101
174
  }
102
175
 
103
- // ============================================================================
104
- // SHA Checking / Deduplication
105
- // ============================================================================
106
-
107
176
  /**
108
177
  * Check which files already exist on the server
109
178
  * @param {Object} options - Options
@@ -161,10 +230,6 @@ export async function checkExistingFiles({
161
230
  };
162
231
  }
163
232
 
164
- // ============================================================================
165
- // File Upload
166
- // ============================================================================
167
-
168
233
  /**
169
234
  * Upload files to Vizzly
170
235
  * @param {Object} options - Options
@@ -230,10 +295,6 @@ export async function uploadFiles({
230
295
  };
231
296
  }
232
297
 
233
- // ============================================================================
234
- // Build Waiting
235
- // ============================================================================
236
-
237
298
  /**
238
299
  * Wait for a build to complete
239
300
  * @param {Object} options - Options
@@ -299,10 +360,6 @@ export async function waitForBuild({
299
360
  });
300
361
  }
301
362
 
302
- // ============================================================================
303
- // Main Upload Operation
304
- // ============================================================================
305
-
306
363
  /**
307
364
  * Upload screenshots to Vizzly
308
365
  * @param {Object} options - Options
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Validate API key is present
3
+ * @param {string|undefined} apiKey - API key to validate
4
+ * @returns {{ valid: boolean, error: string|null }}
5
+ */
6
+ export function validateApiKey(apiKey) {
7
+ if (!apiKey) {
8
+ return {
9
+ valid: false,
10
+ error: 'API key is required'
11
+ };
12
+ }
13
+ return {
14
+ valid: true,
15
+ error: null
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Validate screenshots directory path
21
+ * @param {string|undefined} screenshotsDir - Directory path
22
+ * @returns {{ valid: boolean, error: string|null }}
23
+ */
24
+ export function validateScreenshotsDir(screenshotsDir) {
25
+ if (!screenshotsDir) {
26
+ return {
27
+ valid: false,
28
+ error: 'Screenshots directory is required'
29
+ };
30
+ }
31
+ return {
32
+ valid: true,
33
+ error: null
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Validate directory stats
39
+ * @param {Object} stats - fs.stat result
40
+ * @param {string} path - Directory path for error message
41
+ * @returns {{ valid: boolean, error: string|null }}
42
+ */
43
+ export function validateDirectoryStats(stats, path) {
44
+ if (!stats.isDirectory()) {
45
+ return {
46
+ valid: false,
47
+ error: `${path} is not a directory`
48
+ };
49
+ }
50
+ return {
51
+ valid: true,
52
+ error: null
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Validate files were found
58
+ * @param {Array} files - Array of file paths
59
+ * @param {string} directory - Directory that was searched
60
+ * @returns {{ valid: boolean, error: string|null, context?: Object }}
61
+ */
62
+ export function validateFilesFound(files, directory) {
63
+ if (!files || files.length === 0) {
64
+ return {
65
+ valid: false,
66
+ error: 'No screenshot files found',
67
+ context: {
68
+ directory,
69
+ pattern: '**/*.png'
70
+ }
71
+ };
72
+ }
73
+ return {
74
+ valid: true,
75
+ error: null
76
+ };
77
+ }
package/dist/index.js CHANGED
@@ -9,18 +9,18 @@ import 'dotenv/config';
9
9
  */
10
10
 
11
11
  // Client exports for convenience
12
- export { configure, setEnabled, vizzlyScreenshot } from './client/index.js';
13
- // Errors
14
- export { AuthError, BuildError, ConfigError, NetworkError, ScreenshotError, TimeoutError, UploadError, ValidationError, VizzlyError } from './errors/vizzly-error.js';
15
- export { createPluginServices } from './plugin-api.js';
12
+ export { configure, setEnabled, vizzlyScreenshot } from './adapters/client/index.js';
13
+ export { createPluginServices } from './adapters/plugins/plugin-api.js';
14
+ export { createServices } from './adapters/plugins/services.js';
16
15
  // Primary SDK export
17
- export { createVizzly, VizzlySDK } from './sdk/index.js';
18
- export { createServices } from './services/index.js';
19
- export { createTDDService } from './tdd/tdd-service.js';
20
- // Core services (for advanced usage)
21
- export { createUploader } from './uploader/index.js';
16
+ export { createVizzly, VizzlySDK } from './adapters/sdk/index.js';
22
17
  // Configuration helper
23
- export { defineConfig } from './utils/config-helpers.js';
18
+ export { defineConfig } from './domains/config/config.js';
24
19
  // Utilities
25
- export { loadConfig } from './utils/config-loader.js';
26
- export * as output from './utils/output.js';
20
+ export { loadConfig } from './domains/config/config-loader.js';
21
+ export { createTDDService } from './domains/tdd/tdd.service.js';
22
+ // Core services (for advanced usage)
23
+ export { createUploader } from './domains/uploads/upload.service.js';
24
+ export * as output from './shared/output.js';
25
+ // Errors
26
+ export { AuthError, BuildError, ConfigError, NetworkError, ScreenshotError, TimeoutError, UploadError, ValidationError, VizzlyError } from './shared/vizzly-error.js';