@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
package/dist/sdk/index.js
CHANGED
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
* @description Full SDK for custom integrations and advanced usage
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { EventEmitter } from 'events';
|
|
14
|
-
import {
|
|
15
|
-
import { createUploader } from '../services/uploader.js';
|
|
16
|
-
import { createTDDService } from '../services/tdd-service.js';
|
|
13
|
+
import { EventEmitter } from 'node:events';
|
|
14
|
+
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
17
15
|
import { ScreenshotServer } from '../services/screenshot-server.js';
|
|
16
|
+
import { createTDDService } from '../services/tdd-service.js';
|
|
17
|
+
import { createUploader } from '../services/uploader.js';
|
|
18
18
|
import { loadConfig } from '../utils/config-loader.js';
|
|
19
|
+
import { resolveImageBuffer } from '../utils/file-helpers.js';
|
|
19
20
|
import * as output from '../utils/output.js';
|
|
20
|
-
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Create a new Vizzly instance with custom configuration
|
|
@@ -59,15 +59,15 @@ export function createVizzly(config = {}, options = {}) {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
// Merge with loaded config
|
|
62
|
-
|
|
62
|
+
const resolvedConfig = {
|
|
63
63
|
...config
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Initialize SDK with config loading
|
|
68
68
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const init = async () => {
|
|
70
|
+
const fileConfig = await loadConfig();
|
|
71
71
|
Object.assign(resolvedConfig, fileConfig, config); // CLI config takes precedence
|
|
72
72
|
return resolvedConfig;
|
|
73
73
|
};
|
|
@@ -75,7 +75,7 @@ export function createVizzly(config = {}, options = {}) {
|
|
|
75
75
|
/**
|
|
76
76
|
* Create uploader service
|
|
77
77
|
*/
|
|
78
|
-
|
|
78
|
+
const createUploaderService = (uploaderOptions = {}) => {
|
|
79
79
|
return createUploader({
|
|
80
80
|
apiKey: resolvedConfig.apiKey,
|
|
81
81
|
apiUrl: resolvedConfig.apiUrl
|
|
@@ -88,7 +88,7 @@ export function createVizzly(config = {}, options = {}) {
|
|
|
88
88
|
/**
|
|
89
89
|
* Create TDD service
|
|
90
90
|
*/
|
|
91
|
-
|
|
91
|
+
const createTDDServiceInstance = (tddOptions = {}) => {
|
|
92
92
|
return createTDDService(resolvedConfig, {
|
|
93
93
|
...options,
|
|
94
94
|
...tddOptions
|
|
@@ -98,16 +98,16 @@ export function createVizzly(config = {}, options = {}) {
|
|
|
98
98
|
/**
|
|
99
99
|
* Upload screenshots (convenience method)
|
|
100
100
|
*/
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
const upload = async uploadOptions => {
|
|
102
|
+
const uploader = createUploaderService();
|
|
103
103
|
return uploader.upload(uploadOptions);
|
|
104
104
|
};
|
|
105
105
|
|
|
106
106
|
/**
|
|
107
107
|
* Start TDD mode (convenience method)
|
|
108
108
|
*/
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
const startTDD = async (tddOptions = {}) => {
|
|
110
|
+
const tddService = createTDDServiceInstance();
|
|
111
111
|
return tddService.start(tddOptions);
|
|
112
112
|
};
|
|
113
113
|
return {
|
|
@@ -195,7 +195,7 @@ export class VizzlySDK extends EventEmitter {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
// Create a simple build manager for screenshot collection
|
|
198
|
-
|
|
198
|
+
const buildManager = {
|
|
199
199
|
screenshots: new Map(),
|
|
200
200
|
currentBuildId: null,
|
|
201
201
|
async addScreenshot(buildId, screenshot) {
|
|
@@ -210,8 +210,8 @@ export class VizzlySDK extends EventEmitter {
|
|
|
210
210
|
};
|
|
211
211
|
this.server = new ScreenshotServer(this.config, buildManager);
|
|
212
212
|
await this.server.start();
|
|
213
|
-
|
|
214
|
-
|
|
213
|
+
const port = this.config.server?.port || 3000;
|
|
214
|
+
const serverInfo = {
|
|
215
215
|
port,
|
|
216
216
|
url: `http://localhost:${port}`
|
|
217
217
|
};
|
|
@@ -235,15 +235,15 @@ export class VizzlySDK extends EventEmitter {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
// Resolve Buffer or file path using shared utility
|
|
238
|
-
|
|
238
|
+
const buffer = resolveImageBuffer(imageBuffer, 'screenshot');
|
|
239
239
|
|
|
240
240
|
// Generate or use provided build ID
|
|
241
|
-
|
|
241
|
+
const buildId = options.buildId || this.currentBuildId || 'default';
|
|
242
242
|
this.currentBuildId = buildId;
|
|
243
243
|
|
|
244
244
|
// Convert Buffer to base64 for JSON transport
|
|
245
|
-
|
|
246
|
-
|
|
245
|
+
const imageBase64 = buffer.toString('base64');
|
|
246
|
+
const screenshotData = {
|
|
247
247
|
buildId,
|
|
248
248
|
name,
|
|
249
249
|
image: imageBase64,
|
|
@@ -251,9 +251,9 @@ export class VizzlySDK extends EventEmitter {
|
|
|
251
251
|
};
|
|
252
252
|
|
|
253
253
|
// POST to the local screenshot server
|
|
254
|
-
|
|
254
|
+
const serverUrl = `http://localhost:${this.config.server?.port || 3000}`;
|
|
255
255
|
try {
|
|
256
|
-
|
|
256
|
+
const response = await fetch(`${serverUrl}/screenshot`, {
|
|
257
257
|
method: 'POST',
|
|
258
258
|
headers: {
|
|
259
259
|
'Content-Type': 'application/json'
|
|
@@ -261,7 +261,7 @@ export class VizzlySDK extends EventEmitter {
|
|
|
261
261
|
body: JSON.stringify(screenshotData)
|
|
262
262
|
});
|
|
263
263
|
if (!response.ok) {
|
|
264
|
-
|
|
264
|
+
const errorData = await response.json().catch(() => ({
|
|
265
265
|
error: 'Unknown error'
|
|
266
266
|
}));
|
|
267
267
|
throw new VizzlyError(`Screenshot capture failed: ${errorData.error}`, 'SCREENSHOT_FAILED', {
|
|
@@ -302,8 +302,8 @@ export class VizzlySDK extends EventEmitter {
|
|
|
302
302
|
}
|
|
303
303
|
|
|
304
304
|
// Get the screenshots directory from config or default
|
|
305
|
-
|
|
306
|
-
|
|
305
|
+
const screenshotsDir = options.screenshotsDir || this.config?.upload?.screenshotsDir || './screenshots';
|
|
306
|
+
const uploadOptions = {
|
|
307
307
|
screenshotsDir,
|
|
308
308
|
buildName: options.buildName || this.config.buildName,
|
|
309
309
|
branch: options.branch || this.config.branch,
|
|
@@ -319,7 +319,7 @@ export class VizzlySDK extends EventEmitter {
|
|
|
319
319
|
}
|
|
320
320
|
};
|
|
321
321
|
try {
|
|
322
|
-
|
|
322
|
+
const result = await this.services.uploader.upload(uploadOptions);
|
|
323
323
|
this.emit('upload:completed', result);
|
|
324
324
|
return result;
|
|
325
325
|
} catch (error) {
|
|
@@ -343,9 +343,9 @@ export class VizzlySDK extends EventEmitter {
|
|
|
343
343
|
}
|
|
344
344
|
|
|
345
345
|
// Resolve Buffer or file path using shared utility
|
|
346
|
-
|
|
346
|
+
const buffer = resolveImageBuffer(imageBuffer, 'compare');
|
|
347
347
|
try {
|
|
348
|
-
|
|
348
|
+
const result = await this.services.tddService.compareScreenshot(name, buffer);
|
|
349
349
|
this.emit('comparison:completed', result);
|
|
350
350
|
return result;
|
|
351
351
|
} catch (error) {
|
|
@@ -357,11 +357,9 @@ export class VizzlySDK extends EventEmitter {
|
|
|
357
357
|
}
|
|
358
358
|
}
|
|
359
359
|
}
|
|
360
|
-
|
|
361
|
-
// Re-export key utilities and errors
|
|
362
|
-
export { loadConfig } from '../utils/config-loader.js';
|
|
363
|
-
export * as output from '../utils/output.js';
|
|
364
|
-
|
|
360
|
+
export { createTDDService } from '../services/tdd-service.js';
|
|
365
361
|
// Export service creators for advanced usage
|
|
366
362
|
export { createUploader } from '../services/uploader.js';
|
|
367
|
-
export
|
|
363
|
+
// Re-export key utilities and errors
|
|
364
|
+
export { loadConfig } from '../utils/config-loader.js';
|
|
365
|
+
export * as output from '../utils/output.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { Buffer } from 'buffer';
|
|
2
|
-
import { existsSync, readFileSync } from 'fs';
|
|
3
|
-
import { resolve } from 'path';
|
|
4
|
-
import * as output from '../../utils/output.js';
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
5
4
|
import { detectImageInputType } from '../../utils/image-input-detector.js';
|
|
5
|
+
import * as output from '../../utils/output.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* API Handler - Non-blocking screenshot upload
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { Buffer } from 'buffer';
|
|
2
|
-
import {
|
|
3
|
-
import { join, resolve } from 'path';
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join, resolve } from 'node:path';
|
|
4
4
|
import { getDimensionsSync } from '@vizzly-testing/honeydiff';
|
|
5
|
-
import * as output from '../../utils/output.js';
|
|
6
5
|
import { TddService } from '../../services/tdd-service.js';
|
|
7
|
-
import { sanitizeScreenshotName, validateScreenshotProperties } from '../../utils/security.js';
|
|
8
6
|
import { detectImageInputType } from '../../utils/image-input-detector.js';
|
|
7
|
+
import * as output from '../../utils/output.js';
|
|
8
|
+
import { sanitizeScreenshotName, validateScreenshotProperties } from '../../utils/security.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Group comparisons by screenshot name with variant structure
|
|
@@ -141,7 +141,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
141
141
|
if (existingIndex >= 0) {
|
|
142
142
|
// Preserve initialStatus from the original comparison
|
|
143
143
|
// This keeps sort order stable when status changes (e.g., after approval)
|
|
144
|
-
|
|
144
|
+
const initialStatus = reportData.comparisons[existingIndex].initialStatus;
|
|
145
145
|
reportData.comparisons[existingIndex] = {
|
|
146
146
|
...newComparison,
|
|
147
147
|
initialStatus: initialStatus || newComparison.status
|
|
@@ -200,7 +200,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
200
200
|
output.debug('tdd', `using baseline: ${baseline.buildName}`);
|
|
201
201
|
}
|
|
202
202
|
};
|
|
203
|
-
const handleScreenshot = async (
|
|
203
|
+
const handleScreenshot = async (_buildId, name, image, properties = {}) => {
|
|
204
204
|
// Validate and sanitize screenshot name
|
|
205
205
|
let sanitizedName;
|
|
206
206
|
try {
|
|
@@ -245,17 +245,18 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
// Extract
|
|
249
|
-
// This ensures signature generation works correctly
|
|
248
|
+
// Extract ALL properties to top-level (matching cloud API behavior)
|
|
249
|
+
// This ensures signature generation works correctly for custom properties like theme, device, etc.
|
|
250
|
+
// Spread all validated properties first, then normalize viewport/browser for cloud format
|
|
250
251
|
const extractedProperties = {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
//
|
|
252
|
+
...validatedProperties,
|
|
253
|
+
// Normalize viewport to top-level viewport_width/height (cloud format)
|
|
254
|
+
// Use nullish coalescing to preserve any existing top-level values
|
|
255
|
+
viewport_width: validatedProperties.viewport?.width ?? validatedProperties.viewport_width ?? null,
|
|
256
|
+
viewport_height: validatedProperties.viewport?.height ?? validatedProperties.viewport_height ?? null,
|
|
257
|
+
browser: validatedProperties.browser ?? null,
|
|
258
|
+
// Preserve nested structure in metadata for backward compatibility
|
|
259
|
+
// Signature generation checks multiple locations: top-level, metadata.*, metadata.properties.*
|
|
259
260
|
metadata: validatedProperties
|
|
260
261
|
};
|
|
261
262
|
|
|
@@ -504,7 +505,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
504
505
|
try {
|
|
505
506
|
const {
|
|
506
507
|
unlinkSync
|
|
507
|
-
} = await import('fs');
|
|
508
|
+
} = await import('node:fs');
|
|
508
509
|
unlinkSync(baselinePath);
|
|
509
510
|
deletedBaselines++;
|
|
510
511
|
// Silent deletion
|
|
@@ -521,7 +522,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
521
522
|
try {
|
|
522
523
|
const {
|
|
523
524
|
unlinkSync
|
|
524
|
-
} = await import('fs');
|
|
525
|
+
} = await import('node:fs');
|
|
525
526
|
unlinkSync(currentPath);
|
|
526
527
|
deletedCurrents++;
|
|
527
528
|
// Silent deletion
|
|
@@ -538,7 +539,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
538
539
|
try {
|
|
539
540
|
const {
|
|
540
541
|
unlinkSync
|
|
541
|
-
} = await import('fs');
|
|
542
|
+
} = await import('node:fs');
|
|
542
543
|
unlinkSync(diffPath);
|
|
543
544
|
deletedDiffs++;
|
|
544
545
|
output.debug(`Deleted diff for ${comparison.name}`);
|
|
@@ -555,7 +556,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
555
556
|
try {
|
|
556
557
|
const {
|
|
557
558
|
unlinkSync
|
|
558
|
-
} = await import('fs');
|
|
559
|
+
} = await import('node:fs');
|
|
559
560
|
unlinkSync(metadataPath);
|
|
560
561
|
output.debug('Deleted baseline metadata');
|
|
561
562
|
} catch (error) {
|
|
@@ -3,29 +3,28 @@
|
|
|
3
3
|
* Thin dispatcher that routes requests to modular routers
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { createServer } from 'http';
|
|
6
|
+
import { createServer } from 'node:http';
|
|
7
7
|
import * as output from '../utils/output.js';
|
|
8
8
|
|
|
9
9
|
// Middleware
|
|
10
10
|
import { corsMiddleware } from './middleware/cors.js';
|
|
11
11
|
import { sendError } from './middleware/response.js';
|
|
12
|
-
|
|
13
|
-
// Routers
|
|
14
|
-
import { createHealthRouter } from './routers/health.js';
|
|
15
12
|
import { createAssetsRouter } from './routers/assets.js';
|
|
16
|
-
import {
|
|
17
|
-
import { createScreenshotRouter } from './routers/screenshot.js';
|
|
13
|
+
import { createAuthRouter } from './routers/auth.js';
|
|
18
14
|
import { createBaselineRouter } from './routers/baseline.js';
|
|
15
|
+
import { createCloudProxyRouter } from './routers/cloud-proxy.js';
|
|
19
16
|
import { createConfigRouter } from './routers/config.js';
|
|
20
|
-
import {
|
|
17
|
+
import { createDashboardRouter } from './routers/dashboard.js';
|
|
18
|
+
// Routers
|
|
19
|
+
import { createHealthRouter } from './routers/health.js';
|
|
21
20
|
import { createProjectsRouter } from './routers/projects.js';
|
|
22
|
-
import {
|
|
23
|
-
export
|
|
21
|
+
import { createScreenshotRouter } from './routers/screenshot.js';
|
|
22
|
+
export const createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
24
23
|
let server = null;
|
|
25
|
-
|
|
24
|
+
const defaultBuildId = services.buildId || null;
|
|
26
25
|
|
|
27
26
|
// Extract services
|
|
28
|
-
|
|
27
|
+
const {
|
|
29
28
|
configService,
|
|
30
29
|
authService,
|
|
31
30
|
projectService,
|
|
@@ -33,7 +32,7 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
33
32
|
} = services;
|
|
34
33
|
|
|
35
34
|
// Create router context
|
|
36
|
-
|
|
35
|
+
const routerContext = {
|
|
37
36
|
port,
|
|
38
37
|
screenshotHandler,
|
|
39
38
|
defaultBuildId,
|
|
@@ -45,9 +44,9 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
45
44
|
};
|
|
46
45
|
|
|
47
46
|
// Initialize routers
|
|
48
|
-
|
|
47
|
+
const routers = [createHealthRouter(routerContext), createAssetsRouter(routerContext), createScreenshotRouter(routerContext), createBaselineRouter(routerContext), createConfigRouter(routerContext), createAuthRouter(routerContext), createProjectsRouter(routerContext), createCloudProxyRouter(routerContext), createDashboardRouter(routerContext) // Catch-all for SPA routes - must be last
|
|
49
48
|
];
|
|
50
|
-
|
|
49
|
+
const handleRequest = async (req, res) => {
|
|
51
50
|
// Apply CORS middleware
|
|
52
51
|
if (corsMiddleware(req, res)) {
|
|
53
52
|
return;
|
|
@@ -57,13 +56,13 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
57
56
|
res.setHeader('Content-Type', 'application/json');
|
|
58
57
|
|
|
59
58
|
// Parse URL
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const parsedUrl = new URL(req.url, `http://${req.headers.host}`);
|
|
60
|
+
const pathname = parsedUrl.pathname;
|
|
62
61
|
|
|
63
62
|
// Try each router in order
|
|
64
|
-
for (
|
|
63
|
+
for (const router of routers) {
|
|
65
64
|
try {
|
|
66
|
-
|
|
65
|
+
const handled = await router(req, res, pathname, parsedUrl);
|
|
67
66
|
if (handled) {
|
|
68
67
|
return;
|
|
69
68
|
}
|
|
@@ -79,7 +78,7 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
79
78
|
// No router handled the request
|
|
80
79
|
sendError(res, 404, 'Not found');
|
|
81
80
|
};
|
|
82
|
-
|
|
81
|
+
const start = () => {
|
|
83
82
|
return new Promise((resolve, reject) => {
|
|
84
83
|
server = createServer(async (req, res) => {
|
|
85
84
|
try {
|
|
@@ -112,7 +111,7 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
112
111
|
});
|
|
113
112
|
});
|
|
114
113
|
};
|
|
115
|
-
|
|
114
|
+
const stop = () => {
|
|
116
115
|
if (server) {
|
|
117
116
|
return new Promise(resolve => {
|
|
118
117
|
server.close(() => {
|
|
@@ -129,10 +128,10 @@ export let createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
129
128
|
* Finish build - flush any pending background operations
|
|
130
129
|
* Call this before finalizing a build to ensure all uploads complete
|
|
131
130
|
*/
|
|
132
|
-
|
|
131
|
+
const finishBuild = async _buildId => {
|
|
133
132
|
// Flush screenshot handler if it has a flush method (API mode)
|
|
134
133
|
if (screenshotHandler?.flush) {
|
|
135
|
-
|
|
134
|
+
const stats = await screenshotHandler.flush();
|
|
136
135
|
if (stats.uploaded > 0 || stats.failed > 0) {
|
|
137
136
|
output.debug('upload', 'flushed', {
|
|
138
137
|
uploaded: stats.uploaded,
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
* Serves static assets (bundle files, images)
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
import { sendFile, sendError, sendNotFound } from '../middleware/response.js';
|
|
6
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
10
9
|
import * as output from '../../utils/output.js';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Create assets router
|
|
@@ -25,10 +25,10 @@ export function createAssetsRouter() {
|
|
|
25
25
|
|
|
26
26
|
// Serve React bundle JS
|
|
27
27
|
if (pathname === '/reporter-bundle.js') {
|
|
28
|
-
|
|
28
|
+
const bundlePath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.iife.js');
|
|
29
29
|
if (existsSync(bundlePath)) {
|
|
30
30
|
try {
|
|
31
|
-
|
|
31
|
+
const bundle = readFileSync(bundlePath, 'utf8');
|
|
32
32
|
sendFile(res, bundle, 'application/javascript');
|
|
33
33
|
return true;
|
|
34
34
|
} catch (error) {
|
|
@@ -46,10 +46,10 @@ export function createAssetsRouter() {
|
|
|
46
46
|
|
|
47
47
|
// Serve React bundle CSS
|
|
48
48
|
if (pathname === '/reporter-bundle.css') {
|
|
49
|
-
|
|
49
|
+
const cssPath = join(PROJECT_ROOT, 'dist', 'reporter', 'reporter-bundle.css');
|
|
50
50
|
if (existsSync(cssPath)) {
|
|
51
51
|
try {
|
|
52
|
-
|
|
52
|
+
const css = readFileSync(cssPath, 'utf8');
|
|
53
53
|
sendFile(res, css, 'text/css');
|
|
54
54
|
return true;
|
|
55
55
|
} catch (error) {
|
|
@@ -67,11 +67,11 @@ export function createAssetsRouter() {
|
|
|
67
67
|
|
|
68
68
|
// Serve images from .vizzly directory
|
|
69
69
|
if (pathname.startsWith('/images/')) {
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
const imagePath = pathname.replace('/images/', '');
|
|
71
|
+
const fullImagePath = join(process.cwd(), '.vizzly', imagePath);
|
|
72
72
|
if (existsSync(fullImagePath)) {
|
|
73
73
|
try {
|
|
74
|
-
|
|
74
|
+
const imageData = readFileSync(fullImagePath);
|
|
75
75
|
sendFile(res, imageData, 'image/png');
|
|
76
76
|
return true;
|
|
77
77
|
} catch (error) {
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Handles authentication endpoints (device flow login, logout, status)
|
|
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 auth router
|
|
@@ -26,10 +26,10 @@ export function createAuthRouter({
|
|
|
26
26
|
// Get auth status and user info
|
|
27
27
|
if (req.method === 'GET' && pathname === '/api/auth/status') {
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
const isAuthenticated = await authService.isAuthenticated();
|
|
30
30
|
let user = null;
|
|
31
31
|
if (isAuthenticated) {
|
|
32
|
-
|
|
32
|
+
const whoami = await authService.whoami();
|
|
33
33
|
user = whoami.user;
|
|
34
34
|
}
|
|
35
35
|
sendSuccess(res, {
|
|
@@ -50,10 +50,10 @@ export function createAuthRouter({
|
|
|
50
50
|
// Initiate device flow login
|
|
51
51
|
if (req.method === 'POST' && pathname === '/api/auth/login') {
|
|
52
52
|
try {
|
|
53
|
-
|
|
53
|
+
const deviceFlow = await authService.initiateDeviceFlow();
|
|
54
54
|
|
|
55
55
|
// Transform snake_case to camelCase for frontend
|
|
56
|
-
|
|
56
|
+
const response = {
|
|
57
57
|
deviceCode: deviceFlow.device_code,
|
|
58
58
|
userCode: deviceFlow.user_code,
|
|
59
59
|
verificationUri: deviceFlow.verification_uri,
|
|
@@ -73,8 +73,8 @@ export function createAuthRouter({
|
|
|
73
73
|
// Poll device authorization status
|
|
74
74
|
if (req.method === 'POST' && pathname === '/api/auth/poll') {
|
|
75
75
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
const body = await parseJsonBody(req);
|
|
77
|
+
const {
|
|
78
78
|
deviceCode
|
|
79
79
|
} = body;
|
|
80
80
|
if (!deviceCode) {
|
|
@@ -86,7 +86,7 @@ export function createAuthRouter({
|
|
|
86
86
|
result = await authService.pollDeviceAuthorization(deviceCode);
|
|
87
87
|
} catch (error) {
|
|
88
88
|
// Handle "Authorization pending" as a valid response
|
|
89
|
-
if (error.message
|
|
89
|
+
if (error.message?.includes('Authorization pending')) {
|
|
90
90
|
sendSuccess(res, {
|
|
91
91
|
status: 'pending'
|
|
92
92
|
});
|
|
@@ -96,11 +96,11 @@ export function createAuthRouter({
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Check if authorization is complete by looking for tokens
|
|
99
|
-
if (result.tokens
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
if (result.tokens?.accessToken) {
|
|
100
|
+
const tokensData = result.tokens;
|
|
101
|
+
const tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
|
|
102
|
+
const tokenExpiresAt = tokenExpiresIn ? new Date(Date.now() + tokenExpiresIn * 1000).toISOString() : result.expires_at || result.expiresAt;
|
|
103
|
+
const tokens = {
|
|
104
104
|
accessToken: tokensData.accessToken || tokensData.access_token,
|
|
105
105
|
refreshToken: tokensData.refreshToken || tokensData.refresh_token,
|
|
106
106
|
expiresAt: tokenExpiresAt,
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Handles baseline management (accept, accept-all, reset)
|
|
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 baseline router
|
|
@@ -28,7 +28,7 @@ export function createBaselineRouter({
|
|
|
28
28
|
return true;
|
|
29
29
|
}
|
|
30
30
|
try {
|
|
31
|
-
|
|
31
|
+
const {
|
|
32
32
|
id
|
|
33
33
|
} = await parseJsonBody(req);
|
|
34
34
|
if (!id) {
|
|
@@ -55,7 +55,7 @@ export function createBaselineRouter({
|
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
try {
|
|
58
|
-
|
|
58
|
+
const result = await screenshotHandler.acceptAllBaselines();
|
|
59
59
|
sendSuccess(res, {
|
|
60
60
|
success: true,
|
|
61
61
|
message: `Accepted ${result.count} baselines`,
|
|
@@ -96,8 +96,8 @@ export function createBaselineRouter({
|
|
|
96
96
|
return true;
|
|
97
97
|
}
|
|
98
98
|
try {
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
const body = await parseJsonBody(req);
|
|
100
|
+
const {
|
|
101
101
|
buildId,
|
|
102
102
|
organizationSlug,
|
|
103
103
|
projectSlug
|
|
@@ -111,7 +111,7 @@ export function createBaselineRouter({
|
|
|
111
111
|
// If organizationSlug and projectSlug are provided, use OAuth-based download
|
|
112
112
|
if (organizationSlug && projectSlug && authService) {
|
|
113
113
|
try {
|
|
114
|
-
|
|
114
|
+
const result = await tddService.downloadBaselinesWithAuth(buildId, organizationSlug, projectSlug, authService);
|
|
115
115
|
sendSuccess(res, {
|
|
116
116
|
success: true,
|
|
117
117
|
message: `Baselines downloaded from build ${buildId}`,
|
|
@@ -138,7 +138,7 @@ export function createBaselineRouter({
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
// Fall back to API token-based download (when no OAuth info or OAuth auth failed)
|
|
141
|
-
|
|
141
|
+
const result = await tddService.downloadBaselines('test',
|
|
142
142
|
// environment
|
|
143
143
|
null,
|
|
144
144
|
// branch (not needed when buildId is specified)
|