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

697 lines (696 loc) 27.5 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.runDoctorCheck = runDoctorCheck; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const chalk_1 = __importDefault(require("chalk")); const child_process_1 = require("child_process"); const monorepo_1 = require("../utils/monorepo"); async function runDoctorCheck(options = {}) { const checks = []; try { // Find monorepo root const monorepoRoot = await (0, monorepo_1.findMonorepoRoot)(process.cwd()); if (!monorepoRoot) { checks.push({ name: 'monorepo-detection', status: 'error', message: 'Not in a monorepo workspace', suggestion: 'Run this command from within a monorepo or use "re-shell init" to create one' }); return displayResults(checks, options); } if (options.spinner) { options.spinner.text = 'Checking monorepo structure...'; } // Check 1: Package.json structure checks.push(await checkPackageJsonStructure(monorepoRoot)); // Check 2: Dependencies health checks.push(...(await checkDependenciesHealth(monorepoRoot))); // Check 3: Security vulnerabilities checks.push(await checkSecurityVulnerabilities(monorepoRoot)); // Check 4: Workspace configuration checks.push(await checkWorkspaceConfiguration(monorepoRoot)); // Check 5: Git configuration checks.push(await checkGitConfiguration(monorepoRoot)); // Check 6: Build configuration checks.push(...(await checkBuildConfiguration(monorepoRoot))); // Check 7: Performance issues checks.push(...(await checkPerformanceIssues(monorepoRoot))); // Check 8: File system health checks.push(...(await checkFileSystemHealth(monorepoRoot))); // Auto-fix issues if requested if (options.fix) { await autoFixIssues(checks, monorepoRoot, options); } return displayResults(checks, options); } catch (error) { checks.push({ name: 'doctor-execution', status: 'error', message: `Doctor check failed: ${error instanceof Error ? error.message : 'Unknown error'}`, suggestion: 'Try running with --verbose for more details' }); return displayResults(checks, options); } } async function checkPackageJsonStructure(monorepoRoot) { try { const packageJsonPath = path.join(monorepoRoot, 'package.json'); if (!await fs.pathExists(packageJsonPath)) { return { name: 'package-json', status: 'error', message: 'Root package.json not found', suggestion: 'Create a root package.json file' }; } const packageJson = await fs.readJson(packageJsonPath); const issues = []; if (!packageJson.workspaces && !packageJson.private) { issues.push('Missing workspaces configuration'); } if (!packageJson.name) { issues.push('Missing package name'); } if (!packageJson.engines) { issues.push('Missing engines specification'); } if (issues.length > 0) { return { name: 'package-json', status: 'warning', message: `Package.json issues: ${issues.join(', ')}`, suggestion: 'Update package.json with missing fields' }; } return { name: 'package-json', status: 'success', message: 'Package.json structure is valid' }; } catch (error) { return { name: 'package-json', status: 'error', message: `Failed to check package.json: ${error instanceof Error ? error.message : 'Unknown error'}`, suggestion: 'Ensure package.json is valid JSON' }; } } async function checkDependenciesHealth(monorepoRoot) { const checks = []; try { // Check for duplicate dependencies const workspaces = await getWorkspaces(monorepoRoot); const allDeps = new Map(); for (const workspace of workspaces) { const pkgPath = path.join(monorepoRoot, workspace, 'package.json'); if (await fs.pathExists(pkgPath)) { const pkg = await fs.readJson(pkgPath); const deps = { ...pkg.dependencies, ...pkg.devDependencies }; for (const [dep, version] of Object.entries(deps)) { if (!allDeps.has(dep)) { allDeps.set(dep, []); } allDeps.get(dep).push(`${workspace}:${version}`); } } } const duplicates = Array.from(allDeps.entries()) .filter(([, versions]) => new Set(versions.map(v => v.split(':')[1])).size > 1) .slice(0, 5); // Limit to top 5 if (duplicates.length > 0) { checks.push({ name: 'dependency-duplicates', status: 'warning', message: `Found ${duplicates.length} dependencies with version conflicts`, suggestion: 'Consider using workspace dependency hoisting or version alignment' }); } else { checks.push({ name: 'dependency-duplicates', status: 'success', message: 'No dependency version conflicts found' }); } // Check for outdated dependencies try { const outdatedCmd = getPackageManager(monorepoRoot) === 'npm' ? 'npm outdated --json' : 'pnpm outdated --format json'; (0, child_process_1.execSync)(outdatedCmd, { cwd: monorepoRoot, stdio: 'pipe' }); checks.push({ name: 'outdated-dependencies', status: 'success', message: 'All dependencies are up to date' }); } catch (error) { // outdated command exits with code 1 when there are outdated packages const output = error.stdout?.toString(); if (output) { try { const outdated = JSON.parse(output); const count = Object.keys(outdated).length; checks.push({ name: 'outdated-dependencies', status: 'warning', message: `Found ${count} outdated dependencies`, suggestion: 'Run package manager update command to update dependencies' }); } catch { checks.push({ name: 'outdated-dependencies', status: 'warning', message: 'Some dependencies may be outdated', suggestion: 'Run your package manager\'s outdated command to check' }); } } } } catch (error) { checks.push({ name: 'dependencies-health', status: 'error', message: `Failed to check dependencies: ${error instanceof Error ? error.message : 'Unknown error'}` }); } return checks; } async function checkSecurityVulnerabilities(monorepoRoot) { try { const packageManager = getPackageManager(monorepoRoot); const auditCommands = { npm: 'npm audit --json', yarn: 'yarn audit --json', pnpm: 'pnpm audit --json', bun: 'bun audit --json' }; const cmd = auditCommands[packageManager] || auditCommands.npm; try { (0, child_process_1.execSync)(cmd, { cwd: monorepoRoot, stdio: 'pipe' }); return { name: 'security-audit', status: 'success', message: 'No security vulnerabilities found' }; } catch (error) { const output = error.stdout?.toString(); if (output) { try { const audit = JSON.parse(output); const vulnCount = audit.metadata?.vulnerabilities?.total || 0; if (vulnCount > 0) { return { name: 'security-audit', status: 'error', message: `Found ${vulnCount} security vulnerabilities`, suggestion: `Run "${packageManager} audit fix" to fix automatically fixable vulnerabilities` }; } } catch { // Fallback if JSON parsing fails } } return { name: 'security-audit', status: 'warning', message: 'Security audit completed with warnings', suggestion: 'Review audit output manually' }; } } catch (error) { return { name: 'security-audit', status: 'error', message: `Security audit failed: ${error instanceof Error ? error.message : 'Unknown error'}`, suggestion: 'Ensure your package manager supports audit command' }; } } async function checkWorkspaceConfiguration(monorepoRoot) { try { const workspaces = await getWorkspaces(monorepoRoot); if (workspaces.length === 0) { return { name: 'workspace-config', status: 'warning', message: 'No workspaces found', suggestion: 'Add workspaces to your monorepo using "re-shell create"' }; } // Check for common workspace issues const issues = []; for (const workspace of workspaces) { const workspacePath = path.join(monorepoRoot, workspace); const pkgPath = path.join(workspacePath, 'package.json'); if (!await fs.pathExists(pkgPath)) { issues.push(`${workspace}: missing package.json`); } } if (issues.length > 0) { return { name: 'workspace-config', status: 'warning', message: `Workspace issues: ${issues.slice(0, 3).join(', ')}${issues.length > 3 ? '...' : ''}`, suggestion: 'Fix workspace configuration issues' }; } return { name: 'workspace-config', status: 'success', message: `Found ${workspaces.length} properly configured workspaces` }; } catch (error) { return { name: 'workspace-config', status: 'error', message: `Failed to check workspace configuration: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } async function checkGitConfiguration(monorepoRoot) { try { const gitPath = path.join(monorepoRoot, '.git'); if (!await fs.pathExists(gitPath)) { return { name: 'git-config', status: 'warning', message: 'Git repository not initialized', suggestion: 'Initialize git repository with "git init"' }; } const issues = []; // Check for .gitignore const gitignorePath = path.join(monorepoRoot, '.gitignore'); if (!await fs.pathExists(gitignorePath)) { issues.push('missing .gitignore'); } // Check for uncommitted changes try { const status = (0, child_process_1.execSync)('git status --porcelain', { cwd: monorepoRoot, encoding: 'utf8' }); if (status.trim().length > 0) { issues.push('uncommitted changes'); } } catch (error) { issues.push('git status check failed'); } if (issues.length > 0) { return { name: 'git-config', status: 'warning', message: `Git issues: ${issues.join(', ')}`, suggestion: 'Review and fix git configuration' }; } return { name: 'git-config', status: 'success', message: 'Git configuration is healthy' }; } catch (error) { return { name: 'git-config', status: 'error', message: `Failed to check git configuration: ${error instanceof Error ? error.message : 'Unknown error'}` }; } } async function checkBuildConfiguration(monorepoRoot) { const checks = []; try { const workspaces = await getWorkspaces(monorepoRoot); let buildableWorkspaces = 0; let configIssues = 0; for (const workspace of workspaces) { const pkgPath = path.join(monorepoRoot, workspace, 'package.json'); if (await fs.pathExists(pkgPath)) { const pkg = await fs.readJson(pkgPath); if (pkg.scripts?.build) { buildableWorkspaces++; // Check for common build files const buildFiles = ['vite.config.ts', 'vite.config.js', 'webpack.config.js', 'rollup.config.js']; const hasConfig = await Promise.all(buildFiles.map(file => fs.pathExists(path.join(monorepoRoot, workspace, file)))); if (!hasConfig.some(exists => exists)) { configIssues++; } } } } checks.push({ name: 'build-config', status: buildableWorkspaces > 0 ? 'success' : 'warning', message: `Found ${buildableWorkspaces} buildable workspaces`, suggestion: buildableWorkspaces === 0 ? 'Add build scripts to your workspaces' : undefined }); if (configIssues > 0) { checks.push({ name: 'build-files', status: 'warning', message: `${configIssues} workspaces missing build configuration files`, suggestion: 'Add build configuration files (vite.config.ts, etc.)' }); } } catch (error) { checks.push({ name: 'build-config', status: 'error', message: `Failed to check build configuration: ${error instanceof Error ? error.message : 'Unknown error'}` }); } return checks; } async function checkPerformanceIssues(monorepoRoot) { const checks = []; try { // Check node_modules size const nodeModulesPath = path.join(monorepoRoot, 'node_modules'); if (await fs.pathExists(nodeModulesPath)) { try { const stats = await fs.stat(nodeModulesPath); // Rough estimation - actual calculation would be recursive and slow const estimatedSize = stats.size; checks.push({ name: 'node-modules-size', status: 'success', message: 'Node modules directory exists', suggestion: 'Consider using pnpm for smaller node_modules footprint' }); } catch (error) { checks.push({ name: 'node-modules-size', status: 'warning', message: 'Could not analyze node_modules size' }); } } // Check for large files that shouldn't be committed const largeFiles = []; const checkLargeFiles = async (dir, prefix = '') => { try { const items = await fs.readdir(dir, { withFileTypes: true }); for (const item of items.slice(0, 20)) { // Limit to prevent performance issues if (item.name.startsWith('.') || item.name === 'node_modules') continue; const fullPath = path.join(dir, item.name); if (item.isFile()) { const stats = await fs.stat(fullPath); if (stats.size > 10 * 1024 * 1024) { // 10MB largeFiles.push(`${prefix}${item.name} (${(stats.size / 1024 / 1024).toFixed(1)}MB)`); } } else if (item.isDirectory() && prefix.split('/').length < 3) { // Max depth 3 await checkLargeFiles(fullPath, `${prefix}${item.name}/`); } } } catch (error) { // Ignore permission errors } }; await checkLargeFiles(monorepoRoot); if (largeFiles.length > 0) { checks.push({ name: 'large-files', status: 'warning', message: `Found ${largeFiles.length} large files: ${largeFiles.slice(0, 2).join(', ')}`, suggestion: 'Consider using Git LFS for large files or add them to .gitignore' }); } else { checks.push({ name: 'large-files', status: 'success', message: 'No large files detected' }); } } catch (error) { checks.push({ name: 'performance-check', status: 'error', message: `Performance check failed: ${error instanceof Error ? error.message : 'Unknown error'}` }); } return checks; } async function checkFileSystemHealth(monorepoRoot) { const checks = []; try { // Check disk space try { const stats = await fs.stat(monorepoRoot); checks.push({ name: 'disk-space', status: 'success', message: 'File system accessible' }); } catch (error) { checks.push({ name: 'disk-space', status: 'error', message: 'File system access issues detected', suggestion: 'Check disk space and permissions' }); } // Check for broken symlinks const brokenLinks = []; const checkSymlinks = async (dir) => { try { const items = await fs.readdir(dir, { withFileTypes: true }); for (const item of items.slice(0, 50)) { // Limit for performance if (item.name.startsWith('.') || item.name === 'node_modules') continue; const fullPath = path.join(dir, item.name); if (item.isSymbolicLink()) { try { await fs.stat(fullPath); } catch (error) { brokenLinks.push(path.relative(monorepoRoot, fullPath)); } } else if (item.isDirectory()) { await checkSymlinks(fullPath); } } } catch (error) { // Ignore permission errors } }; await checkSymlinks(monorepoRoot); if (brokenLinks.length > 0) { checks.push({ name: 'broken-symlinks', status: 'warning', message: `Found ${brokenLinks.length} broken symlinks`, suggestion: 'Remove or fix broken symbolic links' }); } else { checks.push({ name: 'broken-symlinks', status: 'success', message: 'No broken symlinks found' }); } } catch (error) { checks.push({ name: 'filesystem-health', status: 'error', message: `File system check failed: ${error instanceof Error ? error.message : 'Unknown error'}` }); } return checks; } async function autoFixIssues(checks, monorepoRoot, options) { if (options.spinner) { options.spinner.text = 'Auto-fixing issues...'; } const fixableIssues = checks.filter(check => check.status === 'warning' || check.status === 'error'); for (const issue of fixableIssues) { try { switch (issue.name) { case 'security-audit': if (issue.message.includes('vulnerabilities')) { const packageManager = getPackageManager(monorepoRoot); (0, child_process_1.execSync)(`${packageManager} audit fix`, { cwd: monorepoRoot, stdio: 'pipe' }); issue.status = 'success'; issue.message = 'Security vulnerabilities fixed automatically'; } break; case 'git-config': if (issue.message.includes('missing .gitignore')) { const gitignoreContent = `node_modules/ dist/ build/ .env .env.local .DS_Store *.log coverage/ .nyc_output/ *.tgz *.tar.gz `; await fs.writeFile(path.join(monorepoRoot, '.gitignore'), gitignoreContent); issue.status = 'success'; issue.message = 'Created .gitignore file'; } break; } } catch (error) { if (options.verbose) { console.log(chalk_1.default.yellow(`Failed to auto-fix ${issue.name}: ${error instanceof Error ? error.message : 'Unknown error'}`)); } } } } async function getWorkspaces(monorepoRoot) { try { const packageJsonPath = path.join(monorepoRoot, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); if (packageJson.workspaces) { if (Array.isArray(packageJson.workspaces)) { return packageJson.workspaces; } else if (packageJson.workspaces.packages) { return packageJson.workspaces.packages; } } // Fallback: scan for package.json files const workspaces = []; const scanDir = async (dir, depth = 0) => { if (depth > 2) return; // Limit depth const items = await fs.readdir(dir, { withFileTypes: true }); for (const item of items) { if (item.isDirectory() && !item.name.startsWith('.') && item.name !== 'node_modules') { const pkgPath = path.join(dir, item.name, 'package.json'); if (await fs.pathExists(pkgPath)) { workspaces.push(path.relative(monorepoRoot, dir === monorepoRoot ? item.name : path.join(path.relative(monorepoRoot, dir), item.name))); } else { await scanDir(path.join(dir, item.name), depth + 1); } } } }; await scanDir(monorepoRoot); return workspaces; } catch (error) { return []; } } function getPackageManager(monorepoRoot) { if (fs.existsSync(path.join(monorepoRoot, 'pnpm-lock.yaml'))) return 'pnpm'; if (fs.existsSync(path.join(monorepoRoot, 'yarn.lock'))) return 'yarn'; if (fs.existsSync(path.join(monorepoRoot, 'bun.lockb'))) return 'bun'; return 'npm'; } function displayResults(checks, options) { if (options.json) { console.log(JSON.stringify({ checks }, null, 2)); return; } if (options.spinner) { options.spinner.stop(); } console.log('\n' + chalk_1.default.bold('🏥 Re-Shell Health Check Results\n')); const successCount = checks.filter(c => c.status === 'success').length; const warningCount = checks.filter(c => c.status === 'warning').length; const errorCount = checks.filter(c => c.status === 'error').length; // Summary console.log(chalk_1.default.bold('Summary:')); console.log(` ${chalk_1.default.green('✓')} ${successCount} checks passed`); if (warningCount > 0) console.log(` ${chalk_1.default.yellow('⚠')} ${warningCount} warnings`); if (errorCount > 0) console.log(` ${chalk_1.default.red('✗')} ${errorCount} errors`); console.log(); // Detailed results for (const check of checks) { const icon = check.status === 'success' ? chalk_1.default.green('✓') : check.status === 'warning' ? chalk_1.default.yellow('⚠') : chalk_1.default.red('✗'); const nameFormatted = check.name.padEnd(20); console.log(`${icon} ${nameFormatted} ${check.message}`); if (check.suggestion && options.verbose) { console.log(` ${chalk_1.default.dim('→')} ${chalk_1.default.dim(check.suggestion)}`); } } console.log(); // Overall health score const totalChecks = checks.length; const healthScore = Math.round((successCount / totalChecks) * 100); let healthColor = chalk_1.default.green; let healthStatus = 'Excellent'; if (healthScore < 90) { healthColor = chalk_1.default.yellow; healthStatus = 'Good'; } if (healthScore < 70) { healthColor = chalk_1.default.red; healthStatus = 'Needs Attention'; } console.log(chalk_1.default.bold(`Overall Health: ${healthColor(healthScore + '%')} (${healthStatus})`)); if (warningCount > 0 || errorCount > 0) { console.log('\n' + chalk_1.default.dim('Run with --fix to automatically fix some issues')); console.log(chalk_1.default.dim('Run with --verbose to see detailed suggestions')); } }