@vizzly-testing/cli 0.39.0 → 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.
- package/README.md +12 -3
- package/bin/vizzly.js +1 -1
- package/dist/{cli.js → adapters/cli/cli.js} +50 -35
- package/dist/adapters/cli/setup.js +34 -0
- package/dist/{client → adapters/client}/index.js +6 -7
- package/dist/adapters/http/cloud-proxy.js +29 -0
- package/dist/{server → adapters/http}/http-server.js +12 -13
- package/dist/{server-manager/operations.js → adapters/http/server-lifecycle.js} +10 -13
- package/dist/{server-manager/index.js → adapters/http/server-manager.js} +23 -27
- package/dist/{plugin-api.js → adapters/plugins/plugin-api.js} +1 -1
- package/dist/{plugin-loader.js → adapters/plugins/plugin-loader.js} +1 -1
- package/dist/{services/index.js → adapters/plugins/services.js} +4 -9
- package/dist/adapters/sdk/index.d.ts +89 -0
- package/dist/{sdk → adapters/sdk}/index.js +22 -19
- package/dist/{commands/api.js → domains/api/api.commands.js} +3 -3
- package/dist/{auth/client.js → domains/auth/auth-client.js} +4 -3
- package/dist/domains/auth/auth-errors.js +50 -0
- package/dist/domains/auth/auth-headers.js +41 -0
- package/dist/domains/auth/auth.normalizers.js +50 -0
- package/dist/{server/routers/auth.js → domains/auth/auth.routes.js} +9 -9
- package/dist/domains/auth/auth.rules.js +19 -0
- package/dist/domains/auth/auth.serializers.js +32 -0
- package/dist/{auth/operations.js → domains/auth/auth.service.js} +115 -17
- package/dist/domains/auth/auth.validations.js +21 -0
- package/dist/{api/client.js → domains/auth/cloud-client.js} +11 -18
- package/dist/{commands/login.js → domains/auth/login.commands.js} +11 -8
- package/dist/{commands/logout.js → domains/auth/logout.commands.js} +6 -4
- package/dist/domains/auth/token-store.js +81 -0
- package/dist/{commands/whoami.js → domains/auth/whoami.commands.js} +6 -3
- package/dist/domains/baselines/baseline-download.js +192 -0
- package/dist/domains/baselines/baseline-write.js +22 -0
- package/dist/{server/routers/baseline.js → domains/baselines/baseline.routes.js} +3 -3
- package/dist/{tdd/services/baseline-manager.js → domains/baselines/baseline.service.js} +1 -39
- package/dist/{commands/baselines.js → domains/baselines/baselines.commands.js} +3 -3
- package/dist/domains/baselines/cloud-baselines.js +9 -0
- package/dist/domains/baselines/cloud-baselines.routes.js +42 -0
- package/dist/domains/builds/build.normalizers.js +35 -0
- package/dist/domains/builds/build.serializers.js +164 -0
- package/dist/domains/builds/build.service.js +119 -0
- package/dist/domains/builds/build.validations.js +39 -0
- package/dist/{commands/builds.js → domains/builds/builds.commands.js} +6 -74
- package/dist/domains/builds/cloud-builds.routes.js +31 -0
- package/dist/{commands/finalize.js → domains/builds/finalize.commands.js} +5 -17
- package/dist/{commands/status.js → domains/builds/status.commands.js} +9 -120
- package/dist/domains/comparisons/cloud-comparisons.js +43 -0
- package/dist/{tdd/services/comparison-service.js → domains/comparisons/comparison.service.js} +1 -1
- package/dist/{commands/comparisons.js → domains/comparisons/comparisons.commands.js} +7 -5
- package/dist/domains/config/config-defaults.js +27 -0
- package/dist/{utils → domains/config}/config-discovery.js +1 -9
- package/dist/{utils → domains/config}/config-loader.js +10 -9
- package/dist/{commands/config-cmd.js → domains/config/config.commands.js} +4 -4
- package/dist/{types → domains/config}/config.d.ts +3 -3
- package/dist/domains/config/config.normalizers.js +126 -0
- package/dist/{server/routers/config.js → domains/config/config.routes.js} +13 -13
- package/dist/domains/config/config.rules.js +47 -0
- package/dist/domains/config/config.serializers.js +202 -0
- package/dist/domains/config/config.service.js +625 -0
- package/dist/{utils/config-schema.js → domains/config/config.validations.js} +9 -9
- package/dist/{utils/environment-config.js → domains/config/environment.js} +1 -8
- package/dist/domains/context/compact-context.validations.js +60 -0
- package/dist/domains/context/context-presentation.js +368 -0
- package/dist/domains/context/context.commands.js +385 -0
- package/dist/domains/context/context.serializers.js +157 -0
- package/dist/domains/context/context.service.js +76 -0
- package/dist/domains/context/context.validations.js +100 -0
- package/dist/{context → domains/context}/local-workspace-provider.js +1 -1
- package/dist/{commands/doctor.js → domains/diagnostics/doctor.commands.js} +6 -5
- package/dist/{server/routers/health.js → domains/diagnostics/health.routes.js} +11 -20
- package/dist/{commands/preview.js → domains/previews/preview.commands.js} +9 -7
- package/dist/domains/previews/preview.service.js +37 -0
- package/dist/domains/projects/cloud-projects.routes.js +28 -0
- package/dist/{commands/orgs.js → domains/projects/orgs.commands.js} +6 -8
- package/dist/{utils → domains/projects}/project-binding.js +3 -3
- package/dist/domains/projects/project-errors.js +61 -0
- package/dist/{utils → domains/projects}/project-link-store.js +2 -2
- package/dist/domains/projects/project-requests.js +61 -0
- package/dist/{commands/project.js → domains/projects/project.commands.js} +9 -41
- package/dist/domains/projects/project.normalizers.js +69 -0
- package/dist/{project/operations.js → domains/projects/project.service.js} +89 -16
- package/dist/domains/projects/project.validations.js +31 -0
- package/dist/{commands/projects.js → domains/projects/projects.commands.js} +6 -8
- package/dist/{server/routers/projects.js → domains/projects/projects.routes.js} +5 -5
- package/dist/domains/reports/dashboard.routes.js +96 -0
- package/dist/{server/routers/events.js → domains/reports/events.routes.js} +14 -36
- package/dist/domains/reports/report-assets.routes.js +81 -0
- package/dist/domains/reports/report-state.js +51 -0
- package/dist/domains/reports/report-store.js +85 -0
- package/dist/domains/reports/report.serializers.js +99 -0
- package/dist/{services/static-report-generator.js → domains/reports/report.service.js} +11 -33
- package/dist/{commands/review.js → domains/review/review.commands.js} +4 -4
- package/dist/domains/runs/run-environment.js +69 -0
- package/dist/{services/test-runner.js → domains/runs/run-events.js} +11 -9
- package/dist/{commands/run.js → domains/runs/run.commands.js} +16 -13
- package/dist/domains/runs/run.normalizers.js +8 -0
- package/dist/domains/runs/run.rules.js +56 -0
- package/dist/domains/runs/run.serializers.js +101 -0
- package/dist/{test-runner/operations.js → domains/runs/run.service.js} +35 -7
- package/dist/domains/runs/run.validations.js +17 -0
- package/dist/{utils/session.js → domains/runs/session-store.js} +3 -2
- package/dist/{server/handlers/api-handler.js → domains/screenshots/cloud-handler.js} +4 -4
- package/dist/domains/screenshots/cloud-screenshots.js +109 -0
- package/dist/{utils/file-helpers.js → domains/screenshots/image-files.js} +1 -1
- package/dist/domains/screenshots/screenshot-hash.js +53 -0
- package/dist/domains/screenshots/screenshot-server-options.js +28 -0
- package/dist/{screenshot-server/operations.js → domains/screenshots/screenshot-server.js} +14 -1
- package/dist/domains/screenshots/screenshot.normalizers.js +40 -0
- package/dist/{server/routers/screenshot.js → domains/screenshots/screenshot.routes.js} +4 -4
- package/dist/domains/screenshots/screenshot.serializers.js +146 -0
- package/dist/{utils/security.js → domains/screenshots/screenshot.validations.js} +20 -70
- package/dist/{init/agents.js → domains/setup/agent-setup.js} +54 -14
- package/dist/{commands/init.js → domains/setup/init.commands.js} +11 -44
- package/dist/{server/handlers/tdd-handler.js → domains/tdd/screenshot-handler.js} +46 -281
- package/dist/{commands/tdd-daemon.js → domains/tdd/tdd-daemon.commands.js} +6 -6
- package/dist/{commands/tdd-screenshots.js → domains/tdd/tdd-screenshots.commands.js} +5 -5
- package/dist/{commands/tdd.js → domains/tdd/tdd.commands.js} +16 -15
- package/dist/{tdd/tdd-service.js → domains/tdd/tdd.service.js} +81 -405
- package/dist/domains/updates/cli-update.js +103 -0
- package/dist/domains/updates/update-check.js +2 -0
- package/dist/domains/updates/update-notifications.js +175 -0
- package/dist/domains/uploads/upload-defaults.js +4 -0
- package/dist/domains/uploads/upload-files.js +32 -0
- package/dist/domains/uploads/upload-settings.js +21 -0
- package/dist/domains/uploads/upload-timing.js +18 -0
- package/dist/{commands/upload.js → domains/uploads/upload.commands.js} +11 -9
- package/dist/domains/uploads/upload.normalizers.js +60 -0
- package/dist/domains/uploads/upload.serializers.js +179 -0
- package/dist/{uploader/operations.js → domains/uploads/upload.service.js} +85 -28
- package/dist/domains/uploads/upload.validations.js +77 -0
- package/dist/index.js +12 -12
- package/dist/reporter/reporter-bundle.iife.js +86 -86
- package/dist/reporter-ssr/ssr-entry.js +3 -6
- package/dist/shared/cloud-protocol.js +171 -0
- package/dist/{utils → shared}/file-changes.js +1 -1
- package/dist/shared/files.js +9 -0
- package/dist/{utils → shared}/global-config.js +7 -78
- package/dist/shared/log-level.js +7 -0
- package/dist/{utils → shared}/output.js +1 -1
- package/dist/{utils → shared}/package-info.js +5 -4
- package/dist/shared/paths.js +57 -0
- package/package.json +37 -36
- package/dist/api/core.js +0 -382
- package/dist/api/endpoints.js +0 -421
- package/dist/api/index.js +0 -19
- package/dist/auth/core.js +0 -237
- package/dist/auth/index.js +0 -31
- package/dist/commands/context.js +0 -1066
- package/dist/config/core.js +0 -439
- package/dist/config/operations.js +0 -327
- package/dist/project/core.js +0 -211
- package/dist/screenshot-server/core.js +0 -161
- package/dist/screenshot-server/index.js +0 -11
- package/dist/server/routers/assets.js +0 -95
- package/dist/server/routers/cloud-proxy.js +0 -146
- package/dist/server/routers/dashboard.js +0 -172
- package/dist/services/auth-service.js +0 -117
- package/dist/services/config-service.js +0 -313
- package/dist/services/project-service.js +0 -88
- package/dist/test-runner/core.js +0 -293
- package/dist/test-runner/index.js +0 -13
- package/dist/types/sdk.d.ts +0 -186
- package/dist/uploader/core.js +0 -432
- package/dist/uploader/index.js +0 -88
- /package/dist/{types/client.d.ts → adapters/client/index.d.ts} +0 -0
- /package/dist/{server → adapters/http}/middleware/cors.js +0 -0
- /package/dist/{server → adapters/http}/middleware/json-parser.js +0 -0
- /package/dist/{server → adapters/http}/middleware/response.js +0 -0
- /package/dist/{server-manager/core.js → adapters/http/server-options.js} +0 -0
- /package/dist/{utils/login-poll-wait.js → domains/auth/login-wait.js} +0 -0
- /package/dist/{tdd/metadata → domains/baselines}/baseline-metadata.js +0 -0
- /package/dist/{services/build-manager.js → domains/builds/build-info.js} +0 -0
- /package/dist/{tdd/services/result-service.js → domains/comparisons/comparison-results.js} +0 -0
- /package/dist/{init/core.js → domains/config/config-plan.js} +0 -0
- /package/dist/{utils/config-helpers.js → domains/config/config.js} +0 -0
- /package/dist/{utils/visual-context-normalizers.js → domains/context/context.normalizers.js} +0 -0
- /package/dist/{context → domains/context}/provider-resolver.js +0 -0
- /package/dist/{utils/context.js → domains/diagnostics/environment-context.js} +0 -0
- /package/dist/{utils/report-data.js → domains/reports/report.normalizers.js} +0 -0
- /package/dist/{utils/image-input-detector.js → domains/screenshots/image-input.js} +0 -0
- /package/dist/{tdd/core/signature.js → domains/screenshots/screenshot-identity.js} +0 -0
- /package/dist/{utils → domains/screenshots}/screenshot-options.js +0 -0
- /package/dist/{init/prompts.js → domains/setup/setup-prompts.js} +0 -0
- /package/dist/{tdd → domains/tdd}/runtime-context.js +0 -0
- /package/dist/{tdd → domains/tdd}/server-registry.js +0 -0
- /package/dist/{commands/tdd-daemon-process.js → domains/tdd/tdd-daemon-process.commands.js} +0 -0
- /package/dist/{types/index.d.ts → index.d.ts} +0 -0
- /package/dist/{utils → shared}/api-url.js +0 -0
- /package/dist/{utils → shared}/async-utils.js +0 -0
- /package/dist/{utils → shared}/browser.js +0 -0
- /package/dist/{utils → shared}/ci-env.js +0 -0
- /package/dist/{utils → shared}/colors.js +0 -0
- /package/dist/{utils → shared}/fetch-utils.js +0 -0
- /package/dist/{utils → shared}/git.js +0 -0
- /package/dist/{utils → shared}/patterns.js +0 -0
- /package/dist/{utils → shared}/secret-store.js +0 -0
- /package/dist/{errors → shared}/vizzly-error.js +0 -0
|
@@ -5,25 +5,31 @@
|
|
|
5
5
|
* This is a thin orchestration layer - most logic lives in the modules.
|
|
6
6
|
*
|
|
7
7
|
* CRITICAL: Signature/filename generation MUST stay in sync with the cloud!
|
|
8
|
-
* See src/
|
|
8
|
+
* See src/domains/screenshots/screenshot-identity.js for details.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { existsSync as defaultExistsSync, mkdirSync as defaultMkdirSync, readFileSync as defaultReadFileSync, writeFileSync as defaultWriteFileSync } from 'node:fs';
|
|
12
12
|
import { compareSync } from '@vizzly-testing/honeydiff';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
13
|
+
import { colors as defaultColors } from '../../shared/colors.js';
|
|
14
|
+
import { fetchWithTimeout as defaultFetchWithTimeout } from '../../shared/fetch-utils.js';
|
|
15
|
+
import { getDefaultBranch as defaultGetDefaultBranch } from '../../shared/git.js';
|
|
16
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
17
|
+
import { safePath as defaultSafePath, validatePathSecurity as defaultValidatePathSecurity } from '../../shared/paths.js';
|
|
18
|
+
import { VizzlyError } from '../../shared/vizzly-error.js';
|
|
19
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
20
|
+
import { baselineExists as defaultBaselineExists, clearBaselineData as defaultClearBaselineData, clearRunData as defaultClearRunData, getBaselinePath as defaultGetBaselinePath, getCurrentPath as defaultGetCurrentPath, getDiffPath as defaultGetDiffPath, initializeDirectories as defaultInitializeDirectories, saveBaseline as defaultSaveBaseline, saveCurrent as defaultSaveCurrent } from '../baselines/baseline.service.js';
|
|
21
|
+
import { installBaselineBuild } from '../baselines/baseline-download.js';
|
|
22
|
+
import { createEmptyBaselineMetadata as defaultCreateEmptyBaselineMetadata, loadBaselineMetadata as defaultLoadBaselineMetadata, saveBaselineMetadata as defaultSaveBaselineMetadata, upsertScreenshotInMetadata as defaultUpsertScreenshotInMetadata } from '../baselines/baseline-metadata.js';
|
|
23
|
+
import { writeBaselineScreenshot } from '../baselines/baseline-write.js';
|
|
24
|
+
import { getTddBaselines as defaultGetTddBaselines } from '../baselines/cloud-baselines.js';
|
|
25
|
+
import { getBuilds as defaultGetBuilds } from '../builds/build.service.js';
|
|
26
|
+
import { getComparison as defaultGetComparison } from '../comparisons/cloud-comparisons.js';
|
|
27
|
+
import { buildErrorComparison as defaultBuildErrorComparison, buildFailedComparison as defaultBuildFailedComparison, buildNewComparison as defaultBuildNewComparison, buildPassedComparison as defaultBuildPassedComparison, compareImages as defaultCompareImages, isDimensionMismatchError as defaultIsDimensionMismatchError } from '../comparisons/comparison.service.js';
|
|
28
|
+
import { buildResults as defaultBuildResults, getFailedComparisons as defaultGetFailedComparisons, getNewComparisons as defaultGetNewComparisons } from '../comparisons/comparison-results.js';
|
|
29
|
+
import { sanitizeScreenshotName as defaultSanitizeScreenshotName, validateScreenshotProperties as defaultValidateScreenshotProperties } from '../screenshots/screenshot.validations.js';
|
|
20
30
|
// Import from extracted modules
|
|
21
|
-
import { generateBaselineFilename as defaultGenerateBaselineFilename, generateComparisonId as defaultGenerateComparisonId, generateScreenshotSignature as defaultGenerateScreenshotSignature } from '
|
|
22
|
-
import { createEmptyBaselineMetadata as defaultCreateEmptyBaselineMetadata, loadBaselineMetadata as defaultLoadBaselineMetadata, saveBaselineMetadata as defaultSaveBaselineMetadata, upsertScreenshotInMetadata as defaultUpsertScreenshotInMetadata } from './metadata/baseline-metadata.js';
|
|
31
|
+
import { generateBaselineFilename as defaultGenerateBaselineFilename, generateComparisonId as defaultGenerateComparisonId, generateScreenshotSignature as defaultGenerateScreenshotSignature } from '../screenshots/screenshot-identity.js';
|
|
23
32
|
import { buildTddDependencyOps, resolveTddPaths, resolveTddWorkingDirectory } from './runtime-context.js';
|
|
24
|
-
import { baselineExists as defaultBaselineExists, clearBaselineData as defaultClearBaselineData, clearRunData as defaultClearRunData, getBaselinePath as defaultGetBaselinePath, getCurrentPath as defaultGetCurrentPath, getDiffPath as defaultGetDiffPath, initializeDirectories as defaultInitializeDirectories, saveBaseline as defaultSaveBaseline, saveCurrent as defaultSaveCurrent } from './services/baseline-manager.js';
|
|
25
|
-
import { buildErrorComparison as defaultBuildErrorComparison, buildFailedComparison as defaultBuildFailedComparison, buildNewComparison as defaultBuildNewComparison, buildPassedComparison as defaultBuildPassedComparison, compareImages as defaultCompareImages, isDimensionMismatchError as defaultIsDimensionMismatchError } from './services/comparison-service.js';
|
|
26
|
-
import { buildResults as defaultBuildResults, getFailedComparisons as defaultGetFailedComparisons, getNewComparisons as defaultGetNewComparisons } from './services/result-service.js';
|
|
27
33
|
|
|
28
34
|
/**
|
|
29
35
|
* Create a new TDD service instance
|
|
@@ -150,20 +156,13 @@ export class TddService {
|
|
|
150
156
|
// Destructure dependencies
|
|
151
157
|
let {
|
|
152
158
|
output,
|
|
153
|
-
colors,
|
|
154
159
|
getDefaultBranch,
|
|
155
160
|
getTddBaselines,
|
|
156
161
|
getBuilds,
|
|
157
162
|
getComparison,
|
|
158
163
|
clearBaselineData,
|
|
159
164
|
generateScreenshotSignature,
|
|
160
|
-
generateBaselineFilename
|
|
161
|
-
sanitizeScreenshotName,
|
|
162
|
-
safePath,
|
|
163
|
-
existsSync,
|
|
164
|
-
fetchWithTimeout,
|
|
165
|
-
writeFileSync,
|
|
166
|
-
saveBaselineMetadata
|
|
165
|
+
generateBaselineFilename
|
|
167
166
|
} = this._deps;
|
|
168
167
|
|
|
169
168
|
// If no branch specified, detect default branch
|
|
@@ -281,181 +280,7 @@ export class TddService {
|
|
|
281
280
|
baselineBuild = apiResponse.build;
|
|
282
281
|
baselineBuild.screenshots = apiResponse.screenshots;
|
|
283
282
|
}
|
|
284
|
-
|
|
285
|
-
if (!buildDetails.screenshots || buildDetails.screenshots.length === 0) {
|
|
286
|
-
output.warn('No screenshots found in baseline build');
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
|
-
output.info(`Using baseline from build: ${colors.info(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
|
|
290
|
-
output.info(`Checking ${colors.info(buildDetails.screenshots.length)} baseline screenshots...`);
|
|
291
|
-
|
|
292
|
-
// Check existing baseline metadata for SHA comparison
|
|
293
|
-
let existingBaseline = await this.loadBaseline();
|
|
294
|
-
let existingShaMap = new Map();
|
|
295
|
-
if (existingBaseline) {
|
|
296
|
-
existingBaseline.screenshots.forEach(s => {
|
|
297
|
-
if (s.sha256 && s.filename) {
|
|
298
|
-
existingShaMap.set(s.filename, s.sha256);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Download screenshots
|
|
304
|
-
let downloadedCount = 0;
|
|
305
|
-
let skippedCount = 0;
|
|
306
|
-
let errorCount = 0;
|
|
307
|
-
let batchSize = 5;
|
|
308
|
-
let screenshotsToProcess = [];
|
|
309
|
-
for (let screenshot of buildDetails.screenshots) {
|
|
310
|
-
let sanitizedName;
|
|
311
|
-
try {
|
|
312
|
-
sanitizedName = sanitizeScreenshotName(screenshot.name);
|
|
313
|
-
} catch (error) {
|
|
314
|
-
output.warn(`Skipping screenshot with invalid name '${screenshot.name}': ${error.message}`);
|
|
315
|
-
errorCount++;
|
|
316
|
-
continue;
|
|
317
|
-
}
|
|
318
|
-
let filename = screenshot.filename;
|
|
319
|
-
if (!filename) {
|
|
320
|
-
output.warn(`Screenshot ${sanitizedName} has no filename from API - skipping`);
|
|
321
|
-
errorCount++;
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
let imagePath = safePath(this.baselinePath, filename);
|
|
325
|
-
|
|
326
|
-
// Check SHA
|
|
327
|
-
if (existsSync(imagePath) && screenshot.sha256) {
|
|
328
|
-
let storedSha = existingShaMap.get(filename);
|
|
329
|
-
if (storedSha === screenshot.sha256) {
|
|
330
|
-
downloadedCount++;
|
|
331
|
-
skippedCount++;
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
let downloadUrl = screenshot.original_url || screenshot.url;
|
|
336
|
-
if (!downloadUrl) {
|
|
337
|
-
output.warn(`Screenshot ${sanitizedName} has no download URL - skipping`);
|
|
338
|
-
errorCount++;
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
screenshotsToProcess.push({
|
|
342
|
-
screenshot,
|
|
343
|
-
sanitizedName,
|
|
344
|
-
imagePath,
|
|
345
|
-
downloadUrl,
|
|
346
|
-
filename
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Process downloads in batches
|
|
351
|
-
if (screenshotsToProcess.length > 0) {
|
|
352
|
-
output.info(`📥 Downloading ${screenshotsToProcess.length} new/updated screenshots...`);
|
|
353
|
-
for (let i = 0; i < screenshotsToProcess.length; i += batchSize) {
|
|
354
|
-
let batch = screenshotsToProcess.slice(i, i + batchSize);
|
|
355
|
-
let batchNum = Math.floor(i / batchSize) + 1;
|
|
356
|
-
let totalBatches = Math.ceil(screenshotsToProcess.length / batchSize);
|
|
357
|
-
output.info(`📦 Processing batch ${batchNum}/${totalBatches}`);
|
|
358
|
-
let downloadPromises = batch.map(async ({
|
|
359
|
-
sanitizedName,
|
|
360
|
-
imagePath,
|
|
361
|
-
downloadUrl
|
|
362
|
-
}) => {
|
|
363
|
-
try {
|
|
364
|
-
let response = await fetchWithTimeout(downloadUrl);
|
|
365
|
-
if (!response.ok) {
|
|
366
|
-
throw new NetworkError(`Failed to download ${sanitizedName}: ${response.statusText}`);
|
|
367
|
-
}
|
|
368
|
-
let arrayBuffer = await response.arrayBuffer();
|
|
369
|
-
let imageBuffer = Buffer.from(arrayBuffer);
|
|
370
|
-
writeFileSync(imagePath, imageBuffer);
|
|
371
|
-
return {
|
|
372
|
-
success: true,
|
|
373
|
-
name: sanitizedName
|
|
374
|
-
};
|
|
375
|
-
} catch (error) {
|
|
376
|
-
output.warn(`Failed to download ${sanitizedName}: ${error.message}`);
|
|
377
|
-
return {
|
|
378
|
-
success: false,
|
|
379
|
-
name: sanitizedName,
|
|
380
|
-
error: error.message
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
let batchResults = await Promise.all(downloadPromises);
|
|
385
|
-
let batchSuccesses = batchResults.filter(r => r.success).length;
|
|
386
|
-
let batchFailures = batchResults.filter(r => !r.success).length;
|
|
387
|
-
downloadedCount += batchSuccesses;
|
|
388
|
-
errorCount += batchFailures;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
if (downloadedCount === 0 && skippedCount === 0) {
|
|
392
|
-
output.error('No screenshots were successfully downloaded');
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// Store baseline metadata
|
|
397
|
-
this.baselineData = {
|
|
398
|
-
buildId: baselineBuild.id,
|
|
399
|
-
buildName: baselineBuild.name,
|
|
400
|
-
environment,
|
|
401
|
-
branch,
|
|
402
|
-
threshold: this.threshold,
|
|
403
|
-
signatureProperties: this.signatureProperties,
|
|
404
|
-
createdAt: new Date().toISOString(),
|
|
405
|
-
buildInfo: {
|
|
406
|
-
commitSha: baselineBuild.commit_sha,
|
|
407
|
-
commitMessage: baselineBuild.commit_message,
|
|
408
|
-
visual_review: baselineBuild.visual_review || null,
|
|
409
|
-
completedAt: baselineBuild.completed_at
|
|
410
|
-
},
|
|
411
|
-
screenshots: buildDetails.screenshots.filter(s => s.filename).map(s => ({
|
|
412
|
-
name: sanitizeScreenshotName(s.name),
|
|
413
|
-
originalName: s.name,
|
|
414
|
-
sha256: s.sha256,
|
|
415
|
-
id: s.id,
|
|
416
|
-
filename: s.filename,
|
|
417
|
-
path: safePath(this.baselinePath, s.filename),
|
|
418
|
-
browser: s.browser,
|
|
419
|
-
viewport_width: s.viewport_width,
|
|
420
|
-
originalUrl: s.original_url,
|
|
421
|
-
fileSize: s.file_size_bytes,
|
|
422
|
-
dimensions: {
|
|
423
|
-
width: s.width,
|
|
424
|
-
height: s.height
|
|
425
|
-
}
|
|
426
|
-
}))
|
|
427
|
-
};
|
|
428
|
-
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
429
|
-
|
|
430
|
-
// Save baseline build metadata for MCP plugin
|
|
431
|
-
let baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
432
|
-
writeFileSync(baselineMetadataPath, JSON.stringify({
|
|
433
|
-
buildId: baselineBuild.id,
|
|
434
|
-
buildName: baselineBuild.name,
|
|
435
|
-
branch,
|
|
436
|
-
environment,
|
|
437
|
-
commitSha: baselineBuild.commit_sha,
|
|
438
|
-
commitMessage: baselineBuild.commit_message,
|
|
439
|
-
visual_review: baselineBuild.visual_review || null,
|
|
440
|
-
completedAt: baselineBuild.completed_at,
|
|
441
|
-
downloadedAt: new Date().toISOString()
|
|
442
|
-
}, null, 2));
|
|
443
|
-
|
|
444
|
-
// Summary
|
|
445
|
-
let actualDownloads = downloadedCount - skippedCount;
|
|
446
|
-
if (skippedCount > 0) {
|
|
447
|
-
if (actualDownloads === 0) {
|
|
448
|
-
output.info(`All ${skippedCount} baselines up-to-date`);
|
|
449
|
-
} else {
|
|
450
|
-
output.info(`Downloaded ${actualDownloads} new screenshots, ${skippedCount} already up-to-date`);
|
|
451
|
-
}
|
|
452
|
-
} else {
|
|
453
|
-
output.info(`Downloaded ${downloadedCount}/${buildDetails.screenshots.length} screenshots successfully`);
|
|
454
|
-
}
|
|
455
|
-
if (errorCount > 0) {
|
|
456
|
-
output.warn(`${errorCount} screenshots failed to download`);
|
|
457
|
-
}
|
|
458
|
-
return this.baselineData;
|
|
283
|
+
return await this._installBaselineBuild(baselineBuild, environment, branch);
|
|
459
284
|
} catch (error) {
|
|
460
285
|
output.error(`Failed to download baseline: ${error.message}`);
|
|
461
286
|
throw error;
|
|
@@ -473,14 +298,7 @@ export class TddService {
|
|
|
473
298
|
// Destructure dependencies
|
|
474
299
|
let {
|
|
475
300
|
output,
|
|
476
|
-
|
|
477
|
-
clearBaselineData,
|
|
478
|
-
sanitizeScreenshotName,
|
|
479
|
-
safePath,
|
|
480
|
-
existsSync,
|
|
481
|
-
fetchWithTimeout,
|
|
482
|
-
writeFileSync,
|
|
483
|
-
saveBaselineMetadata
|
|
301
|
+
clearBaselineData
|
|
484
302
|
} = this._deps;
|
|
485
303
|
|
|
486
304
|
// Clear local state before downloading
|
|
@@ -506,181 +324,47 @@ export class TddService {
|
|
|
506
324
|
output.warn(`Build ${buildId} has status: ${baselineBuild.status} (expected: completed)`);
|
|
507
325
|
}
|
|
508
326
|
baselineBuild.screenshots = apiResponse.screenshots;
|
|
509
|
-
|
|
510
|
-
|
|
327
|
+
return this._installBaselineBuild(baselineBuild, 'test', null);
|
|
328
|
+
}
|
|
329
|
+
async _installBaselineBuild(baselineBuild, environment, branch) {
|
|
330
|
+
let {
|
|
331
|
+
output,
|
|
332
|
+
colors,
|
|
333
|
+
fetchWithTimeout,
|
|
334
|
+
existsSync,
|
|
335
|
+
writeFileSync,
|
|
336
|
+
safePath,
|
|
337
|
+
sanitizeScreenshotName,
|
|
338
|
+
saveBaselineMetadata
|
|
339
|
+
} = this._deps;
|
|
340
|
+
if (!baselineBuild.screenshots || baselineBuild.screenshots.length === 0) {
|
|
511
341
|
output.warn('No screenshots found in baseline build');
|
|
512
342
|
return null;
|
|
513
343
|
}
|
|
514
344
|
output.info(`Using baseline from build: ${colors.info(baselineBuild.name || 'Unknown')} (${baselineBuild.id || 'Unknown ID'})`);
|
|
515
|
-
output.info(`Checking ${colors.info(
|
|
516
|
-
|
|
517
|
-
// Check existing baseline metadata for SHA comparison
|
|
345
|
+
output.info(`Checking ${colors.info(baselineBuild.screenshots.length)} baseline screenshots...`);
|
|
518
346
|
let existingBaseline = await this.loadBaseline();
|
|
519
|
-
let
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
existingShaMap.set(s.filename, s.sha256);
|
|
524
|
-
}
|
|
525
|
-
});
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
// Download screenshots
|
|
529
|
-
let downloadedCount = 0;
|
|
530
|
-
let skippedCount = 0;
|
|
531
|
-
let errorCount = 0;
|
|
532
|
-
let batchSize = 5;
|
|
533
|
-
let screenshotsToProcess = [];
|
|
534
|
-
for (let screenshot of buildDetails.screenshots) {
|
|
535
|
-
let sanitizedName;
|
|
536
|
-
try {
|
|
537
|
-
sanitizedName = sanitizeScreenshotName(screenshot.name);
|
|
538
|
-
} catch (error) {
|
|
539
|
-
output.warn(`Skipping screenshot with invalid name '${screenshot.name}': ${error.message}`);
|
|
540
|
-
errorCount++;
|
|
541
|
-
continue;
|
|
542
|
-
}
|
|
543
|
-
let filename = screenshot.filename;
|
|
544
|
-
if (!filename) {
|
|
545
|
-
output.warn(`Screenshot ${sanitizedName} has no filename from API - skipping`);
|
|
546
|
-
errorCount++;
|
|
547
|
-
continue;
|
|
548
|
-
}
|
|
549
|
-
let imagePath = safePath(this.baselinePath, filename);
|
|
550
|
-
|
|
551
|
-
// Check SHA
|
|
552
|
-
if (existsSync(imagePath) && screenshot.sha256) {
|
|
553
|
-
let storedSha = existingShaMap.get(filename);
|
|
554
|
-
if (storedSha === screenshot.sha256) {
|
|
555
|
-
downloadedCount++;
|
|
556
|
-
skippedCount++;
|
|
557
|
-
continue;
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
let downloadUrl = screenshot.original_url || screenshot.url;
|
|
561
|
-
if (!downloadUrl) {
|
|
562
|
-
output.warn(`Screenshot ${sanitizedName} has no download URL - skipping`);
|
|
563
|
-
errorCount++;
|
|
564
|
-
continue;
|
|
565
|
-
}
|
|
566
|
-
screenshotsToProcess.push({
|
|
567
|
-
screenshot,
|
|
568
|
-
sanitizedName,
|
|
569
|
-
imagePath,
|
|
570
|
-
downloadUrl,
|
|
571
|
-
filename
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
// Process downloads in batches
|
|
576
|
-
if (screenshotsToProcess.length > 0) {
|
|
577
|
-
output.info(`📥 Downloading ${screenshotsToProcess.length} new/updated screenshots...`);
|
|
578
|
-
for (let i = 0; i < screenshotsToProcess.length; i += batchSize) {
|
|
579
|
-
let batch = screenshotsToProcess.slice(i, i + batchSize);
|
|
580
|
-
let batchNum = Math.floor(i / batchSize) + 1;
|
|
581
|
-
let totalBatches = Math.ceil(screenshotsToProcess.length / batchSize);
|
|
582
|
-
output.info(`📦 Processing batch ${batchNum}/${totalBatches}`);
|
|
583
|
-
let downloadPromises = batch.map(async ({
|
|
584
|
-
sanitizedName,
|
|
585
|
-
imagePath,
|
|
586
|
-
downloadUrl
|
|
587
|
-
}) => {
|
|
588
|
-
try {
|
|
589
|
-
let response = await fetchWithTimeout(downloadUrl);
|
|
590
|
-
if (!response.ok) {
|
|
591
|
-
throw new Error(`Failed to download ${sanitizedName}: ${response.statusText}`);
|
|
592
|
-
}
|
|
593
|
-
let arrayBuffer = await response.arrayBuffer();
|
|
594
|
-
let imageBuffer = Buffer.from(arrayBuffer);
|
|
595
|
-
writeFileSync(imagePath, imageBuffer);
|
|
596
|
-
return {
|
|
597
|
-
success: true,
|
|
598
|
-
name: sanitizedName
|
|
599
|
-
};
|
|
600
|
-
} catch (error) {
|
|
601
|
-
output.warn(`Failed to download ${sanitizedName}: ${error.message}`);
|
|
602
|
-
return {
|
|
603
|
-
success: false,
|
|
604
|
-
name: sanitizedName,
|
|
605
|
-
error: error.message
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
});
|
|
609
|
-
let batchResults = await Promise.all(downloadPromises);
|
|
610
|
-
let batchSuccesses = batchResults.filter(r => r.success).length;
|
|
611
|
-
let batchFailures = batchResults.filter(r => !r.success).length;
|
|
612
|
-
downloadedCount += batchSuccesses;
|
|
613
|
-
errorCount += batchFailures;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
if (downloadedCount === 0 && skippedCount === 0) {
|
|
617
|
-
output.error('No screenshots were successfully downloaded');
|
|
618
|
-
return null;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// Store baseline metadata
|
|
622
|
-
this.baselineData = {
|
|
623
|
-
buildId: baselineBuild.id,
|
|
624
|
-
buildName: baselineBuild.name,
|
|
625
|
-
environment: 'test',
|
|
626
|
-
branch: null,
|
|
347
|
+
let installedBaseline = await installBaselineBuild({
|
|
348
|
+
baselineBuild,
|
|
349
|
+
baselinePath: this.baselinePath,
|
|
350
|
+
workingDir: this.workingDir,
|
|
627
351
|
threshold: this.threshold,
|
|
628
352
|
signatureProperties: this.signatureProperties,
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
id: s.id,
|
|
641
|
-
filename: s.filename,
|
|
642
|
-
path: safePath(this.baselinePath, s.filename),
|
|
643
|
-
browser: s.browser,
|
|
644
|
-
viewport_width: s.viewport_width,
|
|
645
|
-
originalUrl: s.original_url,
|
|
646
|
-
fileSize: s.file_size_bytes,
|
|
647
|
-
dimensions: {
|
|
648
|
-
width: s.width,
|
|
649
|
-
height: s.height
|
|
650
|
-
}
|
|
651
|
-
}))
|
|
652
|
-
};
|
|
653
|
-
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
654
|
-
|
|
655
|
-
// Save baseline build metadata for MCP plugin
|
|
656
|
-
let baselineMetadataPath = safePath(this.workingDir, '.vizzly', 'baseline-metadata.json');
|
|
657
|
-
writeFileSync(baselineMetadataPath, JSON.stringify({
|
|
658
|
-
buildId: baselineBuild.id,
|
|
659
|
-
buildName: baselineBuild.name,
|
|
660
|
-
branch: null,
|
|
661
|
-
environment: 'test',
|
|
662
|
-
commitSha: baselineBuild.commit_sha,
|
|
663
|
-
commitMessage: baselineBuild.commit_message,
|
|
664
|
-
visual_review: baselineBuild.visual_review || null,
|
|
665
|
-
completedAt: baselineBuild.completed_at,
|
|
666
|
-
downloadedAt: new Date().toISOString()
|
|
667
|
-
}, null, 2));
|
|
668
|
-
|
|
669
|
-
// Summary
|
|
670
|
-
let actualDownloads = downloadedCount - skippedCount;
|
|
671
|
-
if (skippedCount > 0) {
|
|
672
|
-
if (actualDownloads === 0) {
|
|
673
|
-
output.info(`All ${skippedCount} baselines up-to-date`);
|
|
674
|
-
} else {
|
|
675
|
-
output.info(`Downloaded ${actualDownloads} new screenshots, ${skippedCount} already up-to-date`);
|
|
353
|
+
existingBaseline,
|
|
354
|
+
environment,
|
|
355
|
+
branch,
|
|
356
|
+
output,
|
|
357
|
+
fetchWithTimeout,
|
|
358
|
+
operations: {
|
|
359
|
+
existsSync,
|
|
360
|
+
writeFileSync,
|
|
361
|
+
safePath,
|
|
362
|
+
sanitizeScreenshotName,
|
|
363
|
+
saveBaselineMetadata
|
|
676
364
|
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (errorCount > 0) {
|
|
681
|
-
output.warn(`${errorCount} screenshots failed to download`);
|
|
682
|
-
}
|
|
683
|
-
return this.baselineData;
|
|
365
|
+
});
|
|
366
|
+
if (installedBaseline) this.baselineData = installedBaseline;
|
|
367
|
+
return installedBaseline;
|
|
684
368
|
}
|
|
685
369
|
|
|
686
370
|
/**
|
|
@@ -1079,6 +763,26 @@ export class TddService {
|
|
|
1079
763
|
}
|
|
1080
764
|
return results;
|
|
1081
765
|
}
|
|
766
|
+
_writeBaselineScreenshot(screenshot, imageBuffer) {
|
|
767
|
+
let {
|
|
768
|
+
writeFileSync,
|
|
769
|
+
createEmptyBaselineMetadata,
|
|
770
|
+
upsertScreenshotInMetadata
|
|
771
|
+
} = this._deps;
|
|
772
|
+
this.baselineData = writeBaselineScreenshot({
|
|
773
|
+
imageBuffer,
|
|
774
|
+
screenshot,
|
|
775
|
+
metadata: this.baselineData,
|
|
776
|
+
baselineOptions: {
|
|
777
|
+
threshold: this.threshold,
|
|
778
|
+
signatureProperties: this.signatureProperties
|
|
779
|
+
}
|
|
780
|
+
}, {
|
|
781
|
+
writeFileSync,
|
|
782
|
+
createEmptyBaselineMetadata,
|
|
783
|
+
upsertScreenshotInMetadata
|
|
784
|
+
});
|
|
785
|
+
}
|
|
1082
786
|
|
|
1083
787
|
/**
|
|
1084
788
|
* Update all baselines with current screenshots
|
|
@@ -1094,9 +798,7 @@ export class TddService {
|
|
|
1094
798
|
getBaselinePath,
|
|
1095
799
|
existsSync,
|
|
1096
800
|
readFileSync,
|
|
1097
|
-
writeFileSync,
|
|
1098
801
|
createEmptyBaselineMetadata,
|
|
1099
|
-
upsertScreenshotInMetadata,
|
|
1100
802
|
saveBaselineMetadata
|
|
1101
803
|
} = this._deps;
|
|
1102
804
|
if (this.comparisons.length === 0) {
|
|
@@ -1132,14 +834,12 @@ export class TddService {
|
|
|
1132
834
|
let baselineImagePath = getBaselinePath(this.baselinePath, filename);
|
|
1133
835
|
try {
|
|
1134
836
|
let currentBuffer = readFileSync(current);
|
|
1135
|
-
|
|
1136
|
-
let screenshotEntry = {
|
|
837
|
+
this._writeBaselineScreenshot({
|
|
1137
838
|
name: sanitizedName,
|
|
1138
839
|
properties: validatedProperties,
|
|
1139
840
|
path: baselineImagePath,
|
|
1140
841
|
signature
|
|
1141
|
-
};
|
|
1142
|
-
upsertScreenshotInMetadata(this.baselineData, screenshotEntry, signature);
|
|
842
|
+
}, currentBuffer);
|
|
1143
843
|
updatedCount++;
|
|
1144
844
|
output.info(`Updated baseline for ${sanitizedName}`);
|
|
1145
845
|
} catch (error) {
|
|
@@ -1171,9 +871,6 @@ export class TddService {
|
|
|
1171
871
|
existsSync,
|
|
1172
872
|
readFileSync,
|
|
1173
873
|
mkdirSync,
|
|
1174
|
-
writeFileSync,
|
|
1175
|
-
createEmptyBaselineMetadata,
|
|
1176
|
-
upsertScreenshotInMetadata,
|
|
1177
874
|
saveBaselineMetadata
|
|
1178
875
|
} = this._deps;
|
|
1179
876
|
let comparison;
|
|
@@ -1219,22 +916,12 @@ export class TddService {
|
|
|
1219
916
|
|
|
1220
917
|
// Update the baseline (filename already includes .png extension)
|
|
1221
918
|
let baselineImagePath = safePath(this.baselinePath, filename);
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
// Update baseline metadata
|
|
1225
|
-
if (!this.baselineData) {
|
|
1226
|
-
this.baselineData = createEmptyBaselineMetadata({
|
|
1227
|
-
threshold: this.threshold,
|
|
1228
|
-
signatureProperties: this.signatureProperties
|
|
1229
|
-
});
|
|
1230
|
-
}
|
|
1231
|
-
let screenshotEntry = {
|
|
919
|
+
this._writeBaselineScreenshot({
|
|
1232
920
|
name: sanitizedName,
|
|
1233
921
|
properties,
|
|
1234
922
|
path: baselineImagePath,
|
|
1235
923
|
signature
|
|
1236
|
-
};
|
|
1237
|
-
upsertScreenshotInMetadata(this.baselineData, screenshotEntry, signature);
|
|
924
|
+
}, imageBuffer);
|
|
1238
925
|
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
1239
926
|
return {
|
|
1240
927
|
name: sanitizedName,
|
|
@@ -1253,27 +940,16 @@ export class TddService {
|
|
|
1253
940
|
output,
|
|
1254
941
|
generateScreenshotSignature,
|
|
1255
942
|
generateComparisonId,
|
|
1256
|
-
writeFileSync,
|
|
1257
|
-
createEmptyBaselineMetadata,
|
|
1258
|
-
upsertScreenshotInMetadata,
|
|
1259
943
|
saveBaselineMetadata
|
|
1260
944
|
} = this._deps;
|
|
1261
945
|
output.info(`Creating baseline for ${name}`);
|
|
1262
|
-
writeFileSync(baselineImagePath, imageBuffer);
|
|
1263
|
-
if (!this.baselineData) {
|
|
1264
|
-
this.baselineData = createEmptyBaselineMetadata({
|
|
1265
|
-
threshold: this.threshold,
|
|
1266
|
-
signatureProperties: this.signatureProperties
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
946
|
let signature = generateScreenshotSignature(name, properties || {}, this.signatureProperties);
|
|
1270
|
-
|
|
947
|
+
this._writeBaselineScreenshot({
|
|
1271
948
|
name,
|
|
1272
949
|
properties: properties || {},
|
|
1273
950
|
path: baselineImagePath,
|
|
1274
951
|
signature
|
|
1275
|
-
};
|
|
1276
|
-
upsertScreenshotInMetadata(this.baselineData, screenshotEntry, signature);
|
|
952
|
+
}, imageBuffer);
|
|
1277
953
|
saveBaselineMetadata(this.baselinePath, this.baselineData);
|
|
1278
954
|
let result = {
|
|
1279
955
|
id: generateComparisonId(signature),
|