UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

492 lines (491 loc) • 21.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.manageWorkspaceHealth = manageWorkspaceHealth; const chalk_1 = __importDefault(require("chalk")); const prompts_1 = __importDefault(require("prompts")); const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const workspace_health_1 = require("../utils/workspace-health"); const DEFAULT_WORKSPACE_FILE = 're-shell.workspaces.yaml'; async function manageWorkspaceHealth(options = {}) { const { spinner, verbose, json } = options; try { if (options.check) { await performFullHealthCheck(options, spinner); return; } if (options.topology) { await validateWorkspaceTopology(options, spinner); return; } if (options.quick) { await performQuickCheck(options, spinner); return; } if (options.watch) { await watchWorkspaceHealth(options, spinner); return; } if (options.fix) { await fixHealthIssues(options, spinner); return; } if (options.interactive) { await interactiveHealthManagement(options, spinner); return; } // Default: show health status await showHealthStatus(options, spinner); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Workspace health operation failed')); throw error; } } async function performFullHealthCheck(options, spinner) { const inputFile = options.file || DEFAULT_WORKSPACE_FILE; const inputPath = path.resolve(inputFile); if (spinner) spinner.setText(`Performing comprehensive health check: ${inputFile}`); try { const checker = await (0, workspace_health_1.createWorkspaceHealthChecker)(inputPath, path.dirname(inputPath)); const report = await checker.performHealthCheck(); if (spinner) spinner.stop(); if (options.output) { // Save report to file const outputPath = path.resolve(options.output); await fs.writeJson(outputPath, report, { spaces: 2 }); console.log(chalk_1.default.green(`Health report saved to: ${options.output}`)); } if (options.json) { console.log(JSON.stringify(report, null, 2)); return; } displayHealthReport(report, inputFile, options.detailed || false, options.category); // Exit with error code if unhealthy if (report.overall.status === 'unhealthy') { process.exit(1); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Health check failed')); throw error; } } async function validateWorkspaceTopology(options, spinner) { const inputFile = options.file || DEFAULT_WORKSPACE_FILE; const inputPath = path.resolve(inputFile); if (spinner) spinner.setText(`Validating workspace topology: ${inputFile}`); try { const checker = await (0, workspace_health_1.createWorkspaceHealthChecker)(inputPath, path.dirname(inputPath)); const validation = await checker.validateTopology(); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify(validation, null, 2)); return; } displayTopologyValidation(validation, inputFile); if (!validation.isValid) { process.exit(1); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Topology validation failed')); throw error; } } async function performQuickCheck(options, spinner) { const inputFile = options.file || DEFAULT_WORKSPACE_FILE; const inputPath = path.resolve(inputFile); if (spinner) spinner.setText(`Quick health check: ${inputFile}`); try { const result = await (0, workspace_health_1.performQuickHealthCheck)(inputPath, path.dirname(inputPath)); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify(result, null, 2)); return; } displayQuickHealthResult(result, inputFile); if (result.status === 'unhealthy') { process.exit(1); } } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Quick health check failed')); throw error; } } async function watchWorkspaceHealth(options, spinner) { const inputFile = options.file || DEFAULT_WORKSPACE_FILE; if (spinner) spinner.setText('Starting health monitoring...'); console.log(chalk_1.default.cyan('šŸ” Workspace Health Monitor')); console.log(chalk_1.default.gray('Press Ctrl+C to stop monitoring\n')); try { // Initial health check await performQuickCheck(options); console.log(chalk_1.default.cyan('\nšŸ‘€ Monitoring workspace health...')); console.log(chalk_1.default.gray('Health checks will run every 30 seconds\n')); // Set up periodic health checks const interval = setInterval(async () => { try { console.log(chalk_1.default.gray(`[${new Date().toLocaleTimeString()}] Running health check...`)); const result = await (0, workspace_health_1.performQuickHealthCheck)(inputFile); const statusIcon = result.status === 'healthy' ? 'āœ…' : result.status === 'degraded' ? 'āš ļø' : 'āŒ'; const statusColor = result.status === 'healthy' ? chalk_1.default.green : result.status === 'degraded' ? chalk_1.default.yellow : chalk_1.default.red; console.log(`${statusIcon} ${statusColor(result.status.toUpperCase())} - Score: ${result.score}%`); if (result.criticalIssues > 0) { console.log(chalk_1.default.red(`🚨 ${result.criticalIssues} critical issue(s) detected`)); } } catch (error) { console.log(chalk_1.default.red(`āŒ Health check failed: ${error.message}`)); } }, 30000); // Handle graceful shutdown process.on('SIGINT', () => { clearInterval(interval); console.log(chalk_1.default.yellow('\nšŸ›‘ Health monitoring stopped')); process.exit(0); }); // Keep the process running await new Promise(() => { }); // Run indefinitely } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Health monitoring failed')); throw error; } } async function fixHealthIssues(options, spinner) { const inputFile = options.file || DEFAULT_WORKSPACE_FILE; const inputPath = path.resolve(inputFile); if (spinner) spinner.setText(`Analyzing health issues for auto-fix: ${inputFile}`); try { const checker = await (0, workspace_health_1.createWorkspaceHealthChecker)(inputPath, path.dirname(inputPath)); const report = await checker.performHealthCheck(); if (spinner) spinner.stop(); // Find fixable issues const fixableIssues = report.categories .flatMap(cat => cat.checks) .filter(check => check.status === 'fail' && check.suggestions && check.suggestions.length > 0); if (fixableIssues.length === 0) { console.log(chalk_1.default.green('āœ… No auto-fixable issues found')); return; } console.log(chalk_1.default.cyan(`\\nšŸ”§ Auto-Fix Analysis`)); console.log(chalk_1.default.gray('═'.repeat(50))); console.log(`\\nFound ${fixableIssues.length} potentially fixable issue(s):`); for (let i = 0; i < fixableIssues.length; i++) { const issue = fixableIssues[i]; console.log(`\\n${i + 1}. ${issue.name}`); console.log(` Issue: ${issue.message}`); console.log(` Suggestions:`); for (const suggestion of issue.suggestions) { console.log(` • ${suggestion}`); } } console.log(chalk_1.default.yellow('\\nāš ļø Auto-fix implementation coming in next update')); console.log(chalk_1.default.gray('For now, please apply fixes manually based on suggestions above.')); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Health fix analysis failed')); throw error; } } async function showHealthStatus(options, spinner) { const inputFile = options.file || DEFAULT_WORKSPACE_FILE; const inputPath = path.resolve(inputFile); if (spinner) spinner.setText('Checking workspace health status...'); try { if (!(await fs.pathExists(inputPath))) { if (spinner) spinner.stop(); console.log(chalk_1.default.yellow('\\nāš ļø No workspace definition found')); console.log(chalk_1.default.gray(`Expected: ${inputFile}`)); console.log(chalk_1.default.cyan('\\nšŸš€ Quick start:')); console.log(' re-shell workspace-def init'); return; } const result = await (0, workspace_health_1.performQuickHealthCheck)(inputPath, path.dirname(inputPath)); if (spinner) spinner.stop(); if (options.json) { console.log(JSON.stringify(result, null, 2)); return; } console.log(chalk_1.default.cyan('\\nšŸ„ Workspace Health Status')); console.log(chalk_1.default.gray('═'.repeat(50))); const statusIcon = result.status === 'healthy' ? 'āœ…' : result.status === 'degraded' ? 'āš ļø' : 'āŒ'; const statusColor = result.status === 'healthy' ? chalk_1.default.green : result.status === 'degraded' ? chalk_1.default.yellow : chalk_1.default.red; console.log(`\\nOverall Status: ${statusIcon} ${statusColor(result.status.toUpperCase())}`); console.log(`Health Score: ${getScoreColor(result.score)}${result.score}%${chalk_1.default.reset()}`); if (result.criticalIssues > 0) { console.log(`Critical Issues: ${chalk_1.default.red(result.criticalIssues)}`); } else { console.log(`Critical Issues: ${chalk_1.default.green('None')}`); } console.log(chalk_1.default.cyan('\\nšŸ› ļø Available Commands:')); console.log(' • re-shell workspace-health check --detailed'); console.log(' • re-shell workspace-health topology'); console.log(' • re-shell workspace-health watch'); console.log(' • re-shell workspace-health interactive'); } catch (error) { if (spinner) spinner.fail(chalk_1.default.red('Health status check failed')); throw error; } } async function interactiveHealthManagement(options, spinner) { if (spinner) spinner.stop(); const inputFile = options.file || DEFAULT_WORKSPACE_FILE; const inputPath = path.resolve(inputFile); const exists = await fs.pathExists(inputPath); if (!exists) { console.log(chalk_1.default.yellow('\\nāš ļø No workspace definition found')); console.log(chalk_1.default.gray('Create one first with: re-shell workspace-def init')); return; } const response = await (0, prompts_1.default)([ { type: 'select', name: 'action', message: 'What would you like to do?', choices: [ { title: 'šŸ„ Full health check', value: 'check' }, { title: '⚔ Quick health check', value: 'quick' }, { title: 'šŸ—ļø Validate topology', value: 'topology' }, { title: 'šŸ‘€ Monitor health', value: 'watch' }, { title: 'šŸ”§ Analyze fixable issues', value: 'fix' }, { title: 'šŸ“Š Show status', value: 'status' } ] } ]); if (!response.action) return; switch (response.action) { case 'check': await performFullHealthCheck({ ...options, interactive: false }); break; case 'quick': await performQuickCheck({ ...options, interactive: false }); break; case 'topology': await validateWorkspaceTopology({ ...options, interactive: false }); break; case 'watch': await watchWorkspaceHealth({ ...options, interactive: false }); break; case 'fix': await fixHealthIssues({ ...options, interactive: false }); break; case 'status': await showHealthStatus({ ...options, interactive: false }); break; } } // Display functions function displayHealthReport(report, fileName, detailed, categoryFilter) { console.log(chalk_1.default.cyan('\\nšŸ„ Workspace Health Report')); console.log(chalk_1.default.gray(`File: ${fileName}`)); console.log(chalk_1.default.gray(`Generated: ${new Date(report.timestamp).toLocaleString()}`)); console.log(chalk_1.default.gray('═'.repeat(60))); // Overall status const statusIcon = report.overall.status === 'healthy' ? 'āœ…' : report.overall.status === 'degraded' ? 'āš ļø' : 'āŒ'; const statusColor = report.overall.status === 'healthy' ? chalk_1.default.green : report.overall.status === 'degraded' ? chalk_1.default.yellow : chalk_1.default.red; console.log(`\\n${statusIcon} Overall: ${statusColor(report.overall.status.toUpperCase())}`); console.log(`šŸ“Š Health Score: ${getScoreColor(report.overall.score)}${report.overall.score}%${chalk_1.default.reset()}`); console.log(`ā±ļø Duration: ${report.duration}ms`); console.log(`šŸ’¬ ${report.overall.summary}`); // Metrics console.log(`\\nšŸ“ˆ Metrics:`); console.log(` Workspaces: ${report.metrics.workspaceCount}`); console.log(` Dependencies: ${report.metrics.dependencyCount}`); console.log(` Cycles: ${report.metrics.cycleCount}`); console.log(` Orphaned: ${report.metrics.orphanedCount}`); console.log(` Coverage: ${report.metrics.coverageScore}%`); // Categories const categoriesToShow = categoryFilter ? report.categories.filter(cat => cat.id === categoryFilter) : report.categories; for (const category of categoriesToShow) { displayHealthCategory(category, detailed); } // Recommendations if (report.recommendations.length > 0) { console.log(chalk_1.default.cyan(`\\nšŸ’” Recommendations:`)); for (let i = 0; i < Math.min(report.recommendations.length, 5); i++) { console.log(` ${i + 1}. ${report.recommendations[i]}`); } if (report.recommendations.length > 5) { console.log(` ... and ${report.recommendations.length - 5} more`); } } } function displayHealthCategory(category, detailed) { const categoryIcon = getCategoryIcon(category.id); const scoreColor = getScoreColor(category.summary.score); console.log(`\\n${categoryIcon} ${chalk_1.default.cyan(category.name)} - ${scoreColor}${category.summary.score}%${chalk_1.default.reset()}`); console.log(` ${chalk_1.default.gray(category.description)}`); if (category.summary.failed > 0) { console.log(` āŒ Failed: ${chalk_1.default.red(category.summary.failed)}`); } if (category.summary.warnings > 0) { console.log(` āš ļø Warnings: ${chalk_1.default.yellow(category.summary.warnings)}`); } if (category.summary.passed > 0) { console.log(` āœ… Passed: ${chalk_1.default.green(category.summary.passed)}`); } if (detailed) { for (const check of category.checks) { if (check.status === 'fail' || check.status === 'warning') { displayHealthCheck(check); } } } } function displayHealthCheck(check) { const statusIcon = check.status === 'pass' ? 'āœ…' : check.status === 'fail' ? 'āŒ' : check.status === 'warning' ? 'āš ļø' : 'ā„¹ļø'; console.log(`\\n ${statusIcon} ${check.name}`); console.log(` ${check.message}`); if (check.suggestions && check.suggestions.length > 0) { console.log(` Suggestions:`); for (const suggestion of check.suggestions) { console.log(` • ${suggestion}`); } } } function displayTopologyValidation(validation, fileName) { console.log(chalk_1.default.cyan('\\nšŸ—ļø Workspace Topology Validation')); console.log(chalk_1.default.gray(`File: ${fileName}`)); console.log(chalk_1.default.gray('═'.repeat(60))); const statusIcon = validation.isValid ? 'āœ…' : 'āŒ'; const statusColor = validation.isValid ? chalk_1.default.green : chalk_1.default.red; console.log(`\\n${statusIcon} Topology: ${statusColor(validation.isValid ? 'VALID' : 'INVALID')}`); // Structure metrics console.log(`\\nšŸ“Š Structure Metrics:`); console.log(` Depth: ${validation.structure.depth}`); console.log(` Breadth: ${validation.structure.breadth}`); console.log(` Complexity: ${validation.structure.complexity.toFixed(2)}`); console.log(` Balance: ${(validation.structure.balance * 100).toFixed(1)}%`); // Errors if (validation.errors.length > 0) { console.log(chalk_1.default.red(`\\nāŒ Errors (${validation.errors.length}):`)); for (const error of validation.errors) { console.log(` • ${error.message}`); } } // Warnings if (validation.warnings.length > 0) { console.log(chalk_1.default.yellow(`\\nāš ļø Warnings (${validation.warnings.length}):`)); for (const warning of validation.warnings) { console.log(` • ${warning}`); } } // Suggestions if (validation.suggestions.length > 0) { console.log(chalk_1.default.cyan(`\\nšŸ’” Suggestions (${validation.suggestions.length}):`)); for (const suggestion of validation.suggestions) { console.log(` • ${suggestion}`); } } } function displayQuickHealthResult(result, fileName) { console.log(chalk_1.default.cyan('\\n⚔ Quick Health Check')); console.log(chalk_1.default.gray(`File: ${fileName}`)); console.log(chalk_1.default.gray('═'.repeat(40))); const statusIcon = result.status === 'healthy' ? 'āœ…' : result.status === 'degraded' ? 'āš ļø' : 'āŒ'; const statusColor = result.status === 'healthy' ? chalk_1.default.green : result.status === 'degraded' ? chalk_1.default.yellow : chalk_1.default.red; console.log(`\\n${statusIcon} Status: ${statusColor(result.status.toUpperCase())}`); console.log(`šŸ“Š Score: ${getScoreColor(result.score)}${result.score}%${chalk_1.default.reset()}`); if (result.criticalIssues > 0) { console.log(`🚨 Critical Issues: ${chalk_1.default.red(result.criticalIssues)}`); } else { console.log(`🚨 Critical Issues: ${chalk_1.default.green('None')}`); } console.log(chalk_1.default.cyan('\\nšŸ’” Run detailed check:')); console.log(' re-shell workspace-health check --detailed'); } // Utility functions function getScoreColor(score) { if (score >= 90) return chalk_1.default.green; if (score >= 70) return chalk_1.default.yellow; return chalk_1.default.red; } function getCategoryIcon(categoryId) { const icons = { structure: 'šŸ—ļø', dependencies: 'šŸ”—', build: 'āš™ļø', filesystem: 'šŸ“', 'package-json': 'šŸ“¦', typescript: 'šŸ”·', security: 'šŸ”’' }; return icons[categoryId] || 'šŸ“‹'; }