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

710 lines (709 loc) 28.9 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; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkspaceHealthChecker = void 0; exports.createWorkspaceHealthChecker = createWorkspaceHealthChecker; exports.performQuickHealthCheck = performQuickHealthCheck; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const error_handler_1 = require("./error-handler"); const workspace_schema_1 = require("./workspace-schema"); const workspace_graph_1 = require("./workspace-graph"); // Workspace health checker class WorkspaceHealthChecker { constructor(definition, rootPath = process.cwd()) { this.definition = definition; this.graph = (0, workspace_graph_1.createWorkspaceDependencyGraph)(definition); this.rootPath = rootPath; } // Perform comprehensive health check async performHealthCheck() { const startTime = Date.now(); const categories = []; // Run all health check categories categories.push(await this.checkWorkspaceStructure()); categories.push(await this.checkDependencyHealth()); categories.push(await this.checkBuildConfiguration()); categories.push(await this.checkFileSystemHealth()); categories.push(await this.checkPackageJsonHealth()); categories.push(await this.checkTypeScriptHealth()); categories.push(await this.checkSecurityHealth()); const duration = Date.now() - startTime; // Calculate overall score and status const totalChecks = categories.reduce((sum, cat) => sum + cat.summary.total, 0); const passedChecks = categories.reduce((sum, cat) => sum + cat.summary.passed, 0); const failedChecks = categories.reduce((sum, cat) => sum + cat.summary.failed, 0); const overallScore = totalChecks > 0 ? Math.round((passedChecks / totalChecks) * 100) : 0; let overallStatus; if (overallScore >= 90) overallStatus = 'healthy'; else if (overallScore >= 70) overallStatus = 'degraded'; else overallStatus = 'unhealthy'; // Generate recommendations const recommendations = this.generateRecommendations(categories); // Calculate metrics const analysis = this.graph.analyzeGraph(); const metrics = { workspaceCount: analysis.nodeCount, dependencyCount: analysis.edgeCount, cycleCount: analysis.cycles.cycles.length, orphanedCount: analysis.orphanedNodes.length, coverageScore: this.calculateCoverageScore() }; return { timestamp: new Date().toISOString(), workspaceFile: 're-shell.workspaces.yaml', duration, overall: { status: overallStatus, score: overallScore, summary: this.generateOverallSummary(overallStatus, overallScore, failedChecks) }, categories, recommendations, metrics }; } // Validate workspace topology async validateTopology() { const errors = []; const warnings = []; const suggestions = []; try { const analysis = this.graph.analyzeGraph(); // Check for cycles if (analysis.cycles.hasCycles) { for (const cycle of analysis.cycles.cycles) { if (cycle.severity === 'error') { errors.push(new error_handler_1.ValidationError(`Circular dependency: ${cycle.path.join(' → ')}`)); } else { warnings.push(`Potential cycle: ${cycle.path.join(' → ')}`); } } } // Check depth (too deep indicates complex dependencies) if (analysis.statistics.maxDepth > 8) { warnings.push(`Dependency depth (${analysis.statistics.maxDepth}) is quite deep. Consider flattening.`); suggestions.push('Break up large workspaces or reduce dependency chains'); } // Check for orphaned workspaces if (analysis.orphanedNodes.length > 0) { warnings.push(`Found ${analysis.orphanedNodes.length} orphaned workspace(s): ${analysis.orphanedNodes.join(', ')}`); suggestions.push('Connect orphaned workspaces or remove them if unused'); } // Check workspace distribution const workspacesByType = this.getWorkspacesByType(); if (workspacesByType.app && workspacesByType.app.length > 5) { suggestions.push('Consider splitting applications if you have many frontend apps'); } // Calculate structure metrics const structure = this.calculateStructureMetrics(analysis); return { isValid: errors.length === 0, errors, warnings, suggestions, structure }; } catch (error) { errors.push(new error_handler_1.ValidationError(`Topology validation failed: ${error.message}`)); return { isValid: false, errors, warnings, suggestions, structure: { depth: 0, breadth: 0, complexity: 0, balance: 0 } }; } } // Check workspace structure async checkWorkspaceStructure() { const checks = []; const startTime = Date.now(); // Check workspace definition exists checks.push(await this.checkWorkspaceDefinitionExists()); // Check workspace directories exist checks.push(await this.checkWorkspaceDirectories()); // Check workspace consistency checks.push(await this.checkWorkspaceConsistency()); // Check workspace naming conventions checks.push(await this.checkNamingConventions()); // Check workspace types are valid checks.push(await this.checkWorkspaceTypes()); const summary = this.calculateCategorySummary(checks); return { id: 'structure', name: 'Workspace Structure', description: 'Validates workspace directory structure and configuration consistency', checks, summary }; } // Check dependency health async checkDependencyHealth() { const checks = []; // Check for circular dependencies checks.push(await this.checkCircularDependencies()); // Check dependency versions checks.push(await this.checkDependencyVersions()); // Check for missing dependencies checks.push(await this.checkMissingDependencies()); // Check dependency optimization checks.push(await this.checkDependencyOptimization()); const summary = this.calculateCategorySummary(checks); return { id: 'dependencies', name: 'Dependency Health', description: 'Analyzes workspace dependencies and detects issues', checks, summary }; } // Check build configuration async checkBuildConfiguration() { const checks = []; // Check build tools configuration checks.push(await this.checkBuildTools()); // Check build scripts checks.push(await this.checkBuildScripts()); // Check output configuration checks.push(await this.checkOutputConfiguration()); const summary = this.calculateCategorySummary(checks); return { id: 'build', name: 'Build Configuration', description: 'Validates build setup and configuration across workspaces', checks, summary }; } // Check file system health async checkFileSystemHealth() { const checks = []; // Check for large files checks.push(await this.checkLargeFiles()); // Check for node_modules bloat checks.push(await this.checkNodeModules()); // Check file permissions checks.push(await this.checkFilePermissions()); const summary = this.calculateCategorySummary(checks); return { id: 'filesystem', name: 'File System', description: 'Checks file system health and organization', checks, summary }; } // Check package.json health async checkPackageJsonHealth() { const checks = []; // Check package.json validity checks.push(await this.checkPackageJsonValidity()); // Check script consistency checks.push(await this.checkScriptConsistency()); // Check dependency consistency checks.push(await this.checkPackageDependencyConsistency()); const summary = this.calculateCategorySummary(checks); return { id: 'package-json', name: 'Package Configuration', description: 'Validates package.json files across workspaces', checks, summary }; } // Check TypeScript health async checkTypeScriptHealth() { const checks = []; // Check TypeScript configuration checks.push(await this.checkTypeScriptConfig()); // Check type definitions checks.push(await this.checkTypeDefinitions()); const summary = this.calculateCategorySummary(checks); return { id: 'typescript', name: 'TypeScript Health', description: 'Validates TypeScript configuration and type safety', checks, summary }; } // Check security health async checkSecurityHealth() { const checks = []; // Check for security vulnerabilities checks.push(await this.checkSecurityVulnerabilities()); // Check for sensitive files checks.push(await this.checkSensitiveFiles()); const summary = this.calculateCategorySummary(checks); return { id: 'security', name: 'Security', description: 'Scans for security issues and vulnerabilities', checks, summary }; } // Individual health check implementations async checkWorkspaceDefinitionExists() { const definitionPath = path.join(this.rootPath, 're-shell.workspaces.yaml'); const exists = await fs.pathExists(definitionPath); return { id: 'workspace-definition-exists', name: 'Workspace Definition', description: 'Checks if workspace definition file exists', severity: 'critical', status: exists ? 'pass' : 'fail', message: exists ? 'Workspace definition file found' : 'Workspace definition file (re-shell.workspaces.yaml) not found', suggestions: exists ? undefined : [ 'Run: re-shell workspace-def init', 'Create re-shell.workspaces.yaml manually' ] }; } async checkWorkspaceDirectories() { const missingDirectories = []; for (const [name, workspace] of Object.entries(this.definition.workspaces)) { const workspacePath = path.resolve(this.rootPath, workspace.path); if (!(await fs.pathExists(workspacePath))) { missingDirectories.push(`${name} (${workspace.path})`); } } return { id: 'workspace-directories', name: 'Workspace Directories', description: 'Verifies all workspace directories exist', severity: 'error', status: missingDirectories.length === 0 ? 'pass' : 'fail', message: missingDirectories.length === 0 ? 'All workspace directories exist' : `${missingDirectories.length} workspace directories missing: ${missingDirectories.join(', ')}`, suggestions: missingDirectories.length > 0 ? [ 'Create missing workspace directories', 'Update workspace paths in definition', 'Remove unused workspace entries' ] : undefined }; } async checkWorkspaceConsistency() { const inconsistencies = []; // Check name consistency between definition and directory for (const [name, workspace] of Object.entries(this.definition.workspaces)) { const expectedDirName = path.basename(workspace.path); if (name !== expectedDirName && name !== workspace.name) { inconsistencies.push(`${name}: name mismatch with directory/config`); } } return { id: 'workspace-consistency', name: 'Workspace Consistency', description: 'Checks for naming and configuration consistency', severity: 'warning', status: inconsistencies.length === 0 ? 'pass' : 'warning', message: inconsistencies.length === 0 ? 'Workspace naming is consistent' : `Found ${inconsistencies.length} naming inconsistencies`, metadata: { inconsistencies } }; } async checkNamingConventions() { const violations = []; const kebabCaseRegex = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/; for (const [name, workspace] of Object.entries(this.definition.workspaces)) { if (!kebabCaseRegex.test(name)) { violations.push(name); } } return { id: 'naming-conventions', name: 'Naming Conventions', description: 'Validates workspace names follow kebab-case convention', severity: 'info', status: violations.length === 0 ? 'pass' : 'warning', message: violations.length === 0 ? 'All workspace names follow conventions' : `${violations.length} workspaces don't follow kebab-case: ${violations.join(', ')}`, suggestions: violations.length > 0 ? [ 'Use kebab-case for workspace names (e.g., my-component)', 'Avoid camelCase, PascalCase, or snake_case' ] : undefined }; } async checkWorkspaceTypes() { const invalidTypes = []; const validTypes = new Set(Object.keys(this.definition.types)); for (const [name, workspace] of Object.entries(this.definition.workspaces)) { if (!validTypes.has(workspace.type)) { invalidTypes.push(`${name}: ${workspace.type}`); } } return { id: 'workspace-types', name: 'Workspace Types', description: 'Validates all workspace types are defined', severity: 'error', status: invalidTypes.length === 0 ? 'pass' : 'fail', message: invalidTypes.length === 0 ? 'All workspace types are valid' : `${invalidTypes.length} workspaces have invalid types: ${invalidTypes.join(', ')}`, suggestions: invalidTypes.length > 0 ? [ 'Define missing workspace types in the types section', 'Update workspace type references to valid types' ] : undefined }; } async checkCircularDependencies() { const analysis = this.graph.analyzeGraph(); const cycles = analysis.cycles.cycles; const errorCycles = cycles.filter(c => c.severity === 'error'); return { id: 'circular-dependencies', name: 'Circular Dependencies', description: 'Detects circular dependencies between workspaces', severity: 'critical', status: errorCycles.length === 0 ? 'pass' : 'fail', message: errorCycles.length === 0 ? 'No circular dependencies detected' : `Found ${errorCycles.length} circular dependencies`, metadata: { cycles: errorCycles }, suggestions: errorCycles.length > 0 ? [ 'Run: re-shell workspace-graph cycles --detailed', 'Restructure dependencies to break cycles', 'Consider using dependency injection patterns' ] : undefined }; } async checkDependencyVersions() { // This would check for version consistency across workspaces // Simplified implementation for now return { id: 'dependency-versions', name: 'Dependency Versions', description: 'Checks for version consistency across workspaces', severity: 'warning', status: 'pass', message: 'Dependency version checking not yet implemented' }; } async checkMissingDependencies() { const missingDeps = []; for (const [workspaceName, deps] of Object.entries(this.definition.dependencies)) { for (const dep of deps) { if (!this.definition.workspaces[dep.name]) { missingDeps.push(`${workspaceName}${dep.name}`); } } } return { id: 'missing-dependencies', name: 'Missing Dependencies', description: 'Checks for references to non-existent workspaces', severity: 'error', status: missingDeps.length === 0 ? 'pass' : 'fail', message: missingDeps.length === 0 ? 'All dependencies reference valid workspaces' : `Found ${missingDeps.length} references to missing workspaces`, metadata: { missingDeps } }; } async checkDependencyOptimization() { const analysis = this.graph.analyzeGraph(); const suggestions = []; if (analysis.statistics.avgDependencies > 5) { suggestions.push('High average dependencies - consider breaking up large workspaces'); } if (analysis.statistics.maxDepth > 6) { suggestions.push('Deep dependency chains - consider flattening architecture'); } return { id: 'dependency-optimization', name: 'Dependency Optimization', description: 'Suggests dependency structure optimizations', severity: 'info', status: suggestions.length === 0 ? 'pass' : 'info', message: suggestions.length === 0 ? 'Dependency structure is well optimized' : 'Found optimization opportunities', suggestions }; } // Placeholder implementations for other checks async checkBuildTools() { return { id: 'build-tools', name: 'Build Tools', description: 'Validates build tool configuration', severity: 'warning', status: 'pass', message: 'Build tool validation not yet implemented' }; } async checkBuildScripts() { return { id: 'build-scripts', name: 'Build Scripts', description: 'Checks build script consistency', severity: 'warning', status: 'pass', message: 'Build script validation not yet implemented' }; } async checkOutputConfiguration() { return { id: 'output-config', name: 'Output Configuration', description: 'Validates build output configuration', severity: 'info', status: 'pass', message: 'Output configuration validation not yet implemented' }; } async checkLargeFiles() { return { id: 'large-files', name: 'Large Files', description: 'Detects uncommonly large files', severity: 'info', status: 'pass', message: 'Large file detection not yet implemented' }; } async checkNodeModules() { return { id: 'node-modules', name: 'Node Modules', description: 'Checks for node_modules bloat', severity: 'info', status: 'pass', message: 'Node modules analysis not yet implemented' }; } async checkFilePermissions() { return { id: 'file-permissions', name: 'File Permissions', description: 'Validates file permissions', severity: 'info', status: 'pass', message: 'File permission checking not yet implemented' }; } async checkPackageJsonValidity() { return { id: 'package-json-validity', name: 'Package.json Validity', description: 'Validates package.json files', severity: 'error', status: 'pass', message: 'Package.json validation not yet implemented' }; } async checkScriptConsistency() { return { id: 'script-consistency', name: 'Script Consistency', description: 'Checks script consistency across workspaces', severity: 'warning', status: 'pass', message: 'Script consistency checking not yet implemented' }; } async checkPackageDependencyConsistency() { return { id: 'package-dependency-consistency', name: 'Package Dependency Consistency', description: 'Validates dependency consistency in package.json files', severity: 'warning', status: 'pass', message: 'Package dependency consistency checking not yet implemented' }; } async checkTypeScriptConfig() { return { id: 'typescript-config', name: 'TypeScript Configuration', description: 'Validates TypeScript configuration', severity: 'warning', status: 'pass', message: 'TypeScript configuration validation not yet implemented' }; } async checkTypeDefinitions() { return { id: 'type-definitions', name: 'Type Definitions', description: 'Checks type definition availability', severity: 'info', status: 'pass', message: 'Type definition checking not yet implemented' }; } async checkSecurityVulnerabilities() { return { id: 'security-vulnerabilities', name: 'Security Vulnerabilities', description: 'Scans for known security vulnerabilities', severity: 'critical', status: 'pass', message: 'Security vulnerability scanning not yet implemented' }; } async checkSensitiveFiles() { return { id: 'sensitive-files', name: 'Sensitive Files', description: 'Detects potentially sensitive files', severity: 'warning', status: 'pass', message: 'Sensitive file detection not yet implemented' }; } // Helper methods calculateCategorySummary(checks) { const total = checks.length; const passed = checks.filter(c => c.status === 'pass').length; const failed = checks.filter(c => c.status === 'fail').length; const warnings = checks.filter(c => c.status === 'warning').length; const score = total > 0 ? Math.round((passed / total) * 100) : 0; return { total, passed, failed, warnings, score }; } generateRecommendations(categories) { const recommendations = []; // High-priority recommendations based on failed checks for (const category of categories) { const failedChecks = category.checks.filter(c => c.status === 'fail'); for (const check of failedChecks) { if (check.suggestions) { recommendations.push(...check.suggestions); } } } // Remove duplicates and limit to most important return Array.from(new Set(recommendations)).slice(0, 10); } generateOverallSummary(status, score, failedChecks) { if (status === 'healthy') { return `Workspace is healthy with ${score}% of checks passing`; } else if (status === 'degraded') { return `Workspace has some issues (${score}% healthy) with ${failedChecks} failed checks`; } else { return `Workspace has significant issues (${score}% healthy) with ${failedChecks} failed checks`; } } calculateCoverageScore() { // Simple coverage calculation based on workspace definition completeness let score = 0; const maxScore = 100; // Base score for having workspaces if (Object.keys(this.definition.workspaces).length > 0) score += 20; // Score for having dependencies defined if (Object.keys(this.definition.dependencies).length > 0) score += 20; // Score for having build configuration if (this.definition.build) score += 20; // Score for having scripts if (this.definition.scripts && Object.keys(this.definition.scripts).length > 0) score += 20; // Score for having workspace types if (this.definition.types && Object.keys(this.definition.types).length > 0) score += 20; return Math.min(score, maxScore); } getWorkspacesByType() { const result = {}; for (const workspace of Object.values(this.definition.workspaces)) { if (!result[workspace.type]) { result[workspace.type] = []; } result[workspace.type].push(workspace); } return result; } calculateStructureMetrics(analysis) { return { depth: analysis.statistics.maxDepth, breadth: Math.max(...analysis.levels.map((level) => level.length)), complexity: analysis.edgeCount / Math.max(analysis.nodeCount, 1), balance: this.calculateBalance(analysis.levels) }; } calculateBalance(levels) { if (levels.length === 0) return 1; const levelSizes = levels.map(level => level.length); const maxSize = Math.max(...levelSizes); const minSize = Math.min(...levelSizes); return minSize / maxSize; } } exports.WorkspaceHealthChecker = WorkspaceHealthChecker; // Utility functions async function createWorkspaceHealthChecker(workspaceFile, rootPath) { const definition = await (0, workspace_schema_1.loadWorkspaceDefinition)(workspaceFile); return new WorkspaceHealthChecker(definition, rootPath); } async function performQuickHealthCheck(workspaceFile, rootPath) { try { const checker = await createWorkspaceHealthChecker(workspaceFile, rootPath); const report = await checker.performHealthCheck(); const criticalIssues = report.categories .flatMap(cat => cat.checks) .filter(check => check.severity === 'critical' && check.status === 'fail') .length; return { status: report.overall.status, score: report.overall.score, criticalIssues }; } catch (error) { return { status: 'unhealthy', score: 0, criticalIssues: 1 }; } }