pruny 1.17.1 → 1.19.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.
Files changed (2) hide show
  1. package/dist/index.js +86 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7649,13 +7649,20 @@ var API_METHOD_PATTERNS = [
7649
7649
  { regex: /axios\.put\s*\(\s*['"`](\/[^'"`\s)]+)['"`]/g, method: "PUT" },
7650
7650
  { regex: /axios\.delete\s*\(\s*['"`](\/[^'"`\s)]+)['"`]/g, method: "DELETE" },
7651
7651
  { regex: /axios\.patch\s*\(\s*['"`](\/[^'"`\s)]+)['"`]/g, method: "PATCH" },
7652
+ { regex: /axios\.get\s*\(\s*`[^`]*?(\/[^`\s)]+)`/g, method: "GET" },
7653
+ { regex: /axios\.post\s*\(\s*`[^`]*?(\/[^`\s)]+)`/g, method: "POST" },
7654
+ { regex: /axios\.put\s*\(\s*`[^`]*?(\/[^`\s)]+)`/g, method: "PUT" },
7655
+ { regex: /axios\.delete\s*\(\s*`[^`]*?(\/[^`\s)]+)`/g, method: "DELETE" },
7656
+ { regex: /axios\.patch\s*\(\s*`[^`]*?(\/[^`\s)]+)`/g, method: "PATCH" },
7652
7657
  { regex: /useSWR\s*\(\s*['"`](\/[^'"`\s)]+)['"`]/g, method: "GET" },
7658
+ { regex: /useSWR\s*\(\s*`[^`]*?(\/[^`\s)]+)`/g, method: "GET" },
7653
7659
  { regex: /fetch\s*\(\s*['"`](\/[^'"`\s)]+)['"`]/g, method: undefined },
7654
- { regex: /fetch\s*\(\s*`[^`]*(\/[^`\s)]+)`/g, method: undefined },
7660
+ { regex: /fetch\s*\(\s*`[^`]*?(\/[^`\s)]+)`/g, method: undefined },
7655
7661
  { regex: /['"`](\/api\/[^'"`\s]+)['"`]/g, method: undefined },
7656
- { regex: /['"`](?:https?:\/\/[^/]+)?(\/api\/[^'"`\s]+)['"`]/g, method: undefined },
7657
- { regex: /`[^`]*(\/[\w-]+\/[^`\s]+)`/g, method: undefined },
7658
- { regex: /['"`](\/[\w-]{2,}\/[\w-./]+)['"`]/g, method: undefined }
7662
+ { regex: /`[^`]*?(\/api\/[^`\s]+)`/g, method: undefined },
7663
+ { regex: /`[^`]*?(\/[\w-]{2,}\/[^`\s]*)`/g, method: undefined },
7664
+ { regex: /['"`](\/[\w-]{2,}\/[^'"`\s]*)['"`]/g, method: undefined },
7665
+ { regex: /['"`](\/api\/[^'"`\s]*)['"`]/g, method: undefined }
7659
7666
  ];
7660
7667
  function extractApiReferences(content) {
7661
7668
  const matches = [];
@@ -9680,14 +9687,21 @@ function extractNestRoutes(filePath, content, globalPrefix = "api") {
9680
9687
  return routes;
9681
9688
  }
9682
9689
  function shouldIgnore(path2, ignorePatterns) {
9683
- const normalizedPath = path2.replace(/\\/g, "/").replace(/^\.\//, "");
9690
+ const cleanPath = path2.replace(/\\/g, "/").replace(/^\//, "").replace(/^\.\//, "");
9684
9691
  return ignorePatterns.some((pattern) => {
9685
- const normalizedPattern = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
9686
- if (minimatch(normalizedPath, normalizedPattern))
9692
+ let cleanPattern = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
9693
+ const isAbsolute = cleanPattern.startsWith("/");
9694
+ if (isAbsolute)
9695
+ cleanPattern = cleanPattern.substring(1);
9696
+ if (minimatch(cleanPath, cleanPattern))
9687
9697
  return true;
9688
- const folderPattern = normalizedPattern.endsWith("/") ? normalizedPattern : normalizedPattern + "/";
9689
- if (normalizedPath.startsWith(folderPattern))
9698
+ const folderPattern = cleanPattern.endsWith("/") ? cleanPattern : cleanPattern + "/";
9699
+ if (cleanPath.startsWith(folderPattern))
9690
9700
  return true;
9701
+ if (!isAbsolute && !cleanPattern.includes("/") && !cleanPattern.includes("*")) {
9702
+ if (cleanPath.endsWith("/" + cleanPattern) || cleanPath === cleanPattern)
9703
+ return true;
9704
+ }
9691
9705
  return false;
9692
9706
  });
9693
9707
  }
@@ -9697,21 +9711,32 @@ function normalizeNextPath(path2) {
9697
9711
  function normalizeNestPath(path2) {
9698
9712
  return path2.replace(/\/$/, "").replace(/\?.*$/, "").replace(/:[^/]+/g, "*").toLowerCase();
9699
9713
  }
9700
- function checkRouteUsage(route, references, nestGlobalPrefix = "api") {
9714
+ function checkRouteUsage(route, references, nestGlobalPrefix = "") {
9701
9715
  const normalize = route.type === "nextjs" ? normalizeNextPath : normalizeNestPath;
9702
9716
  const normalizedRoute = normalize(route.path);
9703
- const prefixToRemove = `/${nestGlobalPrefix}`;
9704
- const normalizedRouteNoPrefix = route.type === "nestjs" && route.path.startsWith(prefixToRemove) ? normalize(route.path.substring(prefixToRemove.length)) : null;
9717
+ const variations = new Set([normalizedRoute]);
9718
+ if (route.type === "nestjs") {
9719
+ if (nestGlobalPrefix) {
9720
+ const prefixToRemove = `/${nestGlobalPrefix}`.replace(/\/+/g, "/");
9721
+ if (route.path.startsWith(prefixToRemove)) {
9722
+ variations.add(normalize(route.path.substring(prefixToRemove.length)));
9723
+ }
9724
+ }
9725
+ if (route.path.startsWith("/api/")) {
9726
+ variations.add(normalize(route.path.substring(4)));
9727
+ } else {
9728
+ variations.add(normalize("/api" + route.path));
9729
+ }
9730
+ }
9705
9731
  const usedMethods = new Set;
9706
9732
  let used = false;
9707
9733
  for (const ref of references) {
9708
9734
  const normalizedFound = ref.path.replace(/\/$/, "").replace(/\?.*$/, "").replace(/\$\{[^}]+\}/g, "*").toLowerCase();
9709
9735
  let match2 = false;
9710
- if (normalizedRoute === normalizedFound || normalizedFound.startsWith(normalizedRoute + "/") || minimatch(normalizedFound, normalizedRoute)) {
9711
- match2 = true;
9712
- } else if (normalizedRouteNoPrefix) {
9713
- if (normalizedRouteNoPrefix === normalizedFound || normalizedFound.startsWith(normalizedRouteNoPrefix + "/") || minimatch(normalizedFound, normalizedRouteNoPrefix)) {
9736
+ for (const v of variations) {
9737
+ if (v === normalizedFound || normalizedFound.startsWith(v + "/") || minimatch(normalizedFound, v)) {
9714
9738
  match2 = true;
9739
+ break;
9715
9740
  }
9716
9741
  }
9717
9742
  if (match2) {
@@ -9808,7 +9833,7 @@ async function scan(config) {
9808
9833
  } catch {}
9809
9834
  }
9810
9835
  for (const route of routes) {
9811
- if (shouldIgnore(route.path, config.ignore.routes)) {
9836
+ if (shouldIgnore(route.path, config.ignore.routes) || shouldIgnore(route.filePath, config.ignore.routes)) {
9812
9837
  route.used = true;
9813
9838
  route.references.push("(ignored by config)");
9814
9839
  route.unusedMethods = [];
@@ -9888,7 +9913,7 @@ var DEFAULT_CONFIG = {
9888
9913
  ]
9889
9914
  },
9890
9915
  extensions: [".ts", ".tsx", ".js", ".jsx"],
9891
- nestGlobalPrefix: "api",
9916
+ nestGlobalPrefix: "",
9892
9917
  extraRoutePatterns: []
9893
9918
  };
9894
9919
  function loadConfig(options) {
@@ -9930,7 +9955,7 @@ function loadConfig(options) {
9930
9955
  return prefixed.includes("/") ? `**/${prefixed}` : `**/${prefixed}`;
9931
9956
  };
9932
9957
  if (config.ignore?.routes)
9933
- mergedIgnore.routes.push(...config.ignore.routes.map(prefixPattern));
9958
+ mergedIgnore.routes.push(...config.ignore.routes);
9934
9959
  if (config.ignore?.folders)
9935
9960
  mergedIgnore.folders.push(...config.ignore.folders.map(prefixPattern));
9936
9961
  if (config.ignore?.files)
@@ -10253,47 +10278,70 @@ program2.action(async (options) => {
10253
10278
  if (unusedRoutes.length > 0) {
10254
10279
  console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused routes...
10255
10280
  `));
10256
- for (const route of unusedRoutes) {
10257
- const fullPath = join8(config.dir, route.filePath);
10281
+ const routesByFile = new Map;
10282
+ for (const r of unusedRoutes) {
10283
+ const list = routesByFile.get(r.filePath) || [];
10284
+ list.push(r);
10285
+ routesByFile.set(r.filePath, list);
10286
+ }
10287
+ for (const [filePath, fileRoutes] of routesByFile) {
10288
+ const fullPath = join8(config.dir, filePath);
10289
+ if (!existsSync7(fullPath))
10290
+ continue;
10291
+ const route = fileRoutes[0];
10258
10292
  const routeDir = dirname4(fullPath);
10259
10293
  try {
10260
- if (!existsSync7(fullPath))
10261
- continue;
10262
10294
  if (route.type === "nextjs") {
10263
- if (route.filePath.includes("app/api") || route.filePath.includes("pages/api")) {
10295
+ if (filePath.includes("app/api") || filePath.includes("pages/api")) {
10264
10296
  rmSync(routeDir, { recursive: true, force: true });
10265
10297
  console.log(source_default.red(` Deleted Folder: ${routeDir}`));
10266
10298
  } else {
10267
10299
  rmSync(fullPath, { force: true });
10268
- console.log(source_default.red(` Deleted File: ${route.filePath}`));
10300
+ console.log(source_default.red(` Deleted File: ${filePath}`));
10269
10301
  }
10302
+ fixedSomething = true;
10270
10303
  } else if (route.type === "nestjs") {
10271
- const isInternallyUnused = result.unusedFiles?.files.some((f) => f.path === route.filePath);
10272
- if (isInternallyUnused || route.filePath.includes("api/")) {
10304
+ const isInternallyUnused = result.unusedFiles?.files.some((f) => f.path === filePath);
10305
+ if (isInternallyUnused || filePath.includes("api/")) {
10273
10306
  rmSync(fullPath, { force: true });
10274
- console.log(source_default.red(` Deleted File: ${route.filePath}`));
10307
+ console.log(source_default.red(` Deleted File: ${filePath}`));
10275
10308
  fixedSomething = true;
10276
10309
  } else {
10277
- console.log(source_default.yellow(` Skipped Deletion (internally used): ${route.filePath}`));
10310
+ console.log(source_default.yellow(` Skipped File Deletion (internally used): ${filePath}`));
10278
10311
  console.log(source_default.dim(` → This controller is imported in another file (e.g. app.module.ts).`));
10279
- continue;
10312
+ const allMethodsToPrune = [];
10313
+ for (const r of fileRoutes) {
10314
+ for (const m of r.unusedMethods) {
10315
+ if (r.methodLines[m] !== undefined) {
10316
+ allMethodsToPrune.push({ method: m, line: r.methodLines[m] });
10317
+ }
10318
+ }
10319
+ }
10320
+ allMethodsToPrune.sort((a, b) => b.line - a.line);
10321
+ for (const { method, line } of allMethodsToPrune) {
10322
+ if (removeMethodFromRoute(config.dir, filePath, method, line)) {
10323
+ console.log(source_default.green(` Fixed: Removed ${method} from ${filePath}`));
10324
+ fixedSomething = true;
10325
+ }
10326
+ }
10280
10327
  }
10281
10328
  } else {
10282
10329
  rmSync(fullPath, { force: true });
10283
- console.log(source_default.red(` Deleted File: ${route.filePath}`));
10330
+ console.log(source_default.red(` Deleted File: ${filePath}`));
10284
10331
  fixedSomething = true;
10285
10332
  }
10286
- fixedSomething = true;
10287
- const idx = result.routes.indexOf(route);
10288
- if (idx !== -1)
10289
- result.routes.splice(idx, 1);
10290
- } catch (_err) {
10291
- console.log(source_default.yellow(` Failed to delete: ${route.filePath}`));
10333
+ for (const r of fileRoutes) {
10334
+ const idx = result.routes.indexOf(r);
10335
+ if (idx !== -1)
10336
+ result.routes.splice(idx, 1);
10337
+ }
10338
+ } catch (err) {
10339
+ console.log(source_default.yellow(` Failed to fix: ${filePath}`));
10292
10340
  }
10293
10341
  }
10294
10342
  console.log("");
10295
10343
  }
10296
- const partiallyRoutes = result.routes.filter((r) => r.used && r.unusedMethods.length > 0);
10344
+ const partiallyRoutes = result.routes.filter((r) => r.used && r.unusedMethods && r.unusedMethods.length > 0);
10297
10345
  if (partiallyRoutes.length > 0) {
10298
10346
  console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing partially unused routes...
10299
10347
  `));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruny",
3
- "version": "1.17.1",
3
+ "version": "1.19.0",
4
4
  "description": "Find and remove unused Next.js API routes & Nest.js Controllers",
5
5
  "type": "module",
6
6
  "files": [