#!/usr/bin/env tsx import fs from 'fs' import path from 'path' import glob from 'fast-glob' interface ImportInfo { source: string imports: string[] } interface DependencyGraph { nodes: Array<{ id: string label: string group: string size: number }> links: Array<{ source: string target: string value: number }> } // Extract imports from a TypeScript/Vue file function extractImports(filePath: string): ImportInfo { const content = fs.readFileSync(filePath, 'utf-8') const imports: string[] = [] // Match ES6 import statements const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s+from\s+)?['"]([^'"]+)['"]/g let match while ((match = importRegex.exec(content)) !== null) { imports.push(match[1]) } // Also match dynamic imports const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g while ((match = dynamicImportRegex.exec(content)) !== null) { imports.push(match[1]) } return { source: filePath, imports: [...new Set(imports)] // Remove duplicates } } // Categorize file by its path function getFileGroup(filePath: string): string { const relativePath = path.relative(process.cwd(), filePath) if (relativePath.includes('node_modules')) return 'external' if (relativePath.startsWith('src/components')) return 'components' if (relativePath.startsWith('src/stores')) return 'stores' if (relativePath.startsWith('src/services')) return 'services' if (relativePath.startsWith('src/views')) return 'views' if (relativePath.startsWith('src/composables')) return 'composables' if (relativePath.startsWith('src/utils')) return 'utils' if (relativePath.startsWith('src/types')) return 'types' if (relativePath.startsWith('src/extensions')) return 'extensions' if (relativePath.startsWith('src/lib')) return 'lib' if (relativePath.startsWith('src/scripts')) return 'scripts' if (relativePath.startsWith('tests')) return 'tests' if (relativePath.startsWith('browser_tests')) return 'browser_tests' return 'other' } // Resolve import path to actual file function resolveImportPath(importPath: string, sourceFile: string): string { // Handle aliases if (importPath.startsWith('@/')) { return path.join(process.cwd(), 'src', importPath.slice(2)) } // Handle relative paths if (importPath.startsWith('.')) { const sourceDir = path.dirname(sourceFile) return path.resolve(sourceDir, importPath) } // External module return importPath } // Generate dependency graph async function generateDependencyGraph(): Promise { const sourceFiles = await glob('src/**/*.{ts,tsx,vue,mts}', { ignore: ['**/node_modules/**', '**/*.d.ts', '**/*.spec.ts', '**/*.test.ts', '**/*.stories.ts'] }) const nodes = new Map() const links = new Map() // Process each file for (const file of sourceFiles) { const importInfo = extractImports(file) const sourceId = path.relative(process.cwd(), file) // Add source node if (!nodes.has(sourceId)) { nodes.set(sourceId, { id: sourceId, label: path.basename(file), group: getFileGroup(file), size: 1 }) } // Process imports for (const importPath of importInfo.imports) { const resolvedPath = resolveImportPath(importPath, file) let targetId: string // Check if it's an external module if (!resolvedPath.startsWith('/') && !resolvedPath.startsWith('.')) { targetId = `external:${importPath}` if (!nodes.has(targetId)) { nodes.set(targetId, { id: targetId, label: importPath, group: 'external', size: 1 }) } } else { // Try to find the actual file const possibleExtensions = ['.ts', '.tsx', '.vue', '.mts', '.js', '.json', '/index.ts', '/index.js'] let actualFile = resolvedPath for (const ext of possibleExtensions) { if (fs.existsSync(resolvedPath + ext)) { actualFile = resolvedPath + ext break } } if (fs.existsSync(actualFile)) { targetId = path.relative(process.cwd(), actualFile) if (!nodes.has(targetId)) { nodes.set(targetId, { id: targetId, label: path.basename(actualFile), group: getFileGroup(actualFile), size: 1 }) } } else { continue // Skip unresolved imports } } // Add link const linkKey = `${sourceId}->${targetId}` if (links.has(linkKey)) { links.get(linkKey)!.value++ } else { links.set(linkKey, { source: sourceId, target: targetId, value: 1 }) } // Increase target node size const targetNode = nodes.get(targetId) if (targetNode) { targetNode.size++ } } } return { nodes: Array.from(nodes.values()), links: Array.from(links.values()) } } // Generate HTML visualization function generateHTML(graph: DependencyGraph): string { return ` ComfyUI Frontend Import Map
` } // Main function async function main() { console.log('Generating import map...') try { const graph = await generateDependencyGraph() console.log(`Found ${graph.nodes.length} nodes and ${graph.links.length} dependencies`) // Save JSON data const jsonPath = path.join(process.cwd(), 'docs', 'import-map.json') fs.mkdirSync(path.dirname(jsonPath), { recursive: true }) fs.writeFileSync(jsonPath, JSON.stringify(graph, null, 2)) console.log(`Saved JSON data to ${jsonPath}`) // Generate and save HTML visualization const html = generateHTML(graph) const htmlPath = path.join(process.cwd(), 'docs', 'import-map.html') fs.writeFileSync(htmlPath, html) console.log(`Saved HTML visualization to ${htmlPath}`) console.log('✅ Import map generation complete!') console.log('Open docs/import-map.html in a browser to view the visualization') } catch (error) { console.error('Error generating import map:', error) process.exit(1) } } void main()