@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.
Files changed (195) hide show
  1. package/README.md +12 -3
  2. package/bin/vizzly.js +1 -1
  3. package/dist/{cli.js → adapters/cli/cli.js} +50 -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} +54 -14
  111. package/dist/{commands/init.js → domains/setup/init.commands.js} +11 -44
  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/domains/updates/cli-update.js +103 -0
  118. package/dist/domains/updates/update-check.js +2 -0
  119. package/dist/domains/updates/update-notifications.js +175 -0
  120. package/dist/domains/uploads/upload-defaults.js +4 -0
  121. package/dist/domains/uploads/upload-files.js +32 -0
  122. package/dist/domains/uploads/upload-settings.js +21 -0
  123. package/dist/domains/uploads/upload-timing.js +18 -0
  124. package/dist/{commands/upload.js → domains/uploads/upload.commands.js} +11 -9
  125. package/dist/domains/uploads/upload.normalizers.js +60 -0
  126. package/dist/domains/uploads/upload.serializers.js +179 -0
  127. package/dist/{uploader/operations.js → domains/uploads/upload.service.js} +85 -28
  128. package/dist/domains/uploads/upload.validations.js +77 -0
  129. package/dist/index.js +12 -12
  130. package/dist/reporter/reporter-bundle.iife.js +86 -86
  131. package/dist/reporter-ssr/ssr-entry.js +3 -6
  132. package/dist/shared/cloud-protocol.js +171 -0
  133. package/dist/{utils → shared}/file-changes.js +1 -1
  134. package/dist/shared/files.js +9 -0
  135. package/dist/{utils → shared}/global-config.js +7 -78
  136. package/dist/shared/log-level.js +7 -0
  137. package/dist/{utils → shared}/output.js +1 -1
  138. package/dist/{utils → shared}/package-info.js +5 -4
  139. package/dist/shared/paths.js +57 -0
  140. package/package.json +37 -36
  141. package/dist/api/core.js +0 -382
  142. package/dist/api/endpoints.js +0 -421
  143. package/dist/api/index.js +0 -19
  144. package/dist/auth/core.js +0 -237
  145. package/dist/auth/index.js +0 -31
  146. package/dist/commands/context.js +0 -1066
  147. package/dist/config/core.js +0 -439
  148. package/dist/config/operations.js +0 -327
  149. package/dist/project/core.js +0 -211
  150. package/dist/screenshot-server/core.js +0 -161
  151. package/dist/screenshot-server/index.js +0 -11
  152. package/dist/server/routers/assets.js +0 -95
  153. package/dist/server/routers/cloud-proxy.js +0 -146
  154. package/dist/server/routers/dashboard.js +0 -172
  155. package/dist/services/auth-service.js +0 -117
  156. package/dist/services/config-service.js +0 -313
  157. package/dist/services/project-service.js +0 -88
  158. package/dist/test-runner/core.js +0 -293
  159. package/dist/test-runner/index.js +0 -13
  160. package/dist/types/sdk.d.ts +0 -186
  161. package/dist/uploader/core.js +0 -432
  162. package/dist/uploader/index.js +0 -88
  163. /package/dist/{types/client.d.ts → adapters/client/index.d.ts} +0 -0
  164. /package/dist/{server → adapters/http}/middleware/cors.js +0 -0
  165. /package/dist/{server → adapters/http}/middleware/json-parser.js +0 -0
  166. /package/dist/{server → adapters/http}/middleware/response.js +0 -0
  167. /package/dist/{server-manager/core.js → adapters/http/server-options.js} +0 -0
  168. /package/dist/{utils/login-poll-wait.js → domains/auth/login-wait.js} +0 -0
  169. /package/dist/{tdd/metadata → domains/baselines}/baseline-metadata.js +0 -0
  170. /package/dist/{services/build-manager.js → domains/builds/build-info.js} +0 -0
  171. /package/dist/{tdd/services/result-service.js → domains/comparisons/comparison-results.js} +0 -0
  172. /package/dist/{init/core.js → domains/config/config-plan.js} +0 -0
  173. /package/dist/{utils/config-helpers.js → domains/config/config.js} +0 -0
  174. /package/dist/{utils/visual-context-normalizers.js → domains/context/context.normalizers.js} +0 -0
  175. /package/dist/{context → domains/context}/provider-resolver.js +0 -0
  176. /package/dist/{utils/context.js → domains/diagnostics/environment-context.js} +0 -0
  177. /package/dist/{utils/report-data.js → domains/reports/report.normalizers.js} +0 -0
  178. /package/dist/{utils/image-input-detector.js → domains/screenshots/image-input.js} +0 -0
  179. /package/dist/{tdd/core/signature.js → domains/screenshots/screenshot-identity.js} +0 -0
  180. /package/dist/{utils → domains/screenshots}/screenshot-options.js +0 -0
  181. /package/dist/{init/prompts.js → domains/setup/setup-prompts.js} +0 -0
  182. /package/dist/{tdd → domains/tdd}/runtime-context.js +0 -0
  183. /package/dist/{tdd → domains/tdd}/server-registry.js +0 -0
  184. /package/dist/{commands/tdd-daemon-process.js → domains/tdd/tdd-daemon-process.commands.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,11 +1,10 @@
1
1
  import { renderToString } from "react-dom/server";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- //#region 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
- //#endregion
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
- //#endregion
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 './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.0",
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"
@@ -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.3",
103
+ "dotenv": "^18.0.9",
103
104
  "form-data": "^4.0.6",
104
- "glob": "^13.0.6",
105
- "react": "^19.3.0",
106
- "react-dom": "^19.3.0",
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/types/index.d.ts",
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.6",
122
- "@babel/preset-env": "^8.0.6",
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.1",
125
- "@biomejs/biome": "^2.5.15",
125
+ "@babel/preset-typescript": "^8.0.7",
126
+ "@biomejs/biome": "^2.6.2",
126
127
  "@heroicons/react": "^2.2.0",
127
- "@modelcontextprotocol/sdk": "^1.31.0",
128
- "@playwright/test": "^1.63.0",
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.0",
131
- "@types/node": "^26.6.3",
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.29",
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.1",
143
- "wouter": "^3.11.1"
143
+ "vite": "^8.3.5",
144
+ "wouter": "^3.13.0"
144
145
  }
145
146
  }