pruny 1.2.2 → 1.2.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.
- package/dist/index.js +184 -22
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7632,12 +7632,12 @@ 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
|
|
7635
|
+
import { dirname as dirname2, join as join7 } from "node:path";
|
|
7636
7636
|
|
|
7637
7637
|
// src/scanner.ts
|
|
7638
|
-
var
|
|
7639
|
-
import { existsSync as existsSync3, readFileSync as
|
|
7640
|
-
import { join as
|
|
7638
|
+
var import_fast_glob4 = __toESM(require_out4(), 1);
|
|
7639
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
|
|
7640
|
+
import { join as join4 } from "node:path";
|
|
7641
7641
|
|
|
7642
7642
|
// src/patterns.ts
|
|
7643
7643
|
var EXPORTED_METHOD_PATTERN = /export\s+(?:async\s+)?(?:function|const)\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)/g;
|
|
@@ -9300,6 +9300,111 @@ function resolveImport(baseDir, impPath, extensions, rootDir) {
|
|
|
9300
9300
|
return null;
|
|
9301
9301
|
}
|
|
9302
9302
|
|
|
9303
|
+
// src/scanners/unused-exports.ts
|
|
9304
|
+
var import_fast_glob3 = __toESM(require_out4(), 1);
|
|
9305
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
9306
|
+
import { join as join3 } from "node:path";
|
|
9307
|
+
var IGNORED_EXPORT_NAMES = new Set([
|
|
9308
|
+
"config",
|
|
9309
|
+
"generateMetadata",
|
|
9310
|
+
"generateStaticParams",
|
|
9311
|
+
"dynamic",
|
|
9312
|
+
"revalidate",
|
|
9313
|
+
"fetchCache",
|
|
9314
|
+
"runtime",
|
|
9315
|
+
"preferredRegion",
|
|
9316
|
+
"metadata",
|
|
9317
|
+
"viewport",
|
|
9318
|
+
"GET",
|
|
9319
|
+
"POST",
|
|
9320
|
+
"PUT",
|
|
9321
|
+
"DELETE",
|
|
9322
|
+
"PATCH",
|
|
9323
|
+
"HEAD",
|
|
9324
|
+
"OPTIONS",
|
|
9325
|
+
"default"
|
|
9326
|
+
]);
|
|
9327
|
+
async function scanUnusedExports(config) {
|
|
9328
|
+
const cwd = config.dir;
|
|
9329
|
+
const extensions = config.extensions;
|
|
9330
|
+
const extGlob = `**/*{${extensions.join(",")}}`;
|
|
9331
|
+
const allFiles = await import_fast_glob3.default(extGlob, {
|
|
9332
|
+
cwd,
|
|
9333
|
+
ignore: [...config.ignore.folders, ...config.ignore.files]
|
|
9334
|
+
});
|
|
9335
|
+
if (allFiles.length === 0) {
|
|
9336
|
+
return { total: 0, used: 0, unused: 0, exports: [] };
|
|
9337
|
+
}
|
|
9338
|
+
const exportMap = new Map;
|
|
9339
|
+
const totalContents = new Map;
|
|
9340
|
+
let allExportsCount = 0;
|
|
9341
|
+
const inlineExportRegex = /^export\s+(?:async\s+)?(?:const|let|var|function|type|interface|enum|class)\s+([a-zA-Z0-9_$]+)/gm;
|
|
9342
|
+
const blockExportRegex = /^export\s*\{([^}]+)\}/gm;
|
|
9343
|
+
for (const file of allFiles) {
|
|
9344
|
+
try {
|
|
9345
|
+
const content = readFileSync3(join3(cwd, file), "utf-8");
|
|
9346
|
+
totalContents.set(file, content);
|
|
9347
|
+
const lines = content.split(`
|
|
9348
|
+
`);
|
|
9349
|
+
for (let i = 0;i < lines.length; i++) {
|
|
9350
|
+
const line = lines[i];
|
|
9351
|
+
inlineExportRegex.lastIndex = 0;
|
|
9352
|
+
let match2;
|
|
9353
|
+
while ((match2 = inlineExportRegex.exec(line)) !== null) {
|
|
9354
|
+
if (addExport(file, match2[1], i + 1)) {
|
|
9355
|
+
allExportsCount++;
|
|
9356
|
+
}
|
|
9357
|
+
}
|
|
9358
|
+
blockExportRegex.lastIndex = 0;
|
|
9359
|
+
while ((match2 = blockExportRegex.exec(line)) !== null) {
|
|
9360
|
+
const names = match2[1].split(",").map((n) => {
|
|
9361
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
9362
|
+
return parts[parts.length - 1];
|
|
9363
|
+
});
|
|
9364
|
+
for (const name of names) {
|
|
9365
|
+
if (addExport(file, name, i + 1)) {
|
|
9366
|
+
allExportsCount++;
|
|
9367
|
+
}
|
|
9368
|
+
}
|
|
9369
|
+
}
|
|
9370
|
+
}
|
|
9371
|
+
} catch {}
|
|
9372
|
+
}
|
|
9373
|
+
function addExport(file, name, line) {
|
|
9374
|
+
if (name && !IGNORED_EXPORT_NAMES.has(name)) {
|
|
9375
|
+
if (!exportMap.has(file))
|
|
9376
|
+
exportMap.set(file, []);
|
|
9377
|
+
exportMap.get(file).push({ name, line, file });
|
|
9378
|
+
return true;
|
|
9379
|
+
}
|
|
9380
|
+
return false;
|
|
9381
|
+
}
|
|
9382
|
+
const unusedExports = [];
|
|
9383
|
+
for (const [file, exports] of exportMap.entries()) {
|
|
9384
|
+
for (const exp of exports) {
|
|
9385
|
+
let isUsed = false;
|
|
9386
|
+
for (const [otherFile, content] of totalContents.entries()) {
|
|
9387
|
+
if (file === otherFile)
|
|
9388
|
+
continue;
|
|
9389
|
+
const referenceRegex = new RegExp(`\\b${exp.name}\\b`);
|
|
9390
|
+
if (referenceRegex.test(content)) {
|
|
9391
|
+
isUsed = true;
|
|
9392
|
+
break;
|
|
9393
|
+
}
|
|
9394
|
+
}
|
|
9395
|
+
if (!isUsed) {
|
|
9396
|
+
unusedExports.push(exp);
|
|
9397
|
+
}
|
|
9398
|
+
}
|
|
9399
|
+
}
|
|
9400
|
+
return {
|
|
9401
|
+
total: allExportsCount,
|
|
9402
|
+
used: allExportsCount - unusedExports.length,
|
|
9403
|
+
unused: unusedExports.length,
|
|
9404
|
+
exports: unusedExports
|
|
9405
|
+
};
|
|
9406
|
+
}
|
|
9407
|
+
|
|
9303
9408
|
// src/scanner.ts
|
|
9304
9409
|
function extractRoutePath(filePath) {
|
|
9305
9410
|
let path2 = filePath.replace(/^src\//, "");
|
|
@@ -9358,12 +9463,12 @@ function checkRouteUsage(routePath, references) {
|
|
|
9358
9463
|
return { used, usedMethods };
|
|
9359
9464
|
}
|
|
9360
9465
|
function getVercelCronPaths(dir) {
|
|
9361
|
-
const vercelPath =
|
|
9466
|
+
const vercelPath = join4(dir, "vercel.json");
|
|
9362
9467
|
if (!existsSync3(vercelPath)) {
|
|
9363
9468
|
return [];
|
|
9364
9469
|
}
|
|
9365
9470
|
try {
|
|
9366
|
-
const content =
|
|
9471
|
+
const content = readFileSync4(vercelPath, "utf-8");
|
|
9367
9472
|
const config = JSON.parse(content);
|
|
9368
9473
|
if (!config.crons) {
|
|
9369
9474
|
return [];
|
|
@@ -9379,12 +9484,12 @@ async function scan(config) {
|
|
|
9379
9484
|
"app/api/**/route.{ts,tsx,js,jsx}",
|
|
9380
9485
|
"src/app/api/**/route.{ts,tsx,js,jsx}"
|
|
9381
9486
|
];
|
|
9382
|
-
const routeFiles = await
|
|
9487
|
+
const routeFiles = await import_fast_glob4.default(routePatterns, {
|
|
9383
9488
|
cwd,
|
|
9384
9489
|
ignore: config.ignore.folders
|
|
9385
9490
|
});
|
|
9386
9491
|
const routes = routeFiles.length > 0 ? routeFiles.map((file) => {
|
|
9387
|
-
const content =
|
|
9492
|
+
const content = readFileSync4(join4(cwd, file), "utf-8");
|
|
9388
9493
|
const methods = extractExportedMethods(content);
|
|
9389
9494
|
return {
|
|
9390
9495
|
path: extractRoutePath(file),
|
|
@@ -9405,16 +9510,16 @@ async function scan(config) {
|
|
|
9405
9510
|
}
|
|
9406
9511
|
}
|
|
9407
9512
|
const extGlob = `**/*{${config.extensions.join(",")}}`;
|
|
9408
|
-
const sourceFiles = await
|
|
9513
|
+
const sourceFiles = await import_fast_glob4.default(extGlob, {
|
|
9409
9514
|
cwd,
|
|
9410
9515
|
ignore: [...config.ignore.folders, ...config.ignore.files]
|
|
9411
9516
|
});
|
|
9412
9517
|
const allReferences = [];
|
|
9413
9518
|
const fileReferences = new Map;
|
|
9414
9519
|
for (const file of sourceFiles) {
|
|
9415
|
-
const filePath =
|
|
9520
|
+
const filePath = join4(cwd, file);
|
|
9416
9521
|
try {
|
|
9417
|
-
const content =
|
|
9522
|
+
const content = readFileSync4(filePath, "utf-8");
|
|
9418
9523
|
const refs = extractApiReferences(content);
|
|
9419
9524
|
if (refs.length > 0) {
|
|
9420
9525
|
fileReferences.set(file, refs);
|
|
@@ -9455,13 +9560,14 @@ async function scan(config) {
|
|
|
9455
9560
|
unused: routes.filter((r) => !r.used).length,
|
|
9456
9561
|
routes,
|
|
9457
9562
|
publicAssets,
|
|
9458
|
-
unusedFiles
|
|
9563
|
+
unusedFiles,
|
|
9564
|
+
unusedExports: await scanUnusedExports(config)
|
|
9459
9565
|
};
|
|
9460
9566
|
}
|
|
9461
9567
|
|
|
9462
9568
|
// src/config.ts
|
|
9463
|
-
import { existsSync as existsSync4, readFileSync as
|
|
9464
|
-
import { join as
|
|
9569
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
9570
|
+
import { join as join5 } from "node:path";
|
|
9465
9571
|
var DEFAULT_CONFIG = {
|
|
9466
9572
|
dir: "./",
|
|
9467
9573
|
ignore: {
|
|
@@ -9492,7 +9598,7 @@ function loadConfig(options) {
|
|
|
9492
9598
|
let fileConfig = {};
|
|
9493
9599
|
if (configPath && existsSync4(configPath)) {
|
|
9494
9600
|
try {
|
|
9495
|
-
const content =
|
|
9601
|
+
const content = readFileSync5(configPath, "utf-8");
|
|
9496
9602
|
fileConfig = JSON.parse(content);
|
|
9497
9603
|
} catch {}
|
|
9498
9604
|
}
|
|
@@ -9519,7 +9625,7 @@ function loadConfig(options) {
|
|
|
9519
9625
|
function findConfigFile(dir) {
|
|
9520
9626
|
const candidates = ["pruny.config.json", ".prunyrc.json", ".prunyrc"];
|
|
9521
9627
|
for (const name of candidates) {
|
|
9522
|
-
const path2 =
|
|
9628
|
+
const path2 = join5(dir, name);
|
|
9523
9629
|
if (existsSync4(path2)) {
|
|
9524
9630
|
return path2;
|
|
9525
9631
|
}
|
|
@@ -9527,6 +9633,32 @@ function findConfigFile(dir) {
|
|
|
9527
9633
|
return null;
|
|
9528
9634
|
}
|
|
9529
9635
|
|
|
9636
|
+
// src/fixer.ts
|
|
9637
|
+
import { readFileSync as readFileSync6, writeFileSync } from "node:fs";
|
|
9638
|
+
import { join as join6 } from "node:path";
|
|
9639
|
+
function removeExportFromLine(rootDir, exp) {
|
|
9640
|
+
const fullPath = join6(rootDir, exp.file);
|
|
9641
|
+
try {
|
|
9642
|
+
const content = readFileSync6(fullPath, "utf-8");
|
|
9643
|
+
const lines = content.split(`
|
|
9644
|
+
`);
|
|
9645
|
+
const lineIndex = exp.line - 1;
|
|
9646
|
+
const originalLine = lines[lineIndex];
|
|
9647
|
+
const exportPrefixRegex = /^(export\s+(?:async\s+)?)/;
|
|
9648
|
+
if (exportPrefixRegex.test(originalLine.trim())) {
|
|
9649
|
+
const newLine = originalLine.replace(/(\s*)export\s+/, "$1");
|
|
9650
|
+
lines[lineIndex] = newLine;
|
|
9651
|
+
writeFileSync(fullPath, lines.join(`
|
|
9652
|
+
`), "utf-8");
|
|
9653
|
+
return true;
|
|
9654
|
+
}
|
|
9655
|
+
return false;
|
|
9656
|
+
} catch (err) {
|
|
9657
|
+
console.error(`Error fixing export in ${exp.file}:`, err);
|
|
9658
|
+
return false;
|
|
9659
|
+
}
|
|
9660
|
+
}
|
|
9661
|
+
|
|
9530
9662
|
// src/index.ts
|
|
9531
9663
|
var program2 = new Command;
|
|
9532
9664
|
program2.name("pruny").description("Find and remove unused Next.js API routes").version("1.0.0").option("-d, --dir <path>", "Target directory to scan", "./").option("--fix", "Delete unused API routes").option("-c, --config <path>", "Path to config file").option("--json", "Output as JSON").option("--no-public", "Disable public assets scanning").option("-v, --verbose", "Show detailed info").action(async (options) => {
|
|
@@ -9535,7 +9667,7 @@ program2.name("pruny").description("Find and remove unused Next.js API routes").
|
|
|
9535
9667
|
config: options.config,
|
|
9536
9668
|
excludePublic: !options.public
|
|
9537
9669
|
});
|
|
9538
|
-
const absoluteDir = config.dir.startsWith("/") ? config.dir :
|
|
9670
|
+
const absoluteDir = config.dir.startsWith("/") ? config.dir : join7(process.cwd(), config.dir);
|
|
9539
9671
|
config.dir = absoluteDir;
|
|
9540
9672
|
if (options.verbose) {
|
|
9541
9673
|
console.log(source_default.dim(`
|
|
@@ -9595,6 +9727,15 @@ Config:`));
|
|
|
9595
9727
|
}
|
|
9596
9728
|
console.log("");
|
|
9597
9729
|
}
|
|
9730
|
+
if (result.unusedExports && result.unusedExports.exports.length > 0) {
|
|
9731
|
+
console.log(source_default.red.bold(`\uD83D\uDD17 Unused Named Exports:
|
|
9732
|
+
`));
|
|
9733
|
+
for (const exp of result.unusedExports.exports) {
|
|
9734
|
+
console.log(source_default.red(` ${exp.name}`));
|
|
9735
|
+
console.log(source_default.dim(` → ${exp.file}:${exp.line}`));
|
|
9736
|
+
}
|
|
9737
|
+
console.log("");
|
|
9738
|
+
}
|
|
9598
9739
|
if (unusedRoutes.length === 0 && partiallyUnusedRoutes.length === 0 && (!result.publicAssets || result.publicAssets.unused === 0)) {
|
|
9599
9740
|
console.log(source_default.green(`✅ Everything is used! Clean as a whistle.
|
|
9600
9741
|
`));
|
|
@@ -9620,6 +9761,14 @@ Config:`));
|
|
|
9620
9761
|
Unused: result.unusedFiles.unused
|
|
9621
9762
|
});
|
|
9622
9763
|
}
|
|
9764
|
+
if (result.unusedExports && result.unusedExports.exports.length > 0) {
|
|
9765
|
+
summary.push({
|
|
9766
|
+
Category: "Exported Items",
|
|
9767
|
+
Total: result.unusedExports.total,
|
|
9768
|
+
Used: result.unusedExports.used,
|
|
9769
|
+
Unused: result.unusedExports.unused
|
|
9770
|
+
});
|
|
9771
|
+
}
|
|
9623
9772
|
console.table(summary);
|
|
9624
9773
|
console.log("");
|
|
9625
9774
|
if (options.verbose) {
|
|
@@ -9646,7 +9795,7 @@ Config:`));
|
|
|
9646
9795
|
console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused routes...
|
|
9647
9796
|
`));
|
|
9648
9797
|
for (const route of unusedRoutes) {
|
|
9649
|
-
const routeDir = dirname2(
|
|
9798
|
+
const routeDir = dirname2(join7(config.dir, route.filePath));
|
|
9650
9799
|
try {
|
|
9651
9800
|
rmSync(routeDir, { recursive: true, force: true });
|
|
9652
9801
|
console.log(source_default.red(` Deleted: ${route.filePath}`));
|
|
@@ -9654,12 +9803,25 @@ Config:`));
|
|
|
9654
9803
|
console.log(source_default.yellow(` Failed to delete: ${route.filePath}`));
|
|
9655
9804
|
}
|
|
9656
9805
|
}
|
|
9657
|
-
|
|
9658
|
-
|
|
9806
|
+
}
|
|
9807
|
+
if (result.unusedExports && result.unusedExports.exports.length > 0) {
|
|
9808
|
+
console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing unused exports (removing "export" keyword)...
|
|
9659
9809
|
`));
|
|
9810
|
+
let fixedCount = 0;
|
|
9811
|
+
for (const exp of result.unusedExports.exports) {
|
|
9812
|
+
if (removeExportFromLine(config.dir, exp)) {
|
|
9813
|
+
console.log(source_default.green(` Fixed: ${exp.name} in ${exp.file}`));
|
|
9814
|
+
fixedCount++;
|
|
9815
|
+
}
|
|
9816
|
+
}
|
|
9817
|
+
if (fixedCount > 0) {
|
|
9818
|
+
console.log(source_default.green(`
|
|
9819
|
+
✅ Removed "export" from ${fixedCount} item(s).
|
|
9820
|
+
`));
|
|
9821
|
+
}
|
|
9660
9822
|
}
|
|
9661
|
-
} else if (unusedRoutes.length > 0) {
|
|
9662
|
-
console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to
|
|
9823
|
+
} else if (unusedRoutes.length > 0 || result.unusedExports && result.unusedExports.exports.length > 0) {
|
|
9824
|
+
console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to automatically clean up unused routes and exports.
|
|
9663
9825
|
`));
|
|
9664
9826
|
}
|
|
9665
9827
|
} catch (_err) {
|