| 56 | } |
| 57 | |
| 58 | function extractImports(sourceFile: ts.SourceFile): string[] { |
| 59 | const imports: string[] = [] |
| 60 | |
| 61 | function visit(node: ts.Node) { |
| 62 | // import x from "path" or import { x } from "path" |
| 63 | if (ts.isImportDeclaration(node)) { |
| 64 | // Skip type-only imports |
| 65 | if (node.importClause?.isTypeOnly) return |
| 66 | |
| 67 | const moduleSpec = node.moduleSpecifier |
| 68 | if (ts.isStringLiteral(moduleSpec)) { |
| 69 | imports.push(moduleSpec.text) |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | // export { x } from "path" |
| 74 | if (ts.isExportDeclaration(node) && node.moduleSpecifier) { |
| 75 | if (ts.isStringLiteral(node.moduleSpecifier)) { |
| 76 | imports.push(node.moduleSpecifier.text) |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | // Dynamic import: import("path") |
| 81 | if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword) { |
| 82 | const arg = node.arguments[0] |
| 83 | if (arg && ts.isStringLiteral(arg)) { |
| 84 | imports.push(arg.text) |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | ts.forEachChild(node, visit) |
| 89 | } |
| 90 | |
| 91 | visit(sourceFile) |
| 92 | return imports |
| 93 | } |
| 94 | |
| 95 | async function traceFile(filePath: string, depth = 0): Promise<void> { |
| 96 | const normalizedPath = path.relative(BASE_DIR, filePath) |