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 +118 -36
- package/dist/workers/file-processor.js +93 -0
- package/package.json +2 -2
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
|
|
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
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
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
|
-
}
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
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 =
|
|
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.
|
|
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",
|