prunify 0.1.6 → 0.1.7
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/README.md +17 -10
- package/dist/cli.cjs +134 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +134 -14
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -215,6 +215,83 @@ function findEntryPoints(rootDir, packageJson) {
|
|
|
215
215
|
];
|
|
216
216
|
return [...new Set(entries)];
|
|
217
217
|
}
|
|
218
|
+
var APP_SPECIAL_ENTRY_BASENAMES_LOWER = /* @__PURE__ */ new Set([
|
|
219
|
+
"page.tsx",
|
|
220
|
+
"page.ts",
|
|
221
|
+
"page.jsx",
|
|
222
|
+
"page.js",
|
|
223
|
+
"layout.tsx",
|
|
224
|
+
"layout.ts",
|
|
225
|
+
"layout.jsx",
|
|
226
|
+
"layout.js",
|
|
227
|
+
"template.tsx",
|
|
228
|
+
"template.ts",
|
|
229
|
+
"template.jsx",
|
|
230
|
+
"template.js",
|
|
231
|
+
"loading.tsx",
|
|
232
|
+
"loading.ts",
|
|
233
|
+
"loading.jsx",
|
|
234
|
+
"loading.js",
|
|
235
|
+
"error.tsx",
|
|
236
|
+
"error.ts",
|
|
237
|
+
"error.jsx",
|
|
238
|
+
"error.js",
|
|
239
|
+
"global-error.tsx",
|
|
240
|
+
"global-error.ts",
|
|
241
|
+
"global-error.jsx",
|
|
242
|
+
"global-error.js",
|
|
243
|
+
"not-found.tsx",
|
|
244
|
+
"not-found.ts",
|
|
245
|
+
"not-found.jsx",
|
|
246
|
+
"not-found.js",
|
|
247
|
+
"forbidden.tsx",
|
|
248
|
+
"forbidden.ts",
|
|
249
|
+
"forbidden.jsx",
|
|
250
|
+
"forbidden.js",
|
|
251
|
+
"unauthorized.tsx",
|
|
252
|
+
"unauthorized.ts",
|
|
253
|
+
"unauthorized.jsx",
|
|
254
|
+
"unauthorized.js",
|
|
255
|
+
"default.tsx",
|
|
256
|
+
"default.ts",
|
|
257
|
+
"default.jsx",
|
|
258
|
+
"default.js",
|
|
259
|
+
"route.ts",
|
|
260
|
+
"route.js",
|
|
261
|
+
"opengraph-image.tsx",
|
|
262
|
+
"opengraph-image.ts",
|
|
263
|
+
"opengraph-image.js",
|
|
264
|
+
"twitter-image.tsx",
|
|
265
|
+
"twitter-image.ts",
|
|
266
|
+
"twitter-image.js",
|
|
267
|
+
"icon.tsx",
|
|
268
|
+
"icon.ts",
|
|
269
|
+
"icon.jsx",
|
|
270
|
+
"icon.js",
|
|
271
|
+
"apple-icon.tsx",
|
|
272
|
+
"apple-icon.ts",
|
|
273
|
+
"apple-icon.js",
|
|
274
|
+
"sitemap.ts",
|
|
275
|
+
"sitemap.js",
|
|
276
|
+
"robots.ts",
|
|
277
|
+
"robots.js",
|
|
278
|
+
"manifest.ts",
|
|
279
|
+
"manifest.js"
|
|
280
|
+
]);
|
|
281
|
+
function isConventionRoutedSourceFile(filePath, rootDir) {
|
|
282
|
+
const rel = path3.relative(rootDir, path3.normalize(filePath)).replaceAll("\\", "/");
|
|
283
|
+
if (rel.startsWith("..")) return false;
|
|
284
|
+
if (rel === "pages" || rel.startsWith("pages/")) return true;
|
|
285
|
+
if (rel.startsWith("src/pages/")) return true;
|
|
286
|
+
if (rel.startsWith("src/routes/") || rel.startsWith("src/views/") || rel.startsWith("src/screens/")) {
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
if (rel.startsWith("app/") || rel.startsWith("src/app/")) {
|
|
290
|
+
const base = path3.basename(rel).toLowerCase();
|
|
291
|
+
if (APP_SPECIAL_ENTRY_BASENAMES_LOWER.has(base)) return true;
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
218
295
|
function detectCycles(graph) {
|
|
219
296
|
const cycles = [];
|
|
220
297
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
@@ -511,7 +588,7 @@ function runDeadCodeModule(project, graph, entryPoints, rootDir) {
|
|
|
511
588
|
chains.set(root, chain);
|
|
512
589
|
}
|
|
513
590
|
const liveFiles = new Set(allFiles.filter((f) => !deadSet.has(f)));
|
|
514
|
-
const deadExports = findDeadExports(project, liveFiles);
|
|
591
|
+
const deadExports = findDeadExports(project, liveFiles, rootDir);
|
|
515
592
|
const report = buildDeadCodeReport(
|
|
516
593
|
safeToDelete,
|
|
517
594
|
transitivelyDead,
|
|
@@ -546,10 +623,12 @@ function collectTransitiveChain(root, graph, deadSet) {
|
|
|
546
623
|
}
|
|
547
624
|
return chain;
|
|
548
625
|
}
|
|
549
|
-
function findDeadExports(project, liveFiles) {
|
|
626
|
+
function findDeadExports(project, liveFiles, rootDir) {
|
|
550
627
|
const importedNames = buildImportedNameMap(project, liveFiles);
|
|
551
628
|
const dead = [];
|
|
552
629
|
for (const filePath of liveFiles) {
|
|
630
|
+
if (isFrameworkFile(filePath, rootDir)) continue;
|
|
631
|
+
if (isConventionRoutedSourceFile(filePath, rootDir)) continue;
|
|
553
632
|
collectFileDeadExports(filePath, project, importedNames, dead);
|
|
554
633
|
}
|
|
555
634
|
return dead;
|
|
@@ -623,7 +702,7 @@ function buildDeadCodeReport(safeToDelete, transitivelyDead, chains, deadExports
|
|
|
623
702
|
if (deadExports.length > 0) {
|
|
624
703
|
lines.push(
|
|
625
704
|
"\u2500\u2500 DEAD EXPORTS \u2500\u2500",
|
|
626
|
-
"(Exported but never imported by any other file)",
|
|
705
|
+
"(Exported but never imported by any other file; pages/routes omitted)",
|
|
627
706
|
""
|
|
628
707
|
);
|
|
629
708
|
for (const entry of deadExports) {
|
|
@@ -1331,6 +1410,19 @@ function countDeadExportsInReport(content) {
|
|
|
1331
1410
|
}
|
|
1332
1411
|
return n;
|
|
1333
1412
|
}
|
|
1413
|
+
function parseDeadCodeReportSummary(content) {
|
|
1414
|
+
const safe = content.match(/Safe to delete\s*:\s*(\d+)/);
|
|
1415
|
+
const trans = content.match(/Transitively dead\s*:\s*(\d+)/);
|
|
1416
|
+
const exp = content.match(/Dead exports\s*:\s*(\d+)/);
|
|
1417
|
+
const rec = content.match(/Recoverable\s*:\s*(~[\d.]+\s*KB)/i);
|
|
1418
|
+
if (!safe || !trans || !exp) return null;
|
|
1419
|
+
return {
|
|
1420
|
+
safeToDelete: Number(safe[1]),
|
|
1421
|
+
transitivelyDead: Number(trans[1]),
|
|
1422
|
+
deadExports: Number(exp[1]),
|
|
1423
|
+
recoverableDisplay: rec ? rec[1].trim() : "~0.0 KB"
|
|
1424
|
+
};
|
|
1425
|
+
}
|
|
1334
1426
|
function parseAssetsReportFile(reportPath, rootDir) {
|
|
1335
1427
|
if (!fs9.existsSync(reportPath)) return [];
|
|
1336
1428
|
const content = fs9.readFileSync(reportPath, "utf-8");
|
|
@@ -1458,23 +1550,35 @@ async function main(opts) {
|
|
|
1458
1550
|
entryPoints = opts.entry ? [path10.resolve(opts.entry)] : findEntryPoints(rootDir, packageJson);
|
|
1459
1551
|
}
|
|
1460
1552
|
console.log();
|
|
1461
|
-
let
|
|
1553
|
+
let deadSafeCount = 0;
|
|
1554
|
+
let deadTransitiveCount = 0;
|
|
1555
|
+
let deadExportsCount = 0;
|
|
1556
|
+
let deadRecoverDisplay = "~0.0 KB";
|
|
1462
1557
|
let dupeCount = 0;
|
|
1463
1558
|
let unusedPkgCount = 0;
|
|
1464
1559
|
let circularCount = 0;
|
|
1465
1560
|
let unusedAssetCount = 0;
|
|
1466
|
-
let deadReportFile = "";
|
|
1467
1561
|
let dupesReportFile = "";
|
|
1468
1562
|
let depsReportFile = "";
|
|
1469
1563
|
let circularReportFile = "";
|
|
1470
1564
|
let assetsReportFile = "";
|
|
1471
1565
|
if (loadDeadFromCache) {
|
|
1472
|
-
deadReportFile = "dead-code.txt";
|
|
1473
1566
|
try {
|
|
1474
1567
|
const raw = fs10.readFileSync(deadReportPath, "utf-8");
|
|
1475
|
-
|
|
1568
|
+
const parsed = parseDeadCodeReportSummary(raw);
|
|
1569
|
+
if (parsed) {
|
|
1570
|
+
deadSafeCount = parsed.safeToDelete;
|
|
1571
|
+
deadTransitiveCount = parsed.transitivelyDead;
|
|
1572
|
+
deadExportsCount = parsed.deadExports;
|
|
1573
|
+
deadRecoverDisplay = parsed.recoverableDisplay;
|
|
1574
|
+
} else {
|
|
1575
|
+
deadExportsCount = countDeadExportsInReport(raw);
|
|
1576
|
+
deadSafeCount = deadFilePaths.length;
|
|
1577
|
+
deadTransitiveCount = 0;
|
|
1578
|
+
deadRecoverDisplay = "\u2014";
|
|
1579
|
+
}
|
|
1476
1580
|
} catch {
|
|
1477
|
-
|
|
1581
|
+
deadSafeCount = deadFilePaths.length;
|
|
1478
1582
|
}
|
|
1479
1583
|
}
|
|
1480
1584
|
if (loadAssetsFromCache) {
|
|
@@ -1484,19 +1588,22 @@ async function main(opts) {
|
|
|
1484
1588
|
if (runDeadCode2) {
|
|
1485
1589
|
const spinner = createSpinner(chalk7.cyan("Analysing dead code\u2026"));
|
|
1486
1590
|
const result = runDeadCodeModule(project, graph, entryPoints, rootDir);
|
|
1487
|
-
deadFileCount = result.safeToDelete.length + result.transitivelyDead.length + result.deadExports.length;
|
|
1488
1591
|
deadFilePaths = [...result.safeToDelete, ...result.transitivelyDead];
|
|
1592
|
+
deadSafeCount = result.safeToDelete.length;
|
|
1593
|
+
deadTransitiveCount = result.transitivelyDead.length;
|
|
1594
|
+
deadExportsCount = result.deadExports.length;
|
|
1595
|
+
const recoverBytes = deadFilePaths.reduce((sum, f) => sum + getFileSize(f), 0);
|
|
1596
|
+
deadRecoverDisplay = `~${(recoverBytes / 1024).toFixed(1)} KB`;
|
|
1489
1597
|
spinner.succeed(
|
|
1490
1598
|
chalk7.green(
|
|
1491
1599
|
`Dead code analysis complete \u2014 ${result.safeToDelete.length} safe to delete, ${result.transitivelyDead.length} transitively dead, ${result.deadExports.length} dead export(s)`
|
|
1492
1600
|
)
|
|
1493
1601
|
);
|
|
1494
1602
|
if (result.report) {
|
|
1495
|
-
deadReportFile = "dead-code.txt";
|
|
1496
1603
|
const banner = `prunify ${PKG_VERSION} \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1497
1604
|
|
|
1498
1605
|
`;
|
|
1499
|
-
writeReport(reportsDir,
|
|
1606
|
+
writeReport(reportsDir, "dead-code.txt", banner + result.report);
|
|
1500
1607
|
}
|
|
1501
1608
|
}
|
|
1502
1609
|
if (modules.includes("dupes")) {
|
|
@@ -1561,13 +1668,25 @@ async function main(opts) {
|
|
|
1561
1668
|
console.log();
|
|
1562
1669
|
console.log(chalk7.bold("Summary"));
|
|
1563
1670
|
console.log();
|
|
1671
|
+
const fmt = (n) => n > 0 ? chalk7.yellow(String(n)) : chalk7.green("0");
|
|
1672
|
+
const showDeadSummary = modules.includes("dead-code") || Boolean(opts.delete) || loadDeadFromCache || runDeadCode2;
|
|
1673
|
+
if (showDeadSummary) {
|
|
1674
|
+
const recoverKb = parseFloat(deadRecoverDisplay.replace(/[^\d.]/g, "")) || 0;
|
|
1675
|
+
const recoverCh = deadRecoverDisplay === "\u2014" ? chalk7.dim("\u2014") : recoverKb > 0 ? chalk7.yellow(deadRecoverDisplay) : chalk7.green(deadRecoverDisplay);
|
|
1676
|
+
console.log(chalk7.dim("========================================"));
|
|
1677
|
+
console.log(chalk7.dim(" DEAD CODE REPORT"));
|
|
1678
|
+
console.log(chalk7.dim(" Safe to delete : ") + fmt(deadSafeCount));
|
|
1679
|
+
console.log(chalk7.dim(" Transitively dead : ") + fmt(deadTransitiveCount));
|
|
1680
|
+
console.log(chalk7.dim(" Dead exports : ") + fmt(deadExportsCount));
|
|
1681
|
+
console.log(chalk7.dim(" Recoverable : ") + recoverCh);
|
|
1682
|
+
console.log(chalk7.dim("========================================"));
|
|
1683
|
+
console.log();
|
|
1684
|
+
}
|
|
1564
1685
|
const table = new Table6({
|
|
1565
1686
|
head: [chalk7.bold("Check"), chalk7.bold("Found"), chalk7.bold("Output File")],
|
|
1566
1687
|
style: { head: [], border: [] }
|
|
1567
1688
|
});
|
|
1568
|
-
const fmt = (n) => n > 0 ? chalk7.yellow(String(n)) : chalk7.green("0");
|
|
1569
1689
|
table.push(
|
|
1570
|
-
["Dead Code (files + exports)", fmt(deadFileCount), deadReportFile || "\u2014"],
|
|
1571
1690
|
["Circular Dependencies", fmt(circularCount), circularReportFile || "\u2014"],
|
|
1572
1691
|
["Duplicate Clusters", fmt(dupeCount), dupesReportFile || "\u2014"],
|
|
1573
1692
|
["Unused Packages", fmt(unusedPkgCount), depsReportFile || "\u2014"],
|
|
@@ -1644,7 +1763,8 @@ async function main(opts) {
|
|
|
1644
1763
|
console.log(chalk7.dim(" --delete-assets: no unused assets in report (nothing to delete)."));
|
|
1645
1764
|
}
|
|
1646
1765
|
if (opts.ci) {
|
|
1647
|
-
const
|
|
1766
|
+
const deadIssueCount = deadSafeCount + deadTransitiveCount + deadExportsCount;
|
|
1767
|
+
const hasIssues = deadIssueCount > 0 || dupeCount > 0 || unusedPkgCount > 0 || circularCount > 0 || unusedAssetCount > 0;
|
|
1648
1768
|
if (hasIssues) process.exit(1);
|
|
1649
1769
|
}
|
|
1650
1770
|
}
|