@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,95 +0,0 @@
1
- /**
2
- * Assets Router
3
- * Serves static assets (bundle files, images)
4
- */
5
-
6
- import { existsSync, readFileSync } from 'node:fs';
7
- import { dirname, join } from 'node:path';
8
- import { fileURLToPath } from 'node:url';
9
- import * as output from '../../utils/output.js';
10
- import { sendError, sendFile, sendNotFound } from '../middleware/response.js';
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = dirname(__filename);
13
- const PROJECT_ROOT = join(__dirname, '..', '..', '..');
14
-
15
- /**
16
- * Create assets router
17
- * @param {Object} context - Router context
18
- * @returns {Function} Route handler
19
- */
20
- export function createAssetsRouter() {
21
- return async function handleAssetsRoute(req, res, pathname) {
22
- if (req.method !== 'GET') {
23
- return false;
24
- }
25
-
26
- // Serve React bundle JS
27
- if (pathname === '/reporter-bundle.js') {
28
- const bundlePath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.iife.js');
29
- if (existsSync(bundlePath)) {
30
- try {
31
- const bundle = readFileSync(bundlePath, 'utf8');
32
- sendFile(res, bundle, 'application/javascript');
33
- return true;
34
- } catch (error) {
35
- output.debug('Error serving reporter bundle:', {
36
- error: error.message
37
- });
38
- sendError(res, 500, 'Error loading reporter bundle');
39
- return true;
40
- }
41
- } else {
42
- sendNotFound(res, 'Reporter bundle not found');
43
- return true;
44
- }
45
- }
46
-
47
- // Serve React bundle CSS
48
- if (pathname === '/reporter-bundle.css') {
49
- const cssPath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.css');
50
- if (existsSync(cssPath)) {
51
- try {
52
- const css = readFileSync(cssPath, 'utf8');
53
- sendFile(res, css, 'text/css');
54
- return true;
55
- } catch (error) {
56
- output.debug('Error serving reporter CSS:', {
57
- error: error.message
58
- });
59
- sendError(res, 500, 'Error loading reporter CSS');
60
- return true;
61
- }
62
- } else {
63
- sendNotFound(res, 'Reporter CSS not found');
64
- return true;
65
- }
66
- }
67
-
68
- // Serve images from .vizzly directory
69
- if (pathname.startsWith('/images/')) {
70
- const imagePath = pathname.replace('/images/', '');
71
- const fullImagePath = join(process.cwd(), '.vizzly', imagePath);
72
- if (existsSync(fullImagePath)) {
73
- try {
74
- const imageData = readFileSync(fullImagePath);
75
- // Images are rewritten in place between TDD runs, so disable browser caching.
76
- res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate');
77
- res.setHeader('Pragma', 'no-cache');
78
- res.setHeader('Expires', '0');
79
- sendFile(res, imageData, 'image/png');
80
- return true;
81
- } catch (error) {
82
- output.debug('Error serving image:', {
83
- error: error.message
84
- });
85
- sendError(res, 500, 'Error loading image');
86
- return true;
87
- }
88
- } else {
89
- sendNotFound(res, 'Image not found');
90
- return true;
91
- }
92
- }
93
- return false;
94
- };
95
- }
@@ -1,146 +0,0 @@
1
- /**
2
- * Cloud Proxy Router
3
- * Proxies requests to Vizzly cloud API with OAuth authentication
4
- *
5
- * This router transparently handles OAuth token management:
6
- * - Reads tokens from ~/.vizzly/config.json via authService
7
- * - Adds Authorization header to outgoing requests
8
- * - Handles token refresh on 401 responses
9
- * - Returns proxied response to React app
10
- */
11
-
12
- import * as output from '../../utils/output.js';
13
- import { parseJsonBody } from '../middleware/json-parser.js';
14
- import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
15
-
16
- /**
17
- * Create cloud proxy router
18
- * @param {Object} context - Router context
19
- * @param {Object} context.authService - Auth service for token management
20
- * @param {string} context.apiUrl - Base API URL (default: https://app.vizzly.dev)
21
- * @returns {Function} Route handler
22
- */
23
- export function createCloudProxyRouter({
24
- authService,
25
- apiUrl: _apiUrl = 'https://app.vizzly.dev'
26
- }) {
27
- /**
28
- * Make an authenticated request to the cloud API
29
- * @param {string} endpoint - API endpoint (e.g., /api/cli/projects)
30
- * @param {Object} options - Fetch options
31
- * @returns {Promise<Object>} Response data
32
- */
33
- async function proxyRequest(endpoint, options = {}) {
34
- if (!authService) {
35
- throw new Error('Auth service not available');
36
- }
37
-
38
- // Use authService.authenticatedRequest which handles token refresh
39
- return authService.authenticatedRequest(endpoint, options);
40
- }
41
- return async function handleCloudProxyRoute(req, res, pathname, parsedUrl) {
42
- // Only handle /api/cloud/* routes
43
- if (!pathname.startsWith('/api/cloud')) {
44
- return false;
45
- }
46
-
47
- // Check auth service availability
48
- if (!authService) {
49
- sendServiceUnavailable(res, 'Auth service');
50
- return true;
51
- }
52
-
53
- // Route: GET /api/cloud/projects - List user's projects
54
- if (req.method === 'GET' && pathname === '/api/cloud/projects') {
55
- try {
56
- const response = await proxyRequest('/api/cli/projects', {
57
- method: 'GET'
58
- });
59
- sendSuccess(res, {
60
- projects: response.projects || []
61
- });
62
- return true;
63
- } catch (error) {
64
- output.error('Error fetching projects from cloud:', error);
65
- // Return empty array instead of error for better UX when not logged in
66
- if (error.message?.includes('not authenticated') || error.code === 'AUTH_ERROR') {
67
- sendSuccess(res, {
68
- projects: [],
69
- authenticated: false
70
- });
71
- } else {
72
- sendError(res, 500, error.message);
73
- }
74
- return true;
75
- }
76
- }
77
-
78
- // Route: GET /api/cloud/organizations/:org/projects/:project/builds
79
- const buildsMatch = pathname.match(/^\/api\/cloud\/organizations\/([^/]+)\/projects\/([^/]+)\/builds$/);
80
- if (req.method === 'GET' && buildsMatch) {
81
- try {
82
- const organizationSlug = decodeURIComponent(buildsMatch[1]);
83
- const projectSlug = decodeURIComponent(buildsMatch[2]);
84
- const limit = parsedUrl.searchParams.get('limit') || '20';
85
- const branch = parsedUrl.searchParams.get('branch');
86
- const queryParams = new URLSearchParams();
87
- if (limit) queryParams.append('limit', limit);
88
- if (branch) queryParams.append('branch', branch);
89
- const query = queryParams.toString();
90
- const endpoint = `/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/builds${query ? `?${query}` : ''}`;
91
- const response = await proxyRequest(endpoint, {
92
- method: 'GET'
93
- });
94
- sendSuccess(res, {
95
- builds: response.builds || []
96
- });
97
- return true;
98
- } catch (error) {
99
- output.error('Error fetching builds from cloud:', error);
100
- sendError(res, 500, error.message);
101
- return true;
102
- }
103
- }
104
-
105
- // Route: POST /api/cloud/baselines/download - Download baselines from build
106
- if (req.method === 'POST' && pathname === '/api/cloud/baselines/download') {
107
- try {
108
- const body = await parseJsonBody(req);
109
- const {
110
- buildId,
111
- screenshotNames
112
- } = body;
113
- if (!buildId) {
114
- sendError(res, 400, 'buildId is required');
115
- return true;
116
- }
117
-
118
- // Download baselines from the specified build
119
- const response = await proxyRequest('/api/cli/baselines/download', {
120
- method: 'POST',
121
- headers: {
122
- 'Content-Type': 'application/json'
123
- },
124
- body: JSON.stringify({
125
- buildId,
126
- screenshotNames
127
- })
128
- });
129
- sendSuccess(res, {
130
- success: true,
131
- message: `Baselines downloaded from build ${buildId}`,
132
- ...response
133
- });
134
- return true;
135
- } catch (error) {
136
- output.error('Error downloading baselines from cloud:', error);
137
- sendError(res, 500, error.message);
138
- return true;
139
- }
140
- }
141
-
142
- // Unknown cloud route
143
- sendError(res, 404, 'Cloud API endpoint not found');
144
- return true;
145
- };
146
- }
@@ -1,172 +0,0 @@
1
- /**
2
- * Dashboard Router
3
- * Serves the React SPA for all dashboard routes
4
- */
5
-
6
- import { existsSync, readFileSync } from 'node:fs';
7
- import { join } from 'node:path';
8
- import * as output from '../../utils/output.js';
9
- import { sendError, sendHtml, sendSuccess } from '../middleware/response.js';
10
-
11
- // SPA routes that should serve the dashboard HTML
12
- const SPA_ROUTES = ['/', '/stats', '/settings', '/projects', '/builds'];
13
- let COMPARISON_DETAIL_FIELDS = new Set(['boundingBox', 'diffClusters', 'intensityStats']);
14
- function presentComparisonDetails(details = {}) {
15
- return Object.fromEntries(Object.entries(details).filter(([field]) => COMPARISON_DETAIL_FIELDS.has(field)));
16
- }
17
-
18
- /**
19
- * Create dashboard router
20
- * @param {Object} context - Router context
21
- * @param {string} context.workingDir - Working directory for report data
22
- * @returns {Function} Route handler
23
- */
24
- export function createDashboardRouter(context) {
25
- const {
26
- workingDir = process.cwd()
27
- } = context || {};
28
-
29
- /**
30
- * Read baseline metadata from baselines/metadata.json
31
- */
32
- const readBaselineMetadata = () => {
33
- const metadataPath = join(workingDir, '.vizzly', 'baselines', 'metadata.json');
34
- if (!existsSync(metadataPath)) {
35
- return null;
36
- }
37
- try {
38
- return JSON.parse(readFileSync(metadataPath, 'utf8'));
39
- } catch {
40
- return null;
41
- }
42
- };
43
- return async function handleDashboardRoute(req, res, pathname) {
44
- if (req.method !== 'GET') {
45
- return false;
46
- }
47
-
48
- // API endpoint for fetching report data
49
- if (pathname === '/api/report-data') {
50
- const reportDataPath = join(workingDir, '.vizzly', 'report-data.json');
51
- if (existsSync(reportDataPath)) {
52
- try {
53
- const data = JSON.parse(readFileSync(reportDataPath, 'utf8'));
54
- // Include baseline metadata for stats view
55
- data.baseline = readBaselineMetadata();
56
- res.setHeader('Content-Type', 'application/json');
57
- res.statusCode = 200;
58
- res.end(JSON.stringify(data));
59
- return true;
60
- } catch (error) {
61
- output.debug('Error reading report data:', {
62
- error: error.message
63
- });
64
- res.statusCode = 500;
65
- res.end(JSON.stringify({
66
- error: 'Failed to read report data'
67
- }));
68
- return true;
69
- }
70
- } else {
71
- sendSuccess(res, null);
72
- return true;
73
- }
74
- }
75
-
76
- // API endpoint for fetching full comparison details (lightweight + heavy fields)
77
- let comparisonMatch = pathname.match(/^\/api\/comparison\/(.+)$/);
78
- if (comparisonMatch) {
79
- let comparisonId = decodeURIComponent(comparisonMatch[1]);
80
- if (!comparisonId) {
81
- sendError(res, 400, 'Comparison ID is required');
82
- return true;
83
- }
84
- let reportDataPath = join(workingDir, '.vizzly', 'report-data.json');
85
- if (!existsSync(reportDataPath)) {
86
- sendError(res, 404, 'No report data found');
87
- return true;
88
- }
89
- try {
90
- let reportData = JSON.parse(readFileSync(reportDataPath, 'utf8'));
91
- let comparison = (reportData.comparisons || []).find(c => c.id === comparisonId || c.signature === comparisonId || c.name === comparisonId);
92
- if (!comparison) {
93
- sendError(res, 404, 'Comparison not found');
94
- return true;
95
- }
96
-
97
- // Merge with heavy fields from comparison-details.json
98
- let detailsPath = join(workingDir, '.vizzly', 'comparison-details.json');
99
- if (existsSync(detailsPath)) {
100
- try {
101
- let details = JSON.parse(readFileSync(detailsPath, 'utf8'));
102
- let heavy = details[comparison.id];
103
- if (heavy) {
104
- comparison = {
105
- ...comparison,
106
- ...presentComparisonDetails(heavy)
107
- };
108
- }
109
- } catch (error) {
110
- output.debug('Failed to read comparison details:', {
111
- error: error.message
112
- });
113
- }
114
- }
115
- sendSuccess(res, comparison);
116
- } catch (error) {
117
- output.debug('Error reading comparison data:', {
118
- error: error.message
119
- });
120
- sendError(res, 500, 'Failed to read comparison data');
121
- }
122
- return true;
123
- }
124
-
125
- // Serve React SPA for dashboard routes
126
- if (SPA_ROUTES.includes(pathname) || pathname.startsWith('/comparison/')) {
127
- const reportDataPath = join(workingDir, '.vizzly', 'report-data.json');
128
- let reportData = null;
129
- if (existsSync(reportDataPath)) {
130
- try {
131
- const data = readFileSync(reportDataPath, 'utf8');
132
- reportData = JSON.parse(data);
133
- // Include baseline metadata for stats view
134
- reportData.baseline = readBaselineMetadata();
135
- } catch (error) {
136
- output.debug('Could not read report data:', {
137
- error: error.message
138
- });
139
- }
140
- }
141
- const dashboardHtml = `
142
- <!DOCTYPE html>
143
- <html>
144
- <head>
145
- <title>Vizzly Dev Dashboard</title>
146
- <meta charset="utf-8">
147
- <meta name="viewport" content="width=device-width, initial-scale=1">
148
- <link rel="stylesheet" href="/reporter-bundle.css">
149
- </head>
150
- <body>
151
- <div id="vizzly-reporter-root">
152
- <div class="reporter-loading">
153
- <div>
154
- <div class="spinner"></div>
155
- <p>Loading Vizzly Dev Dashboard...</p>
156
- </div>
157
- </div>
158
- </div>
159
-
160
- <script>
161
- // Inject report data if available
162
- ${reportData ? `window.VIZZLY_REPORTER_DATA = ${JSON.stringify(reportData)};` : ''}
163
- </script>
164
- <script src="/reporter-bundle.js"></script>
165
- </body>
166
- </html>`;
167
- sendHtml(res, 200, dashboardHtml);
168
- return true;
169
- }
170
- return false;
171
- };
172
- }
@@ -1,117 +0,0 @@
1
- /**
2
- * Auth Service
3
- * Wraps auth operations for use by the HTTP server
4
- *
5
- * Provides the interface expected by src/server/routers/auth.js:
6
- * - isAuthenticated() - Returns boolean, false if no tokens or API call fails
7
- * - whoami() - Throws if not authenticated or tokens invalid
8
- * - initiateDeviceFlow() - Throws on API error
9
- * - pollDeviceAuthorization(deviceCode) - Returns pending status or tokens, throws on error
10
- * - completeDeviceFlow(tokens) - Saves tokens to global config
11
- * - logout() - Clears local tokens, may warn if server revocation fails
12
- * - authenticatedRequest(endpoint, options) - Throws 'Not authenticated' if no tokens
13
- *
14
- * Error handling:
15
- * - isAuthenticated() never throws, returns false on any error
16
- * - whoami() throws if tokens are missing/invalid (caller should check isAuthenticated first)
17
- * - authenticatedRequest() throws 'Not authenticated' if no access token
18
- * - Device flow methods throw on API errors (network, server errors)
19
- */
20
-
21
- import { createAuthClient } from '../auth/client.js';
22
- import * as authOps from '../auth/operations.js';
23
- import { getApiUrl } from '../utils/environment-config.js';
24
- import { clearAuthTokens, getAuthTokens, saveAuthTokens } from '../utils/global-config.js';
25
-
26
- /**
27
- * Create an auth service instance
28
- * @param {Object} [options]
29
- * @param {string} [options.apiUrl] - API base URL (defaults to VIZZLY_API_URL or https://app.vizzly.dev)
30
- * @param {Object} [options.httpClient] - Injectable HTTP client (for testing)
31
- * @param {Object} [options.tokenStore] - Injectable token store (for testing)
32
- * @returns {Object} Auth service
33
- */
34
- export function createAuthService(options = {}) {
35
- let apiUrl = options.apiUrl || getApiUrl();
36
-
37
- // Create HTTP client for API requests (uses auth client for proper auth handling)
38
- // Allow injection for testing
39
- let httpClient = options.httpClient || createAuthClient({
40
- baseUrl: apiUrl
41
- });
42
-
43
- // Create token store adapter for global config
44
- // Allow injection for testing
45
- let tokenStore = options.tokenStore || {
46
- getTokens: getAuthTokens,
47
- saveTokens: saveAuthTokens,
48
- clearTokens: clearAuthTokens
49
- };
50
- return {
51
- /**
52
- * Check if user is authenticated
53
- * @returns {Promise<boolean>}
54
- */
55
- async isAuthenticated() {
56
- return authOps.isAuthenticated(httpClient, tokenStore);
57
- },
58
- /**
59
- * Get current user information
60
- * @returns {Promise<Object>} User and organization data
61
- */
62
- async whoami() {
63
- return authOps.whoami(httpClient, tokenStore);
64
- },
65
- /**
66
- * Initiate OAuth device flow
67
- * @returns {Promise<Object>} Device code info
68
- */
69
- async initiateDeviceFlow() {
70
- return authOps.initiateDeviceFlow(httpClient);
71
- },
72
- /**
73
- * Poll for device authorization
74
- * @param {string} deviceCode
75
- * @returns {Promise<Object>} Token data or pending status
76
- */
77
- async pollDeviceAuthorization(deviceCode) {
78
- return authOps.pollDeviceAuthorization(httpClient, deviceCode);
79
- },
80
- /**
81
- * Complete device flow and save tokens
82
- * @param {Object} tokens - Token data
83
- * @returns {Promise<Object>}
84
- */
85
- async completeDeviceFlow(tokens) {
86
- return authOps.completeDeviceFlow(tokenStore, tokens);
87
- },
88
- /**
89
- * Logout and revoke tokens
90
- * @returns {Promise<void>}
91
- */
92
- async logout() {
93
- return authOps.logout(httpClient, tokenStore);
94
- },
95
- /**
96
- * Refresh access token
97
- * @returns {Promise<Object>} New tokens
98
- */
99
- async refresh() {
100
- return authOps.refresh(httpClient, tokenStore);
101
- },
102
- /**
103
- * Make an authenticated request to the API
104
- * Used by cloud-proxy router for proxying requests
105
- * @param {string} endpoint - API endpoint
106
- * @param {Object} options - Fetch options
107
- * @returns {Promise<Object>} Response data
108
- */
109
- async authenticatedRequest(endpoint, options = {}) {
110
- let auth = await tokenStore.getTokens();
111
- if (!auth?.accessToken) {
112
- throw new Error('Not authenticated');
113
- }
114
- return httpClient.authenticatedRequest(endpoint, auth.accessToken, options);
115
- }
116
- };
117
- }