@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.
Files changed (195) hide show
  1. package/README.md +8 -0
  2. package/bin/vizzly.js +1 -1
  3. package/dist/{cli.js → adapters/cli/cli.js} +41 -35
  4. package/dist/adapters/cli/setup.js +34 -0
  5. package/dist/{client → adapters/client}/index.js +6 -7
  6. package/dist/adapters/http/cloud-proxy.js +29 -0
  7. package/dist/{server → adapters/http}/http-server.js +12 -13
  8. package/dist/{server-manager/operations.js → adapters/http/server-lifecycle.js} +10 -13
  9. package/dist/{server-manager/index.js → adapters/http/server-manager.js} +23 -27
  10. package/dist/{plugin-api.js → adapters/plugins/plugin-api.js} +1 -1
  11. package/dist/{plugin-loader.js → adapters/plugins/plugin-loader.js} +1 -1
  12. package/dist/{services/index.js → adapters/plugins/services.js} +4 -9
  13. package/dist/adapters/sdk/index.d.ts +89 -0
  14. package/dist/{sdk → adapters/sdk}/index.js +22 -19
  15. package/dist/{commands/api.js → domains/api/api.commands.js} +3 -3
  16. package/dist/{auth/client.js → domains/auth/auth-client.js} +4 -3
  17. package/dist/domains/auth/auth-errors.js +50 -0
  18. package/dist/domains/auth/auth-headers.js +41 -0
  19. package/dist/domains/auth/auth.normalizers.js +50 -0
  20. package/dist/{server/routers/auth.js → domains/auth/auth.routes.js} +9 -9
  21. package/dist/domains/auth/auth.rules.js +19 -0
  22. package/dist/domains/auth/auth.serializers.js +32 -0
  23. package/dist/{auth/operations.js → domains/auth/auth.service.js} +115 -17
  24. package/dist/domains/auth/auth.validations.js +21 -0
  25. package/dist/{api/client.js → domains/auth/cloud-client.js} +11 -18
  26. package/dist/{commands/login.js → domains/auth/login.commands.js} +11 -8
  27. package/dist/{commands/logout.js → domains/auth/logout.commands.js} +6 -4
  28. package/dist/domains/auth/token-store.js +81 -0
  29. package/dist/{commands/whoami.js → domains/auth/whoami.commands.js} +6 -3
  30. package/dist/domains/baselines/baseline-download.js +192 -0
  31. package/dist/domains/baselines/baseline-write.js +22 -0
  32. package/dist/{server/routers/baseline.js → domains/baselines/baseline.routes.js} +3 -3
  33. package/dist/{tdd/services/baseline-manager.js → domains/baselines/baseline.service.js} +1 -39
  34. package/dist/{commands/baselines.js → domains/baselines/baselines.commands.js} +3 -3
  35. package/dist/domains/baselines/cloud-baselines.js +9 -0
  36. package/dist/domains/baselines/cloud-baselines.routes.js +42 -0
  37. package/dist/domains/builds/build.normalizers.js +35 -0
  38. package/dist/domains/builds/build.serializers.js +164 -0
  39. package/dist/domains/builds/build.service.js +119 -0
  40. package/dist/domains/builds/build.validations.js +39 -0
  41. package/dist/{commands/builds.js → domains/builds/builds.commands.js} +6 -74
  42. package/dist/domains/builds/cloud-builds.routes.js +31 -0
  43. package/dist/{commands/finalize.js → domains/builds/finalize.commands.js} +5 -17
  44. package/dist/{commands/status.js → domains/builds/status.commands.js} +9 -120
  45. package/dist/domains/comparisons/cloud-comparisons.js +43 -0
  46. package/dist/{tdd/services/comparison-service.js → domains/comparisons/comparison.service.js} +1 -1
  47. package/dist/{commands/comparisons.js → domains/comparisons/comparisons.commands.js} +7 -5
  48. package/dist/domains/config/config-defaults.js +27 -0
  49. package/dist/{utils → domains/config}/config-discovery.js +1 -9
  50. package/dist/{utils → domains/config}/config-loader.js +10 -9
  51. package/dist/{commands/config-cmd.js → domains/config/config.commands.js} +4 -4
  52. package/dist/{types → domains/config}/config.d.ts +3 -3
  53. package/dist/domains/config/config.normalizers.js +126 -0
  54. package/dist/{server/routers/config.js → domains/config/config.routes.js} +13 -13
  55. package/dist/domains/config/config.rules.js +47 -0
  56. package/dist/domains/config/config.serializers.js +202 -0
  57. package/dist/domains/config/config.service.js +625 -0
  58. package/dist/{utils/config-schema.js → domains/config/config.validations.js} +9 -9
  59. package/dist/{utils/environment-config.js → domains/config/environment.js} +1 -8
  60. package/dist/domains/context/compact-context.validations.js +60 -0
  61. package/dist/domains/context/context-presentation.js +368 -0
  62. package/dist/domains/context/context.commands.js +385 -0
  63. package/dist/domains/context/context.serializers.js +157 -0
  64. package/dist/domains/context/context.service.js +76 -0
  65. package/dist/domains/context/context.validations.js +100 -0
  66. package/dist/{context → domains/context}/local-workspace-provider.js +1 -1
  67. package/dist/{commands/doctor.js → domains/diagnostics/doctor.commands.js} +6 -5
  68. package/dist/{server/routers/health.js → domains/diagnostics/health.routes.js} +11 -20
  69. package/dist/{commands/preview.js → domains/previews/preview.commands.js} +9 -7
  70. package/dist/domains/previews/preview.service.js +37 -0
  71. package/dist/domains/projects/cloud-projects.routes.js +28 -0
  72. package/dist/{commands/orgs.js → domains/projects/orgs.commands.js} +6 -8
  73. package/dist/{utils → domains/projects}/project-binding.js +3 -3
  74. package/dist/domains/projects/project-errors.js +61 -0
  75. package/dist/{utils → domains/projects}/project-link-store.js +2 -2
  76. package/dist/domains/projects/project-requests.js +61 -0
  77. package/dist/{commands/project.js → domains/projects/project.commands.js} +9 -41
  78. package/dist/domains/projects/project.normalizers.js +69 -0
  79. package/dist/{project/operations.js → domains/projects/project.service.js} +89 -16
  80. package/dist/domains/projects/project.validations.js +31 -0
  81. package/dist/{commands/projects.js → domains/projects/projects.commands.js} +6 -8
  82. package/dist/{server/routers/projects.js → domains/projects/projects.routes.js} +5 -5
  83. package/dist/domains/reports/dashboard.routes.js +96 -0
  84. package/dist/{server/routers/events.js → domains/reports/events.routes.js} +14 -36
  85. package/dist/domains/reports/report-assets.routes.js +81 -0
  86. package/dist/domains/reports/report-state.js +51 -0
  87. package/dist/domains/reports/report-store.js +85 -0
  88. package/dist/domains/reports/report.serializers.js +99 -0
  89. package/dist/{services/static-report-generator.js → domains/reports/report.service.js} +11 -33
  90. package/dist/{commands/review.js → domains/review/review.commands.js} +4 -4
  91. package/dist/domains/runs/run-environment.js +69 -0
  92. package/dist/{services/test-runner.js → domains/runs/run-events.js} +11 -9
  93. package/dist/{commands/run.js → domains/runs/run.commands.js} +16 -13
  94. package/dist/domains/runs/run.normalizers.js +8 -0
  95. package/dist/domains/runs/run.rules.js +56 -0
  96. package/dist/domains/runs/run.serializers.js +101 -0
  97. package/dist/{test-runner/operations.js → domains/runs/run.service.js} +35 -7
  98. package/dist/domains/runs/run.validations.js +17 -0
  99. package/dist/{utils/session.js → domains/runs/session-store.js} +3 -2
  100. package/dist/{server/handlers/api-handler.js → domains/screenshots/cloud-handler.js} +4 -4
  101. package/dist/domains/screenshots/cloud-screenshots.js +109 -0
  102. package/dist/{utils/file-helpers.js → domains/screenshots/image-files.js} +1 -1
  103. package/dist/domains/screenshots/screenshot-hash.js +53 -0
  104. package/dist/domains/screenshots/screenshot-server-options.js +28 -0
  105. package/dist/{screenshot-server/operations.js → domains/screenshots/screenshot-server.js} +14 -1
  106. package/dist/domains/screenshots/screenshot.normalizers.js +40 -0
  107. package/dist/{server/routers/screenshot.js → domains/screenshots/screenshot.routes.js} +4 -4
  108. package/dist/domains/screenshots/screenshot.serializers.js +146 -0
  109. package/dist/{utils/security.js → domains/screenshots/screenshot.validations.js} +20 -70
  110. package/dist/{init/agents.js → domains/setup/agent-setup.js} +4 -5
  111. package/dist/{commands/init.js → domains/setup/init.commands.js} +10 -41
  112. package/dist/{server/handlers/tdd-handler.js → domains/tdd/screenshot-handler.js} +46 -281
  113. package/dist/{commands/tdd-daemon.js → domains/tdd/tdd-daemon.commands.js} +6 -6
  114. package/dist/{commands/tdd-screenshots.js → domains/tdd/tdd-screenshots.commands.js} +5 -5
  115. package/dist/{commands/tdd.js → domains/tdd/tdd.commands.js} +16 -15
  116. package/dist/{tdd/tdd-service.js → domains/tdd/tdd.service.js} +81 -405
  117. package/dist/{utils → domains/updates}/cli-update.js +4 -6
  118. package/dist/{utils → domains/updates}/update-notifications.js +7 -7
  119. package/dist/domains/uploads/upload-defaults.js +4 -0
  120. package/dist/domains/uploads/upload-files.js +32 -0
  121. package/dist/domains/uploads/upload-settings.js +21 -0
  122. package/dist/domains/uploads/upload-timing.js +18 -0
  123. package/dist/{commands/upload.js → domains/uploads/upload.commands.js} +11 -9
  124. package/dist/domains/uploads/upload.normalizers.js +60 -0
  125. package/dist/domains/uploads/upload.serializers.js +179 -0
  126. package/dist/{uploader/operations.js → domains/uploads/upload.service.js} +85 -28
  127. package/dist/domains/uploads/upload.validations.js +77 -0
  128. package/dist/index.js +12 -12
  129. package/dist/reporter/reporter-bundle.iife.js +86 -86
  130. package/dist/reporter-ssr/ssr-entry.js +2 -2
  131. package/dist/shared/cloud-protocol.js +171 -0
  132. package/dist/{utils → shared}/file-changes.js +1 -1
  133. package/dist/shared/files.js +9 -0
  134. package/dist/{utils → shared}/global-config.js +7 -78
  135. package/dist/shared/log-level.js +7 -0
  136. package/dist/{utils → shared}/output.js +1 -1
  137. package/dist/{utils → shared}/package-info.js +5 -4
  138. package/dist/shared/paths.js +57 -0
  139. package/package.json +23 -22
  140. package/dist/api/core.js +0 -382
  141. package/dist/api/endpoints.js +0 -421
  142. package/dist/api/index.js +0 -19
  143. package/dist/auth/core.js +0 -237
  144. package/dist/auth/index.js +0 -31
  145. package/dist/commands/context.js +0 -1066
  146. package/dist/config/core.js +0 -439
  147. package/dist/config/operations.js +0 -327
  148. package/dist/project/core.js +0 -211
  149. package/dist/screenshot-server/core.js +0 -161
  150. package/dist/screenshot-server/index.js +0 -11
  151. package/dist/server/routers/assets.js +0 -95
  152. package/dist/server/routers/cloud-proxy.js +0 -146
  153. package/dist/server/routers/dashboard.js +0 -172
  154. package/dist/services/auth-service.js +0 -117
  155. package/dist/services/config-service.js +0 -313
  156. package/dist/services/project-service.js +0 -88
  157. package/dist/test-runner/core.js +0 -293
  158. package/dist/test-runner/index.js +0 -13
  159. package/dist/types/sdk.d.ts +0 -186
  160. package/dist/uploader/core.js +0 -432
  161. package/dist/uploader/index.js +0 -88
  162. /package/dist/{types/client.d.ts → adapters/client/index.d.ts} +0 -0
  163. /package/dist/{server → adapters/http}/middleware/cors.js +0 -0
  164. /package/dist/{server → adapters/http}/middleware/json-parser.js +0 -0
  165. /package/dist/{server → adapters/http}/middleware/response.js +0 -0
  166. /package/dist/{server-manager/core.js → adapters/http/server-options.js} +0 -0
  167. /package/dist/{utils/login-poll-wait.js → domains/auth/login-wait.js} +0 -0
  168. /package/dist/{tdd/metadata → domains/baselines}/baseline-metadata.js +0 -0
  169. /package/dist/{services/build-manager.js → domains/builds/build-info.js} +0 -0
  170. /package/dist/{tdd/services/result-service.js → domains/comparisons/comparison-results.js} +0 -0
  171. /package/dist/{init/core.js → domains/config/config-plan.js} +0 -0
  172. /package/dist/{utils/config-helpers.js → domains/config/config.js} +0 -0
  173. /package/dist/{utils/visual-context-normalizers.js → domains/context/context.normalizers.js} +0 -0
  174. /package/dist/{context → domains/context}/provider-resolver.js +0 -0
  175. /package/dist/{utils/context.js → domains/diagnostics/environment-context.js} +0 -0
  176. /package/dist/{utils/report-data.js → domains/reports/report.normalizers.js} +0 -0
  177. /package/dist/{utils/image-input-detector.js → domains/screenshots/image-input.js} +0 -0
  178. /package/dist/{tdd/core/signature.js → domains/screenshots/screenshot-identity.js} +0 -0
  179. /package/dist/{utils → domains/screenshots}/screenshot-options.js +0 -0
  180. /package/dist/{init/prompts.js → domains/setup/setup-prompts.js} +0 -0
  181. /package/dist/{tdd → domains/tdd}/runtime-context.js +0 -0
  182. /package/dist/{tdd → domains/tdd}/server-registry.js +0 -0
  183. /package/dist/{commands/tdd-daemon-process.js → domains/tdd/tdd-daemon-process.commands.js} +0 -0
  184. /package/dist/{utils → domains/updates}/update-check.js +0 -0
  185. /package/dist/{types/index.d.ts → index.d.ts} +0 -0
  186. /package/dist/{utils → shared}/api-url.js +0 -0
  187. /package/dist/{utils → shared}/async-utils.js +0 -0
  188. /package/dist/{utils → shared}/browser.js +0 -0
  189. /package/dist/{utils → shared}/ci-env.js +0 -0
  190. /package/dist/{utils → shared}/colors.js +0 -0
  191. /package/dist/{utils → shared}/fetch-utils.js +0 -0
  192. /package/dist/{utils → shared}/git.js +0 -0
  193. /package/dist/{utils → shared}/patterns.js +0 -0
  194. /package/dist/{utils → shared}/secret-store.js +0 -0
  195. /package/dist/{errors → shared}/vizzly-error.js +0 -0
