@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.
- package/README.md +8 -0
- package/bin/vizzly.js +1 -1
- package/dist/{cli.js → adapters/cli/cli.js} +41 -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} +4 -5
- package/dist/{commands/init.js → domains/setup/init.commands.js} +10 -41
- 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/{utils → domains/updates}/cli-update.js +4 -6
- package/dist/{utils → domains/updates}/update-notifications.js +7 -7
- 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 +2 -2
- 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 +23 -22
- 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/{utils → domains/updates}/update-check.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
|
@@ -2,119 +2,15 @@ import { Buffer as defaultBuffer } from 'node:buffer';
|
|
|
2
2
|
import { existsSync as defaultExistsSync, readFileSync as defaultReadFileSync, unlinkSync as defaultUnlinkSync, writeFileSync as defaultWriteFileSync } from 'node:fs';
|
|
3
3
|
import { join as defaultJoin, resolve as defaultResolve } from 'node:path';
|
|
4
4
|
import { getDimensionsSync as defaultGetDimensionsSync } from '@vizzly-testing/honeydiff';
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
export const extractProperties = (validatedProperties, imageDimensions = {}) => {
|
|
15
|
-
if (!validatedProperties) return {};
|
|
16
|
-
return {
|
|
17
|
-
...validatedProperties,
|
|
18
|
-
viewport_width: imageDimensions.width ?? null,
|
|
19
|
-
viewport_height: imageDimensions.height ?? null,
|
|
20
|
-
browser: validatedProperties.browser ?? null,
|
|
21
|
-
metadata: validatedProperties
|
|
22
|
-
};
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Convert absolute file paths to web-accessible URLs
|
|
27
|
-
*/
|
|
28
|
-
export const convertPathToUrl = (filePath, vizzlyDir) => {
|
|
29
|
-
if (!filePath) return null;
|
|
30
|
-
// Convert absolute path to relative path from .vizzly directory
|
|
31
|
-
if (filePath.startsWith(vizzlyDir)) {
|
|
32
|
-
let relativePath = filePath.substring(vizzlyDir.length + 1);
|
|
33
|
-
return `/images/${relativePath}`;
|
|
34
|
-
}
|
|
35
|
-
return filePath;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Group comparisons by screenshot name with variant structure
|
|
40
|
-
* Matches cloud product's grouping logic from comparison.js
|
|
41
|
-
*/
|
|
42
|
-
export const groupComparisons = comparisons => {
|
|
43
|
-
const groups = new Map();
|
|
44
|
-
|
|
45
|
-
// Group by screenshot name
|
|
46
|
-
for (const comp of comparisons) {
|
|
47
|
-
if (!groups.has(comp.name)) {
|
|
48
|
-
groups.set(comp.name, {
|
|
49
|
-
name: comp.name,
|
|
50
|
-
comparisons: [],
|
|
51
|
-
browsers: new Set(),
|
|
52
|
-
viewports: new Set(),
|
|
53
|
-
devices: new Set(),
|
|
54
|
-
totalVariants: 0
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
const group = groups.get(comp.name);
|
|
58
|
-
group.comparisons.push(comp);
|
|
59
|
-
group.totalVariants++;
|
|
60
|
-
|
|
61
|
-
// Track unique browsers, viewports, devices
|
|
62
|
-
if (comp.properties?.browser) {
|
|
63
|
-
group.browsers.add(comp.properties.browser);
|
|
64
|
-
}
|
|
65
|
-
if (comp.properties?.viewport_width && comp.properties?.viewport_height) {
|
|
66
|
-
group.viewports.add(`${comp.properties.viewport_width}x${comp.properties.viewport_height}`);
|
|
67
|
-
}
|
|
68
|
-
if (comp.properties?.device) {
|
|
69
|
-
group.devices.add(comp.properties.device);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Convert to final structure
|
|
74
|
-
return Array.from(groups.values()).map(group => {
|
|
75
|
-
const browsers = Array.from(group.browsers);
|
|
76
|
-
const viewports = Array.from(group.viewports);
|
|
77
|
-
const devices = Array.from(group.devices);
|
|
78
|
-
|
|
79
|
-
// Build variants structure (browser -> viewport -> comparisons)
|
|
80
|
-
const variants = {};
|
|
81
|
-
group.comparisons.forEach(comp => {
|
|
82
|
-
const browser = comp.properties?.browser || null;
|
|
83
|
-
const viewport = comp.properties?.viewport_width && comp.properties?.viewport_height ? `${comp.properties.viewport_width}x${comp.properties.viewport_height}` : null;
|
|
84
|
-
if (!variants[browser]) variants[browser] = {};
|
|
85
|
-
if (!variants[browser][viewport]) variants[browser][viewport] = [];
|
|
86
|
-
variants[browser][viewport].push(comp);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// Determine grouping strategy
|
|
90
|
-
let groupingStrategy = 'flat';
|
|
91
|
-
if (browsers.length > 1) groupingStrategy = 'browser';else if (viewports.length > 1) groupingStrategy = 'viewport';
|
|
92
|
-
|
|
93
|
-
// Sort comparisons by viewport area (largest first)
|
|
94
|
-
group.comparisons.sort((a, b) => {
|
|
95
|
-
const aArea = (a.properties?.viewport_width || 0) * (a.properties?.viewport_height || 0);
|
|
96
|
-
const bArea = (b.properties?.viewport_width || 0) * (b.properties?.viewport_height || 0);
|
|
97
|
-
if (bArea !== aArea) return bArea - aArea;
|
|
98
|
-
return (b.properties?.viewport_width || 0) - (a.properties?.viewport_width || 0);
|
|
99
|
-
});
|
|
100
|
-
return {
|
|
101
|
-
...group,
|
|
102
|
-
browsers,
|
|
103
|
-
viewports,
|
|
104
|
-
devices: Array.from(devices),
|
|
105
|
-
variants,
|
|
106
|
-
groupingStrategy
|
|
107
|
-
};
|
|
108
|
-
}).sort((a, b) => {
|
|
109
|
-
// Sort groups: multi-variant first (by variant count), then singles alphabetically
|
|
110
|
-
if (a.totalVariants > 1 && b.totalVariants === 1) return -1;
|
|
111
|
-
if (a.totalVariants === 1 && b.totalVariants > 1) return 1;
|
|
112
|
-
if (a.totalVariants > 1 && b.totalVariants > 1) {
|
|
113
|
-
return b.totalVariants - a.totalVariants;
|
|
114
|
-
}
|
|
115
|
-
return a.name.localeCompare(b.name);
|
|
116
|
-
});
|
|
117
|
-
};
|
|
5
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
6
|
+
import { safePath as defaultSafePath } from '../../shared/paths.js';
|
|
7
|
+
import { convertPathToUrl } from '../reports/report.serializers.js';
|
|
8
|
+
import { createEmptyReport, removeReportComparison, upsertReportComparison } from '../reports/report-state.js';
|
|
9
|
+
import { createReportStore } from '../reports/report-store.js';
|
|
10
|
+
import { detectImageInputType as defaultDetectImageInputType } from '../screenshots/image-input.js';
|
|
11
|
+
import { extractProperties } from '../screenshots/screenshot.normalizers.js';
|
|
12
|
+
import { sanitizeScreenshotName as defaultSanitizeScreenshotName, validateScreenshotProperties as defaultValidateScreenshotProperties } from '../screenshots/screenshot.validations.js';
|
|
13
|
+
import { TddService as DefaultTddService } from './tdd.service.js';
|
|
118
14
|
export const createTddHandler = (config, workingDir, baselineBuild, baselineComparison, setBaseline = false, deps = {}) => {
|
|
119
15
|
// Inject dependencies with defaults
|
|
120
16
|
let {
|
|
@@ -133,133 +29,45 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
133
29
|
validateScreenshotProperties = defaultValidateScreenshotProperties,
|
|
134
30
|
output = defaultOutput
|
|
135
31
|
} = deps;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
32
|
+
let tddService = new TddService(config, workingDir, setBaseline);
|
|
33
|
+
let reportStore = createReportStore(workingDir, {
|
|
34
|
+
existsSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
writeFileSync,
|
|
37
|
+
unlinkSync,
|
|
38
|
+
join,
|
|
39
|
+
output
|
|
40
|
+
});
|
|
139
41
|
let capture = {
|
|
140
42
|
accepted: 0,
|
|
141
43
|
rejected: 0
|
|
142
44
|
};
|
|
143
|
-
let
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
};
|
|
45
|
+
let {
|
|
46
|
+
updateDetails: updateComparisonDetails,
|
|
47
|
+
removeDetails: removeComparisonDetails
|
|
48
|
+
} = reportStore;
|
|
148
49
|
let prepareRunArtifacts = () => {
|
|
149
50
|
capture = {
|
|
150
51
|
accepted: 0,
|
|
151
52
|
rejected: 0
|
|
152
53
|
};
|
|
153
|
-
|
|
154
|
-
clearJsonFile(detailsPath);
|
|
54
|
+
reportStore.clear();
|
|
155
55
|
tddService.clearRunData?.();
|
|
156
56
|
};
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Read heavy comparison details from comparison-details.json
|
|
160
|
-
* Returns a map of comparison ID -> heavy fields
|
|
161
|
-
*/
|
|
162
|
-
const readComparisonDetails = () => {
|
|
163
|
-
try {
|
|
164
|
-
if (!existsSync(detailsPath)) return {};
|
|
165
|
-
return JSON.parse(readFileSync(detailsPath, 'utf8'));
|
|
166
|
-
} catch (error) {
|
|
167
|
-
output.debug('Failed to read comparison details:', error);
|
|
168
|
-
return {};
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Persist heavy fields for a comparison to comparison-details.json
|
|
174
|
-
* This file is NOT watched by SSE, so writes here don't trigger broadcasts
|
|
175
|
-
* Skips writing if all heavy fields are empty (passed comparisons)
|
|
176
|
-
*/
|
|
177
|
-
const updateComparisonDetails = (id, heavyFields) => {
|
|
178
|
-
let hasData = Object.values(heavyFields).some(v => v != null && (!Array.isArray(v) || v.length > 0));
|
|
179
|
-
if (!hasData) return;
|
|
180
|
-
let details = readComparisonDetails();
|
|
181
|
-
details[id] = heavyFields;
|
|
182
|
-
writeFileSync(detailsPath, JSON.stringify(details));
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Remove a comparison's heavy fields from comparison-details.json
|
|
187
|
-
*/
|
|
188
|
-
const removeComparisonDetails = id => {
|
|
189
|
-
let details = readComparisonDetails();
|
|
190
|
-
delete details[id];
|
|
191
|
-
writeFileSync(detailsPath, JSON.stringify(details));
|
|
192
|
-
};
|
|
193
|
-
const readReportData = () => {
|
|
57
|
+
let readReportData = () => {
|
|
194
58
|
try {
|
|
195
|
-
|
|
196
|
-
return {
|
|
197
|
-
timestamp: Date.now(),
|
|
198
|
-
comparisons: [],
|
|
199
|
-
summary: {
|
|
200
|
-
total: 0,
|
|
201
|
-
passed: 0,
|
|
202
|
-
failed: 0,
|
|
203
|
-
errors: 0
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
const data = readFileSync(reportPath, 'utf8');
|
|
208
|
-
return JSON.parse(data);
|
|
59
|
+
return reportStore.readReport() || createEmptyReport(Date.now());
|
|
209
60
|
} catch (error) {
|
|
210
61
|
output.error('Failed to read report data:', error);
|
|
211
|
-
return
|
|
212
|
-
timestamp: Date.now(),
|
|
213
|
-
comparisons: [],
|
|
214
|
-
summary: {
|
|
215
|
-
total: 0,
|
|
216
|
-
passed: 0,
|
|
217
|
-
failed: 0,
|
|
218
|
-
errors: 0
|
|
219
|
-
}
|
|
220
|
-
};
|
|
62
|
+
return createEmptyReport(Date.now());
|
|
221
63
|
}
|
|
222
64
|
};
|
|
223
|
-
|
|
65
|
+
let updateComparison = comparison => {
|
|
224
66
|
try {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
// Ensure comparisons array exists (backward compatibility)
|
|
228
|
-
if (!reportData.comparisons) {
|
|
229
|
-
reportData.comparisons = [];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Find existing comparison by unique ID
|
|
233
|
-
// This ensures we update the correct variant even with same name
|
|
234
|
-
const existingIndex = reportData.comparisons.findIndex(c => c.id === newComparison.id);
|
|
235
|
-
if (existingIndex >= 0) {
|
|
236
|
-
// Preserve initialStatus from the original comparison
|
|
237
|
-
// This keeps sort order stable when status changes (e.g., after approval)
|
|
238
|
-
const initialStatus = reportData.comparisons[existingIndex].initialStatus;
|
|
239
|
-
reportData.comparisons[existingIndex] = {
|
|
240
|
-
...newComparison,
|
|
241
|
-
initialStatus: initialStatus || newComparison.status
|
|
242
|
-
};
|
|
243
|
-
} else {
|
|
244
|
-
// New comparison - set initialStatus to current status
|
|
245
|
-
reportData.comparisons.push({
|
|
246
|
-
...newComparison,
|
|
247
|
-
initialStatus: newComparison.status
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Update summary (groups computed client-side from comparisons)
|
|
252
|
-
reportData.timestamp = Date.now();
|
|
253
|
-
reportData.summary = {
|
|
254
|
-
total: reportData.comparisons.length,
|
|
255
|
-
passed: reportData.comparisons.filter(c => c.status === 'passed' || c.status === 'baseline-created' || c.status === 'new').length,
|
|
256
|
-
failed: reportData.comparisons.filter(c => c.status === 'failed').length,
|
|
257
|
-
rejected: reportData.comparisons.filter(c => c.status === 'rejected').length,
|
|
258
|
-
errors: reportData.comparisons.filter(c => c.status === 'error').length
|
|
259
|
-
};
|
|
260
|
-
writeFileSync(reportPath, JSON.stringify(reportData));
|
|
67
|
+
reportStore.writeReport(upsertReportComparison(readReportData(), comparison, Date.now()));
|
|
261
68
|
} catch (error) {
|
|
262
69
|
output.error('Failed to update comparison:', error);
|
|
70
|
+
throw error;
|
|
263
71
|
}
|
|
264
72
|
};
|
|
265
73
|
const initialize = async () => {
|
|
@@ -281,13 +89,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
281
89
|
}
|
|
282
90
|
const baseline = await tddService.loadBaseline();
|
|
283
91
|
if (!baseline) {
|
|
284
|
-
|
|
285
|
-
if ((baselineBuild || baselineComparison) && config.apiKey) {
|
|
286
|
-
output.debug('tdd', 'downloading baselines');
|
|
287
|
-
await tddService.downloadBaselines(config.build?.environment || 'test', config.build?.branch || null, baselineBuild, baselineComparison);
|
|
288
|
-
} else {
|
|
289
|
-
output.debug('tdd', 'no baselines yet');
|
|
290
|
-
}
|
|
92
|
+
output.debug('tdd', 'no baselines yet');
|
|
291
93
|
} else {
|
|
292
94
|
output.debug('tdd', `baseline: ${baseline.buildName}`);
|
|
293
95
|
}
|
|
@@ -637,27 +439,16 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
637
439
|
let deletedBaselines = 0;
|
|
638
440
|
let deletedCurrents = 0;
|
|
639
441
|
let deletedDiffs = 0;
|
|
640
|
-
let deleteResetFile = (imagePath, label, name) => {
|
|
641
|
-
if (!imagePath) return 0;
|
|
642
|
-
try {
|
|
643
|
-
let filePath = safePath(workingDir, '.vizzly', imagePath.replace('/images/', ''));
|
|
644
|
-
if (!existsSync(filePath)) return 0;
|
|
645
|
-
unlinkSync(filePath);
|
|
646
|
-
if (label === 'diff') {
|
|
647
|
-
output.debug(`Deleted diff for ${name}`);
|
|
648
|
-
}
|
|
649
|
-
return 1;
|
|
650
|
-
} catch (error) {
|
|
651
|
-
output.warn(`Failed to delete ${label} for ${name}: ${error.message}`);
|
|
652
|
-
return 0;
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
442
|
|
|
656
443
|
// Delete all baseline, current, and diff images
|
|
657
444
|
for (let comparison of reportData.comparisons) {
|
|
658
|
-
deletedBaselines +=
|
|
659
|
-
|
|
660
|
-
|
|
445
|
+
deletedBaselines += safeDeleteFile(comparison.baseline, 'baseline', comparison.name, {
|
|
446
|
+
logDeletion: false
|
|
447
|
+
});
|
|
448
|
+
deletedCurrents += safeDeleteFile(comparison.current, 'current screenshot', comparison.name, {
|
|
449
|
+
logDeletion: false
|
|
450
|
+
});
|
|
451
|
+
deletedDiffs += safeDeleteFile(comparison.diff, 'diff', comparison.name);
|
|
661
452
|
}
|
|
662
453
|
|
|
663
454
|
// Delete baseline metadata
|
|
@@ -670,24 +461,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
670
461
|
output.warn(`Failed to delete baseline metadata: ${error.message}`);
|
|
671
462
|
}
|
|
672
463
|
}
|
|
673
|
-
|
|
674
|
-
// Clear the report data entirely - fresh start
|
|
675
|
-
const freshReportData = {
|
|
676
|
-
timestamp: Date.now(),
|
|
677
|
-
comparisons: [],
|
|
678
|
-
summary: {
|
|
679
|
-
total: 0,
|
|
680
|
-
passed: 0,
|
|
681
|
-
failed: 0,
|
|
682
|
-
errors: 0
|
|
683
|
-
}
|
|
684
|
-
};
|
|
685
|
-
writeFileSync(reportPath, JSON.stringify(freshReportData));
|
|
686
|
-
|
|
687
|
-
// Clear comparison details
|
|
688
|
-
if (existsSync(detailsPath)) {
|
|
689
|
-
writeFileSync(detailsPath, JSON.stringify({}));
|
|
690
|
-
}
|
|
464
|
+
reportStore.reset();
|
|
691
465
|
output.info(`Baselines reset - ${deletedBaselines} baselines deleted, ${deletedCurrents} current screenshots deleted, ${deletedDiffs} diffs deleted`);
|
|
692
466
|
return {
|
|
693
467
|
success: true,
|
|
@@ -707,19 +481,23 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
707
481
|
* @param {string} label - Label for logging (e.g., "baseline", "current", "diff")
|
|
708
482
|
* @param {string} name - Screenshot name for logging
|
|
709
483
|
*/
|
|
710
|
-
|
|
711
|
-
|
|
484
|
+
let safeDeleteFile = (imagePath, label, name, {
|
|
485
|
+
logDeletion = true
|
|
486
|
+
} = {}) => {
|
|
487
|
+
if (!imagePath) return 0;
|
|
712
488
|
try {
|
|
713
489
|
// Use safePath to validate the path stays within workingDir
|
|
714
490
|
const filePath = safePath(workingDir, '.vizzly', imagePath.replace('/images/', ''));
|
|
715
491
|
if (existsSync(filePath)) {
|
|
716
492
|
unlinkSync(filePath);
|
|
717
|
-
output.debug(`Deleted ${label} for ${name}`);
|
|
493
|
+
if (logDeletion) output.debug(`Deleted ${label} for ${name}`);
|
|
494
|
+
return 1;
|
|
718
495
|
}
|
|
719
496
|
} catch (error) {
|
|
720
497
|
// safePath throws if path traversal is attempted
|
|
721
498
|
output.warn(`Failed to delete ${label} for ${name}: ${error.message}`);
|
|
722
499
|
}
|
|
500
|
+
return 0;
|
|
723
501
|
};
|
|
724
502
|
const deleteComparison = async comparisonId => {
|
|
725
503
|
const reportData = readReportData();
|
|
@@ -755,20 +533,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
755
533
|
|
|
756
534
|
// Remove heavy fields from comparison-details.json
|
|
757
535
|
removeComparisonDetails(comparisonId);
|
|
758
|
-
|
|
759
|
-
// Remove comparison from report data
|
|
760
|
-
reportData.comparisons = reportData.comparisons.filter(c => c.id !== comparisonId);
|
|
761
|
-
|
|
762
|
-
// Regenerate summary (groups computed client-side)
|
|
763
|
-
reportData.timestamp = Date.now();
|
|
764
|
-
reportData.summary = {
|
|
765
|
-
total: reportData.comparisons.length,
|
|
766
|
-
passed: reportData.comparisons.filter(c => c.status === 'passed' || c.status === 'baseline-created' || c.status === 'new').length,
|
|
767
|
-
failed: reportData.comparisons.filter(c => c.status === 'failed').length,
|
|
768
|
-
rejected: reportData.comparisons.filter(c => c.status === 'rejected').length,
|
|
769
|
-
errors: reportData.comparisons.filter(c => c.status === 'error').length
|
|
770
|
-
};
|
|
771
|
-
writeFileSync(reportPath, JSON.stringify(reportData));
|
|
536
|
+
reportStore.writeReport(removeReportComparison(reportData, comparisonId, Date.now()));
|
|
772
537
|
output.info(`Deleted comparison ${comparisonId} (${comparison.name})`);
|
|
773
538
|
return {
|
|
774
539
|
success: true,
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { basename, join } from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import { tddCommand } from './tdd.js';
|
|
8
|
-
import { parsePositiveInteger, readDaemonPidFile, resolveDaemonPid, waitForProcessExit } from './tdd-daemon-process.js';
|
|
9
|
-
export { findDaemonPidByPort, readDaemonPidFile, resolveDaemonPid, waitForProcessExit } from './tdd-daemon-process.js';
|
|
4
|
+
import { withTimeout } from '../../shared/async-utils.js';
|
|
5
|
+
import * as output from '../../shared/output.js';
|
|
6
|
+
import { getServerRegistry } from './server-registry.js';
|
|
7
|
+
import { tddCommand } from './tdd.commands.js';
|
|
8
|
+
import { parsePositiveInteger, readDaemonPidFile, resolveDaemonPid, waitForProcessExit } from './tdd-daemon-process.commands.js';
|
|
9
|
+
export { findDaemonPidByPort, readDaemonPidFile, resolveDaemonPid, waitForProcessExit } from './tdd-daemon-process.commands.js';
|
|
10
10
|
let defaultTimers = {
|
|
11
11
|
setTimeout,
|
|
12
12
|
clearTimeout
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { basename, join, resolve } from 'node:path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { loadConfig as defaultLoadConfig } from '../
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
3
|
+
import * as output from '../../shared/output.js';
|
|
4
|
+
import { safePath } from '../../shared/paths.js';
|
|
5
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
6
|
+
import { generateScreenshotSignature } from '../screenshots/screenshot-identity.js';
|
|
7
|
+
import { TddService as defaultTddService } from './tdd.service.js';
|
|
8
8
|
let DEFAULT_PAGE_SIZE = 20;
|
|
9
9
|
let MAX_PAGE_SIZE = 100;
|
|
10
10
|
let TDD_SCREENSHOT_STATUSES = new Set(['baseline-created', 'baseline-updated', 'error', 'failed', 'new', 'passed', 'rejected']);
|
|
@@ -4,21 +4,22 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { spawn as defaultSpawn } from 'node:child_process';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
7
|
+
import { createServerManager as defaultCreateServerManager } from '../../adapters/http/server-manager.js';
|
|
8
|
+
import { openBrowser as defaultOpenBrowser } from '../../shared/browser.js';
|
|
9
|
+
import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit, detectCommitAuthor as defaultDetectCommitAuthor, detectCommitMessage as defaultDetectCommitMessage, detectPullRequestNumber as defaultDetectPullRequestNumber, generateBuildNameWithGit as defaultGenerateBuildNameWithGit } from '../../shared/git.js';
|
|
10
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
11
|
+
import { VizzlyError } from '../../shared/vizzly-error.js';
|
|
12
|
+
import { createAuthService as defaultCreateAuthService } from '../auth/auth.service.js';
|
|
13
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
14
|
+
import { createBuild as defaultCreateApiBuild, finalizeBuild as defaultFinalizeApiBuild, getBuild as defaultGetBuild } from '../builds/build.service.js';
|
|
15
|
+
import { createBuildObject as defaultCreateBuildObject } from '../builds/build-info.js';
|
|
16
|
+
import { createConfigService as defaultCreateConfigService } from '../config/config.service.js';
|
|
17
|
+
import { CONFIG_DEFAULTS } from '../config/config-defaults.js';
|
|
18
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
19
|
+
import { createProjectService as defaultCreateProjectService } from '../projects/project.service.js';
|
|
20
|
+
import { generateStaticReport as defaultGenerateStaticReport, getReportFileUrl } from '../reports/report.service.js';
|
|
21
|
+
import { initializeDaemon as defaultInitializeDaemon, runTests as defaultRunTests } from '../runs/run.service.js';
|
|
22
|
+
import { getServerRegistry } from './server-registry.js';
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Build the follow-up command for local TDD evidence.
|