pruny 1.19.0 → 1.20.0

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/index.js CHANGED
@@ -9369,6 +9369,8 @@ var IGNORED_EXPORT_NAMES = new Set([
9369
9369
  "OPTIONS",
9370
9370
  "default"
9371
9371
  ]);
9372
+ var NEST_LIFECYCLE_METHODS = new Set(["constructor", "onModuleInit", "onApplicationBootstrap", "onModuleDestroy", "beforeApplicationShutdown", "onApplicationShutdown"]);
9373
+ var classMethodRegex = /^\s*(?:async\s+)?([a-zA-Z0-9_$]+)\s*\([^)]*\)\s*(?::\s*[^\{]*)?\{/gm;
9372
9374
  async function processFilesInParallel(files, cwd, workerCount) {
9373
9375
  const __filename2 = fileURLToPath(import.meta.url);
9374
9376
  const __dirname2 = dirname2(__filename2);
@@ -9475,6 +9477,7 @@ async function scanUnusedExports(config) {
9475
9477
  }
9476
9478
  const content = readFileSync3(join3(cwd, file), "utf-8");
9477
9479
  totalContents.set(file, content);
9480
+ const isService = file.endsWith(".service.ts") || file.endsWith(".service.tsx");
9478
9481
  const lines = content.split(`
9479
9482
  `);
9480
9483
  for (let i = 0;i < lines.length; i++) {
@@ -9498,6 +9501,20 @@ async function scanUnusedExports(config) {
9498
9501
  }
9499
9502
  }
9500
9503
  }
9504
+ if (isService) {
9505
+ classMethodRegex.lastIndex = 0;
9506
+ while ((match2 = classMethodRegex.exec(line)) !== null) {
9507
+ const name = match2[1];
9508
+ if (name && !NEST_LIFECYCLE_METHODS.has(name) && !IGNORED_EXPORT_NAMES.has(name)) {
9509
+ const existing = exportMap.get(file)?.find((e) => e.name === name);
9510
+ if (!existing) {
9511
+ if (addExport(file, name, i + 1)) {
9512
+ allExportsCount++;
9513
+ }
9514
+ }
9515
+ }
9516
+ }
9517
+ }
9501
9518
  }
9502
9519
  } catch {}
9503
9520
  }
