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/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 deadFileCount = 0;
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
- deadFileCount = deadFilePaths.length + countDeadExportsInReport(raw);
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
- deadFileCount = deadFilePaths.length;
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, deadReportFile, banner + result.report);
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 hasIssues = deadFileCount > 0 || dupeCount > 0 || unusedPkgCount > 0 || circularCount > 0 || unusedAssetCount > 0;
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
  }