/ 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));