@vizzly-testing/cli 0.20.1-beta.0 → 0.20.1
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 +16 -18
- package/dist/cli.js +177 -2
- package/dist/client/index.js +144 -77
- package/dist/commands/doctor.js +118 -33
- package/dist/commands/finalize.js +8 -3
- package/dist/commands/init.js +13 -18
- package/dist/commands/login.js +42 -49
- package/dist/commands/logout.js +13 -5
- package/dist/commands/project.js +95 -67
- package/dist/commands/run.js +32 -6
- package/dist/commands/status.js +81 -50
- package/dist/commands/tdd-daemon.js +61 -32
- package/dist/commands/tdd.js +14 -26
- package/dist/commands/upload.js +18 -9
- package/dist/commands/whoami.js +40 -38
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +204 -22
- package/dist/server/handlers/tdd-handler.js +113 -7
- package/dist/server/http-server.js +9 -3
- package/dist/server/routers/baseline.js +58 -0
- package/dist/server/routers/dashboard.js +10 -6
- package/dist/server/routers/screenshot.js +32 -0
- package/dist/server-manager/core.js +5 -2
- package/dist/server-manager/operations.js +2 -1
- package/dist/services/config-service.js +306 -0
- package/dist/tdd/tdd-service.js +190 -126
- package/dist/types/client.d.ts +25 -2
- package/dist/utils/colors.js +187 -39
- package/dist/utils/config-loader.js +3 -6
- package/dist/utils/context.js +228 -0
- package/dist/utils/output.js +449 -14
- package/docs/api-reference.md +173 -8
- package/docs/tui-elements.md +560 -0
- package/package.json +13 -7
- package/dist/report-generator/core.js +0 -315
- package/dist/report-generator/index.js +0 -8
- package/dist/report-generator/operations.js +0 -196
- package/dist/services/static-report-generator.js +0 -65
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Buffer as defaultBuffer } from 'node:buffer';
|
|
2
|
-
import { existsSync as defaultExistsSync, readFileSync as defaultReadFileSync, writeFileSync as defaultWriteFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync as defaultExistsSync, readFileSync as defaultReadFileSync, unlinkSync as defaultUnlinkSync, writeFileSync as defaultWriteFileSync } from 'node:fs';
|
|
3
3
|
import { join as defaultJoin, resolve as defaultResolve } from 'node:path';
|
|
4
4
|
import { getDimensionsSync as defaultGetDimensionsSync } from '@vizzly-testing/honeydiff';
|
|
5
5
|
import { TddService as DefaultTddService } from '../../tdd/tdd-service.js';
|
|
6
6
|
import { detectImageInputType as defaultDetectImageInputType } from '../../utils/image-input-detector.js';
|
|
7
7
|
import * as defaultOutput from '../../utils/output.js';
|
|
8
|
-
import { sanitizeScreenshotName as defaultSanitizeScreenshotName, validateScreenshotProperties as defaultValidateScreenshotProperties } from '../../utils/security.js';
|
|
8
|
+
import { safePath as defaultSafePath, sanitizeScreenshotName as defaultSanitizeScreenshotName, validateScreenshotProperties as defaultValidateScreenshotProperties } from '../../utils/security.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Unwrap double-nested properties if needed
|
|
@@ -142,12 +142,14 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
142
142
|
TddService = DefaultTddService,
|
|
143
143
|
existsSync = defaultExistsSync,
|
|
144
144
|
readFileSync = defaultReadFileSync,
|
|
145
|
+
unlinkSync = defaultUnlinkSync,
|
|
145
146
|
writeFileSync = defaultWriteFileSync,
|
|
146
147
|
join = defaultJoin,
|
|
147
148
|
resolve = defaultResolve,
|
|
148
149
|
Buffer = defaultBuffer,
|
|
149
150
|
getDimensionsSync = defaultGetDimensionsSync,
|
|
150
151
|
detectImageInputType = defaultDetectImageInputType,
|
|
152
|
+
safePath = defaultSafePath,
|
|
151
153
|
sanitizeScreenshotName = defaultSanitizeScreenshotName,
|
|
152
154
|
validateScreenshotProperties = defaultValidateScreenshotProperties,
|
|
153
155
|
output = defaultOutput
|
|
@@ -228,6 +230,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
228
230
|
groups: reportData.groups.length,
|
|
229
231
|
passed: reportData.comparisons.filter(c => c.status === 'passed' || c.status === 'baseline-created' || c.status === 'new').length,
|
|
230
232
|
failed: reportData.comparisons.filter(c => c.status === 'failed').length,
|
|
233
|
+
rejected: reportData.comparisons.filter(c => c.status === 'rejected').length,
|
|
231
234
|
errors: reportData.comparisons.filter(c => c.status === 'error').length
|
|
232
235
|
};
|
|
233
236
|
writeFileSync(reportPath, JSON.stringify(reportData, null, 2));
|
|
@@ -236,7 +239,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
236
239
|
}
|
|
237
240
|
};
|
|
238
241
|
const initialize = async () => {
|
|
239
|
-
output.debug('tdd', '
|
|
242
|
+
output.debug('tdd', 'initializing local mode');
|
|
240
243
|
|
|
241
244
|
// In baseline update mode, skip all baseline loading/downloading
|
|
242
245
|
if (setBaseline) {
|
|
@@ -247,7 +250,7 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
247
250
|
// Check if we have baseline override flags that should force a fresh download
|
|
248
251
|
const shouldForceDownload = (baselineBuild || baselineComparison) && config.apiKey;
|
|
249
252
|
if (shouldForceDownload) {
|
|
250
|
-
output.debug('tdd', 'downloading baselines
|
|
253
|
+
output.debug('tdd', 'downloading baselines');
|
|
251
254
|
await tddService.downloadBaselines(config.build?.environment || 'test', config.build?.branch || null, baselineBuild, baselineComparison);
|
|
252
255
|
return;
|
|
253
256
|
}
|
|
@@ -255,13 +258,13 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
255
258
|
if (!baseline) {
|
|
256
259
|
// Only download baselines if explicitly requested via baseline flags
|
|
257
260
|
if ((baselineBuild || baselineComparison) && config.apiKey) {
|
|
258
|
-
output.debug('tdd', 'downloading baselines
|
|
261
|
+
output.debug('tdd', 'downloading baselines');
|
|
259
262
|
await tddService.downloadBaselines(config.build?.environment || 'test', config.build?.branch || null, baselineBuild, baselineComparison);
|
|
260
263
|
} else {
|
|
261
|
-
output.debug('tdd', 'no baselines
|
|
264
|
+
output.debug('tdd', 'no baselines yet');
|
|
262
265
|
}
|
|
263
266
|
} else {
|
|
264
|
-
output.debug('tdd', `
|
|
267
|
+
output.debug('tdd', `baseline: ${baseline.buildName}`);
|
|
265
268
|
}
|
|
266
269
|
};
|
|
267
270
|
const handleScreenshot = async (_buildId, name, image, properties = {}) => {
|
|
@@ -488,6 +491,33 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
488
491
|
throw error;
|
|
489
492
|
}
|
|
490
493
|
};
|
|
494
|
+
const rejectBaseline = async comparisonId => {
|
|
495
|
+
try {
|
|
496
|
+
// Read current report data to get the comparison
|
|
497
|
+
const reportData = readReportData();
|
|
498
|
+
const comparison = reportData.comparisons.find(c => c.id === comparisonId);
|
|
499
|
+
if (!comparison) {
|
|
500
|
+
throw new Error(`Comparison not found with ID: ${comparisonId}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Rejecting means: keep the current baseline, mark comparison as rejected
|
|
504
|
+
// The user is saying "I don't want this change, the baseline is correct"
|
|
505
|
+
// We update the status to 'rejected' so the UI shows the decision was made
|
|
506
|
+
const updatedComparison = {
|
|
507
|
+
...comparison,
|
|
508
|
+
status: 'rejected'
|
|
509
|
+
};
|
|
510
|
+
updateComparison(updatedComparison);
|
|
511
|
+
output.info(`Changes rejected for comparison ${comparisonId}`);
|
|
512
|
+
return {
|
|
513
|
+
success: true,
|
|
514
|
+
id: comparisonId
|
|
515
|
+
};
|
|
516
|
+
} catch (error) {
|
|
517
|
+
output.error(`Failed to reject baseline for ${comparisonId}:`, error);
|
|
518
|
+
throw error;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
491
521
|
const acceptAllBaselines = async () => {
|
|
492
522
|
try {
|
|
493
523
|
output.debug('tdd', 'accepting all baselines');
|
|
@@ -622,6 +652,80 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
622
652
|
throw error;
|
|
623
653
|
}
|
|
624
654
|
};
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Safely delete a file within the .vizzly directory
|
|
658
|
+
* @param {string} imagePath - Path like "/images/baselines/foo.png"
|
|
659
|
+
* @param {string} label - Label for logging (e.g., "baseline", "current", "diff")
|
|
660
|
+
* @param {string} name - Screenshot name for logging
|
|
661
|
+
*/
|
|
662
|
+
const safeDeleteFile = (imagePath, label, name) => {
|
|
663
|
+
if (!imagePath) return;
|
|
664
|
+
try {
|
|
665
|
+
// Use safePath to validate the path stays within workingDir
|
|
666
|
+
const filePath = safePath(workingDir, '.vizzly', imagePath.replace('/images/', ''));
|
|
667
|
+
if (existsSync(filePath)) {
|
|
668
|
+
unlinkSync(filePath);
|
|
669
|
+
output.debug(`Deleted ${label} for ${name}`);
|
|
670
|
+
}
|
|
671
|
+
} catch (error) {
|
|
672
|
+
// safePath throws if path traversal is attempted
|
|
673
|
+
output.warn(`Failed to delete ${label} for ${name}: ${error.message}`);
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
const deleteComparison = async comparisonId => {
|
|
677
|
+
const reportData = readReportData();
|
|
678
|
+
const comparison = reportData.comparisons.find(c => c.id === comparisonId);
|
|
679
|
+
if (!comparison) {
|
|
680
|
+
const error = new Error(`Comparison not found with ID: ${comparisonId}`);
|
|
681
|
+
error.code = 'NOT_FOUND';
|
|
682
|
+
throw error;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Delete image files (safePath validates paths stay within workingDir)
|
|
686
|
+
safeDeleteFile(comparison.baseline, 'baseline', comparison.name);
|
|
687
|
+
safeDeleteFile(comparison.current, 'current', comparison.name);
|
|
688
|
+
safeDeleteFile(comparison.diff, 'diff', comparison.name);
|
|
689
|
+
|
|
690
|
+
// Remove from baseline metadata if it exists
|
|
691
|
+
try {
|
|
692
|
+
const metadataPath = safePath(workingDir, '.vizzly', 'baselines', 'metadata.json');
|
|
693
|
+
if (existsSync(metadataPath) && comparison.signature) {
|
|
694
|
+
const metadata = JSON.parse(readFileSync(metadataPath, 'utf8'));
|
|
695
|
+
if (metadata.screenshots) {
|
|
696
|
+
const originalLength = metadata.screenshots.length;
|
|
697
|
+
metadata.screenshots = metadata.screenshots.filter(s => s.signature !== comparison.signature);
|
|
698
|
+
if (metadata.screenshots.length < originalLength) {
|
|
699
|
+
writeFileSync(metadataPath, JSON.stringify(metadata, null, 2));
|
|
700
|
+
output.debug(`Removed ${comparison.signature} from baseline metadata`);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
} catch (error) {
|
|
705
|
+
output.warn(`Failed to update baseline metadata: ${error.message}`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Remove comparison from report data
|
|
709
|
+
reportData.comparisons = reportData.comparisons.filter(c => c.id !== comparisonId);
|
|
710
|
+
|
|
711
|
+
// Regenerate groups and summary
|
|
712
|
+
reportData.groups = groupComparisons(reportData.comparisons);
|
|
713
|
+
reportData.timestamp = Date.now();
|
|
714
|
+
reportData.summary = {
|
|
715
|
+
total: reportData.comparisons.length,
|
|
716
|
+
groups: reportData.groups.length,
|
|
717
|
+
passed: reportData.comparisons.filter(c => c.status === 'passed' || c.status === 'baseline-created' || c.status === 'new').length,
|
|
718
|
+
failed: reportData.comparisons.filter(c => c.status === 'failed').length,
|
|
719
|
+
rejected: reportData.comparisons.filter(c => c.status === 'rejected').length,
|
|
720
|
+
errors: reportData.comparisons.filter(c => c.status === 'error').length
|
|
721
|
+
};
|
|
722
|
+
writeFileSync(reportPath, JSON.stringify(reportData, null, 2));
|
|
723
|
+
output.info(`Deleted comparison ${comparisonId} (${comparison.name})`);
|
|
724
|
+
return {
|
|
725
|
+
success: true,
|
|
726
|
+
id: comparisonId
|
|
727
|
+
};
|
|
728
|
+
};
|
|
625
729
|
const cleanup = () => {
|
|
626
730
|
// Report data is persisted to file, no in-memory cleanup needed
|
|
627
731
|
};
|
|
@@ -630,8 +734,10 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
630
734
|
handleScreenshot,
|
|
631
735
|
getResults,
|
|
632
736
|
acceptBaseline,
|
|
737
|
+
rejectBaseline,
|
|
633
738
|
acceptAllBaselines,
|
|
634
739
|
resetBaselines,
|
|
740
|
+
deleteComparison,
|
|
635
741
|
cleanup,
|
|
636
742
|
// Expose tddService for baseline download operations
|
|
637
743
|
tddService
|
|
@@ -28,7 +28,8 @@ export const createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
28
28
|
configService,
|
|
29
29
|
authService,
|
|
30
30
|
projectService,
|
|
31
|
-
tddService
|
|
31
|
+
tddService,
|
|
32
|
+
workingDir
|
|
32
33
|
} = services;
|
|
33
34
|
|
|
34
35
|
// Create router context
|
|
@@ -40,6 +41,7 @@ export const createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
40
41
|
authService,
|
|
41
42
|
projectService,
|
|
42
43
|
tddService,
|
|
44
|
+
workingDir: workingDir || process.cwd(),
|
|
43
45
|
apiUrl: 'https://app.vizzly.dev'
|
|
44
46
|
};
|
|
45
47
|
|
|
@@ -98,7 +100,8 @@ export const createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
98
100
|
if (error) {
|
|
99
101
|
reject(error);
|
|
100
102
|
} else {
|
|
101
|
-
|
|
103
|
+
// Don't log here - let the caller handle success logging via onServerReady callback
|
|
104
|
+
// This prevents duplicate "listening on" messages
|
|
102
105
|
resolve();
|
|
103
106
|
}
|
|
104
107
|
});
|
|
@@ -114,9 +117,12 @@ export const createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
|
114
117
|
const stop = () => {
|
|
115
118
|
if (server) {
|
|
116
119
|
return new Promise(resolve => {
|
|
120
|
+
// Close all keep-alive connections immediately (Node 18.2+)
|
|
121
|
+
if (server.closeAllConnections) {
|
|
122
|
+
server.closeAllConnections();
|
|
123
|
+
}
|
|
117
124
|
server.close(() => {
|
|
118
125
|
server = null;
|
|
119
|
-
output.debug('server', 'stopped');
|
|
120
126
|
resolve();
|
|
121
127
|
});
|
|
122
128
|
});
|
|
@@ -67,6 +67,64 @@ export function createBaselineRouter({
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
// Reject a single comparison (keep current baseline, discard changes)
|
|
71
|
+
if (req.method === 'POST' && pathname === '/api/baseline/reject') {
|
|
72
|
+
if (!screenshotHandler?.rejectBaseline) {
|
|
73
|
+
sendError(res, 400, 'Baseline management not available');
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const {
|
|
78
|
+
id
|
|
79
|
+
} = await parseJsonBody(req);
|
|
80
|
+
if (!id) {
|
|
81
|
+
sendError(res, 400, 'Comparison ID required');
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
await screenshotHandler.rejectBaseline(id);
|
|
85
|
+
sendSuccess(res, {
|
|
86
|
+
success: true,
|
|
87
|
+
message: `Changes rejected for comparison ${id}`
|
|
88
|
+
});
|
|
89
|
+
return true;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
output.error('Error rejecting baseline:', error);
|
|
92
|
+
sendError(res, 500, error.message);
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Delete a comparison entirely (removes from report and deletes files)
|
|
98
|
+
if (req.method === 'POST' && pathname === '/api/baseline/delete') {
|
|
99
|
+
if (!screenshotHandler?.deleteComparison) {
|
|
100
|
+
sendError(res, 400, 'Baseline management not available');
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const {
|
|
105
|
+
id
|
|
106
|
+
} = await parseJsonBody(req);
|
|
107
|
+
if (!id) {
|
|
108
|
+
sendError(res, 400, 'Comparison ID required');
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
await screenshotHandler.deleteComparison(id);
|
|
112
|
+
sendSuccess(res, {
|
|
113
|
+
success: true,
|
|
114
|
+
message: `Comparison ${id} deleted`
|
|
115
|
+
});
|
|
116
|
+
return true;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
if (error.code === 'NOT_FOUND') {
|
|
119
|
+
sendError(res, 404, error.message);
|
|
120
|
+
} else {
|
|
121
|
+
output.error('Error deleting comparison:', error);
|
|
122
|
+
sendError(res, 500, error.message);
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
70
128
|
// Reset baselines to previous state
|
|
71
129
|
if (req.method === 'POST' && pathname === '/api/baseline/reset') {
|
|
72
130
|
if (!screenshotHandler?.resetBaselines) {
|
|
@@ -9,14 +9,18 @@ import * as output from '../../utils/output.js';
|
|
|
9
9
|
import { sendHtml, sendSuccess } from '../middleware/response.js';
|
|
10
10
|
|
|
11
11
|
// SPA routes that should serve the dashboard HTML
|
|
12
|
-
const SPA_ROUTES = ['/', '/
|
|
12
|
+
const SPA_ROUTES = ['/', '/stats', '/settings', '/projects', '/builds'];
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* Create dashboard router
|
|
16
16
|
* @param {Object} context - Router context
|
|
17
|
+
* @param {string} context.workingDir - Working directory for report data
|
|
17
18
|
* @returns {Function} Route handler
|
|
18
19
|
*/
|
|
19
|
-
export function createDashboardRouter() {
|
|
20
|
+
export function createDashboardRouter(context) {
|
|
21
|
+
const {
|
|
22
|
+
workingDir = process.cwd()
|
|
23
|
+
} = context || {};
|
|
20
24
|
return async function handleDashboardRoute(req, res, pathname) {
|
|
21
25
|
if (req.method !== 'GET') {
|
|
22
26
|
return false;
|
|
@@ -24,7 +28,7 @@ export function createDashboardRouter() {
|
|
|
24
28
|
|
|
25
29
|
// API endpoint for fetching report data
|
|
26
30
|
if (pathname === '/api/report-data') {
|
|
27
|
-
const reportDataPath = join(
|
|
31
|
+
const reportDataPath = join(workingDir, '.vizzly', 'report-data.json');
|
|
28
32
|
if (existsSync(reportDataPath)) {
|
|
29
33
|
try {
|
|
30
34
|
const data = readFileSync(reportDataPath, 'utf8');
|
|
@@ -50,8 +54,8 @@ export function createDashboardRouter() {
|
|
|
50
54
|
|
|
51
55
|
// API endpoint for real-time status
|
|
52
56
|
if (pathname === '/api/status') {
|
|
53
|
-
const reportDataPath = join(
|
|
54
|
-
const baselineMetadataPath = join(
|
|
57
|
+
const reportDataPath = join(workingDir, '.vizzly', 'report-data.json');
|
|
58
|
+
const baselineMetadataPath = join(workingDir, '.vizzly', 'baselines', 'metadata.json');
|
|
55
59
|
let reportData = null;
|
|
56
60
|
let baselineInfo = null;
|
|
57
61
|
if (existsSync(reportDataPath)) {
|
|
@@ -84,7 +88,7 @@ export function createDashboardRouter() {
|
|
|
84
88
|
|
|
85
89
|
// Serve React SPA for dashboard routes
|
|
86
90
|
if (SPA_ROUTES.includes(pathname) || pathname.startsWith('/comparison/')) {
|
|
87
|
-
const reportDataPath = join(
|
|
91
|
+
const reportDataPath = join(workingDir, '.vizzly', 'report-data.json');
|
|
88
92
|
let reportData = null;
|
|
89
93
|
if (existsSync(reportDataPath)) {
|
|
90
94
|
try {
|
|
@@ -52,6 +52,38 @@ export function createScreenshotRouter({
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Flush endpoint - signals test completion and prints summary
|
|
56
|
+
if (pathname === '/flush') {
|
|
57
|
+
try {
|
|
58
|
+
if (screenshotHandler.getResults) {
|
|
59
|
+
// This triggers printResults() which outputs the summary
|
|
60
|
+
const results = await screenshotHandler.getResults();
|
|
61
|
+
sendJson(res, 200, {
|
|
62
|
+
success: true,
|
|
63
|
+
summary: {
|
|
64
|
+
total: results.total || 0,
|
|
65
|
+
passed: results.passed || 0,
|
|
66
|
+
failed: results.failed || 0,
|
|
67
|
+
new: results.new || 0,
|
|
68
|
+
errors: results.errors || 0
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
sendJson(res, 200, {
|
|
73
|
+
success: true,
|
|
74
|
+
message: 'No TDD results'
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
output.debug('Flush error:', {
|
|
80
|
+
error: error.message
|
|
81
|
+
});
|
|
82
|
+
sendError(res, 500, 'Failed to flush');
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
// Legacy accept-baseline endpoint
|
|
56
88
|
if (pathname === '/accept-baseline') {
|
|
57
89
|
try {
|
|
@@ -75,17 +75,20 @@ export function buildServerInfo({
|
|
|
75
75
|
* @param {Object} [options.services] - Base services object
|
|
76
76
|
* @param {string|null} [options.buildId] - Build ID
|
|
77
77
|
* @param {Object|null} [options.tddService] - TDD service (only in TDD mode)
|
|
78
|
+
* @param {string|null} [options.workingDir] - Working directory for report data
|
|
78
79
|
* @returns {Object} Services object with extras
|
|
79
80
|
*/
|
|
80
81
|
export function buildServicesWithExtras({
|
|
81
82
|
services = {},
|
|
82
83
|
buildId = null,
|
|
83
|
-
tddService = null
|
|
84
|
+
tddService = null,
|
|
85
|
+
workingDir = null
|
|
84
86
|
}) {
|
|
85
87
|
return {
|
|
86
88
|
...services,
|
|
87
89
|
buildId,
|
|
88
|
-
tddService
|
|
90
|
+
tddService,
|
|
91
|
+
workingDir
|
|
89
92
|
};
|
|
90
93
|
}
|
|
91
94
|
|
|
@@ -71,7 +71,8 @@ export async function startServer({
|
|
|
71
71
|
let servicesWithExtras = buildServicesWithExtras({
|
|
72
72
|
services,
|
|
73
73
|
buildId,
|
|
74
|
-
tddService: tddMode ? handler.tddService : null
|
|
74
|
+
tddService: tddMode ? handler.tddService : null,
|
|
75
|
+
workingDir: projectRoot
|
|
75
76
|
});
|
|
76
77
|
|
|
77
78
|
// Create and start HTTP server
|