@vizzly-testing/cli 0.13.1 → 0.13.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 +552 -88
- package/claude-plugin/.claude-plugin/README.md +4 -0
- package/claude-plugin/.mcp.json +4 -0
- package/claude-plugin/CHANGELOG.md +27 -0
- package/claude-plugin/mcp/vizzly-docs-server/README.md +95 -0
- package/claude-plugin/mcp/vizzly-docs-server/docs-fetcher.js +110 -0
- package/claude-plugin/mcp/vizzly-docs-server/index.js +283 -0
- package/claude-plugin/mcp/vizzly-server/cloud-api-provider.js +26 -10
- package/claude-plugin/mcp/vizzly-server/index.js +14 -1
- package/claude-plugin/mcp/vizzly-server/local-tdd-provider.js +61 -28
- package/dist/cli.js +4 -4
- package/dist/commands/run.js +1 -1
- package/dist/commands/tdd-daemon.js +54 -8
- package/dist/commands/tdd.js +8 -8
- package/dist/container/index.js +34 -3
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +29 -59
- package/dist/server/handlers/tdd-handler.js +18 -16
- package/dist/server/http-server.js +473 -4
- package/dist/services/config-service.js +371 -0
- package/dist/services/project-service.js +245 -0
- package/dist/services/server-manager.js +4 -5
- package/dist/services/static-report-generator.js +208 -0
- package/dist/services/tdd-service.js +14 -6
- package/dist/types/reporter/src/components/ui/form-field.d.ts +16 -0
- package/dist/types/reporter/src/components/views/projects-view.d.ts +1 -0
- package/dist/types/reporter/src/components/views/settings-view.d.ts +1 -0
- package/dist/types/reporter/src/hooks/use-auth.d.ts +10 -0
- package/dist/types/reporter/src/hooks/use-config.d.ts +9 -0
- package/dist/types/reporter/src/hooks/use-projects.d.ts +10 -0
- package/dist/types/reporter/src/services/api-client.d.ts +7 -0
- package/dist/types/server/http-server.d.ts +1 -1
- package/dist/types/services/config-service.d.ts +98 -0
- package/dist/types/services/project-service.d.ts +103 -0
- package/dist/types/services/server-manager.d.ts +2 -1
- package/dist/types/services/static-report-generator.d.ts +25 -0
- package/dist/types/services/tdd-service.d.ts +2 -2
- package/dist/utils/console-ui.js +26 -2
- package/docs/tdd-mode.md +31 -15
- package/package.json +4 -4
|
@@ -427,24 +427,24 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
427
427
|
};
|
|
428
428
|
const acceptBaseline = async comparisonId => {
|
|
429
429
|
try {
|
|
430
|
-
//
|
|
431
|
-
const result = await tddService.acceptBaseline(comparisonId);
|
|
432
|
-
|
|
433
|
-
// Read current report data and update the comparison status
|
|
430
|
+
// Read current report data to get the comparison
|
|
434
431
|
const reportData = readReportData();
|
|
435
432
|
const comparison = reportData.comparisons.find(c => c.id === comparisonId);
|
|
436
|
-
if (comparison) {
|
|
437
|
-
|
|
438
|
-
const updatedComparison = {
|
|
439
|
-
...comparison,
|
|
440
|
-
status: 'passed',
|
|
441
|
-
diffPercentage: 0,
|
|
442
|
-
diff: null
|
|
443
|
-
};
|
|
444
|
-
updateComparison(updatedComparison);
|
|
445
|
-
} else {
|
|
446
|
-
logger.error(`Comparison not found in report data for ID: ${comparisonId}`);
|
|
433
|
+
if (!comparison) {
|
|
434
|
+
throw new Error(`Comparison not found with ID: ${comparisonId}`);
|
|
447
435
|
}
|
|
436
|
+
|
|
437
|
+
// Pass the comparison object to tddService instead of just the ID
|
|
438
|
+
const result = await tddService.acceptBaseline(comparison);
|
|
439
|
+
|
|
440
|
+
// Update the comparison to passed status
|
|
441
|
+
const updatedComparison = {
|
|
442
|
+
...comparison,
|
|
443
|
+
status: 'passed',
|
|
444
|
+
diffPercentage: 0,
|
|
445
|
+
diff: null
|
|
446
|
+
};
|
|
447
|
+
updateComparison(updatedComparison);
|
|
448
448
|
logger.info(`Baseline accepted for comparison ${comparisonId}`);
|
|
449
449
|
return result;
|
|
450
450
|
} catch (error) {
|
|
@@ -461,7 +461,9 @@ export const createTddHandler = (config, workingDir, baselineBuild, baselineComp
|
|
|
461
461
|
// Accept all failed or new comparisons
|
|
462
462
|
for (const comparison of reportData.comparisons) {
|
|
463
463
|
if (comparison.status === 'failed' || comparison.status === 'new') {
|
|
464
|
-
|
|
464
|
+
// Pass the comparison object directly instead of just the ID
|
|
465
|
+
// This allows tddService to work with comparisons from report-data.json
|
|
466
|
+
await tddService.acceptBaseline(comparison);
|
|
465
467
|
|
|
466
468
|
// Update the comparison to passed status
|
|
467
469
|
updateComparison({
|
|
@@ -7,8 +7,13 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
7
7
|
const __dirname = dirname(__filename);
|
|
8
8
|
const PROJECT_ROOT = join(__dirname, '..', '..');
|
|
9
9
|
const logger = createServiceLogger('HTTP-SERVER');
|
|
10
|
-
export const createHttpServer = (port, screenshotHandler) => {
|
|
10
|
+
export const createHttpServer = (port, screenshotHandler, services = {}) => {
|
|
11
11
|
let server = null;
|
|
12
|
+
|
|
13
|
+
// Extract services for config/auth/project management
|
|
14
|
+
let configService = services.configService;
|
|
15
|
+
let authService = services.authService;
|
|
16
|
+
let projectService = services.projectService;
|
|
12
17
|
const parseRequestBody = req => {
|
|
13
18
|
return new Promise((resolve, reject) => {
|
|
14
19
|
let body = '';
|
|
@@ -80,7 +85,7 @@ export const createHttpServer = (port, screenshotHandler) => {
|
|
|
80
85
|
}
|
|
81
86
|
|
|
82
87
|
// Serve the main React app for all non-API routes
|
|
83
|
-
if (req.method === 'GET' && (parsedUrl.pathname === '/' || parsedUrl.pathname === '/dashboard' || parsedUrl.pathname === '/stats')) {
|
|
88
|
+
if (req.method === 'GET' && (parsedUrl.pathname === '/' || parsedUrl.pathname === '/dashboard' || parsedUrl.pathname === '/stats' || parsedUrl.pathname === '/settings' || parsedUrl.pathname === '/projects')) {
|
|
84
89
|
// Serve React-powered dashboard
|
|
85
90
|
const reportDataPath = join(process.cwd(), '.vizzly', 'report-data.json');
|
|
86
91
|
|
|
@@ -98,7 +103,7 @@ export const createHttpServer = (port, screenshotHandler) => {
|
|
|
98
103
|
<!DOCTYPE html>
|
|
99
104
|
<html>
|
|
100
105
|
<head>
|
|
101
|
-
<title>Vizzly
|
|
106
|
+
<title>Vizzly Dev Dashboard</title>
|
|
102
107
|
<meta charset="utf-8">
|
|
103
108
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
104
109
|
<link rel="stylesheet" href="/reporter-bundle.css">
|
|
@@ -108,7 +113,7 @@ export const createHttpServer = (port, screenshotHandler) => {
|
|
|
108
113
|
<div class="reporter-loading">
|
|
109
114
|
<div>
|
|
110
115
|
<div class="spinner"></div>
|
|
111
|
-
<p>Loading Vizzly
|
|
116
|
+
<p>Loading Vizzly Dev Dashboard...</p>
|
|
112
117
|
</div>
|
|
113
118
|
</div>
|
|
114
119
|
</div>
|
|
@@ -401,6 +406,470 @@ export const createHttpServer = (port, screenshotHandler) => {
|
|
|
401
406
|
}
|
|
402
407
|
return;
|
|
403
408
|
}
|
|
409
|
+
|
|
410
|
+
// ===== CONFIG MANAGEMENT ENDPOINTS =====
|
|
411
|
+
|
|
412
|
+
if (req.method === 'GET' && parsedUrl.pathname === '/api/config') {
|
|
413
|
+
// Get merged config with sources
|
|
414
|
+
if (!configService) {
|
|
415
|
+
res.statusCode = 503;
|
|
416
|
+
res.end(JSON.stringify({
|
|
417
|
+
error: 'Config service not available'
|
|
418
|
+
}));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
const configData = await configService.getConfig('merged');
|
|
423
|
+
res.statusCode = 200;
|
|
424
|
+
res.end(JSON.stringify(configData));
|
|
425
|
+
} catch (error) {
|
|
426
|
+
logger.error('Error fetching config:', error);
|
|
427
|
+
res.statusCode = 500;
|
|
428
|
+
res.end(JSON.stringify({
|
|
429
|
+
error: error.message
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (req.method === 'GET' && parsedUrl.pathname === '/api/config/project') {
|
|
435
|
+
// Get project-level config
|
|
436
|
+
if (!configService) {
|
|
437
|
+
res.statusCode = 503;
|
|
438
|
+
res.end(JSON.stringify({
|
|
439
|
+
error: 'Config service not available'
|
|
440
|
+
}));
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
const configData = await configService.getConfig('project');
|
|
445
|
+
res.statusCode = 200;
|
|
446
|
+
res.end(JSON.stringify(configData));
|
|
447
|
+
} catch (error) {
|
|
448
|
+
logger.error('Error fetching project config:', error);
|
|
449
|
+
res.statusCode = 500;
|
|
450
|
+
res.end(JSON.stringify({
|
|
451
|
+
error: error.message
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (req.method === 'GET' && parsedUrl.pathname === '/api/config/global') {
|
|
457
|
+
// Get global config
|
|
458
|
+
if (!configService) {
|
|
459
|
+
res.statusCode = 503;
|
|
460
|
+
res.end(JSON.stringify({
|
|
461
|
+
error: 'Config service not available'
|
|
462
|
+
}));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const configData = await configService.getConfig('global');
|
|
467
|
+
res.statusCode = 200;
|
|
468
|
+
res.end(JSON.stringify(configData));
|
|
469
|
+
} catch (error) {
|
|
470
|
+
logger.error('Error fetching global config:', error);
|
|
471
|
+
res.statusCode = 500;
|
|
472
|
+
res.end(JSON.stringify({
|
|
473
|
+
error: error.message
|
|
474
|
+
}));
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/api/config/project') {
|
|
479
|
+
// Update project config
|
|
480
|
+
if (!configService) {
|
|
481
|
+
res.statusCode = 503;
|
|
482
|
+
res.end(JSON.stringify({
|
|
483
|
+
error: 'Config service not available'
|
|
484
|
+
}));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
try {
|
|
488
|
+
const body = await parseRequestBody(req);
|
|
489
|
+
const result = await configService.updateConfig('project', body);
|
|
490
|
+
res.statusCode = 200;
|
|
491
|
+
res.end(JSON.stringify({
|
|
492
|
+
success: true,
|
|
493
|
+
...result
|
|
494
|
+
}));
|
|
495
|
+
} catch (error) {
|
|
496
|
+
logger.error('Error updating project config:', error);
|
|
497
|
+
res.statusCode = 500;
|
|
498
|
+
res.end(JSON.stringify({
|
|
499
|
+
error: error.message
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/api/config/global') {
|
|
505
|
+
// Update global config
|
|
506
|
+
if (!configService) {
|
|
507
|
+
res.statusCode = 503;
|
|
508
|
+
res.end(JSON.stringify({
|
|
509
|
+
error: 'Config service not available'
|
|
510
|
+
}));
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
try {
|
|
514
|
+
const body = await parseRequestBody(req);
|
|
515
|
+
const result = await configService.updateConfig('global', body);
|
|
516
|
+
res.statusCode = 200;
|
|
517
|
+
res.end(JSON.stringify({
|
|
518
|
+
success: true,
|
|
519
|
+
...result
|
|
520
|
+
}));
|
|
521
|
+
} catch (error) {
|
|
522
|
+
logger.error('Error updating global config:', error);
|
|
523
|
+
res.statusCode = 500;
|
|
524
|
+
res.end(JSON.stringify({
|
|
525
|
+
error: error.message
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/api/config/validate') {
|
|
531
|
+
// Validate config
|
|
532
|
+
if (!configService) {
|
|
533
|
+
res.statusCode = 503;
|
|
534
|
+
res.end(JSON.stringify({
|
|
535
|
+
error: 'Config service not available'
|
|
536
|
+
}));
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
try {
|
|
540
|
+
const body = await parseRequestBody(req);
|
|
541
|
+
const result = await configService.validateConfig(body);
|
|
542
|
+
res.statusCode = 200;
|
|
543
|
+
res.end(JSON.stringify(result));
|
|
544
|
+
} catch (error) {
|
|
545
|
+
logger.error('Error validating config:', error);
|
|
546
|
+
res.statusCode = 500;
|
|
547
|
+
res.end(JSON.stringify({
|
|
548
|
+
error: error.message
|
|
549
|
+
}));
|
|
550
|
+
}
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ===== AUTH ENDPOINTS =====
|
|
555
|
+
|
|
556
|
+
if (req.method === 'GET' && parsedUrl.pathname === '/api/auth/status') {
|
|
557
|
+
// Get auth status and user info
|
|
558
|
+
if (!authService) {
|
|
559
|
+
res.statusCode = 503;
|
|
560
|
+
res.end(JSON.stringify({
|
|
561
|
+
error: 'Auth service not available'
|
|
562
|
+
}));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
try {
|
|
566
|
+
const isAuthenticated = await authService.isAuthenticated();
|
|
567
|
+
let user = null;
|
|
568
|
+
if (isAuthenticated) {
|
|
569
|
+
const whoami = await authService.whoami();
|
|
570
|
+
user = whoami.user;
|
|
571
|
+
}
|
|
572
|
+
res.statusCode = 200;
|
|
573
|
+
res.end(JSON.stringify({
|
|
574
|
+
authenticated: isAuthenticated,
|
|
575
|
+
user
|
|
576
|
+
}));
|
|
577
|
+
} catch (error) {
|
|
578
|
+
logger.error('Error getting auth status:', error);
|
|
579
|
+
res.statusCode = 200;
|
|
580
|
+
res.end(JSON.stringify({
|
|
581
|
+
authenticated: false,
|
|
582
|
+
user: null
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/api/auth/login') {
|
|
588
|
+
// Initiate device flow login
|
|
589
|
+
if (!authService) {
|
|
590
|
+
res.statusCode = 503;
|
|
591
|
+
res.end(JSON.stringify({
|
|
592
|
+
error: 'Auth service not available'
|
|
593
|
+
}));
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
try {
|
|
597
|
+
const deviceFlow = await authService.initiateDeviceFlow();
|
|
598
|
+
|
|
599
|
+
// Transform snake_case to camelCase for frontend
|
|
600
|
+
const response = {
|
|
601
|
+
deviceCode: deviceFlow.device_code,
|
|
602
|
+
userCode: deviceFlow.user_code,
|
|
603
|
+
verificationUri: deviceFlow.verification_uri,
|
|
604
|
+
verificationUriComplete: deviceFlow.verification_uri_complete,
|
|
605
|
+
expiresIn: deviceFlow.expires_in,
|
|
606
|
+
interval: deviceFlow.interval
|
|
607
|
+
};
|
|
608
|
+
res.statusCode = 200;
|
|
609
|
+
res.end(JSON.stringify(response));
|
|
610
|
+
} catch (error) {
|
|
611
|
+
logger.error('Error initiating device flow:', error);
|
|
612
|
+
res.statusCode = 500;
|
|
613
|
+
res.end(JSON.stringify({
|
|
614
|
+
error: error.message
|
|
615
|
+
}));
|
|
616
|
+
}
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/api/auth/poll') {
|
|
620
|
+
// Poll device authorization status
|
|
621
|
+
if (!authService) {
|
|
622
|
+
res.statusCode = 503;
|
|
623
|
+
res.end(JSON.stringify({
|
|
624
|
+
error: 'Auth service not available'
|
|
625
|
+
}));
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
const body = await parseRequestBody(req);
|
|
630
|
+
const {
|
|
631
|
+
deviceCode
|
|
632
|
+
} = body;
|
|
633
|
+
if (!deviceCode) {
|
|
634
|
+
res.statusCode = 400;
|
|
635
|
+
res.end(JSON.stringify({
|
|
636
|
+
error: 'deviceCode is required'
|
|
637
|
+
}));
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
let result;
|
|
641
|
+
try {
|
|
642
|
+
result = await authService.pollDeviceAuthorization(deviceCode);
|
|
643
|
+
} catch (error) {
|
|
644
|
+
// Handle "Authorization pending" as a valid response
|
|
645
|
+
if (error.message && error.message.includes('Authorization pending')) {
|
|
646
|
+
res.statusCode = 200;
|
|
647
|
+
res.end(JSON.stringify({
|
|
648
|
+
status: 'pending'
|
|
649
|
+
}));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
// Other errors are actual failures
|
|
653
|
+
throw error;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Check if authorization is complete by looking for tokens
|
|
657
|
+
if (result.tokens && result.tokens.accessToken) {
|
|
658
|
+
// Handle both snake_case and camelCase for token data
|
|
659
|
+
let tokensData = result.tokens;
|
|
660
|
+
let tokenExpiresIn = tokensData.expiresIn || tokensData.expires_in;
|
|
661
|
+
let tokenExpiresAt = tokenExpiresIn ? new Date(Date.now() + tokenExpiresIn * 1000).toISOString() : result.expires_at || result.expiresAt;
|
|
662
|
+
let tokens = {
|
|
663
|
+
accessToken: tokensData.accessToken || tokensData.access_token,
|
|
664
|
+
refreshToken: tokensData.refreshToken || tokensData.refresh_token,
|
|
665
|
+
expiresAt: tokenExpiresAt,
|
|
666
|
+
user: result.user
|
|
667
|
+
};
|
|
668
|
+
await authService.completeDeviceFlow(tokens);
|
|
669
|
+
|
|
670
|
+
// Return a simplified response to the client
|
|
671
|
+
res.statusCode = 200;
|
|
672
|
+
res.end(JSON.stringify({
|
|
673
|
+
status: 'complete',
|
|
674
|
+
user: result.user
|
|
675
|
+
}));
|
|
676
|
+
} else {
|
|
677
|
+
// Still pending or other status
|
|
678
|
+
res.statusCode = 200;
|
|
679
|
+
res.end(JSON.stringify({
|
|
680
|
+
status: 'pending'
|
|
681
|
+
}));
|
|
682
|
+
}
|
|
683
|
+
} catch (error) {
|
|
684
|
+
logger.error('Error polling device authorization:', error);
|
|
685
|
+
res.statusCode = 500;
|
|
686
|
+
res.end(JSON.stringify({
|
|
687
|
+
error: error.message
|
|
688
|
+
}));
|
|
689
|
+
}
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/api/auth/logout') {
|
|
693
|
+
// Logout user
|
|
694
|
+
if (!authService) {
|
|
695
|
+
res.statusCode = 503;
|
|
696
|
+
res.end(JSON.stringify({
|
|
697
|
+
error: 'Auth service not available'
|
|
698
|
+
}));
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
try {
|
|
702
|
+
await authService.logout();
|
|
703
|
+
res.statusCode = 200;
|
|
704
|
+
res.end(JSON.stringify({
|
|
705
|
+
success: true,
|
|
706
|
+
message: 'Logged out successfully'
|
|
707
|
+
}));
|
|
708
|
+
} catch (error) {
|
|
709
|
+
logger.error('Error logging out:', error);
|
|
710
|
+
res.statusCode = 500;
|
|
711
|
+
res.end(JSON.stringify({
|
|
712
|
+
error: error.message
|
|
713
|
+
}));
|
|
714
|
+
}
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// ===== PROJECT MANAGEMENT ENDPOINTS =====
|
|
719
|
+
|
|
720
|
+
if (req.method === 'GET' && parsedUrl.pathname === '/api/projects') {
|
|
721
|
+
// List all projects from API
|
|
722
|
+
if (!projectService) {
|
|
723
|
+
res.statusCode = 503;
|
|
724
|
+
res.end(JSON.stringify({
|
|
725
|
+
error: 'Project service not available'
|
|
726
|
+
}));
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
const projects = await projectService.listProjects();
|
|
731
|
+
res.statusCode = 200;
|
|
732
|
+
res.end(JSON.stringify({
|
|
733
|
+
projects
|
|
734
|
+
}));
|
|
735
|
+
} catch (error) {
|
|
736
|
+
logger.error('Error listing projects:', error);
|
|
737
|
+
res.statusCode = 500;
|
|
738
|
+
res.end(JSON.stringify({
|
|
739
|
+
error: error.message
|
|
740
|
+
}));
|
|
741
|
+
}
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (req.method === 'GET' && parsedUrl.pathname === '/api/projects/mappings') {
|
|
745
|
+
// List project directory mappings
|
|
746
|
+
if (!projectService) {
|
|
747
|
+
res.statusCode = 503;
|
|
748
|
+
res.end(JSON.stringify({
|
|
749
|
+
error: 'Project service not available'
|
|
750
|
+
}));
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
try {
|
|
754
|
+
const mappings = await projectService.listMappings();
|
|
755
|
+
res.statusCode = 200;
|
|
756
|
+
res.end(JSON.stringify({
|
|
757
|
+
mappings
|
|
758
|
+
}));
|
|
759
|
+
} catch (error) {
|
|
760
|
+
logger.error('Error listing project mappings:', error);
|
|
761
|
+
res.statusCode = 500;
|
|
762
|
+
res.end(JSON.stringify({
|
|
763
|
+
error: error.message
|
|
764
|
+
}));
|
|
765
|
+
}
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
if (req.method === 'POST' && parsedUrl.pathname === '/api/projects/mappings') {
|
|
769
|
+
// Create or update project mapping
|
|
770
|
+
if (!projectService) {
|
|
771
|
+
res.statusCode = 503;
|
|
772
|
+
res.end(JSON.stringify({
|
|
773
|
+
error: 'Project service not available'
|
|
774
|
+
}));
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
const body = await parseRequestBody(req);
|
|
779
|
+
const {
|
|
780
|
+
directory,
|
|
781
|
+
projectSlug,
|
|
782
|
+
organizationSlug,
|
|
783
|
+
token,
|
|
784
|
+
projectName
|
|
785
|
+
} = body;
|
|
786
|
+
const mapping = await projectService.createMapping(directory, {
|
|
787
|
+
projectSlug,
|
|
788
|
+
organizationSlug,
|
|
789
|
+
token,
|
|
790
|
+
projectName
|
|
791
|
+
});
|
|
792
|
+
res.statusCode = 200;
|
|
793
|
+
res.end(JSON.stringify({
|
|
794
|
+
success: true,
|
|
795
|
+
mapping
|
|
796
|
+
}));
|
|
797
|
+
} catch (error) {
|
|
798
|
+
logger.error('Error creating project mapping:', error);
|
|
799
|
+
res.statusCode = 500;
|
|
800
|
+
res.end(JSON.stringify({
|
|
801
|
+
error: error.message
|
|
802
|
+
}));
|
|
803
|
+
}
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (req.method === 'DELETE' && parsedUrl.pathname.startsWith('/api/projects/mappings/')) {
|
|
807
|
+
// Delete project mapping
|
|
808
|
+
if (!projectService) {
|
|
809
|
+
res.statusCode = 503;
|
|
810
|
+
res.end(JSON.stringify({
|
|
811
|
+
error: 'Project service not available'
|
|
812
|
+
}));
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
try {
|
|
816
|
+
const directory = decodeURIComponent(parsedUrl.pathname.replace('/api/projects/mappings/', ''));
|
|
817
|
+
await projectService.removeMapping(directory);
|
|
818
|
+
res.statusCode = 200;
|
|
819
|
+
res.end(JSON.stringify({
|
|
820
|
+
success: true,
|
|
821
|
+
message: 'Mapping deleted'
|
|
822
|
+
}));
|
|
823
|
+
} catch (error) {
|
|
824
|
+
logger.error('Error deleting project mapping:', error);
|
|
825
|
+
res.statusCode = 500;
|
|
826
|
+
res.end(JSON.stringify({
|
|
827
|
+
error: error.message
|
|
828
|
+
}));
|
|
829
|
+
}
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
if (req.method === 'GET' && parsedUrl.pathname === '/api/builds/recent') {
|
|
833
|
+
// Get recent builds for current project
|
|
834
|
+
if (!projectService || !configService) {
|
|
835
|
+
res.statusCode = 503;
|
|
836
|
+
res.end(JSON.stringify({
|
|
837
|
+
error: 'Required services not available'
|
|
838
|
+
}));
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
try {
|
|
842
|
+
const config = await configService.getConfig('merged');
|
|
843
|
+
const {
|
|
844
|
+
projectSlug,
|
|
845
|
+
organizationSlug
|
|
846
|
+
} = config.config;
|
|
847
|
+
if (!projectSlug || !organizationSlug) {
|
|
848
|
+
res.statusCode = 400;
|
|
849
|
+
res.end(JSON.stringify({
|
|
850
|
+
error: 'No project configured for this directory'
|
|
851
|
+
}));
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const limit = parseInt(parsedUrl.searchParams.get('limit') || '10', 10);
|
|
855
|
+
const branch = parsedUrl.searchParams.get('branch') || undefined;
|
|
856
|
+
const builds = await projectService.getRecentBuilds(projectSlug, organizationSlug, {
|
|
857
|
+
limit,
|
|
858
|
+
branch
|
|
859
|
+
});
|
|
860
|
+
res.statusCode = 200;
|
|
861
|
+
res.end(JSON.stringify({
|
|
862
|
+
builds
|
|
863
|
+
}));
|
|
864
|
+
} catch (error) {
|
|
865
|
+
logger.error('Error fetching recent builds:', error);
|
|
866
|
+
res.statusCode = 500;
|
|
867
|
+
res.end(JSON.stringify({
|
|
868
|
+
error: error.message
|
|
869
|
+
}));
|
|
870
|
+
}
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
404
873
|
res.statusCode = 404;
|
|
405
874
|
res.end(JSON.stringify({
|
|
406
875
|
error: 'Not found'
|