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
JavaScript
"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