@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
JavaScript
;
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] || 'š';
}