pruny 1.14.0 → 1.15.1

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.
Files changed (2) hide show
  1. package/dist/index.js +108 -8
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9621,13 +9621,22 @@ function extractRoutePath(filePath) {
9621
9621
  }
9622
9622
  function extractExportedMethods(content) {
9623
9623
  const methods = [];
9624
+ const methodLines = {};
9625
+ const lines = content.split(`
9626
+ `);
9624
9627
  let match2;
9628
+ EXPORTED_METHOD_PATTERN.lastIndex = 0;
9625
9629
  while ((match2 = EXPORTED_METHOD_PATTERN.exec(content)) !== null) {
9626
9630
  if (match2[1]) {
9627
- methods.push(match2[1]);
9631
+ const methodName = match2[1];
9632
+ methods.push(methodName);
9633
+ const pos = match2.index;
9634
+ const lineNum = content.substring(0, pos).split(`
9635
+ `).length;
9636
+ methodLines[methodName] = lineNum;
9628
9637
  }
9629
9638
  }
9630
- return methods;
9639
+ return { methods, methodLines };
9631
9640
  }
9632
9641
  function extractNestRoutes(filePath, content, globalPrefix = "api") {
9633
9642
  const controllerMatch = content.match(NEST_CONTROLLER_PATTERN);
@@ -9640,12 +9649,16 @@ function extractNestRoutes(filePath, content, globalPrefix = "api") {
9640
9649
  while ((methodMatch = NEST_METHOD_PATTERN.exec(content)) !== null) {
9641
9650
  const methodType = methodMatch[1].toUpperCase();
9642
9651
  const methodPath = methodMatch[2] || "";
9652
+ const pos = methodMatch.index;
9653
+ const lineNum = content.substring(0, pos).split(`
9654
+ `).length;
9643
9655
  const fullPath = `/${globalPrefix}/${controllerPath}/${methodPath}`.replace(/\/+/g, "/").replace(/\/$/, "");
9644
9656
  const existing = routes.find((r) => r.path === fullPath);
9645
9657
  if (existing) {
9646
9658
  if (!existing.methods.includes(methodType)) {
9647
9659
  existing.methods.push(methodType);
9648
9660
  existing.unusedMethods.push(methodType);
9661
+ existing.methodLines[methodType] = lineNum;
9649
9662
  }
9650
9663
  } else {
9651
9664
  routes.push({
@@ -9655,7 +9668,8 @@ function extractNestRoutes(filePath, content, globalPrefix = "api") {
9655
9668
  used: false,
9656
9669
  references: [],
9657
9670
  methods: [methodType],
9658
- unusedMethods: [methodType]
9671
+ unusedMethods: [methodType],
9672
+ methodLines: { [methodType]: lineNum }
9659
9673
  });
9660
9674
  }
9661
9675
  }
@@ -9733,7 +9747,7 @@ async function scan(config) {
9733
9747
  });
9734
9748
  const nextRoutes = nextFiles.map((file) => {
9735
9749
  const content = readFileSync4(join4(cwd, file), "utf-8");
9736
- const methods = extractExportedMethods(content);
9750
+ const { methods, methodLines } = extractExportedMethods(content);
9737
9751
  return {
9738
9752
  type: "nextjs",
9739
9753
  path: extractRoutePath(file),
@@ -9741,7 +9755,8 @@ async function scan(config) {
9741
9755
  used: false,
9742
9756
  references: [],
9743
9757
  methods,
9744
- unusedMethods: [...methods]
9758
+ unusedMethods: [...methods],
9759
+ methodLines
9745
9760
  };
9746
9761
  });
9747
9762
  const nestPatterns = ["**/*.controller.ts"];
@@ -9953,7 +9968,7 @@ function findConfigFile(dir) {
9953
9968
  }
9954
9969
 
9955
9970
  // src/fixer.ts
9956
- import { readFileSync as readFileSync6, writeFileSync, unlinkSync } from "node:fs";
9971
+ import { readFileSync as readFileSync6, writeFileSync, unlinkSync, existsSync as existsSync5 } from "node:fs";
9957
9972
  import { join as join6 } from "node:path";
9958
9973
  function removeExportFromLine(rootDir, exp) {
9959
9974
  const fullPath = join6(rootDir, exp.file);
@@ -10039,6 +10054,32 @@ function isFileEmpty(content) {
10039
10054
  }
10040
10055
  return true;
10041
10056
  }
10057
+ function removeMethodFromRoute(rootDir, filePath, methodName, lineNum) {
10058
+ const fullPath = join6(rootDir, filePath);
10059
+ if (!existsSync5(fullPath))
10060
+ return false;
10061
+ try {
10062
+ const content = readFileSync6(fullPath, "utf-8");
10063
+ const lines = content.split(`
10064
+ `);
10065
+ const lineIndex = lineNum - 1;
10066
+ const deletedLines = deleteDeclaration(lines, lineIndex);
10067
+ if (deletedLines > 0) {
10068
+ const newContent = lines.join(`
10069
+ `);
10070
+ if (isFileEmpty(newContent)) {
10071
+ unlinkSync(fullPath);
10072
+ } else {
10073
+ writeFileSync(fullPath, newContent, "utf-8");
10074
+ }
10075
+ return true;
10076
+ }
10077
+ return false;
10078
+ } catch (err) {
10079
+ console.error(`Error removing method ${methodName} in ${filePath}:`, err);
10080
+ return false;
10081
+ }
10082
+ }
10042
10083
 
10043
10084
  // src/init.ts
10044
10085
  import { writeFileSync as writeFileSync2, existsSync as existsSync6 } from "node:fs";
@@ -10189,6 +10230,7 @@ program2.action(async (options) => {
10189
10230
  `));
10190
10231
  }
10191
10232
  if (options.fix) {
10233
+ let fixedSomething = false;
10192
10234
  if (unusedRoutes.length > 0) {
10193
10235
  console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused routes...
10194
10236
  `));
@@ -10197,10 +10239,57 @@ program2.action(async (options) => {
10197
10239
  try {
10198
10240
  rmSync(routeDir, { recursive: true, force: true });
10199
10241
  console.log(source_default.red(` Deleted: ${route.filePath}`));
10242
+ fixedSomething = true;
10243
+ const idx = result.routes.indexOf(route);
10244
+ if (idx !== -1)
10245
+ result.routes.splice(idx, 1);
10200
10246
  } catch (_err) {
10201
10247
  console.log(source_default.yellow(` Failed to delete: ${route.filePath}`));
10202
10248
  }
10203
10249
  }
10250
+ console.log("");
10251
+ }
10252
+ const partiallyRoutes = result.routes.filter((r) => r.used && r.unusedMethods.length > 0);
10253
+ if (partiallyRoutes.length > 0) {
10254
+ console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing partially unused routes...
10255
+ `));
10256
+ for (const route of partiallyRoutes) {
10257
+ const sortedMethods = [...route.unusedMethods].filter((m) => route.methodLines[m] !== undefined).sort((a, b) => route.methodLines[b] - route.methodLines[a]);
10258
+ let fixedCount = 0;
10259
+ for (const method of sortedMethods) {
10260
+ const lineNum = route.methodLines[method];
10261
+ if (removeMethodFromRoute(config.dir, route.filePath, method, lineNum)) {
10262
+ console.log(source_default.green(` Fixed: Removed ${method} from ${route.path}`));
10263
+ fixedCount++;
10264
+ fixedSomething = true;
10265
+ }
10266
+ }
10267
+ if (fixedCount === route.methods.length) {
10268
+ const idx = result.routes.indexOf(route);
10269
+ if (idx !== -1)
10270
+ result.routes.splice(idx, 1);
10271
+ } else {
10272
+ route.unusedMethods = route.unusedMethods.filter((m) => !sortedMethods.includes(m));
10273
+ }
10274
+ }
10275
+ console.log("");
10276
+ }
10277
+ if (result.unusedFiles && result.unusedFiles.files.length > 0) {
10278
+ console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused source files...
10279
+ `));
10280
+ for (const file of result.unusedFiles.files) {
10281
+ try {
10282
+ const fullPath = join8(config.dir, file.path);
10283
+ rmSync(fullPath, { force: true });
10284
+ console.log(source_default.red(` Deleted: ${file.path}`));
10285
+ fixedSomething = true;
10286
+ } catch (_err) {
10287
+ console.log(source_default.yellow(` Failed to delete: ${file.path}`));
10288
+ }
10289
+ }
10290
+ result.unusedFiles.files = [];
10291
+ result.unusedFiles.unused = 0;
10292
+ console.log("");
10204
10293
  }
10205
10294
  if (result.unusedExports && result.unusedExports.exports.length > 0) {
10206
10295
  console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing unused exports (removing "export" keyword)...
@@ -10219,6 +10308,12 @@ program2.action(async (options) => {
10219
10308
  if (removeExportFromLine(config.dir, exp)) {
10220
10309
  console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
10221
10310
  fixedCount++;
10311
+ fixedSomething = true;
10312
+ const expIdx = result.unusedExports.exports.indexOf(exp);
10313
+ if (expIdx !== -1) {
10314
+ result.unusedExports.exports.splice(expIdx, 1);
10315
+ result.unusedExports.unused--;
10316
+ }
10222
10317
  }
10223
10318
  }
10224
10319
  }
@@ -10228,8 +10323,13 @@ program2.action(async (options) => {
10228
10323
  `));
10229
10324
  }
10230
10325
  }
10231
- } else if (unusedRoutes.length > 0 || result.unusedExports && result.unusedExports.exports.length > 0) {
10232
- console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to automatically clean up unused routes and exports.
10326
+ if (fixedSomething) {
10327
+ result.unused = result.routes.filter((r) => !r.used).length;
10328
+ result.used = result.routes.filter((r) => r.used).length;
10329
+ result.total = result.routes.length;
10330
+ }
10331
+ } else if (unusedRoutes.length > 0 || result.unusedExports && result.unusedExports.exports.length > 0 || result.unusedFiles && result.unusedFiles.files.length > 0) {
10332
+ console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to automatically clean up unused routes, files, and exports.
10233
10333
  `));
10234
10334
  }
10235
10335
  console.log(source_default.bold(`\uD83D\uDCCA Summary Report
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruny",
3
- "version": "1.14.0",
3
+ "version": "1.15.1",
4
4
  "description": "Find and remove unused Next.js API routes & Nest.js Controllers",
5
5
  "type": "module",
6
6
  "files": [