import fs from "fs"; import path from "path"; import { fileURLToPath } from "url"; /** * Scan simple de archivos JS alcanzables desde un entrypoint, * siguiendo imports estáticos: `import ... from "./x.js"` y `await import("./x.js")`. * * OJO: no entiende requires dinámicos ni construcciones complejas. * Útil para detectar archivos claramente no usados. */ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const root = path.resolve(__dirname, ".."); const entry = path.join(root, "index.js"); const srcDir = path.join(root, "src"); function listJsFiles(dir) { const out = []; const stack = [dir]; while (stack.length) { const cur = stack.pop(); const items = fs.readdirSync(cur, { withFileTypes: true }); for (const it of items) { const p = path.join(cur, it.name); if (it.isDirectory()) stack.push(p); else if (it.isFile() && p.endsWith(".js")) out.push(p); } } return out; } function readText(p) { return fs.readFileSync(p, "utf8"); } function resolveLocalImport(fromFile, spec) { if (!spec.startsWith(".")) return null; const base = path.resolve(path.dirname(fromFile), spec); const candidates = []; if (base.endsWith(".js")) candidates.push(base); else { candidates.push(`${base}.js`); candidates.push(path.join(base, "index.js")); } for (const c of candidates) { if (fs.existsSync(c) && fs.statSync(c).isFile()) return c; } return null; } function extractImports(code) { const out = new Set(); // static import ... from "x" for (const m of code.matchAll(/import\s+[^;]*?\s+from\s+["']([^"']+)["']/g)) { out.add(m[1]); } // side-effect import "x" for (const m of code.matchAll(/import\s+["']([^"']+)["']/g)) { out.add(m[1]); } // dynamic import("x") or await import("x") for (const m of code.matchAll(/import\(\s*["']([^"']+)["']\s*\)/g)) { out.add(m[1]); } return [...out]; } const allSrc = listJsFiles(srcDir); const all = [entry, ...allSrc]; const reachable = new Set(); const queue = [entry]; while (queue.length) { const f = queue.shift(); if (reachable.has(f)) continue; reachable.add(f); let code = ""; try { code = readText(f); } catch { continue; } const specs = extractImports(code); for (const s of specs) { const resolved = resolveLocalImport(f, s); if (resolved && !reachable.has(resolved)) queue.push(resolved); } } const unused = allSrc .filter((f) => !reachable.has(f)) .map((f) => path.relative(root, f).replace(/\\/g, "/")) .sort(); console.log(JSON.stringify({ entry: path.relative(root, entry), reachable_count: reachable.size, total_src: allSrc.length, unused }, null, 2));