prunify 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +202 -101
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +202 -101
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -232,40 +232,12 @@ function buildGraph(files, getImports) {
|
|
|
232
232
|
function findEntryPoints(rootDir, packageJson) {
|
|
233
233
|
const entries = [
|
|
234
234
|
...resolveNextJsEntries(rootDir),
|
|
235
|
+
...resolveFileBrowserEntries(rootDir),
|
|
235
236
|
...resolvePkgFieldEntries(rootDir, packageJson),
|
|
236
237
|
...resolveFallbackEntries(rootDir)
|
|
237
238
|
];
|
|
238
239
|
return [...new Set(entries)];
|
|
239
240
|
}
|
|
240
|
-
function findRootFiles(graph) {
|
|
241
|
-
const imported = /* @__PURE__ */ new Set();
|
|
242
|
-
for (const deps of graph.values()) {
|
|
243
|
-
for (const dep of deps) imported.add(dep);
|
|
244
|
-
}
|
|
245
|
-
const roots = [...graph.keys()].filter((f) => !imported.has(f));
|
|
246
|
-
return roots.length > 0 ? roots : [...graph.keys()];
|
|
247
|
-
}
|
|
248
|
-
function runDFS(graph, entryPoints) {
|
|
249
|
-
const visited = /* @__PURE__ */ new Set();
|
|
250
|
-
const stack = [...entryPoints];
|
|
251
|
-
let node;
|
|
252
|
-
while ((node = stack.pop()) !== void 0) {
|
|
253
|
-
if (visited.has(node)) continue;
|
|
254
|
-
visited.add(node);
|
|
255
|
-
for (const neighbor of graph.get(node) ?? []) {
|
|
256
|
-
if (!visited.has(neighbor)) stack.push(neighbor);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
return visited;
|
|
260
|
-
}
|
|
261
|
-
function findDeadChains(graph, deadFiles) {
|
|
262
|
-
const reverseGraph = buildReverseGraph(graph);
|
|
263
|
-
const result = /* @__PURE__ */ new Map();
|
|
264
|
-
for (const deadRoot of deadFiles) {
|
|
265
|
-
result.set(deadRoot, dfsDeadChain(deadRoot, graph, deadFiles, reverseGraph));
|
|
266
|
-
}
|
|
267
|
-
return result;
|
|
268
|
-
}
|
|
269
241
|
function detectCycles(graph) {
|
|
270
242
|
const cycles = [];
|
|
271
243
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -284,12 +256,21 @@ function resolveNextJsEntries(rootDir) {
|
|
|
284
256
|
const isNext = import_node_fs3.default.existsSync(import_node_path3.default.join(rootDir, "next.config.js")) || import_node_fs3.default.existsSync(import_node_path3.default.join(rootDir, "next.config.ts")) || import_node_fs3.default.existsSync(import_node_path3.default.join(rootDir, "next.config.mjs"));
|
|
285
257
|
if (!isNext) return [];
|
|
286
258
|
const entries = [];
|
|
287
|
-
for (const dir of ["pages", "app"]) {
|
|
259
|
+
for (const dir of ["pages", "app", "src/pages", "src/app"]) {
|
|
288
260
|
const dirPath = import_node_path3.default.join(rootDir, dir);
|
|
289
261
|
if (import_node_fs3.default.existsSync(dirPath)) entries.push(...collectSourceFiles(dirPath));
|
|
290
262
|
}
|
|
291
263
|
return entries;
|
|
292
264
|
}
|
|
265
|
+
function resolveFileBrowserEntries(rootDir) {
|
|
266
|
+
const PAGE_DIRS = ["src/pages", "src/routes", "src/views", "src/screens"];
|
|
267
|
+
const entries = [];
|
|
268
|
+
for (const rel of PAGE_DIRS) {
|
|
269
|
+
const dirPath = import_node_path3.default.join(rootDir, rel);
|
|
270
|
+
if (import_node_fs3.default.existsSync(dirPath)) entries.push(...collectSourceFiles(dirPath));
|
|
271
|
+
}
|
|
272
|
+
return entries;
|
|
273
|
+
}
|
|
293
274
|
function resolvePkgFieldEntries(rootDir, packageJson) {
|
|
294
275
|
const entries = [];
|
|
295
276
|
for (const field of ["main", "module"]) {
|
|
@@ -366,27 +347,6 @@ function recordCycle(cycleStart, path10, acc) {
|
|
|
366
347
|
acc.cycles.push(cycle);
|
|
367
348
|
}
|
|
368
349
|
}
|
|
369
|
-
function dfsDeadChain(deadRoot, graph, deadFiles, reverseGraph) {
|
|
370
|
-
const chain = [];
|
|
371
|
-
const visited = /* @__PURE__ */ new Set();
|
|
372
|
-
const stack = [...graph.get(deadRoot) ?? []];
|
|
373
|
-
let node;
|
|
374
|
-
while ((node = stack.pop()) !== void 0) {
|
|
375
|
-
if (visited.has(node) || node === deadRoot) continue;
|
|
376
|
-
visited.add(node);
|
|
377
|
-
if (deadFiles.has(node) || isOnlyImportedByDead(node, deadFiles, reverseGraph)) {
|
|
378
|
-
chain.push(node);
|
|
379
|
-
for (const next of graph.get(node) ?? []) {
|
|
380
|
-
if (!visited.has(next)) stack.push(next);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return chain;
|
|
385
|
-
}
|
|
386
|
-
function isOnlyImportedByDead(file, deadFiles, reverseGraph) {
|
|
387
|
-
const importers = reverseGraph.get(file) ?? /* @__PURE__ */ new Set();
|
|
388
|
-
return importers.size === 0 || [...importers].every((imp) => deadFiles.has(imp));
|
|
389
|
-
}
|
|
390
350
|
function buildReverseGraph(graph) {
|
|
391
351
|
const rev = /* @__PURE__ */ new Map();
|
|
392
352
|
for (const [file] of graph) {
|
|
@@ -513,16 +473,101 @@ function ensureDir(dir) {
|
|
|
513
473
|
}
|
|
514
474
|
|
|
515
475
|
// src/modules/dead-code.ts
|
|
476
|
+
var FRAMEWORK_FILE_PATTERNS = [
|
|
477
|
+
/^next\.config\.(js|ts|mjs|cjs)$/,
|
|
478
|
+
/^middleware\.(ts|js)$/,
|
|
479
|
+
/^instrumentation\.(ts|js)$/,
|
|
480
|
+
/^tailwind\.config\.(js|ts|mjs|cjs)$/,
|
|
481
|
+
/^postcss\.config\.(js|ts|mjs|cjs)$/,
|
|
482
|
+
/^jest\.config\.(js|ts|mjs|cjs)$/,
|
|
483
|
+
/^vitest\.config\.(js|ts|mjs|cjs)$/,
|
|
484
|
+
/^vite\.config\.(js|ts|mjs|cjs)$/,
|
|
485
|
+
/^webpack\.config\.(js|ts|mjs|cjs)$/,
|
|
486
|
+
/^babel\.config\.(js|ts|cjs|mjs|json)$/,
|
|
487
|
+
/^\.babelrc\.(js|cjs)$/,
|
|
488
|
+
/^\.eslintrc\.(js|cjs)$/,
|
|
489
|
+
/^eslint\.config\.(js|ts|mjs|cjs)$/,
|
|
490
|
+
/^prettier\.config\.(js|ts|mjs|cjs)$/,
|
|
491
|
+
/^tsup\.config\.(ts|js)$/,
|
|
492
|
+
/^rollup\.config\.(js|ts|mjs)$/,
|
|
493
|
+
/^esbuild\.config\.(js|ts|mjs)$/,
|
|
494
|
+
/^commitlint\.config\.(js|ts)$/,
|
|
495
|
+
/^lint-staged\.config\.(js|ts|mjs|cjs)$/,
|
|
496
|
+
/^sentry\.(client|server|edge)\.config\.(ts|js)$/
|
|
497
|
+
];
|
|
498
|
+
function isFrameworkFile(filePath, rootDir) {
|
|
499
|
+
const rel = import_node_path5.default.relative(rootDir, filePath);
|
|
500
|
+
const basename = import_node_path5.default.basename(rel);
|
|
501
|
+
return FRAMEWORK_FILE_PATTERNS.some((re) => re.test(basename));
|
|
502
|
+
}
|
|
516
503
|
function runDeadCodeModule(project, graph, entryPoints, rootDir) {
|
|
517
504
|
const allFiles = [...graph.keys()];
|
|
518
|
-
const
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
const
|
|
522
|
-
|
|
505
|
+
const entrySet = new Set(entryPoints);
|
|
506
|
+
const reverseGraph = buildReverseGraph(graph);
|
|
507
|
+
const excludedFiles = /* @__PURE__ */ new Set();
|
|
508
|
+
for (const file of allFiles) {
|
|
509
|
+
if (isFrameworkFile(file, rootDir)) excludedFiles.add(file);
|
|
510
|
+
}
|
|
511
|
+
const safeToDelete = allFiles.filter((f) => {
|
|
512
|
+
if (entrySet.has(f) || excludedFiles.has(f)) return false;
|
|
513
|
+
const importers = reverseGraph.get(f);
|
|
514
|
+
return !importers || importers.size === 0;
|
|
515
|
+
});
|
|
516
|
+
const deadSet = new Set(safeToDelete);
|
|
517
|
+
let changed = true;
|
|
518
|
+
while (changed) {
|
|
519
|
+
changed = false;
|
|
520
|
+
for (const file of allFiles) {
|
|
521
|
+
if (deadSet.has(file) || entrySet.has(file) || excludedFiles.has(file)) continue;
|
|
522
|
+
const importers = reverseGraph.get(file);
|
|
523
|
+
if (!importers || importers.size === 0) continue;
|
|
524
|
+
if ([...importers].every((imp) => deadSet.has(imp))) {
|
|
525
|
+
deadSet.add(file);
|
|
526
|
+
changed = true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const transitivelyDead = [...deadSet].filter((f) => !safeToDelete.includes(f));
|
|
531
|
+
const chains = /* @__PURE__ */ new Map();
|
|
532
|
+
for (const root of safeToDelete) {
|
|
533
|
+
const chain = collectTransitiveChain(root, graph, deadSet);
|
|
534
|
+
chains.set(root, chain);
|
|
535
|
+
}
|
|
536
|
+
const liveFiles = new Set(allFiles.filter((f) => !deadSet.has(f)));
|
|
523
537
|
const deadExports = findDeadExports(project, liveFiles);
|
|
524
|
-
const report = buildDeadCodeReport(
|
|
525
|
-
|
|
538
|
+
const report = buildDeadCodeReport(
|
|
539
|
+
safeToDelete,
|
|
540
|
+
transitivelyDead,
|
|
541
|
+
chains,
|
|
542
|
+
deadExports,
|
|
543
|
+
rootDir
|
|
544
|
+
);
|
|
545
|
+
return {
|
|
546
|
+
safeToDelete,
|
|
547
|
+
transitivelyDead,
|
|
548
|
+
deadFiles: [...deadSet],
|
|
549
|
+
liveFiles,
|
|
550
|
+
chains,
|
|
551
|
+
deadExports,
|
|
552
|
+
report
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
function collectTransitiveChain(root, graph, deadSet) {
|
|
556
|
+
const chain = [];
|
|
557
|
+
const visited = /* @__PURE__ */ new Set();
|
|
558
|
+
const stack = [...graph.get(root) ?? []];
|
|
559
|
+
let node;
|
|
560
|
+
while ((node = stack.pop()) !== void 0) {
|
|
561
|
+
if (visited.has(node) || node === root) continue;
|
|
562
|
+
visited.add(node);
|
|
563
|
+
if (deadSet.has(node)) {
|
|
564
|
+
chain.push(node);
|
|
565
|
+
for (const next of graph.get(node) ?? []) {
|
|
566
|
+
if (!visited.has(next)) stack.push(next);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return chain;
|
|
526
571
|
}
|
|
527
572
|
function findDeadExports(project, liveFiles) {
|
|
528
573
|
const importedNames = buildImportedNameMap(project, liveFiles);
|
|
@@ -555,51 +600,61 @@ function getFileSize(filePath) {
|
|
|
555
600
|
return 0;
|
|
556
601
|
}
|
|
557
602
|
}
|
|
558
|
-
function buildDeadCodeReport(
|
|
603
|
+
function buildDeadCodeReport(safeToDelete, transitivelyDead, chains, deadExports, rootDir) {
|
|
559
604
|
const rel = (p) => import_node_path5.default.relative(rootDir, p).replaceAll("\\", "/");
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
return sum + getFileSize(f) + chain.reduce((s, c) => s + getFileSize(c), 0);
|
|
563
|
-
}, 0);
|
|
605
|
+
const allDeadFiles = [...safeToDelete, ...transitivelyDead];
|
|
606
|
+
const totalBytes = allDeadFiles.reduce((sum, f) => sum + getFileSize(f), 0);
|
|
564
607
|
const totalKb = (totalBytes / 1024).toFixed(1);
|
|
565
608
|
const lines = [
|
|
566
609
|
"========================================",
|
|
567
610
|
" DEAD CODE REPORT",
|
|
568
|
-
`
|
|
569
|
-
`
|
|
570
|
-
`
|
|
611
|
+
` Safe to delete : ${safeToDelete.length}`,
|
|
612
|
+
` Transitively dead : ${transitivelyDead.length}`,
|
|
613
|
+
` Dead exports : ${deadExports.length}`,
|
|
614
|
+
` Recoverable : ~${totalKb} KB`,
|
|
571
615
|
"========================================",
|
|
572
616
|
""
|
|
573
617
|
];
|
|
574
|
-
if (
|
|
575
|
-
lines.push(
|
|
576
|
-
|
|
618
|
+
if (safeToDelete.length > 0) {
|
|
619
|
+
lines.push(
|
|
620
|
+
"\u2500\u2500 SAFE TO DELETE \u2500\u2500",
|
|
621
|
+
"(These files are not imported by any other file in the codebase)",
|
|
622
|
+
""
|
|
623
|
+
);
|
|
624
|
+
for (const filePath of safeToDelete) {
|
|
577
625
|
const chain = chains.get(filePath) ?? [];
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
lines.push(
|
|
584
|
-
`DEAD FILE \u2014 ${rel(filePath)}`,
|
|
585
|
-
`Reason: Not imported anywhere in the codebase`,
|
|
586
|
-
`Chain: ${chainStr}`,
|
|
587
|
-
`Size: ~${sizeKb} KB removable across ${allFiles.length} file${plural}`,
|
|
588
|
-
`Action: Safe to delete all ${allFiles.length} file${plural}`,
|
|
589
|
-
""
|
|
590
|
-
);
|
|
626
|
+
const sizeKb = (getFileSize(filePath) / 1024).toFixed(1);
|
|
627
|
+
lines.push(` ${rel(filePath)} (~${sizeKb} KB)`);
|
|
628
|
+
if (chain.length > 0) {
|
|
629
|
+
lines.push(` \u2514\u2500 also makes dead: ${chain.map(rel).join(", ")}`);
|
|
630
|
+
}
|
|
591
631
|
}
|
|
632
|
+
lines.push("");
|
|
633
|
+
}
|
|
634
|
+
if (transitivelyDead.length > 0) {
|
|
635
|
+
lines.push(
|
|
636
|
+
"\u2500\u2500 TRANSITIVELY DEAD \u2500\u2500",
|
|
637
|
+
"(These files are only imported by dead files \u2014 they become orphaned too)",
|
|
638
|
+
""
|
|
639
|
+
);
|
|
640
|
+
for (const filePath of transitivelyDead) {
|
|
641
|
+
const sizeKb = (getFileSize(filePath) / 1024).toFixed(1);
|
|
642
|
+
lines.push(` ${rel(filePath)} (~${sizeKb} KB)`);
|
|
643
|
+
}
|
|
644
|
+
lines.push("");
|
|
592
645
|
}
|
|
593
646
|
if (deadExports.length > 0) {
|
|
594
|
-
lines.push(
|
|
647
|
+
lines.push(
|
|
648
|
+
"\u2500\u2500 DEAD EXPORTS \u2500\u2500",
|
|
649
|
+
"(Exported but never imported by any other file)",
|
|
650
|
+
""
|
|
651
|
+
);
|
|
595
652
|
for (const entry of deadExports) {
|
|
596
653
|
lines.push(
|
|
597
|
-
`
|
|
598
|
-
`Reason: Exported but never imported`,
|
|
599
|
-
`Action: Remove the export (file itself is still live)`,
|
|
600
|
-
""
|
|
654
|
+
` ${rel(entry.filePath)} \u2192 ${entry.exportName} [line ${entry.line}]`
|
|
601
655
|
);
|
|
602
656
|
}
|
|
657
|
+
lines.push("");
|
|
603
658
|
}
|
|
604
659
|
return lines.join("\n");
|
|
605
660
|
}
|
|
@@ -616,7 +671,8 @@ async function runDeadCode(dir, opts) {
|
|
|
616
671
|
const entries = findEntryPoints(dir, packageJson);
|
|
617
672
|
const result = runDeadCodeModule(project, graph, entries, dir);
|
|
618
673
|
const dead = [
|
|
619
|
-
...result.
|
|
674
|
+
...result.safeToDelete.map((f) => ({ file: f, exportName: "(entire file \u2014 safe to delete)" })),
|
|
675
|
+
...result.transitivelyDead.map((f) => ({ file: f, exportName: "(entire file \u2014 transitively dead)" })),
|
|
620
676
|
...result.deadExports.map((e) => ({ file: e.filePath, exportName: e.exportName }))
|
|
621
677
|
];
|
|
622
678
|
spinner.succeed(import_chalk.default.green(`Dead code scan complete \u2014 ${dead.length} item(s) found`));
|
|
@@ -692,7 +748,11 @@ function writeDeadOutput(result, opts) {
|
|
|
692
748
|
if (!opts.output) return;
|
|
693
749
|
if (opts.json) {
|
|
694
750
|
writeJson(
|
|
695
|
-
{
|
|
751
|
+
{
|
|
752
|
+
safeToDelete: result.safeToDelete,
|
|
753
|
+
transitivelyDead: result.transitivelyDead,
|
|
754
|
+
deadExports: result.deadExports
|
|
755
|
+
},
|
|
696
756
|
opts.output
|
|
697
757
|
);
|
|
698
758
|
console.log(import_chalk.default.cyan(` JSON written to ${opts.output}`));
|
|
@@ -701,16 +761,21 @@ function writeDeadOutput(result, opts) {
|
|
|
701
761
|
writeMarkdown(
|
|
702
762
|
{
|
|
703
763
|
title: "Dead Code Report",
|
|
704
|
-
summary: `${result.
|
|
764
|
+
summary: `${result.safeToDelete.length} safe to delete, ${result.transitivelyDead.length} transitively dead, ${result.deadExports.length} dead export(s)`,
|
|
705
765
|
sections: [
|
|
706
766
|
{
|
|
707
|
-
title: "
|
|
767
|
+
title: "Safe to Delete",
|
|
708
768
|
headers: ["File", "Chain"],
|
|
709
|
-
rows: result.
|
|
769
|
+
rows: result.safeToDelete.map((f) => [
|
|
710
770
|
f,
|
|
711
771
|
(result.chains.get(f) ?? []).join(" \u2192 ") || "\u2014"
|
|
712
772
|
])
|
|
713
773
|
},
|
|
774
|
+
{
|
|
775
|
+
title: "Transitively Dead",
|
|
776
|
+
headers: ["File"],
|
|
777
|
+
rows: result.transitivelyDead.map((f) => [f])
|
|
778
|
+
},
|
|
714
779
|
{
|
|
715
780
|
title: "Dead Exports",
|
|
716
781
|
headers: ["File", "Export", "Line"],
|
|
@@ -736,17 +801,30 @@ var import_node_crypto = __toESM(require("crypto"), 1);
|
|
|
736
801
|
var import_ts_morph3 = require("ts-morph");
|
|
737
802
|
|
|
738
803
|
// src/modules/dupe-finder.ts
|
|
804
|
+
var MIN_BLOCK_CHARS = 120;
|
|
739
805
|
async function runDupeFinder(dir, opts) {
|
|
740
|
-
const minLines = parseInt(opts.minLines ?? "
|
|
806
|
+
const minLines = parseInt(opts.minLines ?? "15", 10);
|
|
741
807
|
const spinner = (0, import_ora3.default)(import_chalk2.default.cyan(`Scanning for duplicate blocks (\u2265${minLines} lines)\u2026`)).start();
|
|
742
808
|
try {
|
|
743
|
-
const files = glob(dir, ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], [
|
|
809
|
+
const files = glob(dir, ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], [
|
|
810
|
+
"node_modules",
|
|
811
|
+
"dist",
|
|
812
|
+
".next",
|
|
813
|
+
"coverage",
|
|
814
|
+
"**/*.test.ts",
|
|
815
|
+
"**/*.test.tsx",
|
|
816
|
+
"**/*.spec.ts",
|
|
817
|
+
"**/*.spec.tsx",
|
|
818
|
+
"**/*.d.ts"
|
|
819
|
+
]);
|
|
744
820
|
const blockMap = /* @__PURE__ */ new Map();
|
|
745
821
|
for (const filePath of files) {
|
|
746
822
|
const content = import_node_fs6.default.readFileSync(filePath, "utf-8");
|
|
747
823
|
const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
748
824
|
for (let i = 0; i <= lines.length - minLines; i++) {
|
|
749
825
|
const block = lines.slice(i, i + minLines).join("\n");
|
|
826
|
+
const meaningfulChars = block.replace(/[\s{}();,]/g, "").length;
|
|
827
|
+
if (meaningfulChars < MIN_BLOCK_CHARS) continue;
|
|
750
828
|
if (!blockMap.has(block)) blockMap.set(block, []);
|
|
751
829
|
blockMap.get(block).push({ file: filePath, startLine: i + 1 });
|
|
752
830
|
}
|
|
@@ -761,6 +839,8 @@ async function runDupeFinder(dir, opts) {
|
|
|
761
839
|
});
|
|
762
840
|
}
|
|
763
841
|
}
|
|
842
|
+
dupes.sort((a, b) => b.occurrences.length - a.occurrences.length);
|
|
843
|
+
if (dupes.length > 200) dupes.splice(200);
|
|
764
844
|
spinner.succeed(import_chalk2.default.green(`Duplicate scan complete \u2014 ${dupes.length} duplicate block(s) found`));
|
|
765
845
|
if (dupes.length === 0) {
|
|
766
846
|
console.log(import_chalk2.default.green(" No duplicate blocks detected."));
|
|
@@ -1274,9 +1354,13 @@ async function main(opts) {
|
|
|
1274
1354
|
if (modules.includes("dead-code")) {
|
|
1275
1355
|
const spinner = createSpinner(import_chalk7.default.cyan("Analysing dead code\u2026"));
|
|
1276
1356
|
const result = runDeadCodeModule(project, graph, entryPoints, rootDir);
|
|
1277
|
-
deadFileCount = result.
|
|
1278
|
-
deadFilePaths.push(...result.
|
|
1279
|
-
spinner.succeed(
|
|
1357
|
+
deadFileCount = result.safeToDelete.length + result.transitivelyDead.length + result.deadExports.length;
|
|
1358
|
+
deadFilePaths.push(...result.safeToDelete, ...result.transitivelyDead);
|
|
1359
|
+
spinner.succeed(
|
|
1360
|
+
import_chalk7.default.green(
|
|
1361
|
+
`Dead code analysis complete \u2014 ${result.safeToDelete.length} safe to delete, ${result.transitivelyDead.length} transitively dead, ${result.deadExports.length} dead export(s)`
|
|
1362
|
+
)
|
|
1363
|
+
);
|
|
1280
1364
|
if (result.report) {
|
|
1281
1365
|
deadReportFile = "dead-code.txt";
|
|
1282
1366
|
writeReport(reportsDir, deadReportFile, result.report);
|
|
@@ -1295,8 +1379,25 @@ async function main(opts) {
|
|
|
1295
1379
|
spinner.succeed(import_chalk7.default.green(`Circular import analysis complete \u2014 ${circularCount} cycle(s) found`));
|
|
1296
1380
|
if (circularCount > 0) {
|
|
1297
1381
|
circularReportFile = "circular.txt";
|
|
1298
|
-
const
|
|
1299
|
-
|
|
1382
|
+
const rel = (p) => import_node_path9.default.relative(rootDir, p).replaceAll("\\", "/");
|
|
1383
|
+
const cycleLines = [
|
|
1384
|
+
"========================================",
|
|
1385
|
+
" CIRCULAR DEPENDENCIES",
|
|
1386
|
+
` Cycles found: ${cycles.length}`,
|
|
1387
|
+
"========================================",
|
|
1388
|
+
""
|
|
1389
|
+
];
|
|
1390
|
+
for (let i = 0; i < cycles.length; i++) {
|
|
1391
|
+
const cycle = cycles[i];
|
|
1392
|
+
cycleLines.push(`Cycle ${i + 1} (${cycle.length} files):`);
|
|
1393
|
+
for (let j = 0; j < cycle.length; j++) {
|
|
1394
|
+
const arrow = j < cycle.length - 1 ? " \u2192 " : " \u2192 ";
|
|
1395
|
+
cycleLines.push(` ${rel(cycle[j])}`);
|
|
1396
|
+
}
|
|
1397
|
+
cycleLines.push(` \u21BB ${rel(cycle[0])} (back to start)`);
|
|
1398
|
+
cycleLines.push("");
|
|
1399
|
+
}
|
|
1400
|
+
writeReport(reportsDir, circularReportFile, cycleLines.join("\n"));
|
|
1300
1401
|
}
|
|
1301
1402
|
}
|
|
1302
1403
|
if (modules.includes("deps")) {
|
|
@@ -1329,10 +1430,10 @@ async function main(opts) {
|
|
|
1329
1430
|
});
|
|
1330
1431
|
const fmt = (n) => n > 0 ? import_chalk7.default.yellow(String(n)) : import_chalk7.default.green("0");
|
|
1331
1432
|
table.push(
|
|
1332
|
-
["Dead
|
|
1433
|
+
["Dead Code (files + exports)", fmt(deadFileCount), deadReportFile || "\u2014"],
|
|
1434
|
+
["Circular Dependencies", fmt(circularCount), circularReportFile || "\u2014"],
|
|
1333
1435
|
["Duplicate Clusters", fmt(dupeCount), dupesReportFile || "\u2014"],
|
|
1334
1436
|
["Unused Packages", fmt(unusedPkgCount), depsReportFile || "\u2014"],
|
|
1335
|
-
["Circular Deps", fmt(circularCount), circularReportFile || "\u2014"],
|
|
1336
1437
|
["Unused Assets", fmt(unusedAssetCount), assetsReportFile || "\u2014"]
|
|
1337
1438
|
);
|
|
1338
1439
|
console.log(table.toString());
|