12import { spawnSync } from 'child_process' 14import * as _ from 'lodash-es' 16export const codegraphImporters = async (filePath: string, {importerPath}: {importerPath?: string} = {}) => { 18 // If specific importer given, explain the chain 19 if (importerPath) return codegraphExplainImport(filePath, importerPath) 20 // Uses grep-first optimization via getPathsThatImport (rg to find candidates, then ptMadge to verify) 23 // Also check deploy includes 26 // Also check anchor includes (files linked via ptAnchorInclude/ptAnchorPath/epFromPath) 29 // Combine ES imports, deploy includes, and anchor includes 30 const resultA = [...importedByPathsA] 31 for (const regEpPath of deployIncluders) { 32 if (!resultA.includes(regEpPath)) { 33 resultA.push(`${regEpPath} (deploy-include)`) 36 for (const anchorer of anchorIncluders) { 37 if (!resultA.includes(anchorer)) { 38 resultA.push(`${anchorer} (anchor-include)`) 42 return resultA.join('\n') 45export const codegraphHowimports = async (filePath: string, { epDir, srcPath }: {epDir?: string, srcPath?: string} = {}) => { 48 if (isPkgSpecifier(srcPath)) return await codegraphHowimportsPackage(filePath, srcPath, {epDir}) 49 return await codegraphExplainImport(filePath, srcPath) 54 return epDir ? `No entrypoints found in ${epDir}/` : 'No entrypoints found in registry' 60export const codegraphExplainImport = async (fileA: string, fileB: string) => { 62 const madgeH = await ptMadge([fileA, fileB]) 63 const traceDir = async (src: string, tgt: string) => { 65 if (!relevantMadgeH[src] && !Object.values(relevantMadgeH).some(imports => imports.includes(src))) return null 66 const transitiveImporters = new Set(Object.keys(relevantMadgeH)) 67 if (!transitiveImporters.has(src)) return null 68 const epPathsA = buildImportTreePaths({madgeH: relevantMadgeH, epA: [src], tgtPath: tgt, transitiveImporters}) 71 const [fwd, rev] = await allPromCalls([[fileA, fileB], [fileB, fileA]], ([src, tgt]) => traceDir(src, tgt)) 72 if (fwd && rev) return `── ${fileA} → ${fileB} ──\n${fwd}\n\n── ${fileB} → ${fileA} ──\n${rev}` 75 return `No import path between ${fileA} and ${fileB}` 78const rgFindPkgImporters = (packageName: string): string[] => { 79 const escapedPkg = packageName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 81 const pattern = `["'${bt}]${escapedPkg}(?:/|["'${bt}])` 82 const { stdout, status } = spawnSync('rg', ['-l', '--glob', extGlob(ptFileExtA), '-e', pattern, '.'], { 83 encoding: 'utf8', cwd: ptDir, maxBuffer: 10 * 1024 * 1024 85 if (status === 1) return [] 86 if (status !== 0) throw new Error(`rg exited with status ${status}`) 87 return stdout.trim().split('\n').filter(Boolean).map(p => p.replace(/^\.\//, '')) 90export const isPkgSpecifier = (arg: string) => 93export const codegraphHowimportsPackage = async (filePath: string, packageName: string, {epDir}: {epDir?: string} = {}) => { 95 const pkgImporterPaths = rgFindPkgImporters(packageName) 96 if (!pkgImporterPaths.length) return `No files import ${packageName}` 99 if (!epA.length) return epDir ? `No entrypoints found in ${epDir}/` : 'No entrypoints found in registry' 104 if (pkgImporterPaths.includes(filePath)) return `${filePath} ← imports ${packageName} (directly)` 106 const filesReachableFromTgt = new Set<string>() 107 const queue = [filePath] 108 while (queue.length) { 109 const cur = queue.shift()! 110 if (filesReachableFromTgt.has(cur)) continue 111 filesReachableFromTgt.add(cur) 112 for (const imp of (madgeH[cur] || [])) queue.push(imp) 115 const pkgImportersReachable = pkgImporterPaths.filter(p => filesReachableFromTgt.has(p)) 116 if (!pkgImportersReachable.length) return `${filePath} does not transitively import ${packageName}` 118 const outputA: string[] = [] 119 for (const pkgImporter of pkgImportersReachable) { 121 const transitiveImporters = new Set(Object.keys(relevantMadgeH)) 122 if (!transitiveImporters.has(filePath)) continue 124 const epPathsA = buildImportTreePaths({madgeH: relevantMadgeH, epA: [filePath], tgtPath: pkgImporter, transitiveImporters}) 125 if (!epPathsA.length) continue 128 outputA.push(tree + ` ← imports ${packageName}`) 131 if (!outputA.length) return `${filePath} does not transitively import ${packageName}` 132 return outputA.join('\n')