@vizzly-testing/cli 0.35.0 → 0.35.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.
package/README.md CHANGED
@@ -40,17 +40,31 @@ Use `vizzly init --agent-skill` to install only the local skill, or
40
40
 
41
41
  ### Start Local TDD
42
42
 
43
- Start the TDD server, run your tests, and open the dashboard at
44
- `http://localhost:47392`.
43
+ Start the TDD server, run your tests, and open the dashboard at the URL the
44
+ command prints. Add `--open` when you want Vizzly to open the dashboard for
45
+ you. If the default port is busy, Vizzly picks the next available port; use the
46
+ printed `--port` value with `vizzly tdd status` or `vizzly tdd stop`.
45
47
 
46
48
  ```bash
47
- vizzly tdd start
49
+ vizzly tdd start --open
48
50
  pnpm test -- --watch
49
51
  ```
50
52
 
51
53
  The dashboard shows screenshots, baselines, and diffs as they arrive. Accept or
52
54
  reject changes right from the UI.
53
55
 
56
+ For a one-off local check, wrap the test command with `tdd run`:
57
+
58
+ ```bash
59
+ vizzly tdd run "pnpm test" --no-open
60
+ vizzly context build current --source local --agent
61
+ ```
62
+
63
+ That run writes review data under `.vizzly/` and prints a context command you
64
+ can use for follow-up inspection. If screenshots were captured, Vizzly also
65
+ generates `.vizzly/report/index.html`; omit `--no-open` when you want that
66
+ report opened automatically.
67
+
54
68
  ### Run With Cloud Review
55
69
 
56
70
  Use cloud builds when you want shared baselines, team review, and CI status:
@@ -162,9 +176,11 @@ vizzly upload ./screenshots --threshold 2 --min-cluster-size 4 --batch-size 10 -
162
176
  `--upload-timeout` controls the upload client's timeout, including how long
163
177
  `--wait` polls for build processing.
164
178
 
165
- CI workflows can force every screenshot through even when the SHA cache says it
166
- already uploaded. Parallel jobs should use the same stable ID, then finalize
167
- that ID after every shard completes:
179
+ When `vizzly run` sends screenshots to Vizzly, the CLI first asks the API to
180
+ resolve each screenshot by SHA. If Vizzly already has the image bytes, the CLI
181
+ creates the screenshot record without re-uploading the image. CI workflows can
182
+ force every screenshot through with `--upload-all`. Parallel jobs should use the
183
+ same stable ID, then finalize that ID after every shard completes:
168
184
 
