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

425 lines (424 loc) โ€ข 17.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.manageChangeImpact = manageChangeImpact; exports.analyzeWorkspaceImpact = analyzeWorkspaceImpact; exports.showDependencyGraph = showDependencyGraph; exports.registerChangeImpactCommands = registerChangeImpactCommands; const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const chalk_1 = __importDefault(require("chalk")); const spinner_1 = require("../utils/spinner"); const error_handler_1 = require("../utils/error-handler"); const change_impact_analyzer_1 = require("../utils/change-impact-analyzer"); // Main command handler for change impact analysis async function manageChangeImpact(options = {}) { const spinner = new spinner_1.ProgressSpinner({ text: 'Analyzing change impact...' }); try { const rootPath = process.cwd(); // Validate that we're in a Re-Shell project const packageJsonPath = path.join(rootPath, 'package.json'); if (!await fs.pathExists(packageJsonPath)) { throw new error_handler_1.ValidationError('Not in a valid project directory (package.json not found)'); } spinner.start(); const analyzer = await (0, change_impact_analyzer_1.createChangeImpactAnalyzer)(rootPath, { maxDepth: options.maxDepth || 10, includeTests: options.includeTests !== false, includeDevDependencies: options.includeDevDeps || false, buildOptimization: true, parallelAnalysis: true }); spinner.setText('Analyzing change impact...'); let result; if (options.files && options.files.length > 0) { // Analyze specific files result = await analyzer.analyzeChangeImpact(options.files); } else { // Analyze all detected changes result = await analyzer.analyzeChangeImpact(); } spinner.stop(); // Output results based on format if (options.format === 'json') { await outputJson(result, options.output); } else if (options.format === 'mermaid') { await outputMermaid(analyzer, result, options.output); } else if (options.visualization) { await outputVisualization(analyzer, result.changedFiles, options.output); } else { await outputText(result, options.verbose || false); } // Save to file if specified if (options.output && options.format !== 'json' && options.format !== 'mermaid' && !options.visualization) { await saveTextOutput(result, options.output, options.verbose || false); } } catch (error) { spinner.stop(); if (error instanceof error_handler_1.ValidationError) { throw error; } throw new error_handler_1.ValidationError(`Change impact analysis failed: ${error}`); } } // Analyze specific workspace async function analyzeWorkspaceImpact(workspaceName, options = {}) { const spinner = new spinner_1.ProgressSpinner({ text: 'Analyzing change impact...' }); try { const rootPath = process.cwd(); spinner.start(); const analyzer = await (0, change_impact_analyzer_1.createChangeImpactAnalyzer)(rootPath); const workspace = analyzer.getWorkspaceInfo(workspaceName); if (!workspace) { throw new error_handler_1.ValidationError(`Workspace '${workspaceName}' not found`); } // Get files in workspace directory const workspaceFiles = await getWorkspaceFiles(workspace.path); const result = await analyzer.analyzeChangeImpact(workspaceFiles); spinner.stop(); console.log(chalk_1.default.cyan(`\n๐Ÿ“Š Impact Analysis for Workspace: ${workspaceName}`)); console.log(chalk_1.default.gray('='.repeat(50))); await outputText(result, options.verbose || false); } catch (error) { spinner.stop(); throw error; } } // Get dependency graph visualization async function showDependencyGraph(options = {}) { const spinner = new spinner_1.ProgressSpinner({ text: 'Analyzing change impact...' }); try { const rootPath = process.cwd(); spinner.start(); const analyzer = await (0, change_impact_analyzer_1.createChangeImpactAnalyzer)(rootPath); const graph = analyzer.getDependencyGraph(); spinner.stop(); if (options.format === 'json') { const graphData = { nodes: Array.from(graph.nodes.values()), edges: Object.fromEntries(graph.edges), reverseEdges: Object.fromEntries(graph.reverseEdges) }; if (options.output) { await fs.writeJson(options.output, graphData, { spaces: 2 }); console.log(chalk_1.default.green(`โœ“ Dependency graph saved to ${options.output}`)); } else { console.log(JSON.stringify(graphData, null, 2)); } } else if (options.format === 'mermaid') { const mermaid = generateMermaidGraph(graph); if (options.output) { await fs.writeFile(options.output, mermaid); console.log(chalk_1.default.green(`โœ“ Mermaid diagram saved to ${options.output}`)); } else { console.log(mermaid); } } else { displayTextGraph(graph, options.verbose || false); } } catch (error) { spinner.stop(); throw error; } } // Output results as formatted text async function outputText(result, verbose) { console.log(chalk_1.default.cyan('\n๐Ÿ” Change Impact Analysis Results')); console.log(chalk_1.default.gray('='.repeat(40))); // Summary console.log(chalk_1.default.bold('\n๐Ÿ“Š Summary:')); console.log(` โ€ข Changed files: ${chalk_1.default.yellow(result.changedFiles.length)}`); console.log(` โ€ข Affected workspaces: ${chalk_1.default.yellow(result.affectedWorkspaces.length)}`); console.log(` โ€ข Total impact score: ${chalk_1.default.yellow(result.totalImpact)}`); console.log(` โ€ข Analysis time: ${chalk_1.default.gray(result.analysisTime + 'ms')}`); // Changed files if (result.changedFiles.length > 0) { console.log(chalk_1.default.bold('\n๐Ÿ“ Changed Files:')); result.changedFiles.forEach(file => { console.log(` ${chalk_1.default.gray('โ€ข')} ${file}`); }); } // Affected workspaces if (result.affectedWorkspaces.length > 0) { console.log(chalk_1.default.bold('\n๐Ÿ—๏ธ Affected Workspaces:')); result.affectedWorkspaces.forEach(ws => { const typeColor = getWorkspaceTypeColor(ws.type); const frameworkBadge = ws.framework ? chalk_1.default.gray(`[${ws.framework}]`) : ''; console.log(` ${chalk_1.default.gray('โ€ข')} ${typeColor(ws.name)} ${frameworkBadge}`); if (verbose) { console.log(` ${chalk_1.default.gray('Path:')} ${ws.path}`); console.log(` ${chalk_1.default.gray('Type:')} ${ws.type}`); if (ws.dependencies.length > 0) { console.log(` ${chalk_1.default.gray('Dependencies:')} ${ws.dependencies.join(', ')}`); } } }); } // Build order if (result.buildOrder.length > 0) { console.log(chalk_1.default.bold('\n๐Ÿ”จ Recommended Build Order:')); result.buildOrder.forEach((workspace, index) => { console.log(` ${chalk_1.default.yellow(index + 1)}. ${workspace}`); }); } // Test order if (result.testOrder.length > 0) { console.log(chalk_1.default.bold('\n๐Ÿงช Recommended Test Order:')); result.testOrder.forEach((workspace, index) => { console.log(` ${chalk_1.default.yellow(index + 1)}. ${workspace}`); }); } // Critical path if (result.criticalPath.length > 0) { console.log(chalk_1.default.bold('\n๐ŸŽฏ Critical Path:')); console.log(` ${result.criticalPath.join(' โ†’ ')}`); } // Recommendations if (result.recommendations.length > 0) { console.log(chalk_1.default.bold('\n๐Ÿ’ก Recommendations:')); result.recommendations.forEach(rec => { console.log(` ${chalk_1.default.gray('โ€ข')} ${rec}`); }); } } // Output results as JSON async function outputJson(result, outputPath) { const json = JSON.stringify(result, null, 2); if (outputPath) { await fs.writeFile(outputPath, json); console.log(chalk_1.default.green(`โœ“ Impact analysis saved to ${outputPath}`)); } else { console.log(json); } } // Output as Mermaid diagram async function outputMermaid(analyzer, result, outputPath) { const graph = analyzer.getDependencyGraph(); const affectedNames = new Set(result.affectedWorkspaces.map(ws => ws.name)); let mermaid = 'graph TD\n'; // Add nodes with styling based on impact for (const [name, workspace] of graph.nodes) { const isAffected = affectedNames.has(name); const nodeStyle = isAffected ? ':::affected' : ':::normal'; const nodeType = getNodeShape(workspace.type); mermaid += ` ${name}${nodeType}${nodeStyle}\n`; } // Add edges for (const [from, targets] of graph.edges) { for (const to of targets) { mermaid += ` ${from} --> ${to}\n`; } } // Add styling mermaid += '\n'; mermaid += ' classDef affected fill:#ff6b6b,stroke:#333,stroke-width:2px;\n'; mermaid += ' classDef normal fill:#51cf66,stroke:#333,stroke-width:1px;\n'; if (outputPath) { await fs.writeFile(outputPath, mermaid); console.log(chalk_1.default.green(`โœ“ Mermaid diagram saved to ${outputPath}`)); } else { console.log(mermaid); } } // Output visualization data async function outputVisualization(analyzer, changedFiles, outputPath) { const visualization = await analyzer.getImpactVisualization(changedFiles); const output = { visualization, metadata: { generated: new Date().toISOString(), changedFiles, totalNodes: visualization.nodes.length, totalEdges: visualization.edges.length, affectedNodes: visualization.nodes.filter(n => n.affected).length } }; if (outputPath) { await fs.writeJson(outputPath, output, { spaces: 2 }); console.log(chalk_1.default.green(`โœ“ Visualization data saved to ${outputPath}`)); } else { console.log(JSON.stringify(output, null, 2)); } } // Save text output to file async function saveTextOutput(result, outputPath, verbose) { // Capture console output let output = ''; const originalLog = console.log; console.log = (...args) => { output += args.join(' ') + '\n'; }; await outputText(result, verbose); console.log = originalLog; await fs.writeFile(outputPath, output); console.log(chalk_1.default.green(`โœ“ Impact analysis saved to ${outputPath}`)); } // Get files in workspace directory async function getWorkspaceFiles(workspacePath) { const files = []; const scanDirectory = async (dirPath) => { const entries = await fs.readdir(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dirPath, entry.name); if (entry.isDirectory()) { // Skip node_modules and build directories if (!['node_modules', 'dist', 'build', '.git'].includes(entry.name)) { await scanDirectory(fullPath); } } else if (entry.isFile()) { files.push(fullPath); } } }; await scanDirectory(workspacePath); return files; } // Generate Mermaid graph representation function generateMermaidGraph(graph) { let mermaid = 'graph TD\n'; // Add nodes for (const [name, workspace] of graph.nodes) { const nodeShape = getNodeShape(workspace.type); mermaid += ` ${name}${nodeShape}\n`; } // Add edges for (const [from, targets] of graph.edges) { for (const to of targets) { mermaid += ` ${from} --> ${to}\n`; } } return mermaid; } // Display text-based dependency graph function displayTextGraph(graph, verbose) { console.log(chalk_1.default.cyan('\n๐Ÿ“Š Workspace Dependency Graph')); console.log(chalk_1.default.gray('='.repeat(35))); console.log(chalk_1.default.bold('\n๐Ÿ—๏ธ Workspaces:')); for (const [name, workspace] of graph.nodes) { const typeColor = getWorkspaceTypeColor(workspace.type); const frameworkBadge = workspace.framework ? chalk_1.default.gray(`[${workspace.framework}]`) : ''; console.log(` ${chalk_1.default.gray('โ€ข')} ${typeColor(name)} ${frameworkBadge}`); if (verbose) { console.log(` ${chalk_1.default.gray('Path:')} ${workspace.path}`); console.log(` ${chalk_1.default.gray('Type:')} ${workspace.type}`); } } console.log(chalk_1.default.bold('\n๐Ÿ”— Dependencies:')); for (const [from, targets] of graph.edges) { if (targets.length > 0) { console.log(` ${chalk_1.default.yellow(from)} โ†’ ${targets.join(', ')}`); } } } // Get workspace type color function getWorkspaceTypeColor(type) { switch (type) { case 'app': return chalk_1.default.blue; case 'package': return chalk_1.default.green; case 'lib': return chalk_1.default.magenta; case 'tool': return chalk_1.default.cyan; default: return chalk_1.default.white; } } // Get node shape for Mermaid diagrams function getNodeShape(type) { switch (type) { case 'app': return '[App]'; case 'package': return '(Package)'; case 'lib': return '{Library}'; case 'tool': return '[[Tool]]'; default: return '[Unknown]'; } } // Register change impact commands function registerChangeImpactCommands(program) { const changeImpact = program .command('change-impact') .alias('impact') .description('Analyze change impact across workspace dependencies'); changeImpact .command('analyze') .description('Analyze impact of file changes') .option('--files <files...>', 'Specific files to analyze') .option('--output <file>', 'Output file path') .option('--format <format>', 'Output format (text|json|mermaid)', 'text') .option('--verbose', 'Show detailed information') .option('--max-depth <depth>', 'Maximum dependency depth', '10') .option('--include-tests', 'Include test dependencies') .option('--include-dev-deps', 'Include dev dependencies') .option('--visualization', 'Generate visualization data') .action(async (options) => { await manageChangeImpact({ ...options, maxDepth: parseInt(options.maxDepth) }); }); changeImpact .command('workspace <name>') .description('Analyze impact for specific workspace') .option('--verbose', 'Show detailed information') .option('--output <file>', 'Output file path') .option('--format <format>', 'Output format (text|json)', 'text') .action(async (name, options) => { await analyzeWorkspaceImpact(name, options); }); changeImpact .command('graph') .description('Show workspace dependency graph') .option('--output <file>', 'Output file path') .option('--format <format>', 'Output format (text|json|mermaid)', 'text') .option('--verbose', 'Show detailed information') .action(async (options) => { await showDependencyGraph(options); }); }