@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
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert absolute file paths to web-accessible URLs
|
|
3
|
+
*/
|
|
4
|
+
export let convertPathToUrl = (filePath, vizzlyDir) => {
|
|
5
|
+
if (!filePath) return null;
|
|
6
|
+
// Convert absolute path to relative path from .vizzly directory
|
|
7
|
+
if (filePath.startsWith(vizzlyDir)) {
|
|
8
|
+
let relativePath = filePath.substring(vizzlyDir.length + 1);
|
|
9
|
+
return `/images/${relativePath}`;
|
|
10
|
+
}
|
|
11
|
+
return filePath;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Group comparisons by screenshot name with variant structure
|
|
16
|
+
* Matches cloud product's grouping logic from comparison.js
|
|
17
|
+
*/
|
|
18
|
+
export let groupComparisons = comparisons => {
|
|
19
|
+
let groups = new Map();
|
|
20
|
+
|
|
21
|
+
// Group by screenshot name
|
|
22
|
+
for (let comp of comparisons) {
|
|
23
|
+
if (!groups.has(comp.name)) {
|
|
24
|
+
groups.set(comp.name, {
|
|
25
|
+
name: comp.name,
|
|
26
|
+
comparisons: [],
|
|
27
|
+
browsers: new Set(),
|
|
28
|
+
viewports: new Set(),
|
|
29
|
+
devices: new Set(),
|
|
30
|
+
totalVariants: 0
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
let group = groups.get(comp.name);
|
|
34
|
+
group.comparisons.push(comp);
|
|
35
|
+
group.totalVariants++;
|
|
36
|
+
|
|
37
|
+
// Track unique browsers, viewports, devices
|
|
38
|
+
if (comp.properties?.browser) {
|
|
39
|
+
group.browsers.add(comp.properties.browser);
|
|
40
|
+
}
|
|
41
|
+
if (comp.properties?.viewport_width && comp.properties?.viewport_height) {
|
|
42
|
+
group.viewports.add(`${comp.properties.viewport_width}x${comp.properties.viewport_height}`);
|
|
43
|
+
}
|
|
44
|
+
if (comp.properties?.device) {
|
|
45
|
+
group.devices.add(comp.properties.device);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Convert to final structure
|
|
50
|
+
return Array.from(groups.values()).map(group => {
|
|
51
|
+
let browsers = Array.from(group.browsers);
|
|
52
|
+
let viewports = Array.from(group.viewports);
|
|
53
|
+
let devices = Array.from(group.devices);
|
|
54
|
+
|
|
55
|
+
// Build variants structure (browser -> viewport -> comparisons)
|
|
56
|
+
let variants = {};
|
|
57
|
+
group.comparisons.forEach(comp => {
|
|
58
|
+
let browser = comp.properties?.browser || null;
|
|
59
|
+
let viewport = comp.properties?.viewport_width && comp.properties?.viewport_height ? `${comp.properties.viewport_width}x${comp.properties.viewport_height}` : null;
|
|
60
|
+
if (!variants[browser]) variants[browser] = {};
|
|
61
|
+
if (!variants[browser][viewport]) variants[browser][viewport] = [];
|
|
62
|
+
variants[browser][viewport].push(comp);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Determine grouping strategy
|
|
66
|
+
let groupingStrategy = 'flat';
|
|
67
|
+
if (browsers.length > 1) groupingStrategy = 'browser';else if (viewports.length > 1) groupingStrategy = 'viewport';
|
|
68
|
+
|
|
69
|
+
// Sort comparisons by viewport area (largest first)
|
|
70
|
+
group.comparisons.sort((a, b) => {
|
|
71
|
+
let aArea = (a.properties?.viewport_width || 0) * (a.properties?.viewport_height || 0);
|
|
72
|
+
let bArea = (b.properties?.viewport_width || 0) * (b.properties?.viewport_height || 0);
|
|
73
|
+
if (bArea !== aArea) return bArea - aArea;
|
|
74
|
+
return (b.properties?.viewport_width || 0) - (a.properties?.viewport_width || 0);
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
...group,
|
|
78
|
+
browsers,
|
|
79
|
+
viewports,
|
|
80
|
+
devices: Array.from(devices),
|
|
81
|
+
variants,
|
|
82
|
+
groupingStrategy
|
|
83
|
+
};
|
|
84
|
+
}).sort((a, b) => {
|
|
85
|
+
// Sort groups: multi-variant first (by variant count), then singles alphabetically
|
|
86
|
+
if (a.totalVariants > 1 && b.totalVariants === 1) return -1;
|
|
87
|
+
if (a.totalVariants === 1 && b.totalVariants > 1) return 1;
|
|
88
|
+
if (a.totalVariants > 1 && b.totalVariants > 1) {
|
|
89
|
+
return b.totalVariants - a.totalVariants;
|
|
90
|
+
}
|
|
91
|
+
return a.name.localeCompare(b.name);
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
let COMPARISON_DETAIL_FIELDS = new Set(['boundingBox', 'diffClusters', 'intensityStats']);
|
|
95
|
+
|
|
96
|
+
/** Heavy detail files may enrich a comparison, never replace its identity/state. */
|
|
97
|
+
export function presentComparisonDetails(details = {}) {
|
|
98
|
+
return Object.fromEntries(Object.entries(details).filter(([field]) => COMPARISON_DETAIL_FIELDS.has(field)));
|
|
99
|
+
}
|
|
@@ -6,22 +6,16 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import { getPackageRoot } from '../../shared/package-info.js';
|
|
12
|
+
import { createReportStore } from './report-store.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Get the path to the dist/reporter directory (for CSS)
|
|
15
16
|
*/
|
|
16
17
|
function getReporterDistPath() {
|
|
17
|
-
|
|
18
|
-
let distPath = join(__dirname, '..', 'reporter');
|
|
19
|
-
if (existsSync(join(distPath, 'reporter-bundle.css'))) {
|
|
20
|
-
return distPath;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Fall back to development path (when running from src/)
|
|
24
|
-
distPath = join(__dirname, '..', '..', 'dist', 'reporter');
|
|
18
|
+
let distPath = join(getPackageRoot(), 'dist', 'reporter');
|
|
25
19
|
if (existsSync(join(distPath, 'reporter-bundle.css'))) {
|
|
26
20
|
return distPath;
|
|
27
21
|
}
|
|
@@ -32,14 +26,7 @@ function getReporterDistPath() {
|
|
|
32
26
|
* Get the path to the SSR module
|
|
33
27
|
*/
|
|
34
28
|
function getSSRModulePath() {
|
|
35
|
-
|
|
36
|
-
let ssrPath = join(__dirname, '..', 'reporter-ssr', 'ssr-entry.js');
|
|
37
|
-
if (existsSync(ssrPath)) {
|
|
38
|
-
return ssrPath;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Fall back to development path (when running from src/)
|
|
42
|
-
ssrPath = join(__dirname, '..', '..', 'dist', 'reporter-ssr', 'ssr-entry.js');
|
|
29
|
+
let ssrPath = join(getPackageRoot(), 'dist', 'reporter-ssr', 'ssr-entry.js');
|
|
43
30
|
if (existsSync(ssrPath)) {
|
|
44
31
|
return ssrPath;
|
|
45
32
|
}
|
|
@@ -139,26 +126,17 @@ export async function generateStaticReport(workingDir, options = {}) {
|
|
|
139
126
|
let outputDir = options.outputDir || join(workingDir, '.vizzly', 'report');
|
|
140
127
|
let vizzlyDir = join(workingDir, '.vizzly');
|
|
141
128
|
try {
|
|
142
|
-
|
|
143
|
-
let
|
|
144
|
-
if (!
|
|
129
|
+
let reports = createReportStore(workingDir);
|
|
130
|
+
let reportData = reports.readReport();
|
|
131
|
+
if (!reportData) {
|
|
145
132
|
return {
|
|
146
133
|
success: false,
|
|
147
134
|
reportPath: null,
|
|
148
135
|
error: 'No report data found. Run tests first.'
|
|
149
136
|
};
|
|
150
137
|
}
|
|
151
|
-
let
|
|
152
|
-
|
|
153
|
-
// Read baseline metadata if available
|
|
154
|
-
let metadataPath = join(vizzlyDir, 'baselines', 'metadata.json');
|
|
155
|
-
if (existsSync(metadataPath)) {
|
|
156
|
-
try {
|
|
157
|
-
reportData.baseline = JSON.parse(readFileSync(metadataPath, 'utf8'));
|
|
158
|
-
} catch {
|
|
159
|
-
// Ignore metadata read errors
|
|
160
|
-
}
|
|
161
|
-
}
|
|
138
|
+
let baseline = reports.readBaselineMetadata();
|
|
139
|
+
if (baseline !== null) reportData.baseline = baseline;
|
|
162
140
|
|
|
163
141
|
// Transform image URLs to relative paths
|
|
164
142
|
let transformedData = transformImageUrls(reportData);
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { randomUUID as defaultRandomUUID } from 'node:crypto';
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import { getAccessToken as defaultGetAccessToken } from '../
|
|
9
|
-
import
|
|
6
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
7
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
8
|
+
import { getAccessToken as defaultGetAccessToken } from '../auth/token-store.js';
|
|
9
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
10
10
|
function createReviewDeps(deps = {}) {
|
|
11
11
|
return {
|
|
12
12
|
loadConfig: deps.loadConfig || defaultLoadConfig,
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build environment variables for test process execution
|
|
3
|
+
* @param {Object} options - Options
|
|
4
|
+
* @param {number} options.port - Server port
|
|
5
|
+
* @param {string} options.buildId - Build ID
|
|
6
|
+
* @param {boolean} [options.setBaseline] - Whether to set baseline
|
|
7
|
+
* @param {boolean} [options.failOnDiff] - Whether visual diffs should fail tests
|
|
8
|
+
* @param {Object} [options.baseEnv] - Base environment (defaults to process.env)
|
|
9
|
+
* @returns {Object} Environment variables object
|
|
10
|
+
*/
|
|
11
|
+
export function buildTestEnv({
|
|
12
|
+
port,
|
|
13
|
+
buildId,
|
|
14
|
+
setBaseline = false,
|
|
15
|
+
failOnDiff = false,
|
|
16
|
+
baseEnv = process.env
|
|
17
|
+
}) {
|
|
18
|
+
return {
|
|
19
|
+
...baseEnv,
|
|
20
|
+
VIZZLY_SERVER_URL: `http://localhost:${port}`,
|
|
21
|
+
VIZZLY_BUILD_ID: buildId,
|
|
22
|
+
VIZZLY_ENABLED: 'true',
|
|
23
|
+
VIZZLY_SET_BASELINE: setBaseline ? 'true' : 'false',
|
|
24
|
+
VIZZLY_FAIL_ON_DIFF: failOnDiff ? 'true' : 'false'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build environment for disabled Vizzly (allowNoToken mode)
|
|
30
|
+
* @param {Object} [baseEnv] - Base environment (defaults to process.env)
|
|
31
|
+
* @returns {Object} Environment variables object
|
|
32
|
+
*/
|
|
33
|
+
export function buildDisabledEnv(baseEnv = process.env) {
|
|
34
|
+
return {
|
|
35
|
+
...baseEnv,
|
|
36
|
+
VIZZLY_ENABLED: 'false'
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build API client options from config
|
|
42
|
+
* @param {Object} config - Configuration object
|
|
43
|
+
* @returns {Object|null} Client options or null if no API key
|
|
44
|
+
*/
|
|
45
|
+
export function buildClientOptions(config) {
|
|
46
|
+
if (!config?.apiKey) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
baseUrl: config.apiUrl,
|
|
51
|
+
token: config.apiKey,
|
|
52
|
+
command: 'run'
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build spawn options for test command execution
|
|
58
|
+
* @param {Object} env - Environment variables
|
|
59
|
+
* @param {Object} [options] - Spawn display options
|
|
60
|
+
* @param {boolean} [options.json] - Keep stdout reserved for CLI JSON output
|
|
61
|
+
* @returns {Object} Spawn options
|
|
62
|
+
*/
|
|
63
|
+
export function buildSpawnOptions(env, options = {}) {
|
|
64
|
+
return {
|
|
65
|
+
env,
|
|
66
|
+
stdio: options.json ? ['inherit', 'pipe', 'pipe'] : 'inherit',
|
|
67
|
+
shell: true
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -1,19 +1,21 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { EventEmitter } from 'node:events';
|
|
3
|
+
import * as output from '../../shared/output.js';
|
|
4
|
+
import { VizzlyError } from '../../shared/vizzly-error.js';
|
|
5
|
+
import { createApiClient } from '../auth/cloud-client.js';
|
|
6
|
+
import { createBuild as createApiBuild, finalizeBuild as finalizeApiBuild, getBuild } from '../builds/build.service.js';
|
|
7
|
+
import { createBuildObject } from '../builds/build-info.js';
|
|
8
|
+
import { cancelTests, createBuild, fetchBuildUrl, finalizeBuild, initializeDaemon, runTests } from './run.service.js';
|
|
9
|
+
import { writeSession as defaultWriteSession } from './session-store.js';
|
|
10
|
+
|
|
1
11
|
/**
|
|
2
12
|
* Test Runner Service
|
|
3
13
|
* Orchestrates the test execution flow
|
|
4
14
|
*
|
|
5
15
|
* This EventEmitter adapter keeps the stable plugin-facing runner contract
|
|
6
|
-
* while delegating execution to the
|
|
16
|
+
* while delegating execution to the domain's run.service.js operations.
|
|
7
17
|
*/
|
|
8
18
|
|
|
9
|
-
import { spawn } from 'node:child_process';
|
|
10
|
-
import { EventEmitter } from 'node:events';
|
|
11
|
-
import { createBuild as createApiBuild, createApiClient, finalizeBuild as finalizeApiBuild, getBuild } from '../api/index.js';
|
|
12
|
-
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
13
|
-
import { cancelTests, createBuild, fetchBuildUrl, finalizeBuild, initializeDaemon, runTests } from '../test-runner/index.js';
|
|
14
|
-
import * as output from '../utils/output.js';
|
|
15
|
-
import { writeSession as defaultWriteSession } from '../utils/session.js';
|
|
16
|
-
import { createBuildObject } from './build-manager.js';
|
|
17
19
|
export class TestRunner extends EventEmitter {
|
|
18
20
|
constructor(config, serverManager, options = {}) {
|
|
19
21
|
super();
|
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
import { spawn as defaultSpawn } from 'node:child_process';
|
|
2
|
+
import { createServerManager as defaultCreateServerManager } from '../../adapters/http/server-manager.js';
|
|
3
|
+
import { getAppBaseUrl } from '../../shared/api-url.js';
|
|
4
|
+
import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit, detectCommitAuthor as defaultDetectCommitAuthor, detectCommitMessage as defaultDetectCommitMessage, detectPullRequestNumber as defaultDetectPullRequestNumber, generateBuildNameWithGit as defaultGenerateBuildNameWithGit } from '../../shared/git.js';
|
|
5
|
+
import * as defaultOutput from '../../shared/output.js';
|
|
6
|
+
import { VizzlyError } from '../../shared/vizzly-error.js';
|
|
7
|
+
import { createApiClient as defaultCreateApiClient } from '../auth/cloud-client.js';
|
|
8
|
+
import { createBuild as defaultCreateApiBuild, finalizeBuild as defaultFinalizeApiBuild, getBuild as defaultGetBuild } from '../builds/build.service.js';
|
|
9
|
+
import { createBuildObject as defaultCreateBuildObject } from '../builds/build-info.js';
|
|
10
|
+
import { CONFIG_DEFAULTS } from '../config/config-defaults.js';
|
|
11
|
+
import { loadConfig as defaultLoadConfig } from '../config/config-loader.js';
|
|
12
|
+
import { getTokenContext as defaultGetTokenContext } from '../context/context.service.js';
|
|
13
|
+
import { createUploader as defaultCreateUploader } from '../uploads/upload.service.js';
|
|
14
|
+
import { finalizeBuild as defaultFinalizeBuild, runTests as defaultRunTests } from './run.service.js';
|
|
15
|
+
import { writeSession as defaultWriteSession } from './session-store.js';
|
|
16
|
+
|
|
1
17
|
/**
|
|
2
18
|
* Run tests and report their visual results in terminal or JSON output.
|
|
3
19
|
*
|
|
@@ -8,19 +24,6 @@
|
|
|
8
24
|
* Uses functional operations directly - no class wrappers needed
|
|
9
25
|
*/
|
|
10
26
|
|
|
11
|
-
import { spawn as defaultSpawn } from 'node:child_process';
|
|
12
|
-
import { createBuild as defaultCreateApiBuild, createApiClient as defaultCreateApiClient, finalizeBuild as defaultFinalizeApiBuild, getBuild as defaultGetBuild, getTokenContext as defaultGetTokenContext } from '../api/index.js';
|
|
13
|
-
import { CONFIG_DEFAULTS } from '../config/core.js';
|
|
14
|
-
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
15
|
-
import { createServerManager as defaultCreateServerManager } from '../server-manager/index.js';
|
|
16
|
-
import { createBuildObject as defaultCreateBuildObject } from '../services/build-manager.js';
|
|
17
|
-
import { finalizeBuild as defaultFinalizeBuild, runTests as defaultRunTests } from '../test-runner/index.js';
|
|
18
|
-
import { createUploader as defaultCreateUploader } from '../uploader/index.js';
|
|
19
|
-
import { getAppBaseUrl } from '../utils/api-url.js';
|
|
20
|
-
import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
|
|
21
|
-
import { detectBranch as defaultDetectBranch, detectCommit as defaultDetectCommit, detectCommitAuthor as defaultDetectCommitAuthor, detectCommitMessage as defaultDetectCommitMessage, detectPullRequestNumber as defaultDetectPullRequestNumber, generateBuildNameWithGit as defaultGenerateBuildNameWithGit } from '../utils/git.js';
|
|
22
|
-
import * as defaultOutput from '../utils/output.js';
|
|
23
|
-
import { writeSession as defaultWriteSession } from '../utils/session.js';
|
|
24
27
|
function formatUploadCounts({
|
|
25
28
|
total,
|
|
26
29
|
uploaded,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize setBaseline option (handles both camelCase and kebab-case)
|
|
3
|
+
* @param {Object} options - Options object
|
|
4
|
+
* @returns {boolean} Whether to set baseline
|
|
5
|
+
*/
|
|
6
|
+
export function normalizeSetBaseline(options) {
|
|
7
|
+
return Boolean(options?.setBaseline || options?.['set-baseline']);
|
|
8
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determine if we should skip Vizzly integration entirely
|
|
3
|
+
* @param {Object} options - Options
|
|
4
|
+
* @param {boolean} options.allowNoToken - Whether to allow running without token
|
|
5
|
+
* @param {boolean} options.hasApiKey - Whether an API key is available
|
|
6
|
+
* @param {boolean} options.tdd - Whether in TDD mode
|
|
7
|
+
* @returns {boolean} True if Vizzly should be disabled
|
|
8
|
+
*/
|
|
9
|
+
export function shouldDisableVizzly({
|
|
10
|
+
allowNoToken,
|
|
11
|
+
hasApiKey,
|
|
12
|
+
tdd
|
|
13
|
+
}) {
|
|
14
|
+
return allowNoToken && !hasApiKey && !tdd;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Determine build mode
|
|
19
|
+
* @param {boolean} tdd - Whether TDD mode is enabled
|
|
20
|
+
* @returns {'tdd'|'api'} Build mode
|
|
21
|
+
*/
|
|
22
|
+
export function determineBuildMode(tdd) {
|
|
23
|
+
return tdd ? 'tdd' : 'api';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate daemon mode requirements
|
|
28
|
+
* @param {Object} options - Options
|
|
29
|
+
* @param {boolean} options.tdd - Whether TDD mode is enabled
|
|
30
|
+
* @param {boolean} options.daemon - Whether daemon mode is enabled
|
|
31
|
+
* @returns {{ valid: boolean, error: string|null }}
|
|
32
|
+
*/
|
|
33
|
+
export function validateDaemonMode({
|
|
34
|
+
tdd,
|
|
35
|
+
daemon
|
|
36
|
+
}) {
|
|
37
|
+
if (!tdd || !daemon) {
|
|
38
|
+
return {
|
|
39
|
+
valid: false,
|
|
40
|
+
error: 'Initialize method is only for TDD daemon mode'
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
valid: true,
|
|
45
|
+
error: null
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check if API key is available
|
|
51
|
+
* @param {Object} config - Configuration object
|
|
52
|
+
* @returns {boolean} Whether API key exists
|
|
53
|
+
*/
|
|
54
|
+
export function hasApiKey(config) {
|
|
55
|
+
return Boolean(config?.apiKey);
|
|
56
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build API build payload from options
|
|
3
|
+
* @param {Object} options - Run options
|
|
4
|
+
* @param {Object} [comparisonConfig] - Comparison configuration (threshold, minClusterSize)
|
|
5
|
+
* @returns {Object} Build payload for API
|
|
6
|
+
*/
|
|
7
|
+
export function buildApiBuildPayload(options, comparisonConfig = null) {
|
|
8
|
+
let payload = {
|
|
9
|
+
name: options.buildName || `Test Run ${new Date().toISOString()}`,
|
|
10
|
+
branch: options.branch || 'main',
|
|
11
|
+
environment: options.environment || 'test',
|
|
12
|
+
commit_sha: options.commit,
|
|
13
|
+
commit_message: options.message,
|
|
14
|
+
commit_author_name: options.commitAuthorName,
|
|
15
|
+
commit_author_email: options.commitAuthorEmail,
|
|
16
|
+
github_pull_request_number: options.pullRequestNumber,
|
|
17
|
+
parallel_id: options.parallelId
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Only include metadata if we have meaningful config to send
|
|
21
|
+
if (comparisonConfig?.threshold != null || comparisonConfig?.minClusterSize != null) {
|
|
22
|
+
payload.metadata = {
|
|
23
|
+
comparison: {
|
|
24
|
+
threshold: comparisonConfig.threshold,
|
|
25
|
+
minClusterSize: comparisonConfig.minClusterSize
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return payload;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Build result object for disabled run (no Vizzly integration)
|
|
34
|
+
* @returns {Object} Result object
|
|
35
|
+
*/
|
|
36
|
+
export function buildDisabledRunResult(reason = null) {
|
|
37
|
+
return {
|
|
38
|
+
testsPassed: 1,
|
|
39
|
+
testsFailed: 0,
|
|
40
|
+
screenshotsCaptured: 0,
|
|
41
|
+
...(reason ? {
|
|
42
|
+
capture: {
|
|
43
|
+
setup: 'disabled',
|
|
44
|
+
reason,
|
|
45
|
+
accepted: 0,
|
|
46
|
+
rejected: 0,
|
|
47
|
+
errors: 0
|
|
48
|
+
}
|
|
49
|
+
} : {})
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build final run result object
|
|
55
|
+
* @param {Object} options - Options
|
|
56
|
+
* @param {string|null} options.buildId - Build ID
|
|
57
|
+
* @param {string|null} options.buildUrl - Build URL
|
|
58
|
+
* @param {boolean} options.testSuccess - Whether tests passed
|
|
59
|
+
* @param {number} options.screenshotCount - Number of screenshots
|
|
60
|
+
* @param {Object|null} options.tddResults - TDD results (comparisons, etc.)
|
|
61
|
+
* @param {Object|null} [options.uploadStats] - Cumulative cloud upload outcomes,
|
|
62
|
+
* exposed as `uploads` independently of test and local comparison results.
|
|
63
|
+
* @returns {Object} Final result object
|
|
64
|
+
*/
|
|
65
|
+
export function buildRunResult({
|
|
66
|
+
buildId,
|
|
67
|
+
buildUrl,
|
|
68
|
+
testSuccess,
|
|
69
|
+
screenshotCount,
|
|
70
|
+
tddResults,
|
|
71
|
+
uploadStats
|
|
72
|
+
}) {
|
|
73
|
+
return {
|
|
74
|
+
buildId,
|
|
75
|
+
url: buildUrl,
|
|
76
|
+
testsPassed: testSuccess ? 1 : 0,
|
|
77
|
+
testsFailed: testSuccess ? 0 : 1,
|
|
78
|
+
screenshotsCaptured: screenshotCount,
|
|
79
|
+
...(uploadStats ? {
|
|
80
|
+
uploads: uploadStats
|
|
81
|
+
} : {}),
|
|
82
|
+
comparisons: tddResults?.comparisons || null,
|
|
83
|
+
failed: (tddResults?.failed || 0) > 0,
|
|
84
|
+
...(tddResults ? {
|
|
85
|
+
summary: {
|
|
86
|
+
total: tddResults.total ?? screenshotCount,
|
|
87
|
+
passed: tddResults.passed || 0,
|
|
88
|
+
failed: tddResults.failed || 0,
|
|
89
|
+
new: tddResults.new || 0,
|
|
90
|
+
errors: tddResults.errors || 0
|
|
91
|
+
},
|
|
92
|
+
capture: {
|
|
93
|
+
setup: 'ready',
|
|
94
|
+
reason: tddResults.error || null,
|
|
95
|
+
accepted: tddResults.capture?.accepted ?? screenshotCount,
|
|
96
|
+
rejected: tddResults.capture?.rejected || 0,
|
|
97
|
+
errors: tddResults.errors || 0
|
|
98
|
+
}
|
|
99
|
+
} : {})
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
+
import { normalizeSetBaseline } from './run.normalizers.js';
|
|
2
|
+
import { hasApiKey, shouldDisableVizzly, validateDaemonMode } from './run.rules.js';
|
|
3
|
+
import { buildApiBuildPayload, buildDisabledRunResult, buildRunResult } from './run.serializers.js';
|
|
4
|
+
import { validateTestCommand } from './run.validations.js';
|
|
5
|
+
import { buildClientOptions, buildDisabledEnv, buildSpawnOptions, buildTestEnv } from './run-environment.js';
|
|
6
|
+
|
|
1
7
|
/**
|
|
2
|
-
* Test
|
|
8
|
+
* Test execution and build lifecycle services
|
|
3
9
|
*
|
|
4
10
|
* Each operation takes its dependencies as parameters:
|
|
5
11
|
* - spawn: child_process.spawn for executing commands
|
|
@@ -11,8 +17,6 @@
|
|
|
11
17
|
* This makes them trivially testable without mocking modules.
|
|
12
18
|
*/
|
|
13
19
|
|
|
14
|
-
import { buildApiBuildPayload, buildClientOptions, buildDisabledEnv, buildDisabledRunResult, buildRunResult, buildSpawnOptions, buildTestEnv, hasApiKey, normalizeSetBaseline, shouldDisableVizzly, validateDaemonMode, validateTestCommand } from './core.js';
|
|
15
|
-
|
|
16
20
|
// ============================================================================
|
|
17
21
|
// Build Operations
|
|
18
22
|
// ============================================================================
|
|
@@ -111,9 +115,10 @@ export async function fetchBuildUrl({
|
|
|
111
115
|
* @param {boolean} options.tdd - Whether in TDD mode
|
|
112
116
|
* @param {boolean} options.success - Whether tests passed
|
|
113
117
|
* @param {number} options.executionTime - Execution time in ms
|
|
118
|
+
* @param {string} [options.failureReason] - Explanation for a failed build
|
|
114
119
|
* @param {Object} options.config - Configuration object
|
|
115
120
|
* @param {Object} options.deps - Dependencies
|
|
116
|
-
* @param {Object} options.deps.serverManager - Server manager
|
|
121
|
+
* @param {Object} [options.deps.serverManager] - Server manager when uploads exist
|
|
117
122
|
* @param {Function} options.deps.createApiClient - API client factory
|
|
118
123
|
* @param {Function} options.deps.finalizeApiBuild - API finalize function
|
|
119
124
|
* @param {Object} options.deps.output - Output utilities
|
|
@@ -126,6 +131,7 @@ export async function finalizeBuild({
|
|
|
126
131
|
tdd,
|
|
127
132
|
success,
|
|
128
133
|
executionTime,
|
|
134
|
+
failureReason,
|
|
129
135
|
config,
|
|
130
136
|
deps
|
|
131
137
|
}) {
|
|
@@ -143,7 +149,7 @@ export async function finalizeBuild({
|
|
|
143
149
|
try {
|
|
144
150
|
if (tdd) {
|
|
145
151
|
// TDD mode: use server handler to finalize (local-only)
|
|
146
|
-
if (serverManager
|
|
152
|
+
if (serverManager?.server?.finishBuild) {
|
|
147
153
|
await serverManager.server.finishBuild(buildId);
|
|
148
154
|
output.debug('build', 'finalized', {
|
|
149
155
|
success
|
|
@@ -151,7 +157,7 @@ export async function finalizeBuild({
|
|
|
151
157
|
}
|
|
152
158
|
} else {
|
|
153
159
|
// API mode: flush uploads first, then finalize build
|
|
154
|
-
if (serverManager
|
|
160
|
+
if (serverManager?.server?.finishBuild) {
|
|
155
161
|
uploadStats = await serverManager.server.finishBuild(buildId);
|
|
156
162
|
}
|
|
157
163
|
|
|
@@ -161,7 +167,7 @@ export async function finalizeBuild({
|
|
|
161
167
|
let client = createApiClient(clientOptions);
|
|
162
168
|
let uploadsFailed = uploadStats?.failed > 0;
|
|
163
169
|
let buildSucceeded = success && !uploadsFailed;
|
|
164
|
-
await finalizeApiBuild(client, buildId, buildSucceeded, executionTime, uploadsFailed ? `${uploadStats.failed} screenshot(s) failed to upload. Visual testing is incomplete.` :
|
|
170
|
+
await finalizeApiBuild(client, buildId, buildSucceeded, executionTime, uploadsFailed ? `${uploadStats.failed} screenshot(s) failed to upload. Visual testing is incomplete.` : failureReason);
|
|
165
171
|
output.debug('build', 'finalized via api', {
|
|
166
172
|
success: buildSucceeded
|
|
167
173
|
});
|
|
@@ -395,6 +401,28 @@ export async function runTests({
|
|
|
395
401
|
error: stopError.message
|
|
396
402
|
});
|
|
397
403
|
}
|
|
404
|
+
if (buildId && !tdd) {
|
|
405
|
+
try {
|
|
406
|
+
await finalizeBuild({
|
|
407
|
+
buildId,
|
|
408
|
+
tdd: false,
|
|
409
|
+
success: false,
|
|
410
|
+
executionTime: Date.now() - startTime,
|
|
411
|
+
failureReason: error.message,
|
|
412
|
+
config,
|
|
413
|
+
deps: {
|
|
414
|
+
createApiClient,
|
|
415
|
+
finalizeApiBuild,
|
|
416
|
+
output,
|
|
417
|
+
onFinalizeFailed
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
} catch (finalizeError) {
|
|
421
|
+
output.debug('run', 'failed to finalize build after setup error', {
|
|
422
|
+
error: finalizeError.message
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
}
|
|
398
426
|
return executeDisabledTestRun({
|
|
399
427
|
testCommand,
|
|
400
428
|
json: runOptions.json,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate test command is provided
|
|
3
|
+
* @param {string|undefined} testCommand - Test command to validate
|
|
4
|
+
* @returns {{ valid: boolean, error: string|null }}
|
|
5
|
+
*/
|
|
6
|
+
export function validateTestCommand(testCommand) {
|
|
7
|
+
if (!testCommand) {
|
|
8
|
+
return {
|
|
9
|
+
valid: false,
|
|
10
|
+
error: 'No test command provided'
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
valid: true,
|
|
15
|
+
error: null
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* Session management for build context
|
|
3
6
|
*
|
|
@@ -9,8 +12,6 @@
|
|
|
9
12
|
* 2. GitHub Actions env ($GITHUB_ENV) - for cross-step persistence in GHA
|
|
10
13
|
*/
|
|
11
14
|
|
|
12
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
13
|
-
import { dirname, join } from 'node:path';
|
|
14
15
|
let SESSION_DIR = '.vizzly';
|
|
15
16
|
let SESSION_FILE = 'session.json';
|
|
16
17
|
let SESSION_MAX_AGE_MS = 60 * 60 * 1000; // 1 hour
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { Buffer } from 'node:buffer';
|
|
2
2
|
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import { normalizeScreenshotOptions } from '
|
|
4
|
+
import * as output from '../../shared/output.js';
|
|
5
|
+
import { uploadScreenshot as defaultUploadScreenshot } from './cloud-screenshots.js';
|
|
6
|
+
import { detectImageInputType } from './image-input.js';
|
|
7
|
+
import { normalizeScreenshotOptions } from './screenshot-options.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Create a cloud screenshot handler for one run.
|