|
/* This script was vibe-coded with Claude 3.7 Sonnet Thinking. */ |
|
|
|
import { Project, SourceFile } from 'ts-morph'; |
|
import * as path from 'path'; |
|
|
|
// EDIT THIS: |
|
const entryFilePath = path.resolve('./src/ui/components/AppPage/useAppPage.ts'); |
|
const tsConfigPath = path.resolve('./tsconfig.json'); |
|
|
|
function traceAllImports(entryFilePath: string, tsConfigPath: string) { |
|
const project = new Project({ |
|
tsConfigFilePath: tsConfigPath, |
|
}); |
|
|
|
// Get project root from tsconfig location |
|
const projectRoot = path.dirname(path.resolve(tsConfigPath)); |
|
|
|
// Convert to absolute path if it's relative |
|
const absoluteEntryPath = path.isAbsolute(entryFilePath) |
|
? entryFilePath |
|
: path.resolve(process.cwd(), entryFilePath); |
|
|
|
const entryFile = project.getSourceFileOrThrow(absoluteEntryPath); |
|
const processedFiles = new Set<string>(); |
|
const importMap = new Map<string, Set<string>>(); |
|
|
|
// Process the entry file and its dependencies |
|
processFileImports(entryFile); |
|
|
|
// Get relative path from project root |
|
function getRelativePath(absolutePath: string): string { |
|
return path.relative(projectRoot, absolutePath); |
|
} |
|
|
|
return { |
|
// Direct dependencies of the entry file |
|
directDependencies: Array.from(importMap.get(absoluteEntryPath) || []) |
|
.map(getRelativePath), |
|
|
|
// All files that would get loaded when importing the entry file |
|
allLoadedFiles: Array.from(processedFiles).map(getRelativePath), |
|
|
|
// Function to print the tree |
|
printTree: () => { |
|
// Track modules where we've already traversed all dependencies |
|
const traversedModules = new Set<string>(); |
|
printDependencyTree(absoluteEntryPath, importMap, traversedModules); |
|
} |
|
}; |
|
|
|
function processFileImports(sourceFile: SourceFile): void { |
|
const filePath = sourceFile.getFilePath(); |
|
if (processedFiles.has(filePath)) return; |
|
processedFiles.add(filePath); |
|
|
|
// Create set for this file's dependencies if it doesn't exist |
|
if (!importMap.has(filePath)) { |
|
importMap.set(filePath, new Set<string>()); |
|
} |
|
|
|
// Process imports |
|
const imports = sourceFile.getImportDeclarations(); |
|
for (const importDecl of imports) { |
|
const moduleSpecifier = importDecl.getModuleSpecifierValue(); |
|
const resolvedModule = resolveModulePath(sourceFile, moduleSpecifier); |
|
|
|
if (resolvedModule) { |
|
const resolvedPath = resolvedModule.getFilePath(); |
|
// Add to dependency set |
|
importMap.get(filePath)?.add(resolvedPath); |
|
|
|
// Process the imported file recursively |
|
processFileImports(resolvedModule); |
|
} |
|
} |
|
|
|
// Process re-exports (export ... from) |
|
const exportDeclarations = sourceFile.getExportDeclarations(); |
|
for (const exportDecl of exportDeclarations) { |
|
const moduleSpecifier = exportDecl.getModuleSpecifierValue(); |
|
if (moduleSpecifier) { |
|
const resolvedModule = resolveModulePath(sourceFile, moduleSpecifier); |
|
|
|
if (resolvedModule) { |
|
const resolvedPath = resolvedModule.getFilePath(); |
|
// Add to dependency set |
|
importMap.get(filePath)?.add(resolvedPath); |
|
|
|
// Process the imported file recursively |
|
processFileImports(resolvedModule); |
|
} |
|
} |
|
} |
|
|
|
// Explicitly check for index files in the same directory |
|
checkForIndexFile(sourceFile); |
|
} |
|
|
|
function checkForIndexFile(sourceFile: SourceFile): void { |
|
const filePath = sourceFile.getFilePath(); |
|
const dirPath = path.dirname(filePath); |
|
|
|
// Don't process index files themselves to avoid recursion |
|
if (path.basename(filePath).startsWith('index.')) return; |
|
|
|
const indexExtensions = ['.ts', '.tsx', '.js', '.jsx']; |
|
|
|
for (const ext of indexExtensions) { |
|
const indexPath = path.join(dirPath, `index${ext}`); |
|
const indexFile = project.getSourceFile(indexPath); |
|
|
|
if (indexFile && !processedFiles.has(indexPath)) { |
|
// If an index file exists and hasn't been processed, add it as a dependency |
|
importMap.get(filePath)?.add(indexPath); |
|
processFileImports(indexFile); |
|
} |
|
} |
|
} |
|
|
|
function resolveModulePath(sourceFile: SourceFile, moduleSpecifier: string): SourceFile | undefined { |
|
try { |
|
// For relative imports |
|
if (moduleSpecifier.startsWith('.')) { |
|
const dirPath = path.dirname(sourceFile.getFilePath()); |
|
const potentialPaths = [ |
|
// Try direct import |
|
path.resolve(dirPath, `${moduleSpecifier}.ts`), |
|
path.resolve(dirPath, `${moduleSpecifier}.tsx`), |
|
path.resolve(dirPath, `${moduleSpecifier}.js`), |
|
path.resolve(dirPath, `${moduleSpecifier}.jsx`), |
|
// Try as directory with index |
|
path.resolve(dirPath, moduleSpecifier, 'index.ts'), |
|
path.resolve(dirPath, moduleSpecifier, 'index.tsx'), |
|
path.resolve(dirPath, moduleSpecifier, 'index.js'), |
|
path.resolve(dirPath, moduleSpecifier, 'index.jsx') |
|
]; |
|
|
|
for (const potentialPath of potentialPaths) { |
|
const file = project.getSourceFile(potentialPath); |
|
if (file) return file; |
|
} |
|
} |
|
|
|
// For absolute imports based on tsconfig paths or node_modules |
|
return project.getSourceFile(sf => |
|
sf.getFilePath().includes(moduleSpecifier.replace(/^[~@]/, '')) |
|
); |
|
} catch (e) { |
|
console.error(`Error resolving module: ${moduleSpecifier}`, e); |
|
return undefined; |
|
} |
|
} |
|
|
|
// Improved tree printing - now with relative paths and smart traversal |
|
function printDependencyTree( |
|
rootPath: string, |
|
importMap: Map<string, Set<string>>, |
|
traversedModules: Set<string>, |
|
depth: number = 0, |
|
currentPath: Set<string> = new Set() |
|
): void { |
|
const indent = ' '.repeat(depth); |
|
const relativePath = getRelativePath(rootPath); |
|
|
|
// Print current node |
|
console.log(`${indent}* ${relativePath}`); |
|
|
|
// Check for circular dependency |
|
if (currentPath.has(rootPath)) { |
|
console.log(`${indent} * (CIRCULAR REFERENCE)`); |
|
return; |
|
} |
|
|
|
// Check if we've already traversed this module |
|
if (traversedModules.has(rootPath)) { |
|
console.log(`${indent} * (MODULE ALREADY TRAVERSED, SKIPPING)`); |
|
return; |
|
} |
|
|
|
// Mark as traversed |
|
traversedModules.add(rootPath); |
|
|
|
// Add current path to the path we're exploring |
|
currentPath.add(rootPath); |
|
|
|
// Get and sort dependencies |
|
const dependencies = importMap.get(rootPath) || new Set<string>(); |
|
const sortedDeps = Array.from(dependencies).sort((a, b) => |
|
getRelativePath(a).localeCompare(getRelativePath(b)) |
|
); |
|
|
|
// Print each dependency |
|
for (const dependency of sortedDeps) { |
|
printDependencyTree( |
|
dependency, |
|
importMap, |
|
traversedModules, |
|
depth + 1, |
|
new Set(currentPath) |
|
); |
|
} |
|
} |
|
} |
|
|
|
const result = traceAllImports(entryFilePath, tsConfigPath); |
|
|
|
console.log(`Direct dependencies of \`${entryFilePath}\`:`, result.directDependencies); |
|
console.log('All modules loaded:', result.allLoadedFiles.length); |
|
console.log('\nDependency Tree:'); |
|
result.printTree(); |