@@ -1,10 +1,10 @@
1
1
  import { renderToString } from "react-dom/server";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- // src/utils/status-utils.js
3
+ // src/domains/review/status-utils.js
4
4
  function isNewComparisonStatus(status) {
5
5
  return status === "new" || status === "baseline-created";
6
6
  }
7
- // src/components/static-report-view.jsx
7
+ // src/domains/reports/static-report-view.jsx
8
8
  /**
9
9
  * Static Report View
10
10
  *
@@ -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 './config-discovery.js';
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 : {
@@ -0,0 +1,9 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ export async function readOptionalFile(filename) {
3
+ try {
4
+ return await readFile(filename, 'utf8');
5
+ } catch (error) {
6
+ if (error.code === 'ENOENT') return null;
7
+ throw error;
8
+ }
9
+ }
@@ -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
- const dir = getGlobalConfigDir();
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
- const configPath = getGlobalConfigPath();
72
+ let configPath = getGlobalConfigPath();
71
73
  if (!existsSync(configPath)) {
72
74
  return {};
73
75
  }
74
- const content = await readFile(configPath, 'utf-8');
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
- const configPath = getGlobalConfigPath();
96
- const content = JSON.stringify(config, null, 2);
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
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Get log level from environment
3
+ * @returns {string} Log level with default
4
+ */
5
+ export function getLogLevel() {
6
+ return process.env.VIZZLY_LOG_LEVEL || 'info';
7
+ }
@@ -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 './environment-config.js';
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 { dirname, join } from 'node:path';
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(__dirname, '../../package.json'), 'utf-8'));
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.1",
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/types/index.d.ts",
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/types/client.d.ts",
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/types/sdk.d.ts",
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/types/config.d.ts",
45
- "import": "./dist/utils/config-helpers.js",
46
- "require": "./dist/utils/config-helpers.js"
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/types/index.d.ts",
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": "mkdir -p dist/types && cp src/types/*.d.ts dist/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 --experimental-test-coverage --test --test-concurrency=1 --test-reporter=spec $(find tests clients/swift/tests-js -name '*.test.js')",
76
- "test:watch": "node --test --test-reporter=spec --watch $(find tests -name '*.test.js')",
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"
@@ -101,14 +102,14 @@
101
102
  "cosmiconfig": "^10.0.1",
102
103
  "dotenv": "^18.0.9",
103
104
  "form-data": "^4.0.6",
104
- "glob": "^13.0.6",
105
+ "glob": "^14.0.2",
105
106
  "react": "^19.3.1",
106
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/types/index.d.ts",
112
+ "typingsFile": "src/index.d.ts",
112
113
  "compilerOptions": {
113
114
  "esModuleInterop": true,
114
115
  "module": "ESNext",