pruny 1.9.2 → 1.10.1
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 +153 -46
- 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);
|
|
@@ -9249,6 +9249,13 @@ async function scanUnusedFiles(config) {
|
|
|
9249
9249
|
} else if (imp.startsWith("@/") || imp.startsWith("~/")) {
|
|
9250
9250
|
const aliasPath = imp.substring(2);
|
|
9251
9251
|
resolvedFile = resolveImport(cwd, aliasPath, extensions, cwd) || resolveImport(join2(cwd, "src"), aliasPath, extensions, cwd) || resolveImport(join2(cwd, "app"), aliasPath, extensions, cwd);
|
|
9252
|
+
if (!resolvedFile) {
|
|
9253
|
+
const pathParts = currentFile.split(/[/\\]/);
|
|
9254
|
+
if (pathParts.length >= 2 && (pathParts[0] === "apps" || pathParts[0] === "packages")) {
|
|
9255
|
+
const projectRoot = join2(cwd, pathParts[0], pathParts[1]);
|
|
9256
|
+
resolvedFile = resolveImport(projectRoot, aliasPath, extensions, cwd) || resolveImport(join2(projectRoot, "src"), aliasPath, extensions, cwd) || resolveImport(join2(projectRoot, "app"), aliasPath, extensions, cwd);
|
|
9257
|
+
}
|
|
9258
|
+
}
|
|
9252
9259
|
}
|
|
9253
9260
|
if (resolvedFile && allFilesSet.has(resolvedFile)) {
|
|
9254
9261
|
usedFiles.add(resolvedFile);
|
|
@@ -9308,6 +9315,9 @@ function resolveImport(baseDir, impPath, extensions, rootDir) {
|
|
|
9308
9315
|
var import_fast_glob3 = __toESM(require_out4(), 1);
|
|
9309
9316
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
9310
9317
|
import { join as join3 } from "node:path";
|
|
9318
|
+
import { Worker } from "node:worker_threads";
|
|
9319
|
+
import { fileURLToPath } from "node:url";
|
|
9320
|
+
import { dirname as dirname2 } from "node:path";
|
|
9311
9321
|
var IGNORED_EXPORT_NAMES = new Set([
|
|
9312
9322
|
"config",
|
|
9313
9323
|
"generateMetadata",
|
|
@@ -9336,6 +9346,71 @@ var IGNORED_EXPORT_NAMES = new Set([
|
|
|
9336
9346
|
"OPTIONS",
|
|
9337
9347
|
"default"
|
|
9338
9348
|
]);
|
|
9349
|
+
async function processFilesInParallel(files, cwd, workerCount) {
|
|
9350
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
9351
|
+
const __dirname2 = dirname2(__filename2);
|
|
9352
|
+
const workerPath = join3(__dirname2, "workers/file-processor.js");
|
|
9353
|
+
const chunkSize = Math.ceil(files.length / workerCount);
|
|
9354
|
+
const chunks = [];
|
|
9355
|
+
for (let i = 0;i < workerCount; i++) {
|
|
9356
|
+
const start = i * chunkSize;
|
|
9357
|
+
const end = Math.min(start + chunkSize, files.length);
|
|
9358
|
+
if (start < files.length) {
|
|
9359
|
+
chunks.push(files.slice(start, end));
|
|
9360
|
+
}
|
|
9361
|
+
}
|
|
9362
|
+
const exportMap = new Map;
|
|
9363
|
+
const contents = new Map;
|
|
9364
|
+
const progressMap = new Map;
|
|
9365
|
+
const workerPromises = chunks.map((chunk, chunkId) => {
|
|
9366
|
+
return new Promise((resolve2, reject) => {
|
|
9367
|
+
const worker = new Worker(workerPath, {
|
|
9368
|
+
workerData: {
|
|
9369
|
+
files: chunk,
|
|
9370
|
+
cwd,
|
|
9371
|
+
chunkId
|
|
9372
|
+
}
|
|
9373
|
+
});
|
|
9374
|
+
worker.on("message", (msg) => {
|
|
9375
|
+
if (msg.type === "progress") {
|
|
9376
|
+
progressMap.set(msg.chunkId, {
|
|
9377
|
+
processed: msg.processed,
|
|
9378
|
+
total: msg.total
|
|
9379
|
+
});
|
|
9380
|
+
let totalProcessed = 0;
|
|
9381
|
+
let totalFiles = 0;
|
|
9382
|
+
for (const [, progress] of progressMap.entries()) {
|
|
9383
|
+
totalProcessed += progress.processed;
|
|
9384
|
+
totalFiles += progress.total;
|
|
9385
|
+
}
|
|
9386
|
+
const percent = Math.round(totalProcessed / totalFiles * 100);
|
|
9387
|
+
process.stdout.write(`\r Progress: ${totalProcessed}/${totalFiles} (${percent}%)...${" ".repeat(10)}`);
|
|
9388
|
+
} else if (msg.type === "complete") {
|
|
9389
|
+
const result = msg.result;
|
|
9390
|
+
const workerExportMap = new Map(Object.entries(result.exports));
|
|
9391
|
+
const workerContents = new Map(Object.entries(result.contents));
|
|
9392
|
+
for (const [file, exports] of workerExportMap.entries()) {
|
|
9393
|
+
exportMap.set(file, exports);
|
|
9394
|
+
}
|
|
9395
|
+
for (const [file, content] of workerContents.entries()) {
|
|
9396
|
+
contents.set(file, content);
|
|
9397
|
+
}
|
|
9398
|
+
worker.terminate();
|
|
9399
|
+
resolve2();
|
|
9400
|
+
}
|
|
9401
|
+
});
|
|
9402
|
+
worker.on("error", reject);
|
|
9403
|
+
worker.on("exit", (code) => {
|
|
9404
|
+
if (code !== 0) {
|
|
9405
|
+
reject(new Error(`Worker stopped with exit code ${code}`));
|
|
9406
|
+
}
|
|
9407
|
+
});
|
|
9408
|
+
});
|
|
9409
|
+
});
|
|
9410
|
+
await Promise.all(workerPromises);
|
|
9411
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
9412
|
+
return { exportMap, contents };
|
|
9413
|
+
}
|
|
9339
9414
|
async function scanUnusedExports(config) {
|
|
9340
9415
|
const cwd = config.dir;
|
|
9341
9416
|
const extensions = config.extensions;
|
|
@@ -9352,46 +9427,60 @@ async function scanUnusedExports(config) {
|
|
|
9352
9427
|
let allExportsCount = 0;
|
|
9353
9428
|
const inlineExportRegex = /^export\s+(?:async\s+)?(?:const|let|var|function|type|interface|enum|class)\s+([a-zA-Z0-9_$]+)/gm;
|
|
9354
9429
|
const blockExportRegex = /^export\s*\{([^}]+)\}/gm;
|
|
9355
|
-
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9362
|
-
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
const content = readFileSync3(join3(cwd, file), "utf-8");
|
|
9430
|
+
const USE_WORKERS = allFiles.length >= 500;
|
|
9431
|
+
const WORKER_COUNT = 2;
|
|
9432
|
+
if (USE_WORKERS) {
|
|
9433
|
+
console.log(`\uD83D\uDCDD Scanning ${allFiles.length} files for exports (using ${WORKER_COUNT} workers)...`);
|
|
9434
|
+
const result = await processFilesInParallel(allFiles, cwd, WORKER_COUNT);
|
|
9435
|
+
for (const [file, exports] of result.exportMap.entries()) {
|
|
9436
|
+
exportMap.set(file, exports);
|
|
9437
|
+
allExportsCount += exports.length;
|
|
9438
|
+
}
|
|
9439
|
+
for (const [file, content] of result.contents.entries()) {
|
|
9366
9440
|
totalContents.set(file, content);
|
|
9367
|
-
|
|
9441
|
+
}
|
|
9442
|
+
} else {
|
|
9443
|
+
console.log(`\uD83D\uDCDD Scanning ${allFiles.length} files for exports...`);
|
|
9444
|
+
let processedFiles = 0;
|
|
9445
|
+
for (const file of allFiles) {
|
|
9446
|
+
try {
|
|
9447
|
+
processedFiles++;
|
|
9448
|
+
if (processedFiles % 10 === 0 || processedFiles === allFiles.length) {
|
|
9449
|
+
const percent = Math.round(processedFiles / allFiles.length * 100);
|
|
9450
|
+
const shortFile = file.length > 50 ? "..." + file.slice(-47) : file;
|
|
9451
|
+
process.stdout.write(`\r Progress: ${processedFiles}/${allFiles.length} (${percent}%) - ${shortFile}${" ".repeat(10)}`);
|
|
9452
|
+
}
|
|
9453
|
+
const content = readFileSync3(join3(cwd, file), "utf-8");
|
|
9454
|
+
totalContents.set(file, content);
|
|
9455
|
+
const lines = content.split(`
|
|
9368
9456
|
`);
|
|
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)) {
|
|
9457
|
+
for (let i = 0;i < lines.length; i++) {
|
|
9458
|
+
const line = lines[i];
|
|
9459
|
+
inlineExportRegex.lastIndex = 0;
|
|
9460
|
+
let match2;
|
|
9461
|
+
while ((match2 = inlineExportRegex.exec(line)) !== null) {
|
|
9462
|
+
if (addExport(file, match2[1], i + 1)) {
|
|
9386
9463
|
allExportsCount++;
|
|
9387
9464
|
}
|
|
9388
9465
|
}
|
|
9466
|
+
blockExportRegex.lastIndex = 0;
|
|
9467
|
+
while ((match2 = blockExportRegex.exec(line)) !== null) {
|
|
9468
|
+
const names = match2[1].split(",").map((n) => {
|
|
9469
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
9470
|
+
return parts[parts.length - 1];
|
|
9471
|
+
});
|
|
9472
|
+
for (const name of names) {
|
|
9473
|
+
if (addExport(file, name, i + 1)) {
|
|
9474
|
+
allExportsCount++;
|
|
9475
|
+
}
|
|
9476
|
+
}
|
|
9477
|
+
}
|
|
9389
9478
|
}
|
|
9390
|
-
}
|
|
9391
|
-
}
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9479
|
+
} catch {}
|
|
9480
|
+
}
|
|
9481
|
+
if (processedFiles > 0) {
|
|
9482
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
9483
|
+
}
|
|
9395
9484
|
}
|
|
9396
9485
|
function addExport(file, name, line) {
|
|
9397
9486
|
if (name && !IGNORED_EXPORT_NAMES.has(name)) {
|
|
@@ -9412,17 +9501,34 @@ async function scanUnusedExports(config) {
|
|
|
9412
9501
|
if (fileContent) {
|
|
9413
9502
|
const lines = fileContent.split(`
|
|
9414
9503
|
`);
|
|
9504
|
+
let fileInMultilineComment = false;
|
|
9505
|
+
let fileInTemplateLiteral = false;
|
|
9415
9506
|
for (let i = 0;i < lines.length; i++) {
|
|
9416
9507
|
if (i === exp.line - 1)
|
|
9417
9508
|
continue;
|
|
9418
9509
|
const line = lines[i];
|
|
9419
9510
|
const trimmed = line.trim();
|
|
9420
|
-
if (trimmed.
|
|
9511
|
+
if (trimmed.includes("/*"))
|
|
9512
|
+
fileInMultilineComment = true;
|
|
9513
|
+
if (trimmed.includes("*/")) {
|
|
9514
|
+
fileInMultilineComment = false;
|
|
9515
|
+
continue;
|
|
9516
|
+
}
|
|
9517
|
+
if (fileInMultilineComment)
|
|
9518
|
+
continue;
|
|
9519
|
+
const backtickCount = (line.match(/`/g) || []).length;
|
|
9520
|
+
if (backtickCount % 2 !== 0) {
|
|
9521
|
+
fileInTemplateLiteral = !fileInTemplateLiteral;
|
|
9522
|
+
}
|
|
9523
|
+
if (fileInTemplateLiteral)
|
|
9421
9524
|
continue;
|
|
9525
|
+
if (trimmed.startsWith("//"))
|
|
9526
|
+
continue;
|
|
9527
|
+
const lineWithoutStrings = line.replace(/'[^']*'/g, "''").replace(/"[^"]*"/g, '""');
|
|
9422
9528
|
const referenceRegex = new RegExp(`\\b${exp.name}\\b`);
|
|
9423
|
-
if (referenceRegex.test(
|
|
9424
|
-
const codePattern = new RegExp(`\\b${exp.name}\\s*[({
|
|
9425
|
-
if (codePattern.test(
|
|
9529
|
+
if (referenceRegex.test(lineWithoutStrings)) {
|
|
9530
|
+
const codePattern = new RegExp(`\\b${exp.name}\\s*[({.,;<>|&)]|\\b${exp.name}\\s*\\)|\\s+${exp.name}\\b`);
|
|
9531
|
+
if (codePattern.test(lineWithoutStrings)) {
|
|
9426
9532
|
usedInternally = true;
|
|
9427
9533
|
break;
|
|
9428
9534
|
}
|
|
@@ -9448,7 +9554,8 @@ async function scanUnusedExports(config) {
|
|
|
9448
9554
|
`);
|
|
9449
9555
|
let inMultilineComment = false;
|
|
9450
9556
|
let inTemplateLiteral = false;
|
|
9451
|
-
for (
|
|
9557
|
+
for (let lineIndex = 0;lineIndex < lines.length; lineIndex++) {
|
|
9558
|
+
const line = lines[lineIndex];
|
|
9452
9559
|
const trimmed = line.trim();
|
|
9453
9560
|
if (trimmed.includes("/*"))
|
|
9454
9561
|
inMultilineComment = true;
|
|
@@ -9466,11 +9573,11 @@ async function scanUnusedExports(config) {
|
|
|
9466
9573
|
continue;
|
|
9467
9574
|
if (trimmed.startsWith("//"))
|
|
9468
9575
|
continue;
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
const
|
|
9473
|
-
if (
|
|
9576
|
+
const lineWithoutStrings = line.replace(/'[^']*'/g, "''").replace(/"[^"]*"/g, '""');
|
|
9577
|
+
if (wordBoundaryPattern.test(lineWithoutStrings)) {
|
|
9578
|
+
const codePattern = new RegExp(`\\b${exp.name}\\s*[({.,;<>|&)]|\\b${exp.name}\\s*\\)|\\s+${exp.name}\\b`);
|
|
9579
|
+
const isMatch = codePattern.test(lineWithoutStrings);
|
|
9580
|
+
if (isMatch) {
|
|
9474
9581
|
isUsed = true;
|
|
9475
9582
|
break;
|
|
9476
9583
|
}
|
|
@@ -10159,7 +10266,7 @@ program2.action(async (options) => {
|
|
|
10159
10266
|
console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused routes...
|
|
10160
10267
|
`));
|
|
10161
10268
|
for (const route of unusedRoutes) {
|
|
10162
|
-
const routeDir =
|
|
10269
|
+
const routeDir = dirname3(join8(config.dir, route.filePath));
|
|
10163
10270
|
try {
|
|
10164
10271
|
rmSync(routeDir, { recursive: true, force: true });
|
|
10165
10272
|
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.1",
|
|
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",
|