169
185
  ```bash
170
186
  vizzly run "pnpm test" --upload-all --parallel-id "$GITHUB_RUN_ID"
@@ -206,10 +222,18 @@ export default {
206
222
  | Command | What it does |
207
223
  | --- | --- |
208
224
  | `vizzly tdd start` | Start the local TDD server and dashboard. |
209
- | `vizzly tdd run "cmd"` | Run tests once and generate a static visual report. |
225
+ | `vizzly tdd status` | Check the local TDD server for this project. |
226
+ | `vizzly tdd list` | List running local TDD servers. |
227
+ | `vizzly tdd stop` | Stop the local TDD server for this project. |
228
+ | `vizzly tdd run "cmd"` | Run tests once and write local review data under `.vizzly/`. |
210
229
  | `vizzly run "cmd"` | Run tests with cloud build and review integration. |
211
230
  | `vizzly context ...` | Fetch visual context for builds, comparisons, screenshots, and review queues. |
212
231
  | `vizzly upload <dir>` | Upload an existing folder of screenshots. |
232
+ | `vizzly preview <dir>` | Upload static build output for in-context review. |
233
+ | `vizzly approve <comparison-id>` | Approve a visual comparison. |
234
+ | `vizzly reject <comparison-id>` | Reject a visual comparison with a reason. |
235
+ | `vizzly comment <build-id>` | Add a build comment. |
236
+ | `vizzly config [key]` | Inspect resolved configuration values. |
213
237
  | `vizzly login` | Authenticate through the browser. |
214
238
  | `vizzly doctor` | Validate your local setup. |
215
239
 
package/dist/api/core.js CHANGED
@@ -70,9 +70,9 @@ export function buildRequestHeaders({
70
70
  // ============================================================================
71
71
 
72
72
  /**
73
- * Build payload for screenshot upload
73
+ * Build payload for screenshot upload or SHA resolution
74
74
  * @param {string} name - Screenshot name
75
- * @param {Buffer} buffer - Image data
75
+ * @param {Buffer|null} buffer - Image data, or null when resolving by SHA
76
76
  * @param {Object} metadata - Screenshot metadata (viewport, browser, etc.)
77
77
  * @param {string|null} sha256 - Pre-computed SHA256 hash (optional)
78
78
  * @returns {Object} Screenshot upload payload
@@ -80,15 +80,28 @@ export function buildRequestHeaders({
80
80
  export function buildScreenshotPayload(name, buffer, metadata = {}, sha256 = null) {
81
81
  let payload = {
82
82
  name,
83
- image_data: buffer.toString('base64'),
84
83
  properties: metadata ?? {}
85
84
  };
85
+ if (buffer) {
86
+ payload.image_data = buffer.toString('base64');
87
+ }
86
88
  if (sha256) {
87
89
  payload.sha256 = sha256;
88
90
  }
89
91
  return payload;
90
92
  }
91
93
 
94
+ /**
95
+ * Build payload for server-side screenshot resolution without sending image bytes
96
+ * @param {string} name - Screenshot name
97
+ * @param {Object} metadata - Screenshot metadata (viewport, browser, etc.)
98
+ * @param {string} sha256 - Pre-computed SHA256 hash
99
+ * @returns {Object} Screenshot resolution payload
100
+ */
101
+ export function buildScreenshotResolvePayload(name, metadata = {}, sha256) {
102
+ return buildScreenshotPayload(name, null, metadata, sha256);
103
+ }
104
+
92
105
  /**
93
106
  * Build payload for build creation
94
107
  * @param {Object} options - Build options
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { VizzlyError } from '../errors/vizzly-error.js';
9
9
  import * as output from '../utils/output.js';
10
- import { buildBuildPayload, buildEndpointWithParams, buildQueryParams, buildScreenshotCheckObject, buildScreenshotPayload, buildShaCheckPayload, computeSha256, findScreenshotBySha, shaExists } from './core.js';
10
+ import { buildBuildPayload, buildEndpointWithParams, buildQueryParams, buildScreenshotPayload, buildScreenshotResolvePayload, buildShaCheckPayload, computeSha256 } from './core.js';
11
11
 
12
12
  // ============================================================================
13
13
  // Build Endpoints
@@ -144,6 +144,16 @@ export async function checkShas(client, screenshots, buildId) {
144
144
  };
145
145
  }
146
146
  }
147
+ function canUploadBytesAfterShaResolveError(error) {
148
+ let status = error?.context?.status;
149
+
150
+ // Network failures, older servers, and server errors should degrade to the
151
+ // slower byte upload path. Auth and ownership errors must still surface.
152
+ if (!status) return true;
153
+ if (status >= 500) return true;
154
+ if (status === 400 && error.message?.includes('image_data')) return true;
155
+ return false;
156
+ }
147
157
 
148
158
  /**
149
159
  * Upload a screenshot with SHA deduplication
@@ -168,23 +178,36 @@ export async function uploadScreenshot(client, buildId, name, buffer, metadata =
168
178
  });
169
179
  }
170
180
 
171
- // Normal flow with SHA deduplication
181
+ // Normal flow with server-side SHA resolution.
172
182
  let sha256 = computeSha256(buffer);
173
- let checkObj = buildScreenshotCheckObject(sha256, name, metadata);
174
- let checkResult = await checkShas(client, [checkObj], buildId);
175
- if (shaExists(checkResult, sha256)) {
176
- // File already exists, screenshot record was automatically created
177
- let screenshot = findScreenshotBySha(checkResult, sha256);
178
- return {
179
- message: 'Screenshot already exists, skipped upload',
180
- sha256,
181
- skipped: true,
182
- screenshot,
183
- fromExisting: true
184
- };
183
+ let resolvePayload = buildScreenshotResolvePayload(name, metadata, sha256);
184
+ try {
185
+ let resolveResult = await client.request(`/api/sdk/builds/${buildId}/screenshots`, {
186
+ method: 'POST',
187
+ headers: {
188
+ 'Content-Type': 'application/json'
189
+ },
190
+ body: JSON.stringify(resolvePayload)
191
+ });
192
+ if (resolveResult?.upload_required === false) {
193
+ return {
194
+ message: 'Screenshot already exists, skipped upload',
195
+ sha256,
196
+ skipped: true,
197
+ screenshot: resolveResult,
198
+ fromExisting: true
199
+ };
200
+ }
201
+ } catch (error) {
202
+ if (!canUploadBytesAfterShaResolveError(error)) {
203
+ throw error;
204
+ }
205
+ output.debug('sha-resolve', 'failed, uploading screenshot bytes', {
206
+ error: error.message
207
+ });
185
208
  }
186
209
 
187
- // File doesn't exist, proceed with upload
210
+ // File doesn't exist, or resolve failed; proceed with upload.
188
211
  let payload = buildScreenshotPayload(name, buffer, metadata, sha256);
189
212
  return client.request(`/api/sdk/builds/${buildId}/screenshots`, {
190
213
  method: 'POST',
package/dist/api/index.js CHANGED
@@ -13,7 +13,7 @@
13
13
  // Client factory
14
14
  export { createApiClient, DEFAULT_API_URL } from './client.js';
15
15
  // Core pure functions
16
- export { buildApiUrl, buildAuthHeader, buildBuildPayload, buildEndpointWithParams, buildQueryParams, buildRequestHeaders, buildScreenshotCheckObject, buildScreenshotPayload, buildShaCheckPayload, buildUserAgent, computeSha256, extractErrorBody, findScreenshotBySha, isAuthError, isRateLimited, parseApiError, partitionByShaExistence, shaExists, shouldRetryWithRefresh } from './core.js';
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
17
 
18
18
  // Endpoint functions
19
19
  export { checkShas, createBuild, finalizeBuild, finalizeParallelBuild, getBatchHotspots, getBuild, getBuildContext, getBuilds, getComparison, getComparisonContext, getPreviewInfo, getReviewQueueContext, getScreenshotContext, getScreenshotHotspots, getSimilarFingerprintContext, getTddBaselines, getTokenContext, searchComparisons, updateBuildStatus, uploadPreviewZip, uploadScreenshot } from './endpoints.js';