/ find-missing-packages.mjs
find-missing-packages.mjs
1 // Finds all bare specifier imports in dist/ that can't be resolved from node_modules 2 import { readFileSync, existsSync, readdirSync } from 'fs'; 3 import { join, dirname } from 'path'; 4 import { fileURLToPath } from 'url'; 5 6 const __dirname = dirname(fileURLToPath(import.meta.url)); 7 const DIST_SRC = join(__dirname, 'dist', 'src'); 8 const NODE_MODULES = join(__dirname, 'node_modules'); 9 10 function walkDir(dir) { 11 const results = []; 12 for (const entry of readdirSync(dir, { withFileTypes: true })) { 13 const full = join(dir, entry.name); 14 if (entry.isDirectory()) walkDir(full).forEach(f => results.push(f)); 15 else if (entry.name.endsWith('.js')) results.push(full); 16 } 17 return results; 18 } 19 20 const missing = new Set(); 21 for (const f of walkDir(DIST_SRC)) { 22 const code = readFileSync(f, 'utf-8'); 23 // Match bare specifier imports (not starting with . or /) 24 const re = /from\s+["']([^."'/][^"']*)["']/g; 25 let m; 26 while ((m = re.exec(code)) !== null) { 27 const spec = m[1]; 28 // Skip node: builtins 29 if (spec.startsWith('node:')) continue; 30 // Get package name (handle scoped packages) 31 let pkgName; 32 if (spec.startsWith('@')) { 33 const parts = spec.split('/'); 34 pkgName = parts[0] + '/' + parts[1]; 35 } else { 36 pkgName = spec.split('/')[0]; 37 } 38 // Check if it exists in node_modules 39 const pkgDir = join(NODE_MODULES, pkgName); 40 if (!existsSync(pkgDir)) { 41 missing.add(pkgName); 42 } 43 } 44 } 45 46 const sorted = [...missing].sort(); 47 console.log('Missing packages: ' + sorted.length); 48 sorted.forEach(p => console.log(p));