pruny 1.9.2 → 1.10.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
@@ -7632,7 +7632,7 @@ var source_default = chalk;
7632
7632
 
7633
7633
  // src/index.ts
7634
7634
  import { rmSync } from "node:fs";
7635
- import { dirname as dirname2, join as join8 } from "node:path";
7635
+ import { dirname as dirname3, join as join8 } from "node:path";
7636
7636
 
7637
7637
  // src/scanner.ts
7638
7638
  var import_fast_glob4 = __toESM(require_out4(), 1);
@@ -9308,6 +9308,9 @@ function resolveImport(baseDir, impPath, extensions, rootDir) {
9308
9308
  var import_fast_glob3 = __toESM(require_out4(), 1);
9309
9309
  import { readFileSync as readFileSync3 } from "node:fs";
9310
9310
  import { join as join3 } from "node:path";
9311
+ import { Worker } from "node:worker_threads";
9312
+ import { fileURLToPath } from "node:url";
9313
+ import { dirname as dirname2 } from "node:path";
9311
9314
  var IGNORED_EXPORT_NAMES = new Set([
9312
9315
  "config",
9313
9316
  "generateMetadata",
@@ -9336,6 +9339,71 @@ var IGNORED_EXPORT_NAMES = new Set([
9336
9339
  "OPTIONS",
9337
9340
  "default"
9338
9341
  ]);
9342
+ async function processFilesInParallel(files, cwd, workerCount) {
9343
+ const __filename2 = fileURLToPath(import.meta.url);
9344
+ const __dirname2 = dirname2(__filename2);
9345
+ const workerPath = join3(__dirname2, "workers/file-processor.js");
9346
+ const chunkSize = Math.ceil(files.length / workerCount);
9347
+ const chunks = [];
9348
+ for (let i = 0;i < workerCount; i++) {
9349
+ const start = i * chunkSize;
9350
+ const end = Math.min(start + chunkSize, files.length);
9351
+ if (start < files.length) {
9352
+ chunks.push(files.slice(start, end));
9353
+ }
9354
+ }
9355
+ const exportMap = new Map;
9356
+ const contents = new Map;
9357
+ const progressMap = new Map;
9358
+ const workerPromises = chunks.map((chunk, chunkId) => {
9359
+ return new Promise((resolve2, reject) => {
9360
+ const worker = new Worker(workerPath, {
9361
+ workerData: {
9362
+ files: chunk,
9363
+ cwd,
9364
+ chunkId
9365
+ }
9366
+ });
9367
+ worker.on("message", (msg) => {
9368
+ if (msg.type === "progress") {
9369
+ progressMap.set(msg.chunkId, {
9370
+ processed: msg.processed,
9371
+ total: msg.total
9372
+ });
9373
+ let totalProcessed = 0;
9374
+ let totalFiles = 0;
9375
+ for (const [, progress] of progressMap.entries()) {
9376
+ totalProcessed += progress.processed;
9377
+ totalFiles += progress.total;
9378
+ }
9379
+ const percent = Math.round(totalProcessed / totalFiles * 100);
9380
+ process.stdout.write(`\r Progress: ${totalProcessed}/${totalFiles} (${percent}%)...${" ".repeat(10)}`);
9381
+ } else if (msg.type === "complete") {
9382
+ const result = msg.result;
9383
+ const workerExportMap = new Map(Object.entries(result.exports));
9384
+ const workerContents = new Map(Object.entries(result.contents));
9385
+ for (const [file, exports] of workerExportMap.entries()) {
9386
+ exportMap.set(file, exports);
9387
+ }
9388
+ for (const [file, content] of workerContents.entries()) {
9389
+ contents.set(file, content);
9390
+ }
9391
+ worker.terminate();
9392
+ resolve2();
9393
+ }
9394
+ });
9395
+ worker.on("error", reject);
9396
+ worker.on("exit", (code) => {
9397
+ if (code !== 0) {
9398
+ reject(new Error(`Worker stopped with exit code ${code}`));
9399
+ }
9400
+ });
9401
+ });
9402
+ });
9403
+ await Promise.all(workerPromises);
9404
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
9405
+ return { exportMap, contents };
9406
+ }
9339
9407
  async function scanUnusedExports(config) {
9340
9408
  const cwd = config.dir;
9341
9409
  const extensions = config.extensions;
@@ -9352,46 +9420,60 @@ async function scanUnusedExports(config) {
9352
9420
  let allExportsCount = 0;
9353
9421
  const inlineExportRegex = /^export\s+(?:async\s+)?(?:const|let|var|function|type|interface|enum|class)\s+([a-zA-Z0-9_$]+)/gm;
9354
9422
  const blockExportRegex = /^export\s*\{([^}]+)\}/gm;
9355
- console.log(`\uD83D\uDCDD Scanning ${allFiles.length} files for exports...`);
9356
- let processedFiles = 0;
9357
- for (const file of allFiles) {
9358
- try {
9359
- processedFiles++;
9360
- if (processedFiles % 10 === 0 || processedFiles === allFiles.length) {
9361
- const percent = Math.round(processedFiles / allFiles.length * 100);
9362
- const shortFile = file.length > 50 ? "..." + file.slice(-47) : file;
9363
- process.stdout.write(`\r Progress: ${processedFiles}/${allFiles.length} (${percent}%) - ${shortFile}${" ".repeat(10)}`);
9364
- }
9365
- const content = readFileSync3(join3(cwd, file), "utf-8");
9423
+ const USE_WORKERS = allFiles.length >= 500;
9424
+ const WORKER_COUNT = 2;
9425
+ if (USE_WORKERS) {
9426
+ console.log(`\uD83D\uDCDD Scanning ${allFiles.length} files for exports (using ${WORKER_COUNT} workers)...`);
9427
+ const result = await processFilesInParallel(allFiles, cwd, WORKER_COUNT);
9428
+ for (const [file, exports] of result.exportMap.entries()) {
9429
+ exportMap.set(file, exports);
9430
+ allExportsCount += exports.length;
9431
+ }
9432
+ for (const [file, content] of result.contents.entries()) {
9366
9433
  totalContents.set(file, content);
9367
- const lines = content.split(`
9434
+ }
9435
+ } else {
9436
+ console.log(`\uD83D\uDCDD Scanning ${allFiles.length} files for exports...`);
9437
+ let processedFiles = 0;
9438
+ for (const file of allFiles) {
9439
+ try {
9440
+ processedFiles++;
9441
+ if (processedFiles % 10 === 0 || processedFiles === allFiles.length) {
9442
+ const percent = Math.round(processedFiles / allFiles.length * 100);
9443
+ const shortFile = file.length > 50 ? "..." + file.slice(-47) : file;
9444
+ process.stdout.write(`\r Progress: ${processedFiles}/${allFiles.length} (${percent}%) - ${shortFile}${" ".repeat(10)}`);
9445
+ }
9446
+ const content = readFileSync3(join3(cwd, file), "utf-8");
9447
+ totalContents.set(file, content);
9448
+ const lines = content.split(`
9368
9449
  `);
9369
- for (let i = 0;i < lines.length; i++) {
9370
- const line = lines[i];
9371
- inlineExportRegex.lastIndex = 0;
9372
- let match2;
9373
- while ((match2 = inlineExportRegex.exec(line)) !== null) {
9374
- if (addExport(file, match2[1], i + 1)) {
9375
- allExportsCount++;
9376
- }
9377
- }
9378
- blockExportRegex.lastIndex = 0;
9379
- while ((match2 = blockExportRegex.exec(line)) !== null) {
9380
- const names = match2[1].split(",").map((n) => {
9381
- const parts = n.trim().split(/\s+as\s+/);
9382
- return parts[parts.length - 1];
9383
- });
9384
- for (const name of names) {
9385
- if (addExport(file, name, i + 1)) {
9450
+ for (let i = 0;i < lines.length; i++) {
9451
+ const line = lines[i];
9452
+ inlineExportRegex.lastIndex = 0;
9453
+ let match2;
9454
+ while ((match2 = inlineExportRegex.exec(line)) !== null) {
9455
+ if (addExport(file, match2[1], i + 1)) {
9386
9456
  allExportsCount++;
9387
9457
  }
9388
9458
  }
9459
+ blockExportRegex.lastIndex = 0;
9460
+ while ((match2 = blockExportRegex.exec(line)) !== null) {
9461
+ const names = match2[1].split(",").map((n) => {
9462
+ const parts = n.trim().split(/\s+as\s+/);
9463
+ return parts[parts.length - 1];
9464
+ });
9465
+ for (const name of names) {
9466
+ if (addExport(file, name, i + 1)) {
9467
+ allExportsCount++;
9468
+ }
9469
+ }
9470
+ }
9389
9471
  }
9390
- }
9391
- } catch {}
9392
- }
9393
- if (processedFiles > 0) {
9394
- process.stdout.write("\r" + " ".repeat(60) + "\r");
9472
+ } catch {}
9473
+ }
9474
+ if (processedFiles > 0) {
9475
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
9476
+ }
9395
9477
  }
9396
9478
  function addExport(file, name, line) {
9397
9479
  if (name && !IGNORED_EXPORT_NAMES.has(name)) {
@@ -10159,7 +10241,7 @@ program2.action(async (options) => {
10159
10241
  console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused routes...
10160
10242
  `));
10161
10243
  for (const route of unusedRoutes) {
10162
- const routeDir = dirname2(join8(config.dir, route.filePath));
10244
+ const routeDir = dirname3(join8(config.dir, route.filePath));
10163
10245
  try {
10164
10246
  rmSync(routeDir, { recursive: true, force: true });
10165
10247
  console.log(source_default.red(` Deleted: ${route.filePath}`));
@@ -0,0 +1,93 @@
1
+ // src/workers/file-processor.ts
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ var IGNORED_EXPORT_NAMES = new Set([
6
+ "metadata",
7
+ "viewport",
8
+ "generateMetadata",
9
+ "generateViewport",
10
+ "generateStaticParams",
11
+ "generateImageMetadata",
12
+ "generateSitemaps",
13
+ "dynamic",
14
+ "dynamicParams",
15
+ "revalidate",
16
+ "fetchCache",
17
+ "runtime",
18
+ "preferredRegion",
19
+ "maxDuration",
20
+ "config",
21
+ "GET",
22
+ "POST",
23
+ "PUT",
24
+ "PATCH",
25
+ "DELETE",
26
+ "HEAD",
27
+ "OPTIONS",
28
+ "default"
29
+ ]);
30
+ var inlineExportRegex = /^export\s+(?:async\s+)?(?:const|let|var|function|type|interface|enum|class)\s+([a-zA-Z0-9_$]+)/gm;
31
+ var blockExportRegex = /^export\s*\{([^}]+)\}/gm;
32
+ if (parentPort && workerData) {
33
+ const { files, cwd, chunkId } = workerData;
34
+ const exportMap = new Map;
35
+ const contents = new Map;
36
+ let processedCount = 0;
37
+ for (const file of files) {
38
+ try {
39
+ const content = readFileSync(join(cwd, file), "utf-8");
40
+ contents.set(file, content);
41
+ const lines = content.split(`
42
+ `);
43
+ for (let i = 0;i < lines.length; i++) {
44
+ const line = lines[i];
45
+ inlineExportRegex.lastIndex = 0;
46
+ let match;
47
+ while ((match = inlineExportRegex.exec(line)) !== null) {
48
+ const name = match[1];
49
+ if (name && !IGNORED_EXPORT_NAMES.has(name)) {
50
+ if (!exportMap.has(file))
51
+ exportMap.set(file, []);
52
+ exportMap.get(file).push({ name, line: i + 1, file });
53
+ }
54
+ }
55
+ blockExportRegex.lastIndex = 0;
56
+ while ((match = blockExportRegex.exec(line)) !== null) {
57
+ const names = match[1].split(",").map((n) => {
58
+ const parts = n.trim().split(/\s+as\s+/);
59
+ return parts[parts.length - 1];
60
+ });
61
+ for (const name of names) {
62
+ if (name && !IGNORED_EXPORT_NAMES.has(name)) {
63
+ if (!exportMap.has(file))
64
+ exportMap.set(file, []);
65
+ exportMap.get(file).push({ name, line: i + 1, file });
66
+ }
67
+ }
68
+ }
69
+ }
70
+ processedCount++;
71
+ if (processedCount % 10 === 0) {
72
+ parentPort.postMessage({
73
+ type: "progress",
74
+ chunkId,
75
+ processed: processedCount,
76
+ total: files.length
77
+ });
78
+ }
79
+ } catch {
80
+ processedCount++;
81
+ }
82
+ }
83
+ const result = {
84
+ chunkId,
85
+ exports: Object.fromEntries(exportMap),
86
+ contents: Object.fromEntries(contents),
87
+ processedCount
88
+ };
89
+ parentPort.postMessage({
90
+ type: "complete",
91
+ result
92
+ });
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pruny",
3
- "version": "1.9.2",
3
+ "version": "1.10.0",
4
4
  "description": "Find and remove unused Next.js API routes & Nest.js Controllers",
5
5
  "type": "module",
6
6
  "files": [
@@ -14,7 +14,7 @@
14
14
  "main": "./dist/index.js",
15
15
  "types": "./dist/index.d.ts",
16
16
  "scripts": {
17
- "build": "bun build ./src/index.ts --outdir ./dist --target node",
17
+ "build": "bun build ./src/index.ts --outdir ./dist --target node && mkdir -p dist/workers && bun build ./src/workers/file-processor.ts --outdir ./dist/workers --target node",
18
18
  "dev": "bun run ./src/index.ts",
19
19
  "lint": "eslint src/**",
20
20
  "audit": "bun run build && node dist/index.js",