pruny 1.15.0 → 1.16.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 +121 -38
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7631,8 +7631,8 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
7631
7631
  var source_default = chalk;
7632
7632
 
7633
7633
  // src/index.ts
7634
- import { rmSync } from "node:fs";
7635
- import { dirname as dirname3, join as join8 } from "node:path";
7634
+ import { rmSync, existsSync as existsSync7 } from "node:fs";
7635
+ import { dirname as dirname4, join as join8 } from "node:path";
7636
7636
 
7637
7637
  // src/scanner.ts
7638
7638
  var import_fast_glob4 = __toESM(require_out4(), 1);
@@ -9231,7 +9231,11 @@ async function scanUnusedFiles(config) {
9231
9231
  "scripts/**/*.{ts,js}",
9232
9232
  "cypress/**/*.{ts,js,tsx}",
9233
9233
  "**/public/sw.js",
9234
- "**/sw.{js,ts}"
9234
+ "**/sw.{js,ts}",
9235
+ "**/main.{ts,js}",
9236
+ "api/index.ts",
9237
+ "**/app.module.ts",
9238
+ "**/api/index.ts"
9235
9239
  ];
9236
9240
  for (const file of allFiles) {
9237
9241
  const isEntry = entryPatterns.some((pattern) => {
@@ -9687,22 +9691,29 @@ function shouldIgnore(path2, ignorePatterns) {
9687
9691
  return false;
9688
9692
  });
9689
9693
  }
9690
- function normalizeApiPath(path2) {
9694
+ function normalizeNextPath(path2) {
9691
9695
  return path2.replace(/\/$/, "").replace(/\?.*$/, "").replace(/\$\{[^}]+\}/g, "*").replace(/\[[^\]]+\]/g, "*").toLowerCase();
9692
9696
  }
