pruny 1.22.0 → 1.23.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 +299 -302
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9791,20 +9791,30 @@ async function scan(config) {
|
|
|
9791
9791
|
"apps/**/app/api/**/route.{ts,tsx,js,jsx}",
|
|
9792
9792
|
"packages/**/app/api/**/route.{ts,tsx,js,jsx}"
|
|
9793
9793
|
];
|
|
9794
|
+
let scanCwd = cwd;
|
|
9795
|
+
let activeNextPatterns = nextPatterns;
|
|
9796
|
+
if (config.appSpecificScan) {
|
|
9797
|
+
scanCwd = config.appSpecificScan.appDir;
|
|
9798
|
+
activeNextPatterns = [
|
|
9799
|
+
"app/api/**/route.{ts,tsx,js,jsx}",
|
|
9800
|
+
"src/app/api/**/route.{ts,tsx,js,jsx}"
|
|
9801
|
+
];
|
|
9802
|
+
}
|
|
9794
9803
|
if (config.extraRoutePatterns) {
|
|
9795
|
-
|
|
9804
|
+
activeNextPatterns.push(...config.extraRoutePatterns);
|
|
9796
9805
|
}
|
|
9797
|
-
const nextFiles = await import_fast_glob4.default(
|
|
9798
|
-
cwd,
|
|
9806
|
+
const nextFiles = await import_fast_glob4.default(activeNextPatterns, {
|
|
9807
|
+
cwd: scanCwd,
|
|
9799
9808
|
ignore: config.ignore.folders
|
|
9800
9809
|
});
|
|
9801
9810
|
const nextRoutes = nextFiles.map((file) => {
|
|
9802
|
-
const
|
|
9811
|
+
const fullPath = join4(scanCwd, file);
|
|
9812
|
+
const content = readFileSync4(fullPath, "utf-8");
|
|
9803
9813
|
const { methods, methodLines } = extractExportedMethods(content);
|
|
9804
9814
|
return {
|
|
9805
9815
|
type: "nextjs",
|
|
9806
9816
|
path: extractRoutePath(file),
|
|
9807
|
-
filePath:
|
|
9817
|
+
filePath: fullPath.replace(config.appSpecificScan ? config.appSpecificScan.rootDir + "/" : cwd + "/", ""),
|
|
9808
9818
|
used: false,
|
|
9809
9819
|
references: [],
|
|
9810
9820
|
methods,
|
|
@@ -9814,12 +9824,14 @@ async function scan(config) {
|
|
|
9814
9824
|
});
|
|
9815
9825
|
const nestPatterns = ["**/*.controller.ts"];
|
|
9816
9826
|
const nestFiles = await import_fast_glob4.default(nestPatterns, {
|
|
9817
|
-
cwd,
|
|
9827
|
+
cwd: scanCwd,
|
|
9818
9828
|
ignore: config.ignore.folders
|
|
9819
9829
|
});
|
|
9820
9830
|
const nestRoutes = nestFiles.flatMap((file) => {
|
|
9821
|
-
const
|
|
9822
|
-
|
|
9831
|
+
const fullPath = join4(scanCwd, file);
|
|
9832
|
+
const content = readFileSync4(fullPath, "utf-8");
|
|
9833
|
+
const relativePathFromRoot = fullPath.replace(config.appSpecificScan ? config.appSpecificScan.rootDir + "/" : cwd + "/", "");
|
|
9834
|
+
return extractNestRoutes(relativePathFromRoot, content, config.nestGlobalPrefix);
|
|
9823
9835
|
});
|
|
9824
9836
|
const routes = [...nextRoutes, ...nestRoutes];
|
|
9825
9837
|
const cronPaths = getVercelCronPaths(cwd);
|
|
@@ -9831,15 +9843,16 @@ async function scan(config) {
|
|
|
9831
9843
|
route.unusedMethods = [];
|
|
9832
9844
|
}
|
|
9833
9845
|
}
|
|
9846
|
+
const referenceScanCwd = config.appSpecificScan ? config.appSpecificScan.rootDir : cwd;
|
|
9834
9847
|
const extGlob = `**/*{${config.extensions.join(",")}}`;
|
|
9835
9848
|
const sourceFiles = await import_fast_glob4.default(extGlob, {
|
|
9836
|
-
cwd,
|
|
9849
|
+
cwd: referenceScanCwd,
|
|
9837
9850
|
ignore: [...config.ignore.folders, ...config.ignore.files]
|
|
9838
9851
|
});
|
|
9839
9852
|
const allReferences = [];
|
|
9840
9853
|
const fileReferences = new Map;
|
|
9841
9854
|
for (const file of sourceFiles) {
|
|
9842
|
-
const filePath = join4(
|
|
9855
|
+
const filePath = join4(referenceScanCwd, file);
|
|
9843
9856
|
try {
|
|
9844
9857
|
const content = readFileSync4(filePath, "utf-8");
|
|
9845
9858
|
const refs = extractApiReferences(content);
|
|
@@ -10248,327 +10261,265 @@ program2.command("init").description("Create a default pruny.config.json file").
|
|
|
10248
10261
|
});
|
|
10249
10262
|
program2.action(async (options) => {
|
|
10250
10263
|
const startTime = Date.now();
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
10255
|
-
|
|
10256
|
-
|
|
10257
|
-
|
|
10258
|
-
|
|
10259
|
-
|
|
10260
|
-
|
|
10261
|
-
|
|
10264
|
+
try {
|
|
10265
|
+
const config = loadConfig({
|
|
10266
|
+
dir: options.dir,
|
|
10267
|
+
config: options.config,
|
|
10268
|
+
excludePublic: !options.public
|
|
10269
|
+
});
|
|
10270
|
+
const absoluteDir = config.dir.startsWith("/") ? config.dir : join8(process.cwd(), config.dir);
|
|
10271
|
+
config.dir = absoluteDir;
|
|
10272
|
+
if (options.verbose)
|
|
10273
|
+
console.log("");
|
|
10274
|
+
console.log(source_default.bold(`
|
|
10262
10275
|
\uD83D\uDD0D Scanning for unused API routes...
|
|
10263
10276
|
`));
|
|
10264
|
-
try {
|
|
10265
10277
|
let result = await scan(config);
|
|
10266
|
-
|
|
10267
|
-
if (filePath.startsWith("apps/"))
|
|
10268
|
-
return filePath.split("/").slice(0, 2).join("/");
|
|
10269
|
-
if (filePath.startsWith("packages/"))
|
|
10270
|
-
return filePath.split("/").slice(0, 2).join("/");
|
|
10271
|
-
return "Root";
|
|
10272
|
-
};
|
|
10278
|
+
logScanStats(result);
|
|
10273
10279
|
if (options.filter) {
|
|
10274
|
-
|
|
10275
|
-
console.log(source_default.blue(`
|
|
10276
|
-
\uD83D\uDD0D Filtering results by "${filter2}"...
|
|
10277
|
-
`));
|
|
10278
|
-
const matchesFilter = (path2) => {
|
|
10279
|
-
const lowerPath = path2.toLowerCase();
|
|
10280
|
-
const appName = getAppName(path2).toLowerCase();
|
|
10281
|
-
if (appName.includes(filter2))
|
|
10282
|
-
return true;
|
|
10283
|
-
const segments = lowerPath.split("/");
|
|
10284
|
-
for (const segment of segments) {
|
|
10285
|
-
if (segment === filter2)
|
|
10286
|
-
return true;
|
|
10287
|
-
const withoutExt = segment.replace(/\.[^.]+$/, "");
|
|
10288
|
-
if (withoutExt === filter2)
|
|
10289
|
-
return true;
|
|
10290
|
-
}
|
|
10291
|
-
return lowerPath.includes(filter2);
|
|
10292
|
-
};
|
|
10293
|
-
result.routes = result.routes.filter((r) => matchesFilter(r.filePath));
|
|
10294
|
-
if (result.publicAssets) {
|
|
10295
|
-
result.publicAssets.assets = result.publicAssets.assets.filter((a) => matchesFilter(a.path));
|
|
10296
|
-
result.publicAssets.total = result.publicAssets.assets.length;
|
|
10297
|
-
result.publicAssets.used = result.publicAssets.assets.filter((a) => a.used).length;
|
|
10298
|
-
result.publicAssets.unused = result.publicAssets.assets.filter((a) => !a.used).length;
|
|
10299
|
-
}
|
|
10300
|
-
if (result.unusedFiles) {
|
|
10301
|
-
result.unusedFiles.files = result.unusedFiles.files.filter((f) => matchesFilter(f.path));
|
|
10302
|
-
result.unusedFiles.total = result.unusedFiles.files.length;
|
|
10303
|
-
result.unusedFiles.unused = result.unusedFiles.files.length;
|
|
10304
|
-
}
|
|
10305
|
-
if (result.unusedExports) {
|
|
10306
|
-
result.unusedExports.exports = result.unusedExports.exports.filter((e) => matchesFilter(e.file));
|
|
10307
|
-
result.unusedExports.total = result.unusedExports.exports.length;
|
|
10308
|
-
result.unusedExports.unused = result.unusedExports.exports.length;
|
|
10309
|
-
}
|
|
10280
|
+
filterResults(result, options.filter);
|
|
10310
10281
|
}
|
|
10311
10282
|
if (options.json) {
|
|
10312
10283
|
console.log(JSON.stringify(result, null, 2));
|
|
10313
10284
|
return;
|
|
10314
10285
|
}
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
for (const route of partiallyUnusedRoutes) {
|
|
10320
|
-
console.log(source_default.yellow(` ${route.path}`));
|
|
10321
|
-
console.log(source_default.red(` ❌ Unused: ${route.unusedMethods.join(", ")}`));
|
|
10322
|
-
console.log(source_default.dim(` → ${route.filePath}`));
|
|
10323
|
-
}
|
|
10324
|
-
console.log("");
|
|
10325
|
-
}
|
|
10326
|
-
const unusedRoutes = result.routes.filter((r) => !r.used);
|
|
10327
|
-
if (unusedRoutes.length > 0) {
|
|
10328
|
-
console.log(source_default.red.bold(`❌ Unused API Routes (Fully Unused):
|
|
10329
|
-
`));
|
|
10330
|
-
for (const route of unusedRoutes) {
|
|
10331
|
-
const methods = route.methods.length > 0 ? ` (${route.methods.join(", ")})` : "";
|
|
10332
|
-
console.log(source_default.red(` ${route.path}${source_default.dim(methods)}`));
|
|
10333
|
-
console.log(source_default.dim(` → ${route.filePath}`));
|
|
10334
|
-
}
|
|
10335
|
-
console.log("");
|
|
10336
|
-
}
|
|
10337
|
-
if (result.publicAssets) {
|
|
10338
|
-
const unusedAssets = result.publicAssets.assets.filter((a) => !a.used);
|
|
10339
|
-
if (unusedAssets.length > 0) {
|
|
10340
|
-
console.log(source_default.red.bold(`\uD83D\uDDBC️ Unused Public Assets:
|
|
10341
|
-
`));
|
|
10342
|
-
for (const asset of unusedAssets) {
|
|
10343
|
-
console.log(source_default.red(` ${asset.relativePath}`));
|
|
10344
|
-
console.log(source_default.dim(` → ${asset.path}`));
|
|
10345
|
-
}
|
|
10346
|
-
console.log("");
|
|
10347
|
-
}
|
|
10348
|
-
}
|
|
10349
|
-
if (result.unusedFiles && result.unusedFiles.files.length > 0) {
|
|
10350
|
-
console.log(source_default.red.bold(`\uD83D\uDCC4 Unused Source Files:
|
|
10351
|
-
`));
|
|
10352
|
-
for (const file of result.unusedFiles.files) {
|
|
10353
|
-
const sizeKb = (file.size / 1024).toFixed(1);
|
|
10354
|
-
console.log(source_default.red(` ${file.path} ${source_default.dim(`(${sizeKb} KB)`)}`));
|
|
10355
|
-
}
|
|
10356
|
-
console.log("");
|
|
10357
|
-
}
|
|
10358
|
-
if (result.unusedExports && result.unusedExports.exports.length > 0) {
|
|
10359
|
-
console.log(source_default.red.bold(`\uD83D\uDD17 Unused Named Exports/Methods:
|
|
10286
|
+
if (options.fix) {
|
|
10287
|
+
await handleFixes(result, config, options);
|
|
10288
|
+
} else if (hasUnusedItems(result)) {
|
|
10289
|
+
console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to automatically clean up unused routes, files, and exports.
|
|
10360
10290
|
`));
|
|
10361
|
-
for (const exp of result.unusedExports.exports) {
|
|
10362
|
-
console.log(source_default.red(` ${exp.name}`));
|
|
10363
|
-
console.log(source_default.dim(` → ${exp.file}:${exp.line}`));
|
|
10364
|
-
}
|
|
10365
|
-
console.log("");
|
|
10366
10291
|
}
|
|
10367
|
-
|
|
10368
|
-
|
|
10292
|
+
printSummaryTable(result);
|
|
10293
|
+
} catch (err) {
|
|
10294
|
+
console.error(source_default.red("Error scanning:"), err);
|
|
10295
|
+
process.exit(1);
|
|
10296
|
+
}
|
|
10297
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(2);
|
|
10298
|
+
console.log(source_default.dim(`
|
|
10299
|
+
⏱️ Completed in ${elapsed}s`));
|
|
10300
|
+
});
|
|
10301
|
+
program2.parse();
|
|
10302
|
+
function logScanStats(result) {
|
|
10303
|
+
console.log(source_default.blue.bold("\uD83D\uDCCA Scan Statistics:"));
|
|
10304
|
+
console.log(source_default.blue(` • API Routes: ${result.total}`));
|
|
10305
|
+
if (result.publicAssets) {
|
|
10306
|
+
console.log(source_default.blue(` • Public Assets: ${result.publicAssets.total}`));
|
|
10307
|
+
}
|
|
10308
|
+
if (result.unusedFiles) {
|
|
10309
|
+
console.log(source_default.blue(` • Source Files: ${result.unusedFiles.total}`));
|
|
10310
|
+
}
|
|
10311
|
+
if (result.unusedExports) {
|
|
10312
|
+
console.log(source_default.blue(` • Exported Items: ${result.unusedExports.total}`));
|
|
10313
|
+
}
|
|
10314
|
+
console.log("");
|
|
10315
|
+
}
|
|
10316
|
+
function filterResults(result, filterPattern) {
|
|
10317
|
+
const filter2 = filterPattern.toLowerCase();
|
|
10318
|
+
console.log(source_default.blue(`\uD83D\uDD0D Filtering results by "${filter2}"...
|
|
10369
10319
|
`));
|
|
10320
|
+
const getAppName = (filePath) => {
|
|
10321
|
+
if (filePath.startsWith("apps/"))
|
|
10322
|
+
return filePath.split("/").slice(0, 2).join("/");
|
|
10323
|
+
if (filePath.startsWith("packages/"))
|
|
10324
|
+
return filePath.split("/").slice(0, 2).join("/");
|
|
10325
|
+
return "Root";
|
|
10326
|
+
};
|
|
10327
|
+
const matchesFilter = (path2) => {
|
|
10328
|
+
const lowerPath = path2.toLowerCase();
|
|
10329
|
+
const appName = getAppName(path2).toLowerCase();
|
|
10330
|
+
if (appName.includes(filter2))
|
|
10331
|
+
return true;
|
|
10332
|
+
const segments = lowerPath.split("/");
|
|
10333
|
+
for (const segment of segments) {
|
|
10334
|
+
if (segment === filter2)
|
|
10335
|
+
return true;
|
|
10336
|
+
const withoutExt = segment.replace(/\.[^.]+$/, "");
|
|
10337
|
+
if (withoutExt === filter2)
|
|
10338
|
+
return true;
|
|
10370
10339
|
}
|
|
10371
|
-
|
|
10372
|
-
|
|
10373
|
-
|
|
10374
|
-
|
|
10340
|
+
return lowerPath.includes(filter2);
|
|
10341
|
+
};
|
|
10342
|
+
result.routes = result.routes.filter((r) => matchesFilter(r.filePath));
|
|
10343
|
+
if (result.publicAssets) {
|
|
10344
|
+
result.publicAssets.assets = result.publicAssets.assets.filter((a) => matchesFilter(a.path));
|
|
10345
|
+
result.publicAssets.total = result.publicAssets.assets.length;
|
|
10346
|
+
result.publicAssets.used = result.publicAssets.assets.filter((a) => a.used).length;
|
|
10347
|
+
result.publicAssets.unused = result.publicAssets.assets.filter((a) => !a.used).length;
|
|
10348
|
+
}
|
|
10349
|
+
if (result.unusedFiles) {
|
|
10350
|
+
result.unusedFiles.files = result.unusedFiles.files.filter((f) => matchesFilter(f.path));
|
|
10351
|
+
result.unusedFiles.total = result.unusedFiles.files.length;
|
|
10352
|
+
result.unusedFiles.unused = result.unusedFiles.files.length;
|
|
10353
|
+
}
|
|
10354
|
+
if (result.unusedExports) {
|
|
10355
|
+
result.unusedExports.exports = result.unusedExports.exports.filter((e) => matchesFilter(e.file));
|
|
10356
|
+
result.unusedExports.total = result.unusedExports.exports.length;
|
|
10357
|
+
result.unusedExports.unused = result.unusedExports.exports.length;
|
|
10358
|
+
}
|
|
10359
|
+
result.total = result.routes.length;
|
|
10360
|
+
result.used = result.routes.filter((r) => r.used).length;
|
|
10361
|
+
result.unused = result.routes.filter((r) => !r.used).length;
|
|
10362
|
+
}
|
|
10363
|
+
function hasUnusedItems(result) {
|
|
10364
|
+
const unusedRoutes = result.routes.filter((r) => !r.used).length;
|
|
10365
|
+
const partialRoutes = result.routes.filter((r) => r.used && r.unusedMethods.length > 0).length;
|
|
10366
|
+
const unusedAssets = result.publicAssets ? result.publicAssets.unused : 0;
|
|
10367
|
+
const unusedFiles = result.unusedFiles ? result.unusedFiles.unused : 0;
|
|
10368
|
+
const unusedExports = result.unusedExports ? result.unusedExports.unused : 0;
|
|
10369
|
+
return unusedRoutes > 0 || partialRoutes > 0 || unusedAssets > 0 || unusedFiles > 0 || unusedExports > 0;
|
|
10370
|
+
}
|
|
10371
|
+
async function handleFixes(result, config, options) {
|
|
10372
|
+
let fixedSomething = false;
|
|
10373
|
+
const unusedRoutes = result.routes.filter((r) => !r.used);
|
|
10374
|
+
if (unusedRoutes.length > 0) {
|
|
10375
|
+
console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused routes...
|
|
10375
10376
|
`));
|
|
10376
|
-
|
|
10377
|
-
|
|
10378
|
-
|
|
10379
|
-
|
|
10380
|
-
|
|
10381
|
-
|
|
10382
|
-
|
|
10383
|
-
|
|
10384
|
-
|
|
10385
|
-
|
|
10386
|
-
|
|
10387
|
-
|
|
10388
|
-
|
|
10389
|
-
|
|
10390
|
-
|
|
10391
|
-
|
|
10392
|
-
|
|
10393
|
-
|
|
10394
|
-
|
|
10395
|
-
|
|
10396
|
-
|
|
10397
|
-
|
|
10398
|
-
|
|
10399
|
-
|
|
10400
|
-
|
|
10401
|
-
|
|
10402
|
-
|
|
10403
|
-
|
|
10404
|
-
|
|
10405
|
-
|
|
10406
|
-
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
}
|
|
10412
|
-
}
|
|
10413
|
-
}
|
|
10414
|
-
allMethodsToPrune.sort((a, b) => b.line - a.line);
|
|
10415
|
-
for (const { method, line } of allMethodsToPrune) {
|
|
10416
|
-
if (removeMethodFromRoute(config.dir, filePath, method, line)) {
|
|
10417
|
-
console.log(source_default.green(` Fixed: Removed ${method} from ${filePath}`));
|
|
10418
|
-
fixedSomething = true;
|
|
10419
|
-
}
|
|
10377
|
+
const routesByFile = new Map;
|
|
10378
|
+
for (const r of unusedRoutes) {
|
|
10379
|
+
const list = routesByFile.get(r.filePath) || [];
|
|
10380
|
+
list.push(r);
|
|
10381
|
+
routesByFile.set(r.filePath, list);
|
|
10382
|
+
}
|
|
10383
|
+
for (const [filePath, fileRoutes] of routesByFile) {
|
|
10384
|
+
const fullPath = join8(config.dir, filePath);
|
|
10385
|
+
if (!existsSync7(fullPath))
|
|
10386
|
+
continue;
|
|
10387
|
+
const route = fileRoutes[0];
|
|
10388
|
+
const routeDir = dirname4(fullPath);
|
|
10389
|
+
try {
|
|
10390
|
+
if (route.type === "nextjs") {
|
|
10391
|
+
if (filePath.includes("app/api") || filePath.includes("pages/api")) {
|
|
10392
|
+
rmSync(routeDir, { recursive: true, force: true });
|
|
10393
|
+
console.log(source_default.red(` Deleted Folder: ${routeDir}`));
|
|
10394
|
+
} else {
|
|
10395
|
+
rmSync(fullPath, { force: true });
|
|
10396
|
+
console.log(source_default.red(` Deleted File: ${filePath}`));
|
|
10397
|
+
}
|
|
10398
|
+
fixedSomething = true;
|
|
10399
|
+
} else if (route.type === "nestjs") {
|
|
10400
|
+
const isInternallyUnused = result.unusedFiles?.files.some((f) => f.path === filePath);
|
|
10401
|
+
if (isInternallyUnused || filePath.includes("api/")) {
|
|
10402
|
+
rmSync(fullPath, { force: true });
|
|
10403
|
+
console.log(source_default.red(` Deleted File: ${filePath}`));
|
|
10404
|
+
fixedSomething = true;
|
|
10405
|
+
} else {
|
|
10406
|
+
console.log(source_default.yellow(` Skipped File Deletion (internally used): ${filePath}`));
|
|
10407
|
+
const allMethodsToPrune = [];
|
|
10408
|
+
for (const r of fileRoutes) {
|
|
10409
|
+
for (const m of r.unusedMethods) {
|
|
10410
|
+
if (r.methodLines[m] !== undefined) {
|
|
10411
|
+
allMethodsToPrune.push({ method: m, line: r.methodLines[m] });
|
|
10420
10412
|
}
|
|
10421
10413
|
}
|
|
10422
|
-
} else {
|
|
10423
|
-
rmSync(fullPath, { force: true });
|
|
10424
|
-
console.log(source_default.red(` Deleted File: ${filePath}`));
|
|
10425
|
-
fixedSomething = true;
|
|
10426
10414
|
}
|
|
10427
|
-
|
|
10428
|
-
|
|
10429
|
-
if (
|
|
10430
|
-
|
|
10415
|
+
allMethodsToPrune.sort((a, b) => b.line - a.line);
|
|
10416
|
+
for (const { method, line } of allMethodsToPrune) {
|
|
10417
|
+
if (removeMethodFromRoute(config.dir, filePath, method, line)) {
|
|
10418
|
+
console.log(source_default.green(` Fixed: Removed ${method} from ${filePath}`));
|
|
10419
|
+
fixedSomething = true;
|
|
10420
|
+
}
|
|
10431
10421
|
}
|
|
10432
|
-
} catch (err) {
|
|
10433
|
-
console.log(source_default.yellow(` Failed to fix: ${filePath}`));
|
|
10434
10422
|
}
|
|
10423
|
+
} else {
|
|
10424
|
+
rmSync(fullPath, { force: true });
|
|
10425
|
+
console.log(source_default.red(` Deleted File: ${filePath}`));
|
|
10426
|
+
fixedSomething = true;
|
|
10435
10427
|
}
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
|
|
10440
|
-
console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing partially unused routes...
|
|
10441
|
-
`));
|
|
10442
|
-
for (const route of partiallyRoutes) {
|
|
10443
|
-
const sortedMethods = [...route.unusedMethods].filter((m) => route.methodLines[m] !== undefined).sort((a, b) => route.methodLines[b] - route.methodLines[a]);
|
|
10444
|
-
let fixedCount = 0;
|
|
10445
|
-
for (const method of sortedMethods) {
|
|
10446
|
-
const lineNum = route.methodLines[method];
|
|
10447
|
-
if (removeMethodFromRoute(config.dir, route.filePath, method, lineNum)) {
|
|
10448
|
-
console.log(source_default.green(` Fixed: Removed ${method} from ${route.path}`));
|
|
10449
|
-
fixedCount++;
|
|
10450
|
-
fixedSomething = true;
|
|
10451
|
-
}
|
|
10452
|
-
}
|
|
10453
|
-
if (fixedCount === route.methods.length) {
|
|
10454
|
-
const idx = result.routes.indexOf(route);
|
|
10455
|
-
if (idx !== -1)
|
|
10456
|
-
result.routes.splice(idx, 1);
|
|
10457
|
-
} else {
|
|
10458
|
-
route.unusedMethods = route.unusedMethods.filter((m) => !sortedMethods.includes(m));
|
|
10459
|
-
}
|
|
10428
|
+
for (const r of fileRoutes) {
|
|
10429
|
+
const idx = result.routes.indexOf(r);
|
|
10430
|
+
if (idx !== -1)
|
|
10431
|
+
result.routes.splice(idx, 1);
|
|
10460
10432
|
}
|
|
10461
|
-
|
|
10433
|
+
} catch (err) {
|
|
10434
|
+
console.log(source_default.yellow(` Failed to fix: ${filePath}`));
|
|
10462
10435
|
}
|
|
10463
|
-
|
|
10464
|
-
|
|
10436
|
+
}
|
|
10437
|
+
console.log("");
|
|
10438
|
+
}
|
|
10439
|
+
const partiallyRoutes = result.routes.filter((r) => r.used && r.unusedMethods && r.unusedMethods.length > 0);
|
|
10440
|
+
if (partiallyRoutes.length > 0) {
|
|
10441
|
+
console.log(source_default.yellow.bold(`\uD83D\uDD27 Fixing partially unused routes...
|
|
10465
10442
|
`));
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10474
|
-
|
|
10475
|
-
|
|
10476
|
-
|
|
10477
|
-
|
|
10478
|
-
result.
|
|
10479
|
-
|
|
10480
|
-
|
|
10481
|
-
}
|
|
10482
|
-
|
|
10483
|
-
fixedSomething = await fixUnusedExports(result, config) || fixedSomething;
|
|
10443
|
+
for (const route of partiallyRoutes) {
|
|
10444
|
+
const sortedMethods = [...route.unusedMethods].filter((m) => route.methodLines[m] !== undefined).sort((a, b) => route.methodLines[b] - route.methodLines[a]);
|
|
10445
|
+
let fixedCount = 0;
|
|
10446
|
+
for (const method of sortedMethods) {
|
|
10447
|
+
const lineNum = route.methodLines[method];
|
|
10448
|
+
if (removeMethodFromRoute(config.dir, route.filePath, method, lineNum)) {
|
|
10449
|
+
console.log(source_default.green(` Fixed: Removed ${method} from ${route.path}`));
|
|
10450
|
+
fixedCount++;
|
|
10451
|
+
fixedSomething = true;
|
|
10452
|
+
}
|
|
10453
|
+
}
|
|
10454
|
+
if (fixedCount === route.methods.length) {
|
|
10455
|
+
const idx = result.routes.indexOf(route);
|
|
10456
|
+
if (idx !== -1)
|
|
10457
|
+
result.routes.splice(idx, 1);
|
|
10458
|
+
} else {
|
|
10459
|
+
route.unusedMethods = route.unusedMethods.filter((m) => !sortedMethods.includes(m));
|
|
10484
10460
|
}
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
|
|
10488
|
-
|
|
10489
|
-
|
|
10490
|
-
const filter2 = options.filter.toLowerCase();
|
|
10491
|
-
const matchesFilterPass2 = (path2) => {
|
|
10492
|
-
const lowerPath = path2.toLowerCase();
|
|
10493
|
-
const appName = getAppName(path2).toLowerCase();
|
|
10494
|
-
if (appName.includes(filter2))
|
|
10495
|
-
return true;
|
|
10496
|
-
const segments = lowerPath.split("/");
|
|
10497
|
-
for (const segment of segments) {
|
|
10498
|
-
if (segment === filter2)
|
|
10499
|
-
return true;
|
|
10500
|
-
const withoutExt = segment.replace(/\.[^.]+$/, "");
|
|
10501
|
-
if (withoutExt === filter2)
|
|
10502
|
-
return true;
|
|
10503
|
-
}
|
|
10504
|
-
return lowerPath.includes(filter2);
|
|
10505
|
-
};
|
|
10506
|
-
secondPass.exports = secondPass.exports.filter((e) => matchesFilterPass2(e.file));
|
|
10507
|
-
secondPass.total = secondPass.exports.length;
|
|
10508
|
-
secondPass.unused = secondPass.exports.length;
|
|
10509
|
-
}
|
|
10510
|
-
if (secondPass.unused > 0) {
|
|
10511
|
-
console.log(source_default.yellow(` Found ${secondPass.unused} newly unused items/methods after pruning.
|
|
10461
|
+
}
|
|
10462
|
+
console.log("");
|
|
10463
|
+
}
|
|
10464
|
+
if (result.unusedFiles && result.unusedFiles.files.length > 0) {
|
|
10465
|
+
console.log(source_default.yellow.bold(`\uD83D\uDDD1️ Deleting unused source files...
|
|
10512
10466
|
`));
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10467
|
+
for (const file of result.unusedFiles.files) {
|
|
10468
|
+
try {
|
|
10469
|
+
const fullPath = join8(config.dir, file.path);
|
|
10470
|
+
if (!existsSync7(fullPath))
|
|
10471
|
+
continue;
|
|
10472
|
+
rmSync(fullPath, { force: true });
|
|
10473
|
+
console.log(source_default.red(` Deleted: ${file.path}`));
|
|
10474
|
+
fixedSomething = true;
|
|
10475
|
+
} catch (_err) {
|
|
10476
|
+
console.log(source_default.yellow(` Failed to delete: ${file.path}`));
|
|
10521
10477
|
}
|
|
10522
|
-
} else if (unusedRoutes.length > 0 || result.unusedExports && result.unusedExports.exports.length > 0 || result.unusedFiles && result.unusedFiles.files.length > 0) {
|
|
10523
|
-
console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to automatically clean up unused routes, files, and exports.
|
|
10524
|
-
`));
|
|
10525
10478
|
}
|
|
10526
|
-
|
|
10479
|
+
result.unusedFiles.files = [];
|
|
10480
|
+
result.unusedFiles.unused = 0;
|
|
10481
|
+
console.log("");
|
|
10482
|
+
}
|
|
10483
|
+
if (result.unusedExports && result.unusedExports.exports.length > 0) {
|
|
10484
|
+
fixedSomething = await fixUnusedExports(result, config) || fixedSomething;
|
|
10485
|
+
}
|
|
10486
|
+
if (fixedSomething) {
|
|
10487
|
+
console.log(source_default.cyan.bold(`
|
|
10488
|
+
\uD83D\uDD04 Checking for cascading dead code (newly unused implementation)...`));
|
|
10489
|
+
const secondPass = await scanUnusedExports(config);
|
|
10490
|
+
if (options.filter) {
|
|
10491
|
+
const filter2 = options.filter.toLowerCase();
|
|
10492
|
+
const getAppName = (filePath) => {
|
|
10493
|
+
if (filePath.startsWith("apps/"))
|
|
10494
|
+
return filePath.split("/").slice(0, 2).join("/");
|
|
10495
|
+
if (filePath.startsWith("packages/"))
|
|
10496
|
+
return filePath.split("/").slice(0, 2).join("/");
|
|
10497
|
+
return "Root";
|
|
10498
|
+
};
|
|
10499
|
+
const matchesFilter = (path2) => {
|
|
10500
|
+
const lowerPath = path2.toLowerCase();
|
|
10501
|
+
const appName = getAppName(path2).toLowerCase();
|
|
10502
|
+
if (appName.includes(filter2))
|
|
10503
|
+
return true;
|
|
10504
|
+
return lowerPath.includes(filter2);
|
|
10505
|
+
};
|
|
10506
|
+
secondPass.exports = secondPass.exports.filter((e) => matchesFilter(e.file));
|
|
10507
|
+
secondPass.total = secondPass.exports.length;
|
|
10508
|
+
secondPass.unused = secondPass.exports.length;
|
|
10509
|
+
}
|
|
10510
|
+
if (secondPass.unused > 0) {
|
|
10511
|
+
console.log(source_default.yellow(` Found ${secondPass.unused} newly unused items/methods after pruning.
|
|
10527
10512
|
`));
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
for (const route of result.routes) {
|
|
10531
|
-
const keyAppName = getAppName(route.filePath);
|
|
10532
|
-
const key = `${keyAppName}::${route.type}`;
|
|
10533
|
-
if (!groupedRoutes.has(key)) {
|
|
10534
|
-
groupedRoutes.set(key, { type: route.type, app: keyAppName, routes: [] });
|
|
10535
|
-
}
|
|
10536
|
-
groupedRoutes.get(key).routes.push(route);
|
|
10537
|
-
}
|
|
10538
|
-
const sortedKeys = Array.from(groupedRoutes.keys()).sort((a, b) => {
|
|
10539
|
-
const [appA, typeA] = a.split("::");
|
|
10540
|
-
const [appB, typeB] = b.split("::");
|
|
10541
|
-
if (typeA !== typeB)
|
|
10542
|
-
return typeA === "nextjs" ? -1 : 1;
|
|
10543
|
-
return appA.localeCompare(appB);
|
|
10544
|
-
});
|
|
10545
|
-
for (const key of sortedKeys) {
|
|
10546
|
-
const group = groupedRoutes.get(key);
|
|
10547
|
-
const typeLabel = group.type === "nextjs" ? "Next.js" : "NestJS";
|
|
10548
|
-
summary.push({
|
|
10549
|
-
Category: `${typeLabel} (${group.app})`,
|
|
10550
|
-
Total: group.routes.length,
|
|
10551
|
-
Used: group.routes.filter((r) => r.used).length,
|
|
10552
|
-
Unused: group.routes.filter((r) => !r.used).length
|
|
10553
|
-
});
|
|
10513
|
+
result.unusedExports = secondPass;
|
|
10514
|
+
await fixUnusedExports(result, config);
|
|
10554
10515
|
}
|
|
10555
|
-
if (summary.length === 0)
|
|
10556
|
-
summary.push({ Category: "API Routes", Total: result.total, Used: result.used, Unused: result.unused });
|
|
10557
|
-
if (result.publicAssets)
|
|
10558
|
-
summary.push({ Category: "Public Assets", Total: result.publicAssets.total, Used: result.publicAssets.used, Unused: result.publicAssets.unused });
|
|
10559
|
-
if (result.unusedFiles)
|
|
10560
|
-
summary.push({ Category: "Source Files", Total: result.unusedFiles.total, Used: result.unusedFiles.used, Unused: result.unusedFiles.unused });
|
|
10561
|
-
if (result.unusedExports)
|
|
10562
|
-
summary.push({ Category: "Exported Items", Total: result.unusedExports.total, Used: result.unusedExports.used, Unused: result.unusedExports.unused });
|
|
10563
|
-
console.table(summary);
|
|
10564
|
-
} catch (_err) {
|
|
10565
|
-
console.error(source_default.red("Error scanning:"), _err);
|
|
10566
|
-
process.exit(1);
|
|
10567
10516
|
}
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10517
|
+
if (fixedSomething) {
|
|
10518
|
+
result.unused = result.routes.filter((r) => !r.used).length;
|
|
10519
|
+
result.used = result.routes.filter((r) => r.used).length;
|
|
10520
|
+
result.total = result.routes.length;
|
|
10521
|
+
}
|
|
10522
|
+
}
|
|
10572
10523
|
async function fixUnusedExports(result, config) {
|
|
10573
10524
|
if (!result.unusedExports || result.unusedExports.exports.length === 0)
|
|
10574
10525
|
return false;
|
|
@@ -10606,4 +10557,50 @@ async function fixUnusedExports(result, config) {
|
|
|
10606
10557
|
`));
|
|
10607
10558
|
return fixedSomething;
|
|
10608
10559
|
}
|
|
10609
|
-
|
|
10560
|
+
function printSummaryTable(result) {
|
|
10561
|
+
console.log(source_default.bold(`\uD83D\uDCCA Summary Report
|
|
10562
|
+
`));
|
|
10563
|
+
const summary = [];
|
|
10564
|
+
const groupedRoutes = new Map;
|
|
10565
|
+
const getAppName = (filePath) => {
|
|
10566
|
+
if (filePath.startsWith("apps/"))
|
|
10567
|
+
return filePath.split("/").slice(0, 2).join("/");
|
|
10568
|
+
if (filePath.startsWith("packages/"))
|
|
10569
|
+
return filePath.split("/").slice(0, 2).join("/");
|
|
10570
|
+
return "Root";
|
|
10571
|
+
};
|
|
10572
|
+
for (const route of result.routes) {
|
|
10573
|
+
const keyAppName = getAppName(route.filePath);
|
|
10574
|
+
const key = `${keyAppName}::${route.type}`;
|
|
10575
|
+
if (!groupedRoutes.has(key)) {
|
|
10576
|
+
groupedRoutes.set(key, { type: route.type, app: keyAppName, routes: [] });
|
|
10577
|
+
}
|
|
10578
|
+
groupedRoutes.get(key).routes.push(route);
|
|
10579
|
+
}
|
|
10580
|
+
const sortedKeys = Array.from(groupedRoutes.keys()).sort((a, b) => {
|
|
10581
|
+
const [appA, typeA] = a.split("::");
|
|
10582
|
+
const [appB, typeB] = b.split("::");
|
|
10583
|
+
if (typeA !== typeB)
|
|
10584
|
+
return typeA === "nextjs" ? -1 : 1;
|
|
10585
|
+
return appA.localeCompare(appB);
|
|
10586
|
+
});
|
|
10587
|
+
for (const key of sortedKeys) {
|
|
10588
|
+
const group = groupedRoutes.get(key);
|
|
10589
|
+
const typeLabel = group.type === "nextjs" ? "Next.js" : "NestJS";
|
|
10590
|
+
summary.push({
|
|
10591
|
+
Category: `${typeLabel} (${group.app})`,
|
|
10592
|
+
Total: group.routes.length,
|
|
10593
|
+
Used: group.routes.filter((r) => r.used).length,
|
|
10594
|
+
Unused: group.routes.filter((r) => !r.used).length
|
|
10595
|
+
});
|
|
10596
|
+
}
|
|
10597
|
+
if (summary.length === 0)
|
|
10598
|
+
summary.push({ Category: "API Routes", Total: result.total, Used: result.used, Unused: result.unused });
|
|
10599
|
+
if (result.publicAssets)
|
|
10600
|
+
summary.push({ Category: "Public Assets", Total: result.publicAssets.total, Used: result.publicAssets.used, Unused: result.publicAssets.unused });
|
|
10601
|
+
if (result.unusedFiles)
|
|
10602
|
+
summary.push({ Category: "Source Files", Total: result.unusedFiles.total, Used: result.unusedFiles.used, Unused: result.unusedFiles.unused });
|
|
10603
|
+
if (result.unusedExports)
|
|
10604
|
+
summary.push({ Category: "Exported Items", Total: result.unusedExports.total, Used: result.unusedExports.used, Unused: result.unusedExports.unused });
|
|
10605
|
+
console.table(summary);
|
|
10606
|
+
}
|