pruny 1.43.5 → 1.43.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.
Files changed (2) hide show
  1. package/dist/index.js +85 -56
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -12563,13 +12563,13 @@ var source_default = chalk;
12563
12563
 
12564
12564
  // src/index.ts
12565
12565
  var import_prompts = __toESM(require_prompts3(), 1);
12566
- import { rmSync, existsSync as existsSync10, readdirSync, lstatSync, writeFileSync as writeFileSync3 } from "node:fs";
12567
- import { dirname as dirname6, join as join10, relative as relative5, resolve as resolve4 } from "node:path";
12566
+ import { rmSync, existsSync as existsSync11, readdirSync, lstatSync, writeFileSync as writeFileSync3 } from "node:fs";
12567
+ import { dirname as dirname6, join as join11, relative as relative5, resolve as resolve4 } from "node:path";
12568
12568
 
12569
12569
  // src/scanner.ts
12570
12570
  var import_fast_glob10 = __toESM(require_out4(), 1);
12571
- import { existsSync as existsSync7, readFileSync as readFileSync10 } from "node:fs";
12572
- import { join as join7 } from "node:path";
12571
+ import { existsSync as existsSync8, readFileSync as readFileSync10 } from "node:fs";
12572
+ import { join as join8 } from "node:path";
12573
12573
 
12574
12574
  // src/patterns.ts
12575
12575
  var EXPORTED_METHOD_PATTERN = /export\s+(?:async\s+)?(?:function|const)\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)/g;
