@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
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { renderToString } from "react-dom/server";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
-
|
|
3
|
+
// src/domains/review/status-utils.js
|
|
4
4
|
function isNewComparisonStatus(status) {
|
|
5
5
|
return status === "new" || status === "baseline-created";
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
//#region src/components/static-report-view.jsx
|
|
7
|
+
// src/domains/reports/static-report-view.jsx
|
|
9
8
|
/**
|
|
10
9
|
* Static Report View
|
|
11
10
|
*
|
|
@@ -637,8 +636,7 @@ function StaticReportView({ reportData }) {
|
|
|
637
636
|
})]
|
|
638
637
|
});
|
|
639
638
|
}
|
|
640
|
-
|
|
641
|
-
//#region src/ssr-entry.jsx
|
|
639
|
+
// src/ssr-entry.jsx
|
|
642
640
|
/**
|
|
643
641
|
* SSR Entry Point
|
|
644
642
|
*
|
|
@@ -653,5 +651,4 @@ function StaticReportView({ reportData }) {
|
|
|
653
651
|
function renderStaticReport(reportData) {
|
|
654
652
|
return renderToString(/* @__PURE__ */ jsx(StaticReportView, { reportData }));
|
|
655
653
|
}
|
|
656
|
-
//#endregion
|
|
657
654
|
export { renderStaticReport };
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { URLSearchParams } from 'node:url';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Build Authorization header for Bearer token auth
|
|
5
|
+
* @param {string|null} token - API token
|
|
6
|
+
* @returns {Object} Headers object with Authorization if token provided
|
|
7
|
+
*/
|
|
8
|
+
export function buildAuthHeader(token) {
|
|
9
|
+
if (!token) return {};
|
|
10
|
+
return {
|
|
11
|
+
Authorization: `Bearer ${token}`
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Build User-Agent string from components
|
|
17
|
+
* @param {string} version - CLI version
|
|
18
|
+
* @param {string} command - Command being executed (run, upload, tdd, etc.)
|
|
19
|
+
* @param {string|null} sdkUserAgent - Optional SDK user agent to append
|
|
20
|
+
* @param {string} nodeVersion - Node.js version to append
|
|
21
|
+
* @returns {string} Complete User-Agent string
|
|
22
|
+
*/
|
|
23
|
+
export function buildUserAgent(version, command, sdkUserAgent = null, nodeVersion = process.versions.node) {
|
|
24
|
+
let baseUserAgent = `vizzly-cli/${version} (${command})`;
|
|
25
|
+
let userAgentParts = [baseUserAgent];
|
|
26
|
+
if (sdkUserAgent) {
|
|
27
|
+
userAgentParts.push(sdkUserAgent);
|
|
28
|
+
}
|
|
29
|
+
if (nodeVersion) userAgentParts.push(`node/${nodeVersion}`);
|
|
30
|
+
return userAgentParts.join(' ');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build complete request headers
|
|
35
|
+
* @param {Object} options - Header options
|
|
36
|
+
* @param {string|null} options.token - API token
|
|
37
|
+
* @param {string} options.userAgent - User-Agent string
|
|
38
|
+
* @param {string|null} options.contentType - Content-Type header
|
|
39
|
+
* @param {Object} options.extra - Additional headers to merge
|
|
40
|
+
* @returns {Object} Complete headers object
|
|
41
|
+
*/
|
|
42
|
+
export function buildRequestHeaders({
|
|
43
|
+
token,
|
|
44
|
+
userAgent,
|
|
45
|
+
contentType = null,
|
|
46
|
+
extra = {}
|
|
47
|
+
}) {
|
|
48
|
+
let headers = {
|
|
49
|
+
'User-Agent': userAgent,
|
|
50
|
+
...buildAuthHeader(token),
|
|
51
|
+
...extra
|
|
52
|
+
};
|
|
53
|
+
if (contentType) {
|
|
54
|
+
headers['Content-Type'] = contentType;
|
|
55
|
+
}
|
|
56
|
+
return headers;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build URL query parameters from filter object
|
|
61
|
+
* @param {Object} filters - Filter key-value pairs
|
|
62
|
+
* @returns {string} URL-encoded query string (without leading ?)
|
|
63
|
+
*/
|
|
64
|
+
export function buildQueryParams(filters) {
|
|
65
|
+
let params = new URLSearchParams();
|
|
66
|
+
for (let [key, value] of Object.entries(filters)) {
|
|
67
|
+
if (value != null && value !== '') {
|
|
68
|
+
params.append(key, String(value));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return params.toString();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Check if HTTP status indicates an auth error
|
|
76
|
+
* @param {number} status - HTTP status code
|
|
77
|
+
* @returns {boolean} True if auth error
|
|
78
|
+
*/
|
|
79
|
+
export function isAuthError(status) {
|
|
80
|
+
return status === 401;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Check if HTTP status indicates rate limiting
|
|
85
|
+
* @param {number} status - HTTP status code
|
|
86
|
+
* @returns {boolean} True if rate limited
|
|
87
|
+
*/
|
|
88
|
+
export function isRateLimited(status) {
|
|
89
|
+
return status === 429;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Parse error information from API response
|
|
94
|
+
* @param {number} status - HTTP status code
|
|
95
|
+
* @param {string} body - Response body text
|
|
96
|
+
* @param {string} url - Request URL
|
|
97
|
+
* @returns {Object} Parsed error info with message and code
|
|
98
|
+
*/
|
|
99
|
+
export function parseApiError(status, body, url) {
|
|
100
|
+
let message = `API request failed: ${status}`;
|
|
101
|
+
let responseDetails = null;
|
|
102
|
+
let responseCode = null;
|
|
103
|
+
let responseMessage = body;
|
|
104
|
+
if (body) {
|
|
105
|
+
try {
|
|
106
|
+
let parsedBody = JSON.parse(body);
|
|
107
|
+
responseDetails = parsedBody.details ?? null;
|
|
108
|
+
responseCode = parsedBody.code ?? parsedBody.details?.code ?? null;
|
|
109
|
+
responseMessage = parsedBody.error ?? parsedBody.message ?? body;
|
|
110
|
+
} catch {
|
|
111
|
+
// Plain-text API errors remain valid and are surfaced unchanged.
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (responseMessage) {
|
|
115
|
+
message += ` - ${responseMessage}`;
|
|
116
|
+
}
|
|
117
|
+
message += ` (URL: ${url})`;
|
|
118
|
+
let code = 'API_ERROR';
|
|
119
|
+
if (status === 401) code = 'AUTH_ERROR';
|
|
120
|
+
if (status === 403) code = 'FORBIDDEN';
|
|
121
|
+
if (status === 404) code = 'NOT_FOUND';
|
|
122
|
+
if (status === 429) code = 'RATE_LIMITED';
|
|
123
|
+
if (status >= 500) code = 'SERVER_ERROR';
|
|
124
|
+
return {
|
|
125
|
+
message,
|
|
126
|
+
code: responseCode || code,
|
|
127
|
+
status,
|
|
128
|
+
details: responseDetails
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extract error message from response body (JSON or text)
|
|
134
|
+
* @param {Response} response - Fetch Response object
|
|
135
|
+
* @returns {Promise<string>} Error message
|
|
136
|
+
*/
|
|
137
|
+
export async function extractErrorBody(response) {
|
|
138
|
+
try {
|
|
139
|
+
if (typeof response.text === 'function') {
|
|
140
|
+
return await response.text();
|
|
141
|
+
}
|
|
142
|
+
return response.statusText || '';
|
|
143
|
+
} catch {
|
|
144
|
+
return '';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Build full API URL from base and endpoint
|
|
150
|
+
* @param {string} baseUrl - Base API URL
|
|
151
|
+
* @param {string} endpoint - API endpoint (should start with /)
|
|
152
|
+
* @returns {string} Full URL
|
|
153
|
+
*/
|
|
154
|
+
export function buildApiUrl(baseUrl, endpoint) {
|
|
155
|
+
// Remove trailing slash from base, ensure endpoint starts with /
|
|
156
|
+
let base = baseUrl.replace(/\/$/, '');
|
|
157
|
+
let path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
158
|
+
return `${base}${path}`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build endpoint URL with optional query params
|
|
163
|
+
* @param {string} endpoint - Base endpoint
|
|
164
|
+
* @param {Object} params - Query parameters
|
|
165
|
+
* @returns {string} Endpoint with query string
|
|
166
|
+
*/
|
|
167
|
+
export function buildEndpointWithParams(endpoint, params = {}) {
|
|
168
|
+
let query = buildQueryParams(params);
|
|
169
|
+
if (!query) return endpoint;
|
|
170
|
+
return `${endpoint}?${query}`;
|
|
171
|
+
}
|
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
3
|
import { access, lstat, mkdir, rename, rm, writeFile } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
|
-
import { readOptionalFile } from './
|
|
5
|
+
import { readOptionalFile } from './files.js';
|
|
6
6
|
export async function fileChange(filename, after) {
|
|
7
7
|
let before = await readOptionalFile(filename);
|
|
8
8
|
return before === after ? null : {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** Global Vizzly JSON settings and external-tool paths. */
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Global User Configuration Utilities
|
|
3
5
|
* Manages ~/.vizzly/config.json for storing authentication tokens
|
|
@@ -52,7 +54,7 @@ export function getGlobalConfigPath() {
|
|
|
52
54
|
* @returns {Promise<void>}
|
|
53
55
|
*/
|
|
54
56
|
async function ensureGlobalConfigDir() {
|
|
55
|
-
|
|
57
|
+
let dir = getGlobalConfigDir();
|
|
56
58
|
if (!existsSync(dir)) {
|
|
57
59
|
await mkdir(dir, {
|
|
58
60
|
recursive: true,
|
|
@@ -67,11 +69,11 @@ async function ensureGlobalConfigDir() {
|
|
|
67
69
|
*/
|
|
68
70
|
export async function loadGlobalConfig() {
|
|
69
71
|
try {
|
|
70
|
-
|
|
72
|
+
let configPath = getGlobalConfigPath();
|
|
71
73
|
if (!existsSync(configPath)) {
|
|
72
74
|
return {};
|
|
73
75
|
}
|
|
74
|
-
|
|
76
|
+
let content = await readFile(configPath, 'utf-8');
|
|
75
77
|
return JSON.parse(content);
|
|
76
78
|
} catch (error) {
|
|
77
79
|
// If file doesn't exist or is corrupted, return empty config
|
|
@@ -92,8 +94,8 @@ export async function loadGlobalConfig() {
|
|
|
92
94
|
*/
|
|
93
95
|
export async function saveGlobalConfig(config) {
|
|
94
96
|
await ensureGlobalConfigDir();
|
|
95
|
-
|
|
96
|
-
|
|
97
|
+
let configPath = getGlobalConfigPath();
|
|
98
|
+
let content = JSON.stringify(config, null, 2);
|
|
97
99
|
|
|
98
100
|
// Write file with secure permissions (owner read/write only)
|
|
99
101
|
await writeFile(configPath, content, {
|
|
@@ -143,77 +145,4 @@ export async function saveUserPath() {
|
|
|
143
145
|
export async function getUserPath() {
|
|
144
146
|
let config = await loadGlobalConfig();
|
|
145
147
|
return config.userPath || null;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Get authentication tokens from global config
|
|
150
|
-
* @returns {Promise<Object|null>} Token object with accessToken, refreshToken, expiresAt, user, or null if not found
|
|
151
|
-
*/
|
|
152
|
-
export async function getAuthTokens() {
|
|
153
|
-
const config = await loadGlobalConfig();
|
|
154
|
-
if (!config.auth?.accessToken) {
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
return config.auth;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Save authentication tokens to global config
|
|
162
|
-
* @param {Object} auth - Auth object with accessToken, refreshToken, expiresAt, user
|
|
163
|
-
* @returns {Promise<void>}
|
|
164
|
-
*/
|
|
165
|
-
export async function saveAuthTokens(auth) {
|
|
166
|
-
const config = await loadGlobalConfig();
|
|
167
|
-
config.auth = {
|
|
168
|
-
accessToken: auth.accessToken,
|
|
169
|
-
refreshToken: auth.refreshToken,
|
|
170
|
-
expiresAt: auth.expiresAt,
|
|
171
|
-
user: auth.user
|
|
172
|
-
};
|
|
173
|
-
await saveGlobalConfig(config);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Clear authentication tokens from global config
|
|
178
|
-
* @returns {Promise<void>}
|
|
179
|
-
*/
|
|
180
|
-
export async function clearAuthTokens() {
|
|
181
|
-
const config = await loadGlobalConfig();
|
|
182
|
-
delete config.auth;
|
|
183
|
-
await saveGlobalConfig(config);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Check if authentication tokens exist and are not expired
|
|
188
|
-
* @returns {Promise<boolean>} True if valid tokens exist
|
|
189
|
-
*/
|
|
190
|
-
export async function hasValidTokens() {
|
|
191
|
-
const auth = await getAuthTokens();
|
|
192
|
-
if (!auth?.accessToken) {
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// Check if token is expired
|
|
197
|
-
if (auth.expiresAt) {
|
|
198
|
-
const expiresAt = new Date(auth.expiresAt);
|
|
199
|
-
const now = new Date();
|
|
200
|
-
|
|
201
|
-
// Consider expired if within 5 minutes of expiry
|
|
202
|
-
const bufferMs = 5 * 60 * 1000;
|
|
203
|
-
if (now.getTime() >= expiresAt.getTime() - bufferMs) {
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Get the access token from global config if valid and not expired
|
|
212
|
-
* @returns {Promise<string|null>} Access token or null
|
|
213
|
-
*/
|
|
214
|
-
export async function getAccessToken() {
|
|
215
|
-
let valid = await hasValidTokens();
|
|
216
|
-
if (!valid) return null;
|
|
217
|
-
let auth = await getAuthTokens();
|
|
218
|
-
return auth?.accessToken || null;
|
|
219
148
|
}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import { appendFileSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
12
12
|
import { dirname } from 'node:path';
|
|
13
13
|
import { createColors } from './colors.js';
|
|
14
|
-
import { getLogLevel as getEnvLogLevel } from './
|
|
14
|
+
import { getLogLevel as getEnvLogLevel } from './log-level.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Log levels in order of severity (lowest to highest)
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
|
-
import {
|
|
7
|
+
import { join } from 'node:path';
|
|
8
8
|
import { fileURLToPath } from 'node:url';
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
9
|
let packageJson;
|
|
10
|
+
export function getPackageRoot() {
|
|
11
|
+
return fileURLToPath(new URL('../..', import.meta.url));
|
|
12
|
+
}
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Get package.json information
|
|
@@ -16,7 +17,7 @@ let packageJson;
|
|
|
16
17
|
*/
|
|
17
18
|
export function getPackageInfo() {
|
|
18
19
|
if (!packageJson) {
|
|
19
|
-
packageJson = JSON.parse(readFileSync(join(
|
|
20
|
+
packageJson = JSON.parse(readFileSync(join(getPackageRoot(), 'package.json'), 'utf-8'));
|
|
20
21
|
}
|
|
21
22
|
return packageJson;
|
|
22
23
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { join, normalize, resolve } from 'node:path';
|
|
2
|
+
import * as output from './output.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validates that a path stays within the allowed working directory bounds
|
|
6
|
+
* @param {string} targetPath - Path to validate
|
|
7
|
+
* @param {string} workingDir - Working directory that serves as the security boundary
|
|
8
|
+
* @returns {string} Resolved and normalized path if valid
|
|
9
|
+
* @throws {Error} If path is invalid or outside bounds
|
|
10
|
+
*/
|
|
11
|
+
export function validatePathSecurity(targetPath, workingDir) {
|
|
12
|
+
if (typeof targetPath !== 'string' || targetPath.length === 0) {
|
|
13
|
+
throw new Error('Path must be a non-empty string');
|
|
14
|
+
}
|
|
15
|
+
if (typeof workingDir !== 'string' || workingDir.length === 0) {
|
|
16
|
+
throw new Error('Working directory must be a non-empty string');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Normalize and resolve both paths
|
|
20
|
+
let resolvedWorkingDir = resolve(normalize(workingDir));
|
|
21
|
+
let resolvedTargetPath = resolve(normalize(targetPath));
|
|
22
|
+
|
|
23
|
+
// Ensure the target path starts with the working directory
|
|
24
|
+
if (!resolvedTargetPath.startsWith(resolvedWorkingDir)) {
|
|
25
|
+
output.warn(`Path traversal attempt blocked: ${targetPath} (resolved: ${resolvedTargetPath}) is outside working directory: ${resolvedWorkingDir}`);
|
|
26
|
+
throw new Error('Path is outside the allowed working directory');
|
|
27
|
+
}
|
|
28
|
+
return resolvedTargetPath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Safely constructs a path within the working directory
|
|
33
|
+
* @param {string} workingDir - Base working directory
|
|
34
|
+
* @param {...string} pathSegments - Path segments to join
|
|
35
|
+
* @returns {string} Safely constructed path
|
|
36
|
+
* @throws {Error} If resulting path would be outside working directory
|
|
37
|
+
*/
|
|
38
|
+
export function safePath(workingDir, ...pathSegments) {
|
|
39
|
+
if (pathSegments.length === 0) {
|
|
40
|
+
return validatePathSecurity(workingDir, workingDir);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Sanitize each path segment
|
|
44
|
+
let sanitizedSegments = pathSegments.map(segment => {
|
|
45
|
+
if (typeof segment !== 'string') {
|
|
46
|
+
throw new Error('Path segment must be a string');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Block directory traversal in segments
|
|
50
|
+
if (segment.includes('..')) {
|
|
51
|
+
throw new Error('Path segment contains directory traversal sequence');
|
|
52
|
+
}
|
|
53
|
+
return segment;
|
|
54
|
+
});
|
|
55
|
+
let targetPath = join(workingDir, ...sanitizedSegments);
|
|
56
|
+
return validatePathSecurity(targetPath, workingDir);
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vizzly-testing/cli",
|
|
3
|
-
"version": "0.39.
|
|
3
|
+
"version": "0.39.2",
|
|
4
4
|
"description": "Visual regression testing from your terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"llm-context",
|
|
@@ -26,28 +26,28 @@
|
|
|
26
26
|
"packageManager": "pnpm@11.3.0",
|
|
27
27
|
"exports": {
|
|
28
28
|
".": {
|
|
29
|
-
"types": "./dist/
|
|
29
|
+
"types": "./dist/index.d.ts",
|
|
30
30
|
"import": "./dist/index.js",
|
|
31
31
|
"require": "./dist/index.js"
|
|
32
32
|
},
|
|
33
33
|
"./client": {
|
|
34
|
-
"types": "./dist/
|
|
35
|
-
"import": "./dist/client/index.js",
|
|
36
|
-
"require": "./dist/client/index.js"
|
|
34
|
+
"types": "./dist/adapters/client/index.d.ts",
|
|
35
|
+
"import": "./dist/adapters/client/index.js",
|
|
36
|
+
"require": "./dist/adapters/client/index.js"
|
|
37
37
|
},
|
|
38
38
|
"./sdk": {
|
|
39
|
-
"types": "./dist/
|
|
40
|
-
"import": "./dist/sdk/index.js",
|
|
41
|
-
"require": "./dist/sdk/index.js"
|
|
39
|
+
"types": "./dist/adapters/sdk/index.d.ts",
|
|
40
|
+
"import": "./dist/adapters/sdk/index.js",
|
|
41
|
+
"require": "./dist/adapters/sdk/index.js"
|
|
42
42
|
},
|
|
43
43
|
"./config": {
|
|
44
|
-
"types": "./dist/
|
|
45
|
-
"import": "./dist/
|
|
46
|
-
"require": "./dist/
|
|
44
|
+
"types": "./dist/domains/config/config.d.ts",
|
|
45
|
+
"import": "./dist/domains/config/config.js",
|
|
46
|
+
"require": "./dist/domains/config/config.js"
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
49
|
"main": "./dist/index.js",
|
|
50
|
-
"types": "./dist/
|
|
50
|
+
"types": "./dist/index.d.ts",
|
|
51
51
|
"bin": {
|
|
52
52
|
"vizzly": "bin/vizzly.js"
|
|
53
53
|
},
|
|
@@ -66,25 +66,26 @@
|
|
|
66
66
|
"build": "pnpm run clean && pnpm run compile && pnpm run build:reporter && pnpm run build:reporter-ssr && pnpm run copy-types",
|
|
67
67
|
"clean": "rimraf dist",
|
|
68
68
|
"compile": "babel src --out-dir dist --ignore '**/*.test.js'",
|
|
69
|
-
"copy-types": "
|
|
69
|
+
"copy-types": "cp src/index.d.ts dist/index.d.ts && cp src/adapters/client/index.d.ts dist/adapters/client/index.d.ts && cp src/adapters/sdk/index.d.ts dist/adapters/sdk/index.d.ts && cp src/domains/config/config.d.ts dist/domains/config/config.d.ts",
|
|
70
70
|
"build:reporter": "cd src/reporter && vite build",
|
|
71
71
|
"build:reporter-ssr": "cd src/reporter && vite build --config vite.ssr.config.js",
|
|
72
72
|
"dev:reporter": "cd src/reporter && vite --config vite.dev.config.js",
|
|
73
73
|
"test:types": "tsd",
|
|
74
74
|
"prepublishOnly": "pnpm run build",
|
|
75
|
-
"test": "node
|
|
76
|
-
"test:watch": "node
|
|
75
|
+
"test": "node scripts/test.js",
|
|
76
|
+
"test:watch": "node scripts/test.js --watch",
|
|
77
77
|
"test:reporter": "playwright test --config=tests/reporter/playwright.config.js",
|
|
78
78
|
"test:reporter:visual": "node bin/vizzly.js tdd run \"pnpm run test:reporter\" --no-open",
|
|
79
79
|
"test:swift:e2e": "pnpm run build && node clients/swift/scripts/run-e2e.js",
|
|
80
80
|
"test:swift:previews:e2e": "node clients/swift/scripts/run-preview-e2e.js",
|
|
81
81
|
"test:tui": "node --test --test-reporter=spec tests/tui/*.test.js",
|
|
82
82
|
"test:tui:docker": "./tests/tui/run-tui-tests.sh",
|
|
83
|
-
"lint": "biome check src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts",
|
|
84
|
-
"lint:fix": "biome check --write --unsafe src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts",
|
|
85
|
-
"format": "biome format --write src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts",
|
|
86
|
-
"format:check": "biome format src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts",
|
|
87
|
-
"fix": "pnpm run format && pnpm run lint:fix"
|
|
83
|
+
"lint": "pnpm run check:architecture && biome check src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts scripts/check-architecture.js scripts/test.js",
|
|
84
|
+
"lint:fix": "biome check --write --unsafe src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts scripts/check-architecture.js scripts/test.js",
|
|
85
|
+
"format": "biome format --write src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts scripts/check-architecture.js scripts/test.js",
|
|
86
|
+
"format:check": "biome format src tests clients/storybook/src clients/storybook/tests clients/static-site/src clients/static-site/tests clients/vitest/src clients/vitest/tests clients/ember/src clients/ember/tests clients/ember/bin clients/swift/src clients/swift/tests-js clients/swift/scripts scripts/check-architecture.js scripts/test.js",
|
|
87
|
+
"fix": "pnpm run format && pnpm run lint:fix",
|
|
88
|
+
"check:architecture": "node scripts/check-architecture.js"
|
|
88
89
|
},
|
|
89
90
|
"engines": {
|
|
90
91
|
"node": "^22.18.0 || >=24.0.0"
|
|
@@ -99,16 +100,16 @@
|
|
|
99
100
|
"ansis": "^4.4.0",
|
|
100
101
|
"commander": "^15.0.0",
|
|
101
102
|
"cosmiconfig": "^10.0.1",
|
|
102
|
-
"dotenv": "^18.0.
|
|
103
|
+
"dotenv": "^18.0.9",
|
|
103
104
|
"form-data": "^4.0.6",
|
|
104
|
-
"glob": "^
|
|
105
|
-
"react": "^19.3.
|
|
106
|
-
"react-dom": "^19.3.
|
|
105
|
+
"glob": "^14.0.2",
|
|
106
|
+
"react": "^19.3.1",
|
|
107
|
+
"react-dom": "^19.3.1",
|
|
107
108
|
"zod": "^4.6.7"
|
|
108
109
|
},
|
|
109
110
|
"tsd": {
|
|
110
111
|
"directory": "test-d",
|
|
111
|
-
"typingsFile": "src/
|
|
112
|
+
"typingsFile": "src/index.d.ts",
|
|
112
113
|
"compilerOptions": {
|
|
113
114
|
"esModuleInterop": true,
|
|
114
115
|
"module": "ESNext",
|
|
@@ -118,28 +119,28 @@
|
|
|
118
119
|
},
|
|
119
120
|
"devDependencies": {
|
|
120
121
|
"@babel/cli": "^8.0.6",
|
|
121
|
-
"@babel/core": "^8.0.
|
|
122
|
-
"@babel/preset-env": "^8.0.
|
|
122
|
+
"@babel/core": "^8.0.7",
|
|
123
|
+
"@babel/preset-env": "^8.0.7",
|
|
123
124
|
"@babel/preset-react": "^8.0.1",
|
|
124
|
-
"@babel/preset-typescript": "^8.0.
|
|
125
|
-
"@biomejs/biome": "^2.
|
|
125
|
+
"@babel/preset-typescript": "^8.0.7",
|
|
126
|
+
"@biomejs/biome": "^2.6.2",
|
|
126
127
|
"@heroicons/react": "^2.2.0",
|
|
127
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
128
|
-
"@playwright/test": "^1.
|
|
128
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
129
|
+
"@playwright/test": "^1.64.1",
|
|
129
130
|
"@tailwindcss/postcss": "^4.3.3",
|
|
130
|
-
"@tanstack/react-query": "^5.104.
|
|
131
|
-
"@types/node": "^26.6.
|
|
131
|
+
"@tanstack/react-query": "^5.104.5",
|
|
132
|
+
"@types/node": "^26.6.5",
|
|
132
133
|
"@vitejs/plugin-react": "^6.1.2",
|
|
133
134
|
"autoprefixer": "^10.6.1",
|
|
134
135
|
"babel-plugin-react-compiler": "^1.0.0",
|
|
135
136
|
"babel-plugin-transform-remove-console": "^6.9.4",
|
|
136
|
-
"postcss": "^8.5.
|
|
137
|
+
"postcss": "^8.5.31",
|
|
137
138
|
"rimraf": "^6.1.3",
|
|
138
139
|
"tailwindcss": "^4.3.3",
|
|
139
140
|
"tsd": "^0.33.0",
|
|
140
141
|
"tui-driver": "github:vizzly-testing/tui-driver",
|
|
141
142
|
"typescript": "^7.0.2",
|
|
142
|
-
"vite": "^8.3.
|
|
143
|
-
"wouter": "^3.
|
|
143
|
+
"vite": "^8.3.5",
|
|
144
|
+
"wouter": "^3.13.0"
|
|
144
145
|
}
|
|
145
146
|
}
|