UNPKG

ruch

Version:

Revolutionary React TypeScript CLI with hexagonal architecture & AI-powered development assistance. Create maintainable, scalable applications with domain-driven design and integrated AI tooling.

239 lines 9.02 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateGraphData = exports.detectCircularDependencies = exports.analyzeDomainDependencies = exports.getSourceFiles = exports.extractDomainFromImportPath = exports.extractImportStatements = void 0; const path_1 = __importDefault(require("path")); const file_operations_1 = require("./file-operations"); const domain_analyzer_1 = require("./domain-analyzer"); /** * Pure function to extract import statements from file content */ const extractImportStatements = (content) => { const lines = content.split('\n'); const imports = []; lines.forEach((line, index) => { const trimmedLine = line.trim(); // Match various import patterns const importPatterns = [ /^import\s+.*\s+from\s+['"`]([^'"`]+)['"`]/, // import ... from '...' /^import\s+['"`]([^'"`]+)['"`]/, // import '...' /^export\s+.*\s+from\s+['"`]([^'"`]+)['"`]/ // export ... from '...' ]; for (const pattern of importPatterns) { const match = trimmedLine.match(pattern); if (match) { imports.push({ statement: trimmedLine, line: index + 1 }); break; } } }); return imports; }; exports.extractImportStatements = extractImportStatements; /** * Pure function to determine if an import path refers to a domain */ const extractDomainFromImportPath = (importPath, currentDomain) => { // Handle relative imports that go to other domains if (importPath.startsWith('../') || importPath.startsWith('./')) { // Extract domain name from relative path const pathParts = importPath.split('/'); const domainsIndex = pathParts.findIndex(part => part === 'domains'); if (domainsIndex !== -1 && pathParts[domainsIndex + 1]) { const targetDomain = pathParts[domainsIndex + 1]; return targetDomain && targetDomain !== currentDomain ? targetDomain : null; } } // Handle absolute imports from domains if (importPath.includes('/domains/')) { const match = importPath.match(/\/domains\/([^\/]+)/); if (match && match[1] && match[1] !== currentDomain) { return match[1]; } } return null; }; exports.extractDomainFromImportPath = extractDomainFromImportPath; /** * Pure function to get all TypeScript/JavaScript files in a directory recursively */ const getSourceFiles = async (fileSystem, dirPath) => { const files = []; try { const entries = await fileSystem.readdir(dirPath); for (const entry of entries) { const fullPath = path_1.default.join(dirPath, entry); const stats = await fileSystem.stat(fullPath); if (stats.isDirectory()) { const subFiles = await (0, exports.getSourceFiles)(fileSystem, fullPath); files.push(...subFiles); } else if (stats.isFile() && /\.(ts|tsx|js|jsx)$/.test(entry)) { files.push(fullPath); } } } catch { // Directory doesn't exist or can't be read } return files; }; exports.getSourceFiles = getSourceFiles; /** * Analyze dependencies for a single domain */ const analyzeDomainDependencies = async (fileSystem, domainPath, domainName) => { const dependencies = []; const sourceFiles = await (0, exports.getSourceFiles)(fileSystem, domainPath); for (const filePath of sourceFiles) { try { const content = await (0, file_operations_1.readFile)(fileSystem, filePath, 'utf8'); const imports = (0, exports.extractImportStatements)(content); for (const { statement, line } of imports) { const importMatch = statement.match(/from\s+['"`]([^'"`]+)['"`]/) || statement.match(/import\s+['"`]([^'"`]+)['"`]/); if (importMatch && importMatch[1]) { const importPath = importMatch[1]; const targetDomain = (0, exports.extractDomainFromImportPath)(importPath, domainName); if (targetDomain) { dependencies.push({ fromDomain: domainName, toDomain: targetDomain, filePath, line, importStatement: statement }); } } } } catch { // File can't be read, skip it } } return dependencies; }; exports.analyzeDomainDependencies = analyzeDomainDependencies; /** * Detect circular dependencies in the graph */ const detectCircularDependencies = (edges) => { const graph = new Map(); const cycles = []; // Build adjacency list edges.forEach(edge => { if (!graph.has(edge.source)) { graph.set(edge.source, []); } graph.get(edge.source).push(edge.target); }); // DFS to detect cycles const visited = new Set(); const recursionStack = new Set(); const dfs = (node, path) => { visited.add(node); recursionStack.add(node); path.push(node); const neighbors = graph.get(node) || []; for (const neighbor of neighbors) { if (!visited.has(neighbor)) { dfs(neighbor, [...path]); } else if (recursionStack.has(neighbor)) { // Found a cycle const cycleStart = path.indexOf(neighbor); if (cycleStart !== -1) { cycles.push([...path.slice(cycleStart), neighbor]); } } } recursionStack.delete(node); }; // Check all nodes graph.forEach((_, node) => { if (!visited.has(node)) { dfs(node, []); } }); return cycles; }; exports.detectCircularDependencies = detectCircularDependencies; /** * Generate complete graph data for all domains */ const generateGraphData = async (fileSystem, projectRoot = process.cwd()) => { const domainsPath = (0, domain_analyzer_1.buildDomainsPath)(projectRoot); const nodes = []; const edges = []; const allDependencies = []; // Check if domains directory exists const domainsExist = await (0, file_operations_1.validateFileExists)(fileSystem, domainsPath); if (!domainsExist) { return { nodes: [], edges: [], metadata: { generatedAt: new Date().toISOString(), projectPath: projectRoot, totalDomains: 0, totalDependencies: 0 } }; } try { // Get all domain directories const entries = await fileSystem.readdir(domainsPath); const domainNames = []; for (const entry of entries) { const domainPath = path_1.default.join(domainsPath, entry); const stats = await fileSystem.stat(domainPath); if (stats.isDirectory()) { domainNames.push(entry); // Create node for this domain nodes.push({ id: entry, label: entry.charAt(0).toUpperCase() + entry.slice(1), type: 'feature' // Could be enhanced to detect type based on structure }); // Analyze dependencies for this domain const dependencies = await (0, exports.analyzeDomainDependencies)(fileSystem, domainPath, entry); allDependencies.push(...dependencies); } } // Convert dependencies to edges allDependencies.forEach(dep => { // Only add edge if target domain exists if (domainNames.includes(dep.toDomain)) { edges.push({ source: dep.fromDomain, target: dep.toDomain, type: 'direct-import', metadata: { path: path_1.default.relative(projectRoot, dep.filePath), line: dep.line } }); } }); return { nodes, edges, metadata: { generatedAt: new Date().toISOString(), projectPath: projectRoot, totalDomains: nodes.length, totalDependencies: edges.length } }; } catch (error) { throw new Error(`Failed to generate graph data: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; exports.generateGraphData = generateGraphData; //# sourceMappingURL=dependency-analyzer.js.map