9693
- function checkRouteUsage(routePath, references) {
9694
- const normalizedRoute = normalizeApiPath(routePath);
9697
+ function normalizeNestPath(path2) {
9698
+ return path2.replace(/\/$/, "").replace(/\?.*$/, "").replace(/:[^/]+/g, "*").toLowerCase();
9699
+ }
9700
+ function checkRouteUsage(route, references, nestGlobalPrefix = "api") {
9701
+ const normalize = route.type === "nextjs" ? normalizeNextPath : normalizeNestPath;
9702
+ 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;
9695
9705
  const usedMethods = new Set;
9696
9706
  let used = false;
9697
9707
  for (const ref of references) {
9698
- const normalizedFound = normalizeApiPath(ref.path);
9708
+ const normalizedFound = ref.path.replace(/\/$/, "").replace(/\?.*$/, "").replace(/\$\{[^}]+\}/g, "*").toLowerCase();
9699
9709
  let match2 = false;
9700
- if (normalizedRoute === normalizedFound)
9701
- match2 = true;
9702
- else if (normalizedFound.startsWith(normalizedRoute))
9703
- match2 = true;
9704
- else if (minimatch(normalizedFound, normalizedRoute))
9710
+ if (normalizedRoute === normalizedFound || normalizedFound.startsWith(normalizedRoute + "/") || minimatch(normalizedFound, normalizedRoute)) {
9705
9711
  match2 = true;
9712
+ } else if (normalizedRouteNoPrefix) {
9713
+ if (normalizedRouteNoPrefix === normalizedFound || normalizedFound.startsWith(normalizedRouteNoPrefix + "/") || minimatch(normalizedFound, normalizedRouteNoPrefix)) {
9714
+ match2 = true;
9715
+ }
9716
+ }
9706
9717
  if (match2) {
9707
9718
  used = true;
9708
9719
  if (ref.method) {
@@ -9803,7 +9814,7 @@ async function scan(config) {
9803
9814
  route.unusedMethods = [];
9804
9815
  continue;
9805
9816
  }
9806
- const { used, usedMethods } = checkRouteUsage(route.path, allReferences);
9817
+ const { used, usedMethods } = checkRouteUsage(route, allReferences, config.nestGlobalPrefix);
9807
9818
  if (used) {
9808
9819
  route.used = true;
9809
9820
  if (usedMethods.has("ALL")) {
@@ -9812,7 +9823,7 @@ async function scan(config) {
9812
9823
  route.unusedMethods = route.methods.filter((m) => !usedMethods.has(m));
9813
9824
  }
9814
9825
  for (const [file, refs] of fileReferences) {
9815
- if (checkRouteUsage(route.path, refs).used) {
9826
+ if (checkRouteUsage(route, refs, config.nestGlobalPrefix).used) {
9816
9827
  route.references.push(file);
9817
9828
  }
9818
9829
  }
@@ -9837,7 +9848,7 @@ async function scan(config) {
9837
9848
  // src/config.ts
9838
9849
  var import_fast_glob5 = __toESM(require_out4(), 1);
9839
9850
  import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
9840
- import { join as join5, resolve as resolve2 } from "node:path";
9851
+ import { join as join5, resolve as resolve2, relative as relative2, dirname as dirname3 } from "node:path";
9841
9852
  var DEFAULT_CONFIG = {
9842
9853
  dir: "./",
9843
9854
  ignore: {
@@ -9859,19 +9870,19 @@ var DEFAULT_CONFIG = {
9859
9870
  "**/vendor/**"
9860
9871
  ],
9861
9872
  files: [
9862
- "*.test.ts",
9863
- "*.spec.ts",
9864
- "*.test.tsx",
9865
- "*.spec.tsx",
9866
- "public/robots.txt",
9867
- "public/sitemap*.xml",
9868
- "public/favicon.ico",
9869
- "public/sw.js",
9870
- "public/manifest.json",
9871
- "public/twitter-image.*",
9872
- "public/opengraph-image.*",
9873
- "public/apple-icon.*",
9874
- "public/icon.*",
9873
+ "**/*.test.ts",
9874
+ "**/*.spec.ts",
9875
+ "**/*.test.tsx",
9876
+ "**/*.spec.tsx",
9877
+ "**/public/robots.txt",
9878
+ "**/public/sitemap*.xml",
9879
+ "**/public/favicon.ico",
9880
+ "**/public/sw.js",
9881
+ "**/public/manifest.json",
9882
+ "**/public/twitter-image.*",
9883
+ "**/public/opengraph-image.*",
9884
+ "**/public/apple-icon.*",
9885
+ "**/public/icon.*",
9875
9886
  "**/proxy.*",
9876
9887
  "**/middleware.*"
9877
9888
  ]
@@ -9910,12 +9921,19 @@ function loadConfig(options) {
9910
9921
  try {
9911
9922
  const content = readFileSync5(configPath, "utf-8");
9912
9923
  const config = JSON.parse(content);
9924
+ const configDir = dirname3(configPath);
9925
+ const relDir = relative2(cwd, configDir);
9926
+ const prefixPattern = (p) => {
9927
+ if (p.startsWith("**/") || p.startsWith("/") || !relDir)
9928
+ return p;
9929
+ return join5(relDir, p).replace(/\\/g, "/");
9930
+ };
9913
9931
  if (config.ignore?.routes)
9914
- mergedIgnore.routes.push(...config.ignore.routes);
9932
+ mergedIgnore.routes.push(...config.ignore.routes.map(prefixPattern));
9915
9933
  if (config.ignore?.folders)
9916
- mergedIgnore.folders.push(...config.ignore.folders);
9934
+ mergedIgnore.folders.push(...config.ignore.folders.map(prefixPattern));
9917
9935
  if (config.ignore?.files)
9918
- mergedIgnore.files.push(...config.ignore.files);
9936
+ mergedIgnore.files.push(...config.ignore.files.map(prefixPattern));
9919
9937
  if (config.extensions)
9920
9938
  mergedExtensions = [...new Set([...mergedExtensions, ...config.extensions])];
9921
9939
  if (config.nestGlobalPrefix)
@@ -9924,8 +9942,6 @@ function loadConfig(options) {
9924
9942
  extraRoutePatterns.push(...config.extraRoutePatterns);
9925
9943
  if (config.excludePublic !== undefined)
9926
9944
  excludePublic = config.excludePublic;
9927
- if (config.excludePublic !== undefined)
9928
- excludePublic = config.excludePublic;
9929
9945
  } catch {}
9930
9946
  }
9931
9947
  const gitIgnorePatterns = parseGitIgnore(cwd);
@@ -9972,6 +9988,8 @@ import { readFileSync as readFileSync6, writeFileSync, unlinkSync, existsSync as
9972
9988
  import { join as join6 } from "node:path";
9973
9989
  function removeExportFromLine(rootDir, exp) {
9974
9990
  const fullPath = join6(rootDir, exp.file);
9991
+ if (!existsSync5(fullPath))
9992
+ return false;
9975
9993
  try {
9976
9994
  const content = readFileSync6(fullPath, "utf-8");
9977
9995
  const lines = content.split(`
@@ -10230,18 +10248,40 @@ program2.action(async (options) => {
10230
10248
  `));
10231
10249
  }
10232
10250
  if (options.fix) {
10251
+ let fixedSomething = false;
10233
10252
  if (unusedRoutes.length > 0) {
10234
10253
  console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused routes...
10235
10254
  `));
10236
10255
  for (const route of unusedRoutes) {
10237
- const routeDir = dirname3(join8(config.dir, route.filePath));
10256
+ const fullPath = join8(config.dir, route.filePath);
10257
+ const routeDir = dirname4(fullPath);
10238
10258
  try {
10239
- rmSync(routeDir, { recursive: true, force: true });
10240
- console.log(source_default.red(` Deleted: ${route.filePath}`));
10259
+ if (!existsSync7(fullPath))
10260
+ continue;
10261
+ if (route.type === "nextjs") {
10262
+ if (route.filePath.includes("app/api") || route.filePath.includes("pages/api")) {
10263
+ rmSync(routeDir, { recursive: true, force: true });
10264
+ console.log(source_default.red(` Deleted Folder: ${routeDir}`));
10265
+ } else {
10266
+ rmSync(fullPath, { force: true });
10267
+ console.log(source_default.red(` Deleted File: ${route.filePath}`));
10268
+ }
10269
+ } else if (route.type === "nestjs") {
10270
+ rmSync(fullPath, { force: true });
10271
+ console.log(source_default.red(` Deleted File: ${route.filePath}`));
10272
+ } else {
10273
+ rmSync(fullPath, { force: true });
10274
+ console.log(source_default.red(` Deleted File: ${route.filePath}`));
10275
+ }
10276
+ fixedSomething = true;
10277
+ const idx = result.routes.indexOf(route);
10278
+ if (idx !== -1)
10279
+ result.routes.splice(idx, 1);
10241
10280
  } catch (_err) {
10242
10281
  console.log(source_default.yellow(` Failed to delete: ${route.filePath}`));
10243
10282
  }
10244
10283
  }
10284
+ console.log("");
10245
10285
  }
10246
10286
  const partiallyRoutes = result.routes.filter((r) => r.used && r.unusedMethods.length > 0);
10247
10287
  if (partiallyRoutes.length > 0) {
@@ -10249,15 +10289,44 @@ program2.action(async (options) => {
10249
10289
  `));
10250
10290
  for (const route of partiallyRoutes) {
10251
10291
  const sortedMethods = [...route.unusedMethods].filter((m) => route.methodLines[m] !== undefined).sort((a, b) => route.methodLines[b] - route.methodLines[a]);
10292
+ let fixedCount = 0;
10252
10293
  for (const method of sortedMethods) {
10253
10294
  const lineNum = route.methodLines[method];
10254
10295
  if (removeMethodFromRoute(config.dir, route.filePath, method, lineNum)) {
10255
10296
  console.log(source_default.green(` Fixed: Removed ${method} from ${route.path}`));
10297
+ fixedCount++;
10298
+ fixedSomething = true;
10256
10299
  }
10257
10300
  }
10301
+ if (fixedCount === route.methods.length) {
10302
+ const idx = result.routes.indexOf(route);
10303
+ if (idx !== -1)
10304
+ result.routes.splice(idx, 1);
10305
+ } else {
10306
+ route.unusedMethods = route.unusedMethods.filter((m) => !sortedMethods.includes(m));
10307
+ }
10258
10308
  }
10259
10309
  console.log("");
10260
10310
  }
10311
+ if (result.unusedFiles && result.unusedFiles.files.length > 0) {
10312
+ console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused source files...
10313
+ `));
10314
+ for (const file of result.unusedFiles.files) {
10315
+ try {
10316
+ const fullPath = join8(config.dir, file.path);
10317
+ if (!existsSync7(fullPath))
10318
+ continue;
10319
+ rmSync(fullPath, { force: true });
10320
+ console.log(source_default.red(` Deleted: ${file.path}`));
10321
+ fixedSomething = true;
10322
+ } catch (_err) {
10323
+ console.log(source_default.yellow(` Failed to delete: ${file.path}`));
10324
+ }
10325
+ }
10326
+ result.unusedFiles.files = [];
10327
+ result.unusedFiles.unused = 0;
10328
+ console.log("");
10329
+ }
10261
10330
  if (result.unusedExports && result.unusedExports.exports.length > 0) {
10262
10331
  console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing unused exports (removing "export" keyword)...
10263
10332
  `));
@@ -10272,9 +10341,18 @@ program2.action(async (options) => {
10272
10341
  for (const [file, exports] of exportsByFile.entries()) {
10273
10342
  const sortedExports = exports.sort((a, b) => b.line - a.line);
10274
10343
  for (const exp of sortedExports) {
10344
+ const fullPath = join8(config.dir, exp.file);
10345
+ if (!existsSync7(fullPath))
10346
+ continue;
10275
10347
  if (removeExportFromLine(config.dir, exp)) {
10276
10348
  console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
10277
10349
  fixedCount++;
10350
+ fixedSomething = true;
10351
+ const expIdx = result.unusedExports.exports.indexOf(exp);
10352
+ if (expIdx !== -1) {
10353
+ result.unusedExports.exports.splice(expIdx, 1);
10354
+ result.unusedExports.unused--;
10355
+ }
10278
10356
  }
10279
10357
  }
10280
10358
  }
@@ -10284,8 +10362,13 @@ program2.action(async (options) => {
10284
10362
  `));
10285
10363
  }
10286
10364
  }
10287
- } else if (unusedRoutes.length > 0 || result.unusedExports && result.unusedExports.exports.length > 0) {
10288
- console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to automatically clean up unused routes and exports.
10365
+ if (fixedSomething) {
10366
+ result.unused = result.routes.filter((r) => !r.used).length;
10367
+ result.used = result.routes.filter((r) => r.used).length;
10368
+ result.total = result.routes.length;
10369
+ }
10370
+ } else if (unusedRoutes.length > 0 || result.unusedExports && result.unusedExports.exports.length > 0 || result.unusedFiles && result.unusedFiles.files.length > 0) {
10371
+ console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to automatically clean up unused routes, files, and exports.
10289
10372
  `));
10290
10373
  }
10291
10374
  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.15.0",
3
+ "version": "1.16.0",
4
4
  "description": "Find and remove unused Next.js API routes & Nest.js Controllers",
5
5
  "type": "module",
6
6
  "files": [