@vizzly-testing/cli 0.17.0 → 0.19.0
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/dist/cli.js +87 -59
- package/dist/client/index.js +6 -6
- package/dist/commands/doctor.js +15 -15
- package/dist/commands/finalize.js +7 -7
- package/dist/commands/init.js +28 -28
- package/dist/commands/login.js +23 -23
- package/dist/commands/logout.js +4 -4
- package/dist/commands/project.js +36 -36
- package/dist/commands/run.js +33 -33
- package/dist/commands/status.js +14 -14
- package/dist/commands/tdd-daemon.js +43 -43
- package/dist/commands/tdd.js +26 -26
- package/dist/commands/upload.js +32 -32
- package/dist/commands/whoami.js +12 -12
- package/dist/index.js +9 -14
- package/dist/plugin-api.js +43 -0
- package/dist/plugin-loader.js +28 -28
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +19 -19
- package/dist/sdk/index.js +33 -35
- package/dist/server/handlers/api-handler.js +4 -4
- package/dist/server/handlers/tdd-handler.js +22 -21
- package/dist/server/http-server.js +21 -22
- package/dist/server/middleware/json-parser.js +1 -1
- package/dist/server/routers/assets.js +14 -14
- package/dist/server/routers/auth.js +14 -14
- package/dist/server/routers/baseline.js +8 -8
- package/dist/server/routers/cloud-proxy.js +15 -15
- package/dist/server/routers/config.js +11 -11
- package/dist/server/routers/dashboard.js +11 -11
- package/dist/server/routers/health.js +4 -4
- package/dist/server/routers/projects.js +19 -19
- package/dist/server/routers/screenshot.js +9 -9
- package/dist/services/api-service.js +16 -16
- package/dist/services/auth-service.js +17 -17
- package/dist/services/build-manager.js +3 -3
- package/dist/services/config-service.js +32 -32
- package/dist/services/html-report-generator.js +8 -8
- package/dist/services/index.js +11 -11
- package/dist/services/project-service.js +19 -19
- package/dist/services/report-generator/report.css +3 -3
- package/dist/services/report-generator/viewer.js +25 -23
- package/dist/services/screenshot-server.js +1 -1
- package/dist/services/server-manager.js +5 -5
- package/dist/services/static-report-generator.js +14 -14
- package/dist/services/tdd-service.js +152 -110
- package/dist/services/test-runner.js +3 -3
- package/dist/services/uploader.js +10 -8
- package/dist/types/config.d.ts +2 -1
- package/dist/types/index.d.ts +95 -1
- package/dist/types/sdk.d.ts +1 -1
- package/dist/utils/browser.js +3 -3
- package/dist/utils/build-history.js +12 -12
- package/dist/utils/config-loader.js +17 -17
- package/dist/utils/config-schema.js +6 -6
- package/dist/utils/environment-config.js +11 -0
- package/dist/utils/fetch-utils.js +2 -2
- package/dist/utils/file-helpers.js +2 -2
- package/dist/utils/git.js +3 -6
- package/dist/utils/global-config.js +28 -25
- package/dist/utils/output.js +136 -28
- package/dist/utils/package-info.js +3 -3
- package/dist/utils/security.js +12 -12
- package/docs/api-reference.md +52 -23
- package/docs/plugins.md +60 -25
- package/package.json +9 -13
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* - Returns proxied response to React app
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
13
|
-
import { sendSuccess, sendError, sendServiceUnavailable } from '../middleware/response.js';
|
|
14
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
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Create cloud proxy router
|
|
@@ -53,7 +53,7 @@ export function createCloudProxyRouter({
|
|
|
53
53
|
// Route: GET /api/cloud/projects - List user's projects
|
|
54
54
|
if (req.method === 'GET' && pathname === '/api/cloud/projects') {
|
|
55
55
|
try {
|
|
56
|
-
|
|
56
|
+
const response = await proxyRequest('/api/cli/projects', {
|
|
57
57
|
method: 'GET'
|
|
58
58
|
});
|
|
59
59
|
sendSuccess(res, {
|
|
@@ -76,19 +76,19 @@ export function createCloudProxyRouter({
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
// Route: GET /api/cloud/organizations/:org/projects/:project/builds
|
|
79
|
-
|
|
79
|
+
const buildsMatch = pathname.match(/^\/api\/cloud\/organizations\/([^/]+)\/projects\/([^/]+)\/builds$/);
|
|
80
80
|
if (req.method === 'GET' && buildsMatch) {
|
|
81
81
|
try {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
87
|
if (limit) queryParams.append('limit', limit);
|
|
88
88
|
if (branch) queryParams.append('branch', branch);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
const query = queryParams.toString();
|
|
90
|
+
const endpoint = `/api/cli/organizations/${organizationSlug}/projects/${projectSlug}/builds${query ? `?${query}` : ''}`;
|
|
91
|
+
const response = await proxyRequest(endpoint, {
|
|
92
92
|
method: 'GET'
|
|
93
93
|
});
|
|
94
94
|
sendSuccess(res, {
|
|
@@ -105,8 +105,8 @@ export function createCloudProxyRouter({
|
|
|
105
105
|
// Route: POST /api/cloud/baselines/download - Download baselines from build
|
|
106
106
|
if (req.method === 'POST' && pathname === '/api/cloud/baselines/download') {
|
|
107
107
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
const body = await parseJsonBody(req);
|
|
109
|
+
const {
|
|
110
110
|
buildId,
|
|
111
111
|
screenshotNames
|
|
112
112
|
} = body;
|
|
@@ -116,7 +116,7 @@ export function createCloudProxyRouter({
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// Download baselines from the specified build
|
|
119
|
-
|
|
119
|
+
const response = await proxyRequest('/api/cli/baselines/download', {
|
|
120
120
|
method: 'POST',
|
|
121
121
|
headers: {
|
|
122
122
|
'Content-Type': 'application/json'
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Handles configuration management endpoints
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
7
|
-
import { sendSuccess, sendError, sendServiceUnavailable } from '../middleware/response.js';
|
|
8
6
|
import * as output from '../../utils/output.js';
|
|
7
|
+
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
8
|
+
import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Create config router
|
|
@@ -26,7 +26,7 @@ export function createConfigRouter({
|
|
|
26
26
|
// Get merged config with sources
|
|
27
27
|
if (req.method === 'GET' && pathname === '/api/config') {
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
const configData = await configService.getConfig('merged');
|
|
30
30
|
sendSuccess(res, configData);
|
|
31
31
|
return true;
|
|
32
32
|
} catch (error) {
|
|
@@ -41,7 +41,7 @@ export function createConfigRouter({
|
|
|
41
41
|
// Get project-level config
|
|
42
42
|
if (req.method === 'GET' && pathname === '/api/config/project') {
|
|
43
43
|
try {
|
|
44
|
-
|
|
44
|
+
const configData = await configService.getConfig('project');
|
|
45
45
|
sendSuccess(res, configData);
|
|
46
46
|
return true;
|
|
47
47
|
} catch (error) {
|
|
@@ -56,7 +56,7 @@ export function createConfigRouter({
|
|
|
56
56
|
// Get global config
|
|
57
57
|
if (req.method === 'GET' && pathname === '/api/config/global') {
|
|
58
58
|
try {
|
|
59
|
-
|
|
59
|
+
const configData = await configService.getConfig('global');
|
|
60
60
|
sendSuccess(res, configData);
|
|
61
61
|
return true;
|
|
62
62
|
} catch (error) {
|
|
@@ -71,8 +71,8 @@ export function createConfigRouter({
|
|
|
71
71
|
// Update project config
|
|
72
72
|
if (req.method === 'POST' && pathname === '/api/config/project') {
|
|
73
73
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
const body = await parseJsonBody(req);
|
|
75
|
+
const result = await configService.updateConfig('project', body);
|
|
76
76
|
sendSuccess(res, {
|
|
77
77
|
success: true,
|
|
78
78
|
...result
|
|
@@ -90,8 +90,8 @@ export function createConfigRouter({
|
|
|
90
90
|
// Update global config
|
|
91
91
|
if (req.method === 'POST' && pathname === '/api/config/global') {
|
|
92
92
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
const body = await parseJsonBody(req);
|
|
94
|
+
const result = await configService.updateConfig('global', body);
|
|
95
95
|
sendSuccess(res, {
|
|
96
96
|
success: true,
|
|
97
97
|
...result
|
|
@@ -109,8 +109,8 @@ export function createConfigRouter({
|
|
|
109
109
|
// Validate config
|
|
110
110
|
if (req.method === 'POST' && pathname === '/api/config/validate') {
|
|
111
111
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const body = await parseJsonBody(req);
|
|
113
|
+
const result = await configService.validateConfig(body);
|
|
114
114
|
sendSuccess(res, result);
|
|
115
115
|
return true;
|
|
116
116
|
} catch (error) {
|
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
* Serves the React SPA for all dashboard routes
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { join } from 'path';
|
|
8
|
-
import { sendHtml, sendSuccess } from '../middleware/response.js';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
9
8
|
import * as output from '../../utils/output.js';
|
|
9
|
+
import { sendHtml, sendSuccess } from '../middleware/response.js';
|
|
10
10
|
|
|
11
11
|
// SPA routes that should serve the dashboard HTML
|
|
12
|
-
|
|
12
|
+
const SPA_ROUTES = ['/', '/dashboard', '/stats', '/settings', '/projects', '/builds'];
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Create dashboard router
|
|
@@ -24,10 +24,10 @@ export function createDashboardRouter() {
|
|
|
24
24
|
|
|
25
25
|
// API endpoint for fetching report data
|
|
26
26
|
if (pathname === '/api/report-data') {
|
|
27
|
-
|
|
27
|
+
const reportDataPath = join(process.cwd(), '.vizzly', 'report-data.json');
|
|
28
28
|
if (existsSync(reportDataPath)) {
|
|
29
29
|
try {
|
|
30
|
-
|
|
30
|
+
const data = readFileSync(reportDataPath, 'utf8');
|
|
31
31
|
res.setHeader('Content-Type', 'application/json');
|
|
32
32
|
res.statusCode = 200;
|
|
33
33
|
res.end(data);
|
|
@@ -50,8 +50,8 @@ export function createDashboardRouter() {
|
|
|
50
50
|
|
|
51
51
|
// API endpoint for real-time status
|
|
52
52
|
if (pathname === '/api/status') {
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const reportDataPath = join(process.cwd(), '.vizzly', 'report-data.json');
|
|
54
|
+
const baselineMetadataPath = join(process.cwd(), '.vizzly', 'baselines', 'metadata.json');
|
|
55
55
|
let reportData = null;
|
|
56
56
|
let baselineInfo = null;
|
|
57
57
|
if (existsSync(reportDataPath)) {
|
|
@@ -84,11 +84,11 @@ export function createDashboardRouter() {
|
|
|
84
84
|
|
|
85
85
|
// Serve React SPA for dashboard routes
|
|
86
86
|
if (SPA_ROUTES.includes(pathname) || pathname.startsWith('/comparison/')) {
|
|
87
|
-
|
|
87
|
+
const reportDataPath = join(process.cwd(), '.vizzly', 'report-data.json');
|
|
88
88
|
let reportData = null;
|
|
89
89
|
if (existsSync(reportDataPath)) {
|
|
90
90
|
try {
|
|
91
|
-
|
|
91
|
+
const data = readFileSync(reportDataPath, 'utf8');
|
|
92
92
|
reportData = JSON.parse(data);
|
|
93
93
|
} catch (error) {
|
|
94
94
|
output.debug('Could not read report data:', {
|
|
@@ -96,7 +96,7 @@ export function createDashboardRouter() {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
-
|
|
99
|
+
const dashboardHtml = `
|
|
100
100
|
<!DOCTYPE html>
|
|
101
101
|
<html>
|
|
102
102
|
<head>
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Health check endpoint with diagnostics
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { join } from 'path';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
8
|
import { sendSuccess } from '../middleware/response.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -22,8 +22,8 @@ export function createHealthRouter({
|
|
|
22
22
|
if (req.method !== 'GET' || pathname !== '/health') {
|
|
23
23
|
return false;
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const reportDataPath = join(process.cwd(), '.vizzly', 'report-data.json');
|
|
26
|
+
const baselineMetadataPath = join(process.cwd(), '.vizzly', 'baselines', 'metadata.json');
|
|
27
27
|
let reportData = null;
|
|
28
28
|
let baselineInfo = null;
|
|
29
29
|
if (existsSync(reportDataPath)) {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Handles project management and builds endpoints
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
7
|
-
import { sendSuccess, sendError, sendServiceUnavailable } from '../middleware/response.js';
|
|
8
6
|
import * as output from '../../utils/output.js';
|
|
7
|
+
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
8
|
+
import { sendError, sendServiceUnavailable, sendSuccess } from '../middleware/response.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Create projects router
|
|
@@ -26,7 +26,7 @@ export function createProjectsRouter({
|
|
|
26
26
|
// List all projects from API
|
|
27
27
|
if (req.method === 'GET' && pathname === '/api/projects') {
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
const projects = await projectService.listProjects();
|
|
30
30
|
sendSuccess(res, {
|
|
31
31
|
projects
|
|
32
32
|
});
|
|
@@ -43,7 +43,7 @@ export function createProjectsRouter({
|
|
|
43
43
|
// List project directory mappings
|
|
44
44
|
if (req.method === 'GET' && pathname === '/api/projects/mappings') {
|
|
45
45
|
try {
|
|
46
|
-
|
|
46
|
+
const mappings = await projectService.listMappings();
|
|
47
47
|
sendSuccess(res, {
|
|
48
48
|
mappings
|
|
49
49
|
});
|
|
@@ -60,15 +60,15 @@ export function createProjectsRouter({
|
|
|
60
60
|
// Create or update project mapping
|
|
61
61
|
if (req.method === 'POST' && pathname === '/api/projects/mappings') {
|
|
62
62
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const body = await parseJsonBody(req);
|
|
64
|
+
const {
|
|
65
65
|
directory,
|
|
66
66
|
projectSlug,
|
|
67
67
|
organizationSlug,
|
|
68
68
|
token,
|
|
69
69
|
projectName
|
|
70
70
|
} = body;
|
|
71
|
-
|
|
71
|
+
const mapping = await projectService.createMapping(directory, {
|
|
72
72
|
projectSlug,
|
|
73
73
|
organizationSlug,
|
|
74
74
|
token,
|
|
@@ -91,7 +91,7 @@ export function createProjectsRouter({
|
|
|
91
91
|
// Delete project mapping
|
|
92
92
|
if (req.method === 'DELETE' && pathname.startsWith('/api/projects/mappings/')) {
|
|
93
93
|
try {
|
|
94
|
-
|
|
94
|
+
const directory = decodeURIComponent(pathname.replace('/api/projects/mappings/', ''));
|
|
95
95
|
await projectService.removeMapping(directory);
|
|
96
96
|
sendSuccess(res, {
|
|
97
97
|
success: true,
|
|
@@ -114,15 +114,15 @@ export function createProjectsRouter({
|
|
|
114
114
|
return true;
|
|
115
115
|
}
|
|
116
116
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
const currentDir = process.cwd();
|
|
118
|
+
const mapping = await projectService.getMapping(currentDir);
|
|
119
119
|
if (!mapping || !mapping.projectSlug || !mapping.organizationSlug) {
|
|
120
120
|
sendError(res, 400, 'No project configured for this directory');
|
|
121
121
|
return true;
|
|
122
122
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
const limit = parseInt(parsedUrl.searchParams.get('limit') || '10', 10);
|
|
124
|
+
const branch = parsedUrl.searchParams.get('branch') || undefined;
|
|
125
|
+
const builds = await projectService.getRecentBuilds(mapping.projectSlug, mapping.organizationSlug, {
|
|
126
126
|
limit,
|
|
127
127
|
branch
|
|
128
128
|
});
|
|
@@ -140,14 +140,14 @@ export function createProjectsRouter({
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
// Get builds for a specific project (used by /builds page)
|
|
143
|
-
|
|
143
|
+
const projectBuildsMatch = pathname.match(/^\/api\/projects\/([^/]+)\/([^/]+)\/builds$/);
|
|
144
144
|
if (req.method === 'GET' && projectBuildsMatch) {
|
|
145
145
|
try {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
146
|
+
const organizationSlug = decodeURIComponent(projectBuildsMatch[1]);
|
|
147
|
+
const projectSlug = decodeURIComponent(projectBuildsMatch[2]);
|
|
148
|
+
const limit = parseInt(parsedUrl.searchParams.get('limit') || '20', 10);
|
|
149
|
+
const branch = parsedUrl.searchParams.get('branch') || undefined;
|
|
150
|
+
const builds = await projectService.getRecentBuilds(projectSlug, organizationSlug, {
|
|
151
151
|
limit,
|
|
152
152
|
branch
|
|
153
153
|
});
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Handles screenshot uploads and legacy baseline accept
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
7
|
-
import { sendJson, sendError } from '../middleware/response.js';
|
|
8
6
|
import * as output from '../../utils/output.js';
|
|
7
|
+
import { parseJsonBody } from '../middleware/json-parser.js';
|
|
8
|
+
import { sendError, sendJson } from '../middleware/response.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Create screenshot router
|
|
@@ -26,8 +26,8 @@ export function createScreenshotRouter({
|
|
|
26
26
|
// Main screenshot upload endpoint
|
|
27
27
|
if (pathname === '/screenshot') {
|
|
28
28
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const body = await parseJsonBody(req);
|
|
30
|
+
const {
|
|
31
31
|
buildId,
|
|
32
32
|
name,
|
|
33
33
|
properties,
|
|
@@ -39,8 +39,8 @@ export function createScreenshotRouter({
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// Use buildId from request body, or fall back to server's buildId
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
const effectiveBuildId = buildId || defaultBuildId;
|
|
43
|
+
const result = await screenshotHandler.handleScreenshot(effectiveBuildId, name, image, properties);
|
|
44
44
|
sendJson(res, result.statusCode, result.body);
|
|
45
45
|
return true;
|
|
46
46
|
} catch (error) {
|
|
@@ -55,8 +55,8 @@ export function createScreenshotRouter({
|
|
|
55
55
|
// Legacy accept-baseline endpoint
|
|
56
56
|
if (pathname === '/accept-baseline') {
|
|
57
57
|
try {
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const body = await parseJsonBody(req);
|
|
59
|
+
const {
|
|
60
60
|
id
|
|
61
61
|
} = body;
|
|
62
62
|
if (!id) {
|
|
@@ -64,7 +64,7 @@ export function createScreenshotRouter({
|
|
|
64
64
|
return true;
|
|
65
65
|
}
|
|
66
66
|
if (screenshotHandler.acceptBaseline) {
|
|
67
|
-
|
|
67
|
+
const result = await screenshotHandler.acceptBaseline(id);
|
|
68
68
|
sendJson(res, 200, {
|
|
69
69
|
success: true,
|
|
70
70
|
...result
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
* Handles HTTP requests to the Vizzly API
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
10
|
-
import { getApiUrl, getApiToken, getUserAgent } from '../utils/environment-config.js';
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
import { URLSearchParams } from 'node:url';
|
|
8
|
+
import { AuthError, VizzlyError } from '../errors/vizzly-error.js';
|
|
9
|
+
import { getApiToken, getApiUrl, getUserAgent } from '../utils/environment-config.js';
|
|
11
10
|
import { getAuthTokens, saveAuthTokens } from '../utils/global-config.js';
|
|
11
|
+
import { getPackageVersion } from '../utils/package-info.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* ApiService class for direct API communication
|
|
@@ -66,11 +66,11 @@ export class ApiService {
|
|
|
66
66
|
// Handle authentication errors with automatic token refresh
|
|
67
67
|
if (response.status === 401 && !isRetry) {
|
|
68
68
|
// Attempt to refresh token if we have refresh token in global config
|
|
69
|
-
|
|
70
|
-
if (auth
|
|
69
|
+
const auth = await getAuthTokens();
|
|
70
|
+
if (auth?.refreshToken) {
|
|
71
71
|
try {
|
|
72
72
|
// Attempt token refresh
|
|
73
|
-
|
|
73
|
+
const refreshResponse = await fetch(`${this.baseUrl}/api/auth/cli/refresh`, {
|
|
74
74
|
method: 'POST',
|
|
75
75
|
headers: {
|
|
76
76
|
'Content-Type': 'application/json',
|
|
@@ -81,7 +81,7 @@ export class ApiService {
|
|
|
81
81
|
})
|
|
82
82
|
});
|
|
83
83
|
if (refreshResponse.ok) {
|
|
84
|
-
|
|
84
|
+
const refreshData = await refreshResponse.json();
|
|
85
85
|
|
|
86
86
|
// Save new tokens to global config
|
|
87
87
|
await saveAuthTokens({
|
|
@@ -128,7 +128,7 @@ export class ApiService {
|
|
|
128
128
|
* @returns {Promise<Object>} Comparison data
|
|
129
129
|
*/
|
|
130
130
|
async getComparison(comparisonId) {
|
|
131
|
-
|
|
131
|
+
const response = await this.request(`/api/sdk/comparisons/${comparisonId}`);
|
|
132
132
|
return response.comparison;
|
|
133
133
|
}
|
|
134
134
|
|
|
@@ -145,7 +145,7 @@ export class ApiService {
|
|
|
145
145
|
if (!name || typeof name !== 'string') {
|
|
146
146
|
throw new VizzlyError('name is required and must be a non-empty string');
|
|
147
147
|
}
|
|
148
|
-
|
|
148
|
+
const {
|
|
149
149
|
branch,
|
|
150
150
|
limit = 50,
|
|
151
151
|
offset = 0
|
|
@@ -274,7 +274,7 @@ export class ApiService {
|
|
|
274
274
|
|
|
275
275
|
// Check if this SHA with signature already exists
|
|
276
276
|
const checkResult = await this.checkShas(screenshotCheck, buildId);
|
|
277
|
-
if (checkResult.existing
|
|
277
|
+
if (checkResult.existing?.includes(sha256)) {
|
|
278
278
|
// File already exists with same signature, screenshot record was automatically created
|
|
279
279
|
const screenshot = checkResult.screenshots?.find(s => s.sha256 === sha256);
|
|
280
280
|
return {
|
|
@@ -366,13 +366,13 @@ export class ApiService {
|
|
|
366
366
|
* @returns {Promise<Object>} Hotspot analysis data
|
|
367
367
|
*/
|
|
368
368
|
async getScreenshotHotspots(screenshotName, options = {}) {
|
|
369
|
-
|
|
369
|
+
const {
|
|
370
370
|
windowSize = 20
|
|
371
371
|
} = options;
|
|
372
|
-
|
|
372
|
+
const queryParams = new URLSearchParams({
|
|
373
373
|
windowSize: String(windowSize)
|
|
374
374
|
});
|
|
375
|
-
|
|
375
|
+
const encodedName = encodeURIComponent(screenshotName);
|
|
376
376
|
return this.request(`/api/sdk/screenshots/${encodedName}/hotspots?${queryParams}`);
|
|
377
377
|
}
|
|
378
378
|
|
|
@@ -385,7 +385,7 @@ export class ApiService {
|
|
|
385
385
|
* @returns {Promise<Object>} Hotspots keyed by screenshot name
|
|
386
386
|
*/
|
|
387
387
|
async getBatchHotspots(screenshotNames, options = {}) {
|
|
388
|
-
|
|
388
|
+
const {
|
|
389
389
|
windowSize = 20
|
|
390
390
|
} = options;
|
|
391
391
|
return this.request('/api/sdk/screenshots/hotspots', {
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { AuthError, VizzlyError } from '../errors/vizzly-error.js';
|
|
7
7
|
import { getApiUrl } from '../utils/environment-config.js';
|
|
8
|
+
import { clearAuthTokens, getAuthTokens, saveAuthTokens } from '../utils/global-config.js';
|
|
8
9
|
import { getPackageVersion } from '../utils/package-info.js';
|
|
9
|
-
import { saveAuthTokens, clearAuthTokens, getAuthTokens } from '../utils/global-config.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* AuthService class for CLI authentication
|
|
@@ -24,12 +24,12 @@ export class AuthService {
|
|
|
24
24
|
* @returns {Promise<Object>} Response data
|
|
25
25
|
*/
|
|
26
26
|
async request(endpoint, options = {}) {
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
28
|
+
const headers = {
|
|
29
29
|
'User-Agent': this.userAgent,
|
|
30
30
|
...options.headers
|
|
31
31
|
};
|
|
32
|
-
|
|
32
|
+
const response = await fetch(url, {
|
|
33
33
|
...options,
|
|
34
34
|
headers
|
|
35
35
|
});
|
|
@@ -37,8 +37,8 @@ export class AuthService {
|
|
|
37
37
|
let errorText = '';
|
|
38
38
|
let errorData = null;
|
|
39
39
|
try {
|
|
40
|
-
|
|
41
|
-
if (contentType
|
|
40
|
+
const contentType = response.headers.get('content-type');
|
|
41
|
+
if (contentType?.includes('application/json')) {
|
|
42
42
|
errorData = await response.json();
|
|
43
43
|
errorText = errorData.error || errorData.message || '';
|
|
44
44
|
} else {
|
|
@@ -65,26 +65,26 @@ export class AuthService {
|
|
|
65
65
|
* @returns {Promise<Object>} Response data
|
|
66
66
|
*/
|
|
67
67
|
async authenticatedRequest(endpoint, options = {}) {
|
|
68
|
-
|
|
68
|
+
const auth = await getAuthTokens();
|
|
69
69
|
if (!auth || !auth.accessToken) {
|
|
70
70
|
throw new AuthError('No authentication token found. Please run "vizzly login" first.');
|
|
71
71
|
}
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
73
|
+
const headers = {
|
|
74
74
|
'User-Agent': this.userAgent,
|
|
75
75
|
Authorization: `Bearer ${auth.accessToken}`,
|
|
76
76
|
...options.headers
|
|
77
77
|
};
|
|
78
|
-
|
|
78
|
+
const response = await fetch(url, {
|
|
79
79
|
...options,
|
|
80
80
|
headers
|
|
81
81
|
});
|
|
82
82
|
if (!response.ok) {
|
|
83
83
|
let errorText = '';
|
|
84
84
|
try {
|
|
85
|
-
|
|
86
|
-
if (contentType
|
|
87
|
-
|
|
85
|
+
const contentType = response.headers.get('content-type');
|
|
86
|
+
if (contentType?.includes('application/json')) {
|
|
87
|
+
const errorData = await response.json();
|
|
88
88
|
errorText = errorData.error || errorData.message || '';
|
|
89
89
|
} else {
|
|
90
90
|
errorText = await response.text();
|
|
@@ -151,11 +151,11 @@ export class AuthService {
|
|
|
151
151
|
* @returns {Promise<Object>} New tokens
|
|
152
152
|
*/
|
|
153
153
|
async refresh() {
|
|
154
|
-
|
|
154
|
+
const auth = await getAuthTokens();
|
|
155
155
|
if (!auth || !auth.refreshToken) {
|
|
156
156
|
throw new AuthError('No refresh token found. Please run "vizzly login" first.');
|
|
157
157
|
}
|
|
158
|
-
|
|
158
|
+
const response = await this.request('/api/auth/cli/refresh', {
|
|
159
159
|
method: 'POST',
|
|
160
160
|
headers: {
|
|
161
161
|
'Content-Type': 'application/json'
|
|
@@ -180,8 +180,8 @@ export class AuthService {
|
|
|
180
180
|
* @returns {Promise<void>}
|
|
181
181
|
*/
|
|
182
182
|
async logout() {
|
|
183
|
-
|
|
184
|
-
if (auth
|
|
183
|
+
const auth = await getAuthTokens();
|
|
184
|
+
if (auth?.refreshToken) {
|
|
185
185
|
try {
|
|
186
186
|
// Attempt to revoke tokens on server
|
|
187
187
|
await this.request('/api/auth/cli/logout', {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Manages the build lifecycle and coordinates test execution
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import crypto from 'crypto';
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
7
|
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -127,7 +127,7 @@ export class BuildManager {
|
|
|
127
127
|
this.buildQueue = [];
|
|
128
128
|
}
|
|
129
129
|
async createBuild(buildOptions) {
|
|
130
|
-
|
|
130
|
+
const build = createBuildObject(buildOptions);
|
|
131
131
|
this.currentBuild = build;
|
|
132
132
|
return build;
|
|
133
133
|
}
|
|
@@ -156,7 +156,7 @@ export class BuildManager {
|
|
|
156
156
|
return this.currentBuild;
|
|
157
157
|
}
|
|
158
158
|
queueBuild(buildOptions) {
|
|
159
|
-
|
|
159
|
+
const queuedBuild = createQueuedBuild(buildOptions);
|
|
160
160
|
this.buildQueue.push(queuedBuild);
|
|
161
161
|
}
|
|
162
162
|
async clear() {
|