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