@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
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;
};
})();
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
};
}
}