@vizzly-testing/cli 0.7.0 → 0.7.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.
@@ -38,7 +38,7 @@ export async function finalizeCommand(parallelId, options = {}, globalOptions =
38
38
  // Create service container and get API service
39
39
  ui.startSpinner('Finalizing parallel build...');
40
40
  const container = await createServiceContainer(config, 'finalize');
41
- const apiService = await container.get('api');
41
+ const apiService = await container.get('apiService');
42
42
  ui.stopSpinner();
43
43
 
44
44
  // Call finalize endpoint
@@ -113,29 +113,44 @@ export class ApiService {
113
113
 
114
114
  /**
115
115
  * Check if SHAs already exist on the server
116
- * @param {string[]} shas - Array of SHA256 hashes to check
116
+ * @param {string[]|Object[]} shas - Array of SHA256 hashes to check, or array of screenshot objects with metadata
117
117
  * @param {string} buildId - Build ID for screenshot record creation
118
118
  * @returns {Promise<Object>} Response with existing SHAs and screenshot data
119
119
  */
120
120
  async checkShas(shas, buildId) {
121
121
  try {
122
+ let requestBody;
123
+
124
+ // Check if we're using the new signature-based format (array of objects) or legacy format (array of strings)
125
+ if (Array.isArray(shas) && shas.length > 0 && typeof shas[0] === 'object' && shas[0].sha256) {
126
+ // New signature-based format
127
+ requestBody = {
128
+ buildId,
129
+ screenshots: shas
130
+ };
131
+ } else {
132
+ // Legacy SHA-only format
133
+ requestBody = {
134
+ shas,
135
+ buildId
136
+ };
137
+ }
122
138
  const response = await this.request('/api/sdk/check-shas', {
123
139
  method: 'POST',
124
140
  headers: {
125
141
  'Content-Type': 'application/json'
126
142
  },
127
- body: JSON.stringify({
128
- shas,
129
- buildId
130
- })
143
+ body: JSON.stringify(requestBody)
131
144
  });
132
145
  return response;
133
146
  } catch (error) {
134
147
  // Continue without deduplication on error
135
148
  console.debug('SHA check failed, continuing without deduplication:', error.message);
149
+ // Extract SHAs for fallback response regardless of format
150
+ const shaList = Array.isArray(shas) && shas.length > 0 && typeof shas[0] === 'object' ? shas.map(s => s.sha256) : shas;
136
151
  return {
137
152
  existing: [],
138
- missing: shas,
153
+ missing: shaList,
139
154
  screenshots: []
140
155
  };
141
156
  }
@@ -167,13 +182,22 @@ export class ApiService {
167
182
  });
168
183
  }
169
184
 
170
- // Normal flow with SHA deduplication
185
+ // Normal flow with SHA deduplication using signature-based format
171
186
  const sha256 = crypto.createHash('sha256').update(buffer).digest('hex');
172
187
 
173
- // Check if this SHA already exists
174
- const checkResult = await this.checkShas([sha256], buildId);
188
+ // Create screenshot object with signature data for checking
189
+ const screenshotCheck = [{
190
+ sha256,
191
+ name,
192
+ browser: metadata?.browser || 'chrome',
193
+ viewport_width: metadata?.viewport?.width || 1920,
194
+ viewport_height: metadata?.viewport?.height || 1080
195
+ }];
196
+
197
+ // Check if this SHA with signature already exists
198
+ const checkResult = await this.checkShas(screenshotCheck, buildId);
175
199
  if (checkResult.existing && checkResult.existing.includes(sha256)) {
176
- // File already exists, screenshot record was automatically created
200
+ // File already exists with same signature, screenshot record was automatically created
177
201
  const screenshot = checkResult.screenshots?.find(s => s.sha256 === sha256);
178
202
  return {
179
203
  message: 'Screenshot already exists, skipped upload',
@@ -184,7 +208,7 @@ export class ApiService {
184
208
  };
185
209
  }
186
210
 
187
- // File doesn't exist, proceed with upload
211
+ // File doesn't exist or has different signature, proceed with upload
188
212
  return this.request(`/api/sdk/builds/${buildId}/screenshots`, {
189
213
  method: 'POST',
190
214
  headers: {
@@ -274,29 +274,31 @@ async function processFiles(files, signal, onProgress) {
274
274
  }
275
275
 
276
276
  /**
277
- * Check which files already exist on the server
277
+ * Check which files already exist on the server using signature-based deduplication
278
278
  */
279
279
  async function checkExistingFiles(fileMetadata, api, signal, buildId) {
280
- const allShas = fileMetadata.map(f => f.sha256);
281
280
  const existingShas = new Set();
282
281
  const allScreenshots = [];
283
282
 
284
- // Check in batches
285
- for (let i = 0; i < allShas.length; i += DEFAULT_SHA_CHECK_BATCH_SIZE) {
283
+ // Check in batches using the new signature-based format
284
+ for (let i = 0; i < fileMetadata.length; i += DEFAULT_SHA_CHECK_BATCH_SIZE) {
286
285
  if (signal.aborted) throw new UploadError('Operation cancelled');
287
- const batch = allShas.slice(i, i + DEFAULT_SHA_CHECK_BATCH_SIZE);
286
+ const batch = fileMetadata.slice(i, i + DEFAULT_SHA_CHECK_BATCH_SIZE);
287
+
288
+ // Convert file metadata to screenshot objects with signature data
289
+ const screenshotBatch = batch.map(file => ({
290
+ sha256: file.sha256,
291
+ name: file.filename.replace(/\.png$/, ''),
292
+ // Remove .png extension for name
293
+ // Extract browser from filename if available (e.g., "homepage-chrome.png" -> "chrome")
294
+ browser: extractBrowserFromFilename(file.filename) || 'chrome',
295
+ // Default to chrome
296
+ // Default viewport dimensions (these could be extracted from filename or metadata if available)
297
+ viewport_width: 1920,
298
+ viewport_height: 1080
299
+ }));
288
300
  try {
289
- const res = await api.request('/api/sdk/check-shas', {
290
- method: 'POST',
291
- headers: {
292
- 'Content-Type': 'application/json'
293
- },
294
- body: JSON.stringify({
295
- shas: batch,
296
- buildId
297
- }),
298
- signal
299
- });
301
+ const res = await api.checkShas(screenshotBatch, buildId);
300
302
  const {
301
303
  existing = [],
302
304
  screenshots = []
@@ -315,6 +317,22 @@ async function checkExistingFiles(fileMetadata, api, signal, buildId) {
315
317
  };
316
318
  }
317
319
 
320
+ /**
321
+ * Extract browser name from filename
322
+ * @param {string} filename - The screenshot filename
323
+ * @returns {string|null} Browser name or null if not found
324
+ */
325
+ function extractBrowserFromFilename(filename) {
326
+ const browsers = ['chrome', 'firefox', 'safari', 'edge', 'webkit'];
327
+ const lowerFilename = filename.toLowerCase();
328
+ for (const browser of browsers) {
329
+ if (lowerFilename.includes(browser)) {
330
+ return browser;
331
+ }
332
+ }
333
+ return null;
334
+ }
335
+
318
336
  /**
319
337
  * Upload files to Vizzly
320
338
  */
@@ -41,11 +41,11 @@ export class ApiService {
41
41
  createBuild(metadata: any): Promise<any>;
42
42
  /**
43
43
  * Check if SHAs already exist on the server
44
- * @param {string[]} shas - Array of SHA256 hashes to check
44
+ * @param {string[]|Object[]} shas - Array of SHA256 hashes to check, or array of screenshot objects with metadata
45
45
  * @param {string} buildId - Build ID for screenshot record creation
46
46
  * @returns {Promise<Object>} Response with existing SHAs and screenshot data
47
47
  */
48
- checkShas(shas: string[], buildId: string): Promise<any>;
48
+ checkShas(shas: string[] | any[], buildId: string): Promise<any>;
49
49
  /**
50
50
  * Upload a screenshot with SHA checking
51
51
  * @param {string} buildId - Build ID
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizzly-testing/cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.2",
4
4
  "description": "Visual review platform for UI developers and designers",
5
5
  "keywords": [
6
6
  "visual-testing",