travel to another computer
This commit is contained in:
100
scripts/scan-unused.mjs
Normal file
100
scripts/scan-unused.mjs
Normal file
@@ -0,0 +1,100 @@
|
||||
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));
|
||||
|
||||
Reference in New Issue
Block a user