@@ -10203,13 +10220,11 @@ program2.action(async (options) => {
10203
10220
  if (result.unusedFiles) {
10204
10221
  result.unusedFiles.files = result.unusedFiles.files.filter((f) => matchesFilter(f.path));
10205
10222
  result.unusedFiles.total = result.unusedFiles.files.length;
10206
- result.unusedFiles.used = 0;
10207
10223
  result.unusedFiles.unused = result.unusedFiles.files.length;
10208
10224
  }
10209
10225
  if (result.unusedExports) {
10210
10226
  result.unusedExports.exports = result.unusedExports.exports.filter((e) => matchesFilter(e.file));
10211
10227
  result.unusedExports.total = result.unusedExports.exports.length;
10212
- result.unusedExports.used = 0;
10213
10228
  result.unusedExports.unused = result.unusedExports.exports.length;
10214
10229
  }
10215
10230
  }
@@ -10261,7 +10276,7 @@ program2.action(async (options) => {
10261
10276
  console.log("");
10262
10277
  }
10263
10278
  if (result.unusedExports && result.unusedExports.exports.length > 0) {
10264
- console.log(source_default.red.bold(`\uD83D\uDD17 Unused Named Exports:
10279
+ console.log(source_default.red.bold(`\uD83D\uDD17 Unused Named Exports/Methods:
10265
10280
  `));
10266
10281
  for (const exp of result.unusedExports.exports) {
10267
10282
  console.log(source_default.red(` ${exp.name}`));
@@ -10308,7 +10323,6 @@ program2.action(async (options) => {
10308
10323
  fixedSomething = true;
10309
10324
  } else {
10310
10325
  console.log(source_default.yellow(` Skipped File Deletion (internally used): ${filePath}`));
10311
- console.log(source_default.dim(` → This controller is imported in another file (e.g. app.module.ts).`));
10312
10326
  const allMethodsToPrune = [];
10313
10327
  for (const r of fileRoutes) {
10314
10328
  for (const m of r.unusedMethods) {
@@ -10386,38 +10400,17 @@ program2.action(async (options) => {
10386
10400
  console.log("");
10387
10401
  }
10388
10402
  if (result.unusedExports && result.unusedExports.exports.length > 0) {
10389
- console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing unused exports (removing "export" keyword)...
10390
- `));
10391
- const exportsByFile = new Map;
10392
- for (const exp of result.unusedExports.exports) {
10393
- if (!exportsByFile.has(exp.file)) {
10394
- exportsByFile.set(exp.file, []);
10395
- }
10396
- exportsByFile.get(exp.file).push(exp);
10397
- }
10398
- let fixedCount = 0;
10399
- for (const [file, exports] of exportsByFile.entries()) {
10400
- const sortedExports = exports.sort((a, b) => b.line - a.line);
10401
- for (const exp of sortedExports) {
10402
- const fullPath = join8(config.dir, exp.file);
10403
- if (!existsSync7(fullPath))
10404
- continue;
10405
- if (removeExportFromLine(config.dir, exp)) {
10406
- console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
10407
- fixedCount++;
10408
- fixedSomething = true;
10409
- const expIdx = result.unusedExports.exports.indexOf(exp);
10410
- if (expIdx !== -1) {
10411
- result.unusedExports.exports.splice(expIdx, 1);
10412
- result.unusedExports.unused--;
10413
- }
10414
- }
10415
- }
10416
- }
10417
- if (fixedCount > 0) {
10418
- console.log(source_default.green(`
10419
- ✅ Removed "export" from ${fixedCount} item(s).
10403
+ fixedSomething = await fixUnusedExports(result, config) || fixedSomething;
10404
+ }
10405
+ if (fixedSomething) {
10406
+ console.log(source_default.cyan.bold(`
10407
+ \uD83D\uDD04 Checking for cascading dead code (newly unused implementation)...`));
10408
+ const secondPass = await scanUnusedExports(config);
10409
+ if (secondPass.unused > 0) {
10410
+ console.log(source_default.yellow(` Found ${secondPass.unused} newly unused items/methods after pruning.
10420
10411
  `));
10412
+ result.unusedExports = secondPass;
10413
+ await fixUnusedExports(result, config);
10421
10414
  }
10422
10415
  }
10423
10416
  if (fixedSomething) {
@@ -10451,41 +10444,21 @@ program2.action(async (options) => {
10451
10444
  for (const key of sortedKeys) {
10452
10445
  const group = groupedRoutes.get(key);
10453
10446
  const typeLabel = group.type === "nextjs" ? "Next.js" : "NestJS";
10454
- const label = `${typeLabel} (${group.app})`;
10455
10447
  summary.push({
10456
- Category: label,
10448
+ Category: `${typeLabel} (${group.app})`,
10457
10449
  Total: group.routes.length,
10458
10450
  Used: group.routes.filter((r) => r.used).length,
10459
10451
  Unused: group.routes.filter((r) => !r.used).length
10460
10452
  });
10461
10453
  }
10462
- if (summary.length === 0) {
10454
+ if (summary.length === 0)
10463
10455
  summary.push({ Category: "API Routes", Total: result.total, Used: result.used, Unused: result.unused });
10464
- }
10465
- if (result.publicAssets) {
10466
- summary.push({
10467
- Category: "Public Assets",
10468
- Total: result.publicAssets.total,
10469
- Used: result.publicAssets.used,
10470
- Unused: result.publicAssets.unused
10471
- });
10472
- }
10473
- if (result.unusedFiles) {
10474
- summary.push({
10475
- Category: "Source Files",
10476
- Total: result.unusedFiles.total,
10477
- Used: result.unusedFiles.used,
10478
- Unused: result.unusedFiles.unused
10479
- });
10480
- }
10481
- if (result.unusedExports) {
10482
- summary.push({
10483
- Category: "Exported Items",
10484
- Total: result.unusedExports.total,
10485
- Used: result.unusedExports.used,
10486
- Unused: result.unusedExports.unused
10487
- });
10488
- }
10456
+ if (result.publicAssets)
10457
+ summary.push({ Category: "Public Assets", Total: result.publicAssets.total, Used: result.publicAssets.used, Unused: result.publicAssets.unused });
10458
+ if (result.unusedFiles)
10459
+ summary.push({ Category: "Source Files", Total: result.unusedFiles.total, Used: result.unusedFiles.used, Unused: result.unusedFiles.unused });
10460
+ if (result.unusedExports)
10461
+ summary.push({ Category: "Exported Items", Total: result.unusedExports.total, Used: result.unusedExports.used, Unused: result.unusedExports.unused });
10489
10462
  console.table(summary);
10490
10463
  } catch (_err) {
10491
10464
  console.error(source_default.red("Error scanning:"), _err);
@@ -10495,4 +10468,41 @@ program2.action(async (options) => {
10495
10468
  console.log(source_default.dim(`
10496
10469
  ⏱️ Completed in ${elapsed}s`));
10497
10470
  });
10471
+ async function fixUnusedExports(result, config) {
10472
+ if (!result.unusedExports || result.unusedExports.exports.length === 0)
10473
+ return false;
10474
+ console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing unused exports/methods...
10475
+ `));
10476
+ const exportsByFile = new Map;
10477
+ for (const exp of result.unusedExports.exports) {
10478
+ if (!exportsByFile.has(exp.file))
10479
+ exportsByFile.set(exp.file, []);
10480
+ exportsByFile.get(exp.file).push(exp);
10481
+ }
10482
+ let fixedCount = 0;
10483
+ let fixedSomething = false;
10484
+ for (const [file, exports] of exportsByFile.entries()) {
10485
+ const sortedExports = exports.sort((a, b) => b.line - a.line);
10486
+ for (const exp of sortedExports) {
10487
+ const fullPath = join8(config.dir, exp.file);
10488
+ if (!existsSync7(fullPath))
10489
+ continue;
10490
+ if (removeExportFromLine(config.dir, exp)) {
10491
+ console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
10492
+ fixedCount++;
10493
+ fixedSomething = true;
10494
+ const expIdx = result.unusedExports.exports.indexOf(exp);
10495
+ if (expIdx !== -1) {
10496
+ result.unusedExports.exports.splice(expIdx, 1);
10497
+ result.unusedExports.unused--;
10498
+ }
10499
+ }
10500
+ }
10501
+ }
10502
+ if (fixedCount > 0)
10503
+ console.log(source_default.green(`
10504
+ ✅ Cleaned up ${fixedCount} unused item(s).
10505
+ `));
10506
+ return fixedSomething;
10507
+ }
10498
10508
  program2.parse();
@@ -22,11 +22,11 @@ var IGNORED_EXPORT_NAMES = new Set([
22
22
  "POST",
23
23
  "PUT",
24
24
  "PATCH",
25
- "DELETE",
26
- "HEAD",
27
25
  "OPTIONS",
28
26
  "default"
29
27
  ]);
28
+ var NEST_LIFECYCLE_METHODS = new Set(["constructor", "onModuleInit", "onApplicationBootstrap", "onModuleDestroy", "beforeApplicationShutdown", "onApplicationShutdown"]);
29
+ var classMethodRegex = /^\s*(?:async\s+)?([a-zA-Z0-9_$]+)\s*\([^)]*\)\s*(?::\s*[^\{]*)?\{/gm;
30
30
  var inlineExportRegex = /^export\s+(?:async\s+)?(?:const|let|var|function|type|interface|enum|class)\s+([a-zA-Z0-9_$]+)/gm;
31
31
  var blockExportRegex = /^export\s*\{([^}]+)\}/gm;
32
32
  if (parentPort && workerData) {
@@ -40,6 +40,7 @@ if (parentPort && workerData) {
40
40
  contents.set(file, content);
41
41
  const lines = content.split(`
42
42
  `);
43
+ const isService = file.endsWith(".service.ts") || file.endsWith(".service.tsx");
43
44
  for (let i = 0;i < lines.length; i++) {
44
45
  const line = lines[i];
45
46
  inlineExportRegex.lastIndex = 0;
@@ -66,6 +67,20 @@ if (parentPort && workerData) {
66
67
  }
67
68
  }
68
69
  }
70
+ if (isService) {
71
+ classMethodRegex.lastIndex = 0;
72
+ while ((match = classMethodRegex.exec(line)) !== null) {
73
+ const name = match[1];
74
+ if (name && !NEST_LIFECYCLE_METHODS.has(name) && !IGNORED_EXPORT_NAMES.has(name)) {
75
+ const existing = exportMap.get(file)?.find((e) => e.name === name);
76
+ if (!existing) {
77
+ if (!exportMap.has(file))
78
+ exportMap.set(file, []);
79
+ exportMap.get(file).push({ name, line: i + 1, file });
80
+ }
81
+ }
82
+ }
83
+ }
69
84
  }
70
85
  processedCount++;
71
86
  if (processedCount % 10 === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruny",
3
- "version": "1.19.0",
3
+ "version": "1.20.0",
4
4
  "description": "Find and remove unused Next.js API routes & Nest.js Controllers",
5
5
  "type": "module",
6
6
  "files": [