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

544 lines (543 loc) 20.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WorkspaceDependencyGraph = void 0; exports.createWorkspaceDependencyGraph = createWorkspaceDependencyGraph; exports.validateWorkspaceDependencies = validateWorkspaceDependencies; const chalk_1 = __importDefault(require("chalk")); const error_handler_1 = require("./error-handler"); // Workspace dependency graph engine class WorkspaceDependencyGraph { constructor(definition) { this.nodes = new Map(); this.edges = new Map(); this.definition = definition; this.buildGraph(); } // Build the dependency graph from workspace definition buildGraph() { // Clear existing graph this.nodes.clear(); this.edges.clear(); // Create nodes for all workspaces for (const [name, workspace] of Object.entries(this.definition.workspaces)) { this.nodes.set(name, { name, workspace, dependencies: new Set(), dependents: new Set(), metadata: {} }); } // Create edges from dependencies for (const [workspaceName, dependencies] of Object.entries(this.definition.dependencies)) { if (!this.nodes.has(workspaceName)) { console.warn(chalk_1.default.yellow(`Warning: Dependencies defined for unknown workspace: ${workspaceName}`)); continue; } for (const dep of dependencies) { if (!this.nodes.has(dep.name)) { console.warn(chalk_1.default.yellow(`Warning: Unknown dependency '${dep.name}' for workspace '${workspaceName}'`)); continue; } this.addEdge(workspaceName, dep); } } } // Add a dependency edge addEdge(from, dependency) { const edge = { from, to: dependency.name, type: dependency.type, optional: dependency.optional || false, version: dependency.version, conditions: dependency.conditions, weight: this.calculateEdgeWeight(dependency) }; // Add to edges map if (!this.edges.has(from)) { this.edges.set(from, []); } this.edges.get(from).push(edge); // Update node dependencies const fromNode = this.nodes.get(from); const toNode = this.nodes.get(dependency.name); fromNode.dependencies.add(dependency.name); toNode.dependents.add(from); } // Calculate edge weight for path optimization calculateEdgeWeight(dependency) { let weight = 1; // Higher weight for build dependencies (more critical) if (dependency.type === 'build') weight += 3; else if (dependency.type === 'runtime') weight += 2; else if (dependency.type === 'dev') weight += 1; // Lower weight for optional dependencies if (dependency.optional) weight *= 0.5; return weight; } // Detect cycles using Depth-First Search detectCycles() { const result = { hasCycles: false, cycles: [], stronglyConnectedComponents: [] }; // Reset metadata for (const node of this.nodes.values()) { node.metadata.visited = false; node.metadata.visiting = false; } const visiting = new Set(); const path = []; // DFS from each unvisited node for (const nodeName of this.nodes.keys()) { if (!this.nodes.get(nodeName).metadata.visited) { this.detectCyclesDFS(nodeName, visiting, path, result); } } // Find strongly connected components using Tarjan's algorithm result.stronglyConnectedComponents = this.findStronglyConnectedComponents(); return result; } // DFS helper for cycle detection detectCyclesDFS(current, visiting, path, result) { const node = this.nodes.get(current); if (visiting.has(current)) { // Found a cycle const cycleStart = path.indexOf(current); const cyclePath = path.slice(cycleStart).concat([current]); const cycle = this.analyzeCycle(cyclePath); result.cycles.push(cycle); result.hasCycles = true; return; } if (node.metadata.visited) { return; } visiting.add(current); path.push(current); node.metadata.visiting = true; // Visit dependencies const edges = this.edges.get(current) || []; for (const edge of edges) { this.detectCyclesDFS(edge.to, visiting, path, result); } visiting.delete(current); path.pop(); node.metadata.visiting = false; node.metadata.visited = true; } // Analyze a detected cycle to determine type and severity analyzeCycle(cyclePath) { const edgeTypes = new Set(); const suggestions = []; // Analyze edges in the cycle for (let i = 0; i < cyclePath.length - 1; i++) { const from = cyclePath[i]; const to = cyclePath[i + 1]; const edges = this.edges.get(from) || []; const edge = edges.find(e => e.to === to); if (edge) { edgeTypes.add(edge.type); // Suggest making optional dependencies to break cycle if (!edge.optional && edge.type !== 'build') { suggestions.push(`Consider making dependency ${from} -> ${to} optional`); } } } // Determine cycle type and severity let type = 'mixed'; let severity = 'error'; if (edgeTypes.size === 1) { type = Array.from(edgeTypes)[0]; } // Build cycles are always errors if (edgeTypes.has('build')) { severity = 'error'; suggestions.push('Build cycles must be resolved by restructuring workspace dependencies'); } // Dev cycles might be warnings else if (edgeTypes.has('dev') && !edgeTypes.has('runtime')) { severity = 'warning'; suggestions.push('Development cycles can often be resolved with careful build ordering'); } // Test cycles are usually not critical else if (edgeTypes.has('test') && !edgeTypes.has('build') && !edgeTypes.has('runtime')) { severity = 'info'; suggestions.push('Test cycles can be resolved by reorganizing test dependencies'); } return { path: cyclePath, type, severity, suggestions }; } // Find strongly connected components using Tarjan's algorithm findStronglyConnectedComponents() { const index = new Map(); const lowLink = new Map(); const onStack = new Set(); const stack = []; const components = []; let currentIndex = 0; const tarjanDFS = (node) => { index.set(node, currentIndex); lowLink.set(node, currentIndex); currentIndex++; stack.push(node); onStack.add(node); // Visit dependencies const edges = this.edges.get(node) || []; for (const edge of edges) { const successor = edge.to; if (!index.has(successor)) { tarjanDFS(successor); lowLink.set(node, Math.min(lowLink.get(node), lowLink.get(successor))); } else if (onStack.has(successor)) { lowLink.set(node, Math.min(lowLink.get(node), index.get(successor))); } } // If node is a root node, pop the stack and create component if (lowLink.get(node) === index.get(node)) { const component = []; let w; do { w = stack.pop(); onStack.delete(w); component.push(w); } while (w !== node); if (component.length > 1) { components.push(component); } } }; // Run Tarjan's algorithm from each unvisited node for (const nodeName of this.nodes.keys()) { if (!index.has(nodeName)) { tarjanDFS(nodeName); } } return components; } // Generate topological ordering generateTopologicalOrder() { const inDegree = new Map(); const queue = []; const result = []; // Initialize in-degrees for (const nodeName of this.nodes.keys()) { inDegree.set(nodeName, 0); } // Calculate in-degrees for (const [from, edges] of this.edges) { for (const edge of edges) { inDegree.set(edge.to, (inDegree.get(edge.to) || 0) + 1); } } // Find nodes with no incoming edges for (const [nodeName, degree] of inDegree) { if (degree === 0) { queue.push(nodeName); } } // Process queue while (queue.length > 0) { const current = queue.shift(); result.push(current); // Update in-degrees of dependent nodes const edges = this.edges.get(current) || []; for (const edge of edges) { const newDegree = inDegree.get(edge.to) - 1; inDegree.set(edge.to, newDegree); if (newDegree === 0) { queue.push(edge.to); } } } // Check if all nodes were processed (no cycles) if (result.length !== this.nodes.size) { throw new error_handler_1.ValidationError('Cannot generate topological order: graph contains cycles'); } return result; } // Calculate workspace levels for parallel execution calculateLevels() { const levels = []; const nodeLevel = new Map(); // Reset metadata for (const node of this.nodes.values()) { node.metadata.level = undefined; } // Calculate levels using longest path from roots const calculateLevel = (nodeName) => { const node = this.nodes.get(nodeName); if (node.metadata.level !== undefined) { return node.metadata.level; } let maxDepLevel = -1; // Find maximum level of dependencies for (const depName of node.dependencies) { const depLevel = calculateLevel(depName); maxDepLevel = Math.max(maxDepLevel, depLevel); } const level = maxDepLevel + 1; node.metadata.level = level; nodeLevel.set(nodeName, level); return level; }; // Calculate levels for all nodes for (const nodeName of this.nodes.keys()) { calculateLevel(nodeName); } // Group nodes by level const maxLevel = Math.max(...Array.from(nodeLevel.values())); for (let i = 0; i <= maxLevel; i++) { levels[i] = []; } for (const [nodeName, level] of nodeLevel) { levels[level].push(nodeName); } return levels.filter(level => level.length > 0); } // Find critical path (longest path through the graph) findCriticalPath() { const distances = new Map(); const predecessors = new Map(); // Initialize distances for (const nodeName of this.nodes.keys()) { distances.set(nodeName, -Infinity); predecessors.set(nodeName, null); } // Find root nodes (no dependencies) const roots = Array.from(this.nodes.keys()).filter(name => this.nodes.get(name).dependencies.size === 0); // Set root distances to 0 for (const root of roots) { distances.set(root, 0); } // Use topological order to calculate longest paths try { const topOrder = this.generateTopologicalOrder(); for (const current of topOrder) { const currentDist = distances.get(current); if (currentDist === -Infinity) continue; const edges = this.edges.get(current) || []; for (const edge of edges) { const newDist = currentDist + edge.weight; if (newDist > distances.get(edge.to)) { distances.set(edge.to, newDist); predecessors.set(edge.to, current); } } } // Find the node with maximum distance (end of critical path) let maxDist = -Infinity; let endNode = ''; for (const [nodeName, dist] of distances) { if (dist > maxDist) { maxDist = dist; endNode = nodeName; } } // Reconstruct path const path = []; let current = endNode; while (current !== null) { path.unshift(current); current = predecessors.get(current); } // Mark critical path nodes for (const nodeName of path) { this.nodes.get(nodeName).metadata.criticalPath = true; } return path; } catch (error) { // Graph has cycles, cannot find critical path return []; } } // Generate optimal build order generateBuildOrder() { try { const levels = this.calculateLevels(); const topOrder = this.generateTopologicalOrder(); // Calculate estimated build times (simplified) const estimatedTime = levels.length * 60; // Assume 1 minute per level // Build dependency map for external use const dependencyMap = new Map(); for (const [nodeName, node] of this.nodes) { dependencyMap.set(nodeName, Array.from(node.dependencies)); } return { order: levels, parallelizable: levels.some(level => level.length > 1), maxParallelism: Math.max(...levels.map(level => level.length)), estimatedTime, dependencies: dependencyMap }; } catch (error) { throw new error_handler_1.ValidationError(`Cannot generate build order: ${error.message}`); } } // Perform comprehensive graph analysis analyzeGraph() { const cycles = this.detectCycles(); let topologicalOrder = []; let levels = []; let criticalPath = []; try { topologicalOrder = this.generateTopologicalOrder(); levels = this.calculateLevels(); criticalPath = this.findCriticalPath(); } catch (error) { // Graph has cycles, some analysis cannot be performed } // Find orphaned nodes (no dependencies and no dependents) const orphanedNodes = Array.from(this.nodes.keys()).filter(name => { const node = this.nodes.get(name); return node.dependencies.size === 0 && node.dependents.size === 0; }); // Calculate statistics const dependencies = Array.from(this.nodes.values()).map(n => n.dependencies.size); const dependents = Array.from(this.nodes.values()).map(n => n.dependents.size); const statistics = { maxDepth: levels.length, avgDependencies: dependencies.length > 0 ? dependencies.reduce((a, b) => a + b, 0) / dependencies.length : 0, avgDependents: dependents.length > 0 ? dependents.reduce((a, b) => a + b, 0) / dependents.length : 0, isolatedComponents: cycles.stronglyConnectedComponents.length }; return { nodeCount: this.nodes.size, edgeCount: Array.from(this.edges.values()).reduce((total, edges) => total + edges.length, 0), cycles, topologicalOrder, levels, criticalPath, orphanedNodes, statistics }; } // Get graph visualization data getVisualizationData() { const nodes = Array.from(this.nodes.entries()).map(([name, node]) => ({ id: name, label: name, group: node.workspace.type, level: node.metadata.level })); const edges = []; for (const [from, edgeList] of this.edges) { for (const edge of edgeList) { const color = this.getEdgeColor(edge.type); edges.push({ from: edge.from, to: edge.to, label: edge.type, color }); } } return { nodes, edges }; } // Get edge color based on dependency type getEdgeColor(type) { const colors = { build: '#ff4444', // Red for build dependencies runtime: '#4444ff', // Blue for runtime dependencies dev: '#44ff44', // Green for dev dependencies test: '#ffaa44' // Orange for test dependencies }; return colors[type] || '#888888'; } // Get workspace node getNode(name) { return this.nodes.get(name); } // Get all nodes getAllNodes() { return new Map(this.nodes); } // Get edges from a node getEdges(from) { return this.edges.get(from) || []; } // Check if there's a path between two nodes hasPath(from, to) { if (from === to) return true; const visited = new Set(); const queue = [from]; while (queue.length > 0) { const current = queue.shift(); if (current === to) return true; if (visited.has(current)) continue; visited.add(current); const edges = this.edges.get(current) || []; for (const edge of edges) { queue.push(edge.to); } } return false; } // Add a new workspace to the graph addWorkspace(name, workspace) { this.definition.workspaces[name] = workspace; this.buildGraph(); // Rebuild graph } // Remove a workspace from the graph removeWorkspace(name) { delete this.definition.workspaces[name]; delete this.definition.dependencies[name]; // Remove dependencies to this workspace for (const [workspaceName, deps] of Object.entries(this.definition.dependencies)) { this.definition.dependencies[workspaceName] = deps.filter(dep => dep.name !== name); } this.buildGraph(); // Rebuild graph } // Update workspace dependencies updateDependencies(workspaceName, dependencies) { this.definition.dependencies[workspaceName] = dependencies; this.buildGraph(); // Rebuild graph } } exports.WorkspaceDependencyGraph = WorkspaceDependencyGraph; // Utility functions function createWorkspaceDependencyGraph(definition) { return new WorkspaceDependencyGraph(definition); } function validateWorkspaceDependencies(definition) { const errors = []; try { const graph = new WorkspaceDependencyGraph(definition); const analysis = graph.analyzeGraph(); // Report cycle errors for (const cycle of analysis.cycles.cycles) { if (cycle.severity === 'error') { errors.push(new error_handler_1.ValidationError(`Circular dependency detected: ${cycle.path.join(' → ')}`)); } } // Report orphaned nodes as warnings (not errors) if (analysis.orphanedNodes.length > 0) { console.warn(chalk_1.default.yellow(`Warning: Orphaned workspaces found: ${analysis.orphanedNodes.join(', ')}`)); } } catch (error) { errors.push(new error_handler_1.ValidationError(`Dependency validation failed: ${error.message}`)); } return errors; }