Skip to main content
Glama
graph.ts4.5 kB
/** * Lightweight module graph builder with alias and re-export support */ import { readFile } from 'fs/promises'; import * as path from 'path'; import type { FileInfo } from '../../../../core/compactor/fileDiscovery'; export interface ModuleGraph { imports: Map<string, Set<string>>; // file -> set(imported files) reverse: Map<string, Set<string>>; // file -> set(importers) } function toPosix(p: string): string { return p.replace(/\\/g, '/'); } function getTsconfigPaths(projectRoot: string): Record<string, string[]> { try { const tsPath = path.join(projectRoot, 'tsconfig.json'); const raw = require('fs').readFileSync(tsPath, 'utf-8'); const ts = JSON.parse(raw); return ts?.compilerOptions?.paths || {}; } catch { return {}; } } function resolveAlias( spec: string, fromFile: string, files: FileInfo[], projectRoot: string, pathsMap: Record<string, string[]> ): string | undefined { const relFrom = toPosix(fromFile); // Relative import if (spec.startsWith('./') || spec.startsWith('../')) { const base = toPosix(path.posix.dirname(relFrom)); const resolved = toPosix(path.posix.normalize(path.posix.join(base, spec))); const found = files.find(f => { const r = toPosix(f.relPath); return ( r === resolved || r === resolved + '.ts' || r === resolved + '.tsx' || r === resolved + '.js' || r === resolved + '.jsx' || (r.endsWith('/index.ts') && r.slice(0, -9) === resolved) || (r.endsWith('/index.tsx') && r.slice(0, -10) === resolved) ); }); return found ? toPosix(found.relPath) : undefined; } // Next.js alias '@/' - try common bases: 'web/', 'app/', project root if (spec.startsWith('@/')) { const tail = spec.slice(2); const bases = ['web', 'app', 'src', '']; for (const base of bases) { const joined = base ? toPosix(path.posix.normalize(path.posix.join(base, tail))) : toPosix(tail); const found = files.find(f => { const rel = toPosix(f.relPath); return ( rel === joined || rel.startsWith(joined + '.') || rel === joined + '/index.tsx' || rel === joined + '/index.ts' ); }); if (found) return toPosix(found.relPath); } } // tsconfig paths for (const [alias, targets] of Object.entries(pathsMap)) { const aliasBase = alias.replace(/\*$/, ''); if (spec.startsWith(aliasBase)) { const rest = spec.slice(aliasBase.length); for (const tgt of targets) { const tgtBase = toPosix(tgt.replace(/\*$/, '')); const candidate = toPosix( path.posix.normalize(path.posix.join(projectRoot, tgtBase, rest)) ); const found = files.find( f => toPosix(f.absPath) === candidate || toPosix(f.absPath).startsWith(candidate + '.') ); if (found) return toPosix(found.relPath); } } } return undefined; } export async function buildModuleGraph( files: FileInfo[], projectRoot: string ): Promise<ModuleGraph> { const imports = new Map<string, Set<string>>(); const reverse = new Map<string, Set<string>>(); const pathsMap = getTsconfigPaths(projectRoot); for (const file of files) { const rel = toPosix(file.relPath); if (!/\.(ts|tsx|js|jsx)$/.test(rel)) continue; let content = ''; try { content = await readFile(file.absPath, 'utf-8'); } catch { continue; } const importSpecs: string[] = []; const imp = /import\s+.*?from\s+['"]([^'"]+)['"]/g; const reexp1 = /export\s+\*\s+from\s+['"]([^'"]+)['"]/g; const reexp2 = /export\s+\{[^}]*\}\s+from\s+['"]([^'"]+)['"]/g; let m; while ((m = imp.exec(content)) !== null) importSpecs.push(m[1]); while ((m = reexp1.exec(content)) !== null) importSpecs.push(m[1]); while ((m = reexp2.exec(content)) !== null) importSpecs.push(m[1]); for (const spec of importSpecs) { const resolved = resolveAlias(spec, rel, files, toPosix(projectRoot), pathsMap); if (!resolved) continue; let importSet = imports.get(rel); if (!importSet) { importSet = new Set<string>(); imports.set(rel, importSet); } importSet.add(resolved); let reverseSet = reverse.get(resolved); if (!reverseSet) { reverseSet = new Set<string>(); reverse.set(resolved, reverseSet); } reverseSet.add(rel); } } return { imports, reverse }; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sbarron/AmbianceMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server