@vizzly-testing/cli 0.39.1 → 0.39.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/bin/vizzly.js +1 -1
- package/dist/{cli.js → adapters/cli/cli.js} +41 -35
- package/dist/adapters/cli/setup.js +34 -0
- package/dist/{client → adapters/client}/index.js +6 -7
- package/dist/adapters/http/cloud-proxy.js +29 -0
- package/dist/{server → adapters/http}/http-server.js +12 -13
- package/dist/{server-manager/operations.js → adapters/http/server-lifecycle.js} +10 -13
- package/dist/{server-manager/index.js → adapters/http/server-manager.js} +23 -27
- package/dist/{plugin-api.js → adapters/plugins/plugin-api.js} +1 -1
- package/dist/{plugin-loader.js → adapters/plugins/plugin-loader.js} +1 -1
- package/dist/{services/index.js → adapters/plugins/services.js} +4 -9
- package/dist/adapters/sdk/index.d.ts +89 -0
- package/dist/{sdk → adapters/sdk}/index.js +22 -19
- package/dist/{commands/api.js → domains/api/api.commands.js} +3 -3
- package/dist/{auth/client.js → domains/auth/auth-client.js} +4 -3
- package/dist/domains/auth/auth-errors.js +50 -0
- package/dist/domains/auth/auth-headers.js +41 -0
- package/dist/domains/auth/auth.normalizers.js +50 -0
- package/dist/{server/routers/auth.js → domains/auth/auth.routes.js} +9 -9
- package/dist/domains/auth/auth.rules.js +19 -0
- package/dist/domains/auth/auth.serializers.js +32 -0
- package/dist/{auth/operations.js → domains/auth/auth.service.js} +115 -17
- package/dist/domains/auth/auth.validations.js +21 -0
- package/dist/{api/client.js → domains/auth/cloud-client.js} +11 -18
- package/dist/{commands/login.js → domains/auth/login.commands.js} +11 -8
- package/dist/{commands/logout.js → domains/auth/logout.commands.js} +6 -4
- package/dist/domains/auth/token-store.js +81 -0
- package/dist/{commands/whoami.js → domains/auth/whoami.commands.js} +6 -3
- package/dist/domains/baselines/baseline-download.js +192 -0
- package/dist/domains/baselines/baseline-write.js +22 -0
- package/dist/{server/routers/baseline.js → domains/baselines/baseline.routes.js} +3 -3
- package/dist/{tdd/services/baseline-manager.js → domains/baselines/baseline.service.js} +1 -39
- package/dist/{commands/baselines.js → domains/baselines/baselines.commands.js} +3 -3
- package/dist/domains/baselines/cloud-baselines.js +9 -0
- package/dist/domains/baselines/cloud-baselines.routes.js +42 -0
- package/dist/domains/builds/build.normalizers.js +35 -0
- package/dist/domains/builds/build.serializers.js +164 -0
- package/dist/domains/builds/build.service.js +119 -0
- package/dist/domains/builds/build.validations.js +39 -0
- package/dist/{commands/builds.js → domains/builds/builds.commands.js} +6 -74
- package/dist/domains/builds/cloud-builds.routes.js +31 -0
- package/dist/{commands/finalize.js → domains/builds/finalize.commands.js} +5 -17
- package/dist/{commands/status.js → domains/builds/status.commands.js} +9 -120
- package/dist/domains/comparisons/cloud-comparisons.js +43 -0
- package/dist/{tdd/services/comparison-service.js → domains/comparisons/comparison.service.js} +1 -1
- package/dist/{commands/comparisons.js → domains/comparisons/comparisons.commands.js} +7 -5
- package/dist/domains/config/config-defaults.js +27 -0
- package/dist/{utils → domains/config}/config-discovery.js +1 -9
- package/dist/{utils → domains/config}/config-loader.js +10 -9
- package/dist/{commands/config-cmd.js → domains/config/config.commands.js} +4 -4
- package/dist/{types → domains/config}/config.d.ts +3 -3
- package/dist/domains/config/config.normalizers.js +126 -0
- package/dist/{server/routers/config.js → domains/config/config.routes.js} +13 -13
- package/dist/domains/config/config.rules.js +47 -0
- package/dist/domains/config/config.serializers.js +202 -0
- package/dist/domains/config/config.service.js +625 -0
- package/dist/{utils/config-schema.js → domains/config/config.validations.js} +9 -9
- package/dist/{utils/environment-config.js → domains/config/environment.js} +1 -8
- package/dist/domains/context/compact-context.validations.js +60 -0
- package/dist/domains/context/context-presentation.js +368 -0
- package/dist/domains/context/context.commands.js +385 -0
- package/dist/domains/context/context.serializers.js +157 -0
- package/dist/domains/context/context.service.js +76 -0
- package/dist/domains/context/context.validations.js +100 -0
- package/dist/{context → domains/context}/local-workspace-provider.js +1 -1
- package/dist/{commands/doctor.js → domains/diagnostics/doctor.commands.js} +6 -5
- package/dist/{server/routers/health.js → domains/diagnostics/health.routes.js} +11 -20
- package/dist/{commands/preview.js → domains/previews/preview.commands.js} +9 -7
- package/dist/domains/previews/preview.service.js +37 -0
- package/dist/domains/projects/cloud-projects.routes.js +28 -0
- package/dist/{commands/orgs.js → domains/projects/orgs.commands.js} +6 -8
- package/dist/{utils → domains/projects}/project-binding.js +3 -3
- package/dist/domains/projects/project-errors.js +61 -0
- package/dist/{utils → domains/projects}/project-link-store.js +2 -2
- package/dist/domains/projects/project-requests.js +61 -0
- package/dist/{commands/project.js → domains/projects/project.commands.js} +9 -41
- package/dist/domains/projects/project.normalizers.js +69 -0
- package/dist/{project/operations.js → domains/projects/project.service.js} +89 -16
- package/dist/domains/projects/project.validations.js +31 -0
- package/dist/{commands/projects.js → domains/projects/projects.commands.js} +6 -8
- package/dist/{server/routers/projects.js → domains/projects/projects.routes.js} +5 -5
- package/dist/domains/reports/dashboard.routes.js +96 -0
- package/dist/{server/routers/events.js → domains/reports/events.routes.js} +14 -36
- package/dist/domains/reports/report-assets.routes.js +81 -0
- package/dist/domains/reports/report-state.js +51 -0
- package/dist/domains/reports/report-store.js +85 -0
- package/dist/domains/reports/report.serializers.js +99 -0
- package/dist/{services/static-report-generator.js → domains/reports/report.service.js} +11 -33
- package/dist/{commands/review.js → domains/review/review.commands.js} +4 -4
- package/dist/domains/runs/run-environment.js +69 -0
- package/dist/{services/test-runner.js → domains/runs/run-events.js} +11 -9
- package/dist/{commands/run.js → domains/runs/run.commands.js} +16 -13
- package/dist/domains/runs/run.normalizers.js +8 -0
- package/dist/domains/runs/run.rules.js +56 -0
- package/dist/domains/runs/run.serializers.js +101 -0
- package/dist/{test-runner/operations.js → domains/runs/run.service.js} +35 -7
- package/dist/domains/runs/run.validations.js +17 -0
- package/dist/{utils/session.js → domains/runs/session-store.js} +3 -2
- package/dist/{server/handlers/api-handler.js → domains/screenshots/cloud-handler.js} +4 -4
- package/dist/domains/screenshots/cloud-screenshots.js +109 -0
- package/dist/{utils/file-helpers.js → domains/screenshots/image-files.js} +1 -1
- package/dist/domains/screenshots/screenshot-hash.js +53 -0
- package/dist/domains/screenshots/screenshot-server-options.js +28 -0
- package/dist/{screenshot-server/operations.js → domains/screenshots/screenshot-server.js} +14 -1
- package/dist/domains/screenshots/screenshot.normalizers.js +40 -0
- package/dist/{server/routers/screenshot.js → domains/screenshots/screenshot.routes.js} +4 -4
- package/dist/domains/screenshots/screenshot.serializers.js +146 -0
- package/dist/{utils/security.js → domains/screenshots/screenshot.validations.js} +20 -70
- package/dist/{init/agents.js → domains/setup/agent-setup.js} +4 -5
- package/dist/{commands/init.js → domains/setup/init.commands.js} +10 -41
- package/dist/{server/handlers/tdd-handler.js → domains/tdd/screenshot-handler.js} +46 -281
- package/dist/{commands/tdd-daemon.js → domains/tdd/tdd-daemon.commands.js} +6 -6
- package/dist/{commands/tdd-screenshots.js → domains/tdd/tdd-screenshots.commands.js} +5 -5
- package/dist/{commands/tdd.js → domains/tdd/tdd.commands.js} +16 -15
- package/dist/{tdd/tdd-service.js → domains/tdd/tdd.service.js} +81 -405
- package/dist/{utils → domains/updates}/cli-update.js +4 -6
- package/dist/{utils → domains/updates}/update-notifications.js +7 -7
- package/dist/domains/uploads/upload-defaults.js +4 -0
- package/dist/domains/uploads/upload-files.js +32 -0
- package/dist/domains/uploads/upload-settings.js +21 -0
- package/dist/domains/uploads/upload-timing.js +18 -0
- package/dist/{commands/upload.js → domains/uploads/upload.commands.js} +11 -9
- package/dist/domains/uploads/upload.normalizers.js +60 -0
- package/dist/domains/uploads/upload.serializers.js +179 -0
- package/dist/{uploader/operations.js → domains/uploads/upload.service.js} +85 -28
- package/dist/domains/uploads/upload.validations.js +77 -0
- package/dist/index.js +12 -12
- package/dist/reporter/reporter-bundle.iife.js +86 -86
- package/dist/reporter-ssr/ssr-entry.js +2 -2
- package/dist/shared/cloud-protocol.js +171 -0
- package/dist/{utils → shared}/file-changes.js +1 -1
- package/dist/shared/files.js +9 -0
- package/dist/{utils → shared}/global-config.js +7 -78
- package/dist/shared/log-level.js +7 -0
- package/dist/{utils → shared}/output.js +1 -1
- package/dist/{utils → shared}/package-info.js +5 -4
- package/dist/shared/paths.js +57 -0
- package/package.json +23 -22
- package/dist/api/core.js +0 -382
- package/dist/api/endpoints.js +0 -421
- package/dist/api/index.js +0 -19
- package/dist/auth/core.js +0 -237
- package/dist/auth/index.js +0 -31
- package/dist/commands/context.js +0 -1066
- package/dist/config/core.js +0 -439
- package/dist/config/operations.js +0 -327
- package/dist/project/core.js +0 -211
- package/dist/screenshot-server/core.js +0 -161
- package/dist/screenshot-server/index.js +0 -11
- package/dist/server/routers/assets.js +0 -95
- package/dist/server/routers/cloud-proxy.js +0 -146
- package/dist/server/routers/dashboard.js +0 -172
- package/dist/services/auth-service.js +0 -117
- package/dist/services/config-service.js +0 -313
- package/dist/services/project-service.js +0 -88
- package/dist/test-runner/core.js +0 -293
- package/dist/test-runner/index.js +0 -13
- package/dist/types/sdk.d.ts +0 -186
- package/dist/uploader/core.js +0 -432
- package/dist/uploader/index.js +0 -88
- /package/dist/{types/client.d.ts → adapters/client/index.d.ts} +0 -0
- /package/dist/{server → adapters/http}/middleware/cors.js +0 -0
- /package/dist/{server → adapters/http}/middleware/json-parser.js +0 -0
- /package/dist/{server → adapters/http}/middleware/response.js +0 -0
- /package/dist/{server-manager/core.js → adapters/http/server-options.js} +0 -0
- /package/dist/{utils/login-poll-wait.js → domains/auth/login-wait.js} +0 -0
- /package/dist/{tdd/metadata → domains/baselines}/baseline-metadata.js +0 -0
- /package/dist/{services/build-manager.js → domains/builds/build-info.js} +0 -0
- /package/dist/{tdd/services/result-service.js → domains/comparisons/comparison-results.js} +0 -0
- /package/dist/{init/core.js → domains/config/config-plan.js} +0 -0
- /package/dist/{utils/config-helpers.js → domains/config/config.js} +0 -0
- /package/dist/{utils/visual-context-normalizers.js → domains/context/context.normalizers.js} +0 -0
- /package/dist/{context → domains/context}/provider-resolver.js +0 -0
- /package/dist/{utils/context.js → domains/diagnostics/environment-context.js} +0 -0
- /package/dist/{utils/report-data.js → domains/reports/report.normalizers.js} +0 -0
- /package/dist/{utils/image-input-detector.js → domains/screenshots/image-input.js} +0 -0
- /package/dist/{tdd/core/signature.js → domains/screenshots/screenshot-identity.js} +0 -0
- /package/dist/{utils → domains/screenshots}/screenshot-options.js +0 -0
- /package/dist/{init/prompts.js → domains/setup/setup-prompts.js} +0 -0
- /package/dist/{tdd → domains/tdd}/runtime-context.js +0 -0
- /package/dist/{tdd → domains/tdd}/server-registry.js +0 -0
- /package/dist/{commands/tdd-daemon-process.js → domains/tdd/tdd-daemon-process.commands.js} +0 -0
- /package/dist/{utils → domains/updates}/update-check.js +0 -0
- /package/dist/{types/index.d.ts → index.d.ts} +0 -0
- /package/dist/{utils → shared}/api-url.js +0 -0
- /package/dist/{utils → shared}/async-utils.js +0 -0
- /package/dist/{utils → shared}/browser.js +0 -0
- /package/dist/{utils → shared}/ci-env.js +0 -0
- /package/dist/{utils → shared}/colors.js +0 -0
- /package/dist/{utils → shared}/fetch-utils.js +0 -0
- /package/dist/{utils → shared}/git.js +0 -0
- /package/dist/{utils → shared}/patterns.js +0 -0
- /package/dist/{utils → shared}/secret-store.js +0 -0
- /package/dist/{errors → shared}/vizzly-error.js +0 -0
|
@@ -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
|
-
}
|