@@ -15873,7 +15873,8 @@ async function scanUnusedServices(config) {
15873
15873
 
15874
15874
  // src/scanners/broken-links.ts
15875
15875
  var import_fast_glob9 = __toESM(require_out4(), 1);
15876
- import { readFileSync as readFileSync9 } from "node:fs";
15876
+ import { readFileSync as readFileSync9, existsSync as existsSync7 } from "node:fs";
15877
+ import { join as join7 } from "node:path";
15877
15878
  var LINK_PATTERNS = [
15878
15879
  /<Link\s+[^>]*href\s*=\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
15879
15880
  /router\.(push|replace)\s*\(\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
@@ -15970,6 +15971,29 @@ function matchSegments(refSegments, routeSegments) {
15970
15971
  }
15971
15972
  return ri === refSegments.length && si === routeSegments.length;
15972
15973
  }
15974
+ function isGitignoredPublicFile(appDir, linkPath) {
15975
+ const publicRelPath = `public${linkPath}`;
15976
+ const dirsToCheck = [appDir];
15977
+ const parentDir = join7(appDir, "..", "..");
15978
+ if (existsSync7(join7(parentDir, ".gitignore"))) {
15979
+ dirsToCheck.push(parentDir);
15980
+ }
15981
+ for (const dir of dirsToCheck) {
15982
+ const gitignorePath = join7(dir, ".gitignore");
15983
+ if (!existsSync7(gitignorePath))
15984
+ continue;
15985
+ try {
15986
+ const patterns = readFileSync9(gitignorePath, "utf-8").split(`
15987
+ `).map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
15988
+ for (const pattern of patterns) {
15989
+ if (minimatch(publicRelPath, pattern, { dot: true }) || minimatch(linkPath.slice(1), pattern, { dot: true }) || minimatch(`**/public${linkPath}`, pattern, { dot: true })) {
15990
+ return true;
15991
+ }
15992
+ }
15993
+ } catch {}
15994
+ }
15995
+ return false;
15996
+ }
15973
15997
  async function scanBrokenLinks(config) {
15974
15998
  const appDir = config.appSpecificScan ? config.appSpecificScan.appDir : config.dir;
15975
15999
  const pagePatterns = [
@@ -16010,13 +16034,13 @@ async function scanBrokenLinks(config) {
16010
16034
  });
16011
16035
  if (config.appSpecificScan) {
16012
16036
  const { readdirSync, lstatSync } = await import("node:fs");
16013
- const { join: join7 } = await import("node:path");
16014
- const appsDir = join7(config.appSpecificScan.rootDir, "apps");
16037
+ const { join: join8 } = await import("node:path");
16038
+ const appsDir = join8(config.appSpecificScan.rootDir, "apps");
16015
16039
  try {
16016
- const apps = readdirSync(appsDir).filter((a) => lstatSync(join7(appsDir, a)).isDirectory());
16040
+ const apps = readdirSync(appsDir).filter((a) => lstatSync(join8(appsDir, a)).isDirectory());
16017
16041
  const expoAppDirs = [];
16018
16042
  for (const app of apps) {
16019
- const appPath = join7(appsDir, app);
16043
+ const appPath = join8(appsDir, app);
16020
16044
  const frameworks = detectAppFramework(appPath);
16021
16045
  if (frameworks.includes("expo") || frameworks.includes("react-native")) {
16022
16046
  expoAppDirs.push(appPath);
@@ -16046,6 +16070,11 @@ async function scanBrokenLinks(config) {
16046
16070
  continue;
16047
16071
  allLinkPaths.add(cleaned);
16048
16072
  if (!matchesRoute(cleaned, knownRoutes, routeSegmentsList)) {
16073
+ const publicPath = join7(appDir, "public", cleaned);
16074
+ if (existsSync7(publicPath))
16075
+ continue;
16076
+ if (isGitignoredPublicFile(appDir, cleaned))
16077
+ continue;
16049
16078
  const ignorePatterns = [
16050
16079
  ...config.ignore.links || [],
16051
16080
  ...config.ignore.routes
@@ -16226,12 +16255,12 @@ function normalizeNestPath(path2) {
16226
16255
  return path2.replace(/\/$/, "").replace(/\?.*$/, "").replace(/\$\{[^}]+\}/g, "*").replace(/:[^/]+/g, "*").toLowerCase();
16227
16256
  }
16228
16257
  async function detectGlobalPrefix(appDir) {
16229
- const mainTsPath = join7(appDir, "src/main.ts");
16230
- const mainTsAltPath = join7(appDir, "main.ts");
16258
+ const mainTsPath = join8(appDir, "src/main.ts");
16259
+ const mainTsAltPath = join8(appDir, "main.ts");
16231
16260
  let content;
16232
- if (existsSync7(mainTsPath)) {
16261
+ if (existsSync8(mainTsPath)) {
16233
16262
  content = readFileSync10(mainTsPath, "utf-8");
16234
- } else if (existsSync7(mainTsAltPath)) {
16263
+ } else if (existsSync8(mainTsAltPath)) {
16235
16264
  content = readFileSync10(mainTsAltPath, "utf-8");
16236
16265
  } else {
16237
16266
  return "";
@@ -16290,8 +16319,8 @@ function checkRouteUsage(route, references, nestGlobalPrefix = "") {
16290
16319
  return { used, usedMethods };
16291
16320
  }
16292
16321
  function getVercelExternalPaths(dir) {
16293
- const vercelPath = join7(dir, "vercel.json");
16294
- if (!existsSync7(vercelPath)) {
16322
+ const vercelPath = join8(dir, "vercel.json");
16323
+ if (!existsSync8(vercelPath)) {
16295
16324
  return [];
16296
16325
  }
16297
16326
  try {
@@ -16322,15 +16351,15 @@ function getVercelExternalPaths(dir) {
16322
16351
  }
16323
16352
  }
16324
16353
  function getGitHubWorkflowPaths(dir) {
16325
- const workflowDir = join7(dir, ".github", "workflows");
16326
- if (!existsSync7(workflowDir))
16354
+ const workflowDir = join8(dir, ".github", "workflows");
16355
+ if (!existsSync8(workflowDir))
16327
16356
  return [];
16328
16357
  const paths = [];
16329
16358
  const apiPathPattern = /\/api\/[a-zA-Z0-9_\-/[\]]+/g;
16330
16359
  try {
16331
16360
  const files = import_fast_glob10.default.sync("*.{yml,yaml}", { cwd: workflowDir });
16332
16361
  for (const file of files) {
16333
- const content = readFileSync10(join7(workflowDir, file), "utf-8");
16362
+ const content = readFileSync10(join8(workflowDir, file), "utf-8");
16334
16363
  let match2;
16335
16364
  apiPathPattern.lastIndex = 0;
16336
16365
  while ((match2 = apiPathPattern.exec(content)) !== null) {
@@ -16349,10 +16378,10 @@ function getGitHubWorkflowPaths(dir) {
16349
16378
  function getAutoDetectedExternalRoutes(dir) {
16350
16379
  const routes = [];
16351
16380
  const packagePaths = [
16352
- join7(dir, "package.json")
16381
+ join8(dir, "package.json")
16353
16382
  ];
16354
16383
  for (const pkgPath of packagePaths) {
16355
- if (!existsSync7(pkgPath))
16384
+ if (!existsSync8(pkgPath))
16356
16385
  continue;
16357
16386
  try {
16358
16387
  const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
@@ -16398,7 +16427,7 @@ async function scan(config) {
16398
16427
  ignore: config.ignore.folders
16399
16428
  });
16400
16429
  const nextRoutes = nextFiles.map((file) => {
16401
- const fullPath = join7(scanCwd, file);
16430
+ const fullPath = join8(scanCwd, file);
16402
16431
  const content = readFileSync10(fullPath, "utf-8");
16403
16432
  const { methods, methodLines } = extractExportedMethods(content);
16404
16433
  return {
@@ -16418,7 +16447,7 @@ async function scan(config) {
16418
16447
  ignore: config.ignore.folders
16419
16448
  });
16420
16449
  const nestRoutes = nestFiles.flatMap((file) => {
16421
- const fullPath = join7(scanCwd, file);
16450
+ const fullPath = join8(scanCwd, file);
16422
16451
  const content = readFileSync10(fullPath, "utf-8");
16423
16452
  const relativePathFromRoot = fullPath.replace(config.appSpecificScan ? config.appSpecificScan.rootDir + "/" : cwd + "/", "");
16424
16453
  return extractNestRoutes(relativePathFromRoot, content, detectedGlobalPrefix);
@@ -16475,7 +16504,7 @@ async function scan(config) {
16475
16504
  const allReferences = [];
16476
16505
  const fileReferences = new Map;
16477
16506
  for (const file of sourceFiles) {
16478
- const filePath = join7(referenceScanCwd, file);
16507
+ const filePath = join8(referenceScanCwd, file);
16479
16508
  try {
16480
16509
  const content = readFileSync10(filePath, "utf-8");
16481
16510
  const refs = extractApiReferences(content);
@@ -16585,8 +16614,8 @@ async function scan(config) {
16585
16614
 
16586
16615
  // src/config.ts
16587
16616
  var import_fast_glob11 = __toESM(require_out4(), 1);
16588
- import { existsSync as existsSync8, readFileSync as readFileSync11 } from "node:fs";
16589
- import { join as join8, resolve as resolve3, relative as relative4, dirname as dirname5 } from "node:path";
16617
+ import { existsSync as existsSync9, readFileSync as readFileSync11 } from "node:fs";
16618
+ import { join as join9, resolve as resolve3, relative as relative4, dirname as dirname5 } from "node:path";
16590
16619
  var DEFAULT_CONFIG = {
16591
16620
  dir: "./",
16592
16621
  ignore: {
@@ -16623,7 +16652,7 @@ function loadConfig(options) {
16623
16652
  ignore: DEFAULT_CONFIG.ignore.folders,
16624
16653
  absolute: true
16625
16654
  });
16626
- if (options.config && existsSync8(options.config)) {
16655
+ if (options.config && existsSync9(options.config)) {
16627
16656
  const absConfig = resolve3(cwd, options.config);
16628
16657
  if (!configFiles.includes(absConfig)) {
16629
16658
  configFiles.push(absConfig);
@@ -16652,7 +16681,7 @@ function loadConfig(options) {
16652
16681
  const prefixPattern = (p) => {
16653
16682
  if (p.startsWith("**/") || p.startsWith("/") || !relDir)
16654
16683
  return p;
16655
- const prefixed = join8(relDir, p).replace(/\\/g, "/");
16684
+ const prefixed = join9(relDir, p).replace(/\\/g, "/");
16656
16685
  return prefixed.includes("/") ? `**/${prefixed}` : `**/${prefixed}`;
16657
16686
  };
16658
16687
  if (config.ignore?.routes)
@@ -16691,8 +16720,8 @@ function loadConfig(options) {
16691
16720
  };
16692
16721
  }
16693
16722
  function parseGitIgnore(dir) {
16694
- const gitIgnorePath = join8(dir, ".gitignore");
16695
- if (!existsSync8(gitIgnorePath))
16723
+ const gitIgnorePath = join9(dir, ".gitignore");
16724
+ if (!existsSync9(gitIgnorePath))
16696
16725
  return [];
16697
16726
  try {
16698
16727
  const content = readFileSync11(gitIgnorePath, "utf-8");
@@ -16709,8 +16738,8 @@ function parseGitIgnore(dir) {
16709
16738
  function findConfigFile(dir) {
16710
16739
  const candidates = ["pruny.config.json", ".prunyrc.json", ".prunyrc"];
16711
16740
  for (const name of candidates) {
16712
- const path2 = join8(dir, name);
16713
- if (existsSync8(path2)) {
16741
+ const path2 = join9(dir, name);
16742
+ if (existsSync9(path2)) {
16714
16743
  return path2;
16715
16744
  }
16716
16745
  }
@@ -16718,11 +16747,11 @@ function findConfigFile(dir) {
16718
16747
  }
16719
16748
 
16720
16749
  // src/init.ts
16721
- import { writeFileSync as writeFileSync2, existsSync as existsSync9 } from "node:fs";
16722
- import { join as join9 } from "node:path";
16750
+ import { writeFileSync as writeFileSync2, existsSync as existsSync10 } from "node:fs";
16751
+ import { join as join10 } from "node:path";
16723
16752
  function init(cwd = process.cwd()) {
16724
- const configPath = join9(cwd, "pruny.config.json");
16725
- if (existsSync9(configPath)) {
16753
+ const configPath = join10(cwd, "pruny.config.json");
16754
+ if (existsSync10(configPath)) {
16726
16755
  console.log(source_default.yellow("⚠️ pruny.config.json already exists. Skipping."));
16727
16756
  return;
16728
16757
  }
@@ -16750,7 +16779,7 @@ program2.action(async (options) => {
16750
16779
  config: options.config,
16751
16780
  excludePublic: !options.public
16752
16781
  });
16753
- const absoluteDir = baseConfig.dir.startsWith("/") ? baseConfig.dir : join10(process.cwd(), baseConfig.dir);
16782
+ const absoluteDir = baseConfig.dir.startsWith("/") ? baseConfig.dir : join11(process.cwd(), baseConfig.dir);
16754
16783
  baseConfig.dir = absoluteDir;
16755
16784
  if (options.verbose)
16756
16785
  console.log("");
@@ -16758,13 +16787,13 @@ program2.action(async (options) => {
16758
16787
  \uD83D\uDD0D Scanning for unused API routes...
16759
16788
  `));
16760
16789
  let monorepoRoot = absoluteDir;
16761
- let appsDir = join10(monorepoRoot, "apps");
16762
- let isMonorepo = existsSync10(appsDir) && lstatSync(appsDir).isDirectory();
16790
+ let appsDir = join11(monorepoRoot, "apps");
16791
+ let isMonorepo = existsSync11(appsDir) && lstatSync(appsDir).isDirectory();
16763
16792
  if (!isMonorepo) {
16764
16793
  let current = absoluteDir;
16765
16794
  while (current !== dirname6(current)) {
16766
- const potentialApps = join10(current, "apps");
16767
- if (existsSync10(potentialApps) && lstatSync(potentialApps).isDirectory()) {
16795
+ const potentialApps = join11(current, "apps");
16796
+ if (existsSync11(potentialApps) && lstatSync(potentialApps).isDirectory()) {
16768
16797
  monorepoRoot = current;
16769
16798
  appsDir = potentialApps;
16770
16799
  isMonorepo = true;
@@ -16782,13 +16811,13 @@ program2.action(async (options) => {
16782
16811
  const ignoredApps = options.ignoreApps ? options.ignoreApps.split(",").map((a) => a.trim()) : [];
16783
16812
  if (isMonorepo) {
16784
16813
  if (monorepoRoot !== absoluteDir) {
16785
- const appName = relative5(join10(monorepoRoot, "apps"), absoluteDir);
16814
+ const appName = relative5(join11(monorepoRoot, "apps"), absoluteDir);
16786
16815
  appsToScan.push(appName);
16787
16816
  } else {
16788
16817
  const apps = readdirSync(appsDir);
16789
16818
  const availableApps = [];
16790
16819
  for (const app of apps) {
16791
- const appPath = join10(appsDir, app);
16820
+ const appPath = join11(appsDir, app);
16792
16821
  if (lstatSync(appPath).isDirectory()) {
16793
16822
  availableApps.push(app);
16794
16823
  }
@@ -16850,7 +16879,7 @@ program2.action(async (options) => {
16850
16879
  let appDir = monorepoRoot;
16851
16880
  if (isMonorepo) {
16852
16881
  appLabel = `App: ${appName}`;
16853
- appDir = join10(monorepoRoot, "apps", appName);
16882
+ appDir = join11(monorepoRoot, "apps", appName);
16854
16883
  currentConfig.appSpecificScan = {
16855
16884
  appDir,
16856
16885
  rootDir: monorepoRoot
@@ -17311,7 +17340,7 @@ Analyzing cascading impact...`));
17311
17340
  }));
17312
17341
  dryRunReport.uniqueFiles = brokenList.length;
17313
17342
  }
17314
- const reportPath = join10(process.cwd(), "pruny-dry-run.json");
17343
+ const reportPath = join11(process.cwd(), "pruny-dry-run.json");
17315
17344
  writeFileSync3(reportPath, JSON.stringify(dryRunReport, null, 2));
17316
17345
  console.log(source_default.green(`
17317
17346
  ✅ Dry run report saved to: ${source_default.bold(reportPath)}`));
@@ -17365,7 +17394,7 @@ Analyzing cascading impact...`));
17365
17394
  if (!asset.used) {
17366
17395
  try {
17367
17396
  const fullPath = asset.path;
17368
- if (existsSync10(fullPath)) {
17397
+ if (existsSync11(fullPath)) {
17369
17398
  rmSync(fullPath, { force: true });
17370
17399
  console.log(source_default.red(` Deleted: ${asset.relativePath}`));
17371
17400
  fixedSomething = true;
@@ -17453,7 +17482,7 @@ Analyzing cascading impact...`));
17453
17482
  }
17454
17483
  const actuallyDeleted = new Set;
17455
17484
  for (const path2 of filesToUnlink) {
17456
- if (existsSync10(path2)) {
17485
+ if (existsSync11(path2)) {
17457
17486
  rmSync(path2, { recursive: true, force: true });
17458
17487
  actuallyDeleted.add(path2);
17459
17488
  console.log(source_default.red(` Deleted: ${relative5(config.dir, path2)}`));
@@ -17466,7 +17495,7 @@ Analyzing cascading impact...`));
17466
17495
  for (const [absPath, removals] of removalsByFile) {
17467
17496
  if (actuallyDeleted.has(absPath) || actuallyDeleted.has(dirname6(absPath)))
17468
17497
  continue;
17469
- if (!existsSync10(absPath))
17498
+ if (!existsSync11(absPath))
17470
17499
  continue;
17471
17500
  const sortedRemovals = removals.sort((a, b) => b.line - a.line);
17472
17501
  for (const rem of sortedRemovals) {
@@ -17496,8 +17525,8 @@ Analyzing cascading impact...`));
17496
17525
  \uD83D\uDDD1️ Deleting unused source files...`));
17497
17526
  for (const file of result.unusedFiles.files) {
17498
17527
  try {
17499
- const fullPath = join10(config.dir, file.path);
17500
- if (!existsSync10(fullPath))
17528
+ const fullPath = join11(config.dir, file.path);
17529
+ if (!existsSync11(fullPath))
17501
17530
  continue;
17502
17531
  rmSync(fullPath, { force: true });
17503
17532
  console.log(source_default.red(` Deleted: ${file.path}`));
@@ -17534,8 +17563,8 @@ Analyzing cascading impact...`));
17534
17563
  }
17535
17564
  let fixedCount = 0;
17536
17565
  for (const [file, methods] of methodsByFile.entries()) {
17537
- const fullPath = join10(config.dir, file);
17538
- if (!existsSync10(fullPath))
17566
+ const fullPath = join11(config.dir, file);
17567
+ if (!existsSync11(fullPath))
17539
17568
  continue;
17540
17569
  const sortedMethods = methods.sort((a, b) => b.line - a.line);
17541
17570
  for (const method of sortedMethods) {
@@ -17608,8 +17637,8 @@ Analyzing cascading impact...`));
17608
17637
  }
17609
17638
  let svcFixedCount = 0;
17610
17639
  for (const [file, methods] of svcByFile.entries()) {
17611
- const fullPath = join10(config.dir, file);
17612
- if (!existsSync10(fullPath))
17640
+ const fullPath = join11(config.dir, file);
17641
+ if (!existsSync11(fullPath))
17613
17642
  continue;
17614
17643
  const sortedMethods = methods.sort((a, b) => b.line - a.line);
17615
17644
  for (const method of sortedMethods) {
@@ -17654,8 +17683,8 @@ async function fixUnusedExports(result, config) {
17654
17683
  for (const [_file, exports] of exportsByFile.entries()) {
17655
17684
  const sortedExports = exports.sort((a, b) => b.line - a.line);
17656
17685
  for (const exp of sortedExports) {
17657
- const fullPath = join10(config.dir, exp.file);
17658
- if (!existsSync10(fullPath))
17686
+ const fullPath = join11(config.dir, exp.file);
17687
+ if (!existsSync11(fullPath))
17659
17688
  continue;
17660
17689
  if (removeExportFromLine(config.dir, exp)) {
17661
17690
  console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
@@ -17952,7 +17981,7 @@ function printTable(summary) {
17952
17981
  function findGitRoot(startDir) {
17953
17982
  let currentDir = startDir;
17954
17983
  while (currentDir !== dirname6(currentDir)) {
17955
- if (existsSync10(join10(currentDir, ".git"))) {
17984
+ if (existsSync11(join11(currentDir, ".git"))) {
17956
17985
  return true;
17957
17986
  }
17958
17987
  currentDir = dirname6(currentDir);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruny",
3
- "version": "1.43.5",
3
+ "version": "1.43.7",
4
4
  "description": "Find and remove unused Next.js API routes & Nest.js Controllers",
5
5
  "type": "module",
6
6
  "files": [