pruny 1.23.0 → 1.24.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 +136 -68
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7631,7 +7631,7 @@ var chalkStderr = createChalk({ level: stderrColor ? stderrColor.level : 0 });
|
|
|
7631
7631
|
var source_default = chalk;
|
|
7632
7632
|
|
|
7633
7633
|
// src/index.ts
|
|
7634
|
-
import { rmSync, existsSync as existsSync7 } from "node:fs";
|
|
7634
|
+
import { rmSync, existsSync as existsSync7, readdirSync, lstatSync } from "node:fs";
|
|
7635
7635
|
import { dirname as dirname4, join as join8 } from "node:path";
|
|
7636
7636
|
|
|
7637
7637
|
// src/scanner.ts
|
|
@@ -9193,12 +9193,16 @@ var import_fast_glob2 = __toESM(require_out4(), 1);
|
|
|
9193
9193
|
import { readFileSync as readFileSync2, statSync, existsSync as existsSync2 } from "node:fs";
|
|
9194
9194
|
import { join as join2, dirname, resolve, relative, sep as sep2 } from "node:path";
|
|
9195
9195
|
async function scanUnusedFiles(config) {
|
|
9196
|
-
const
|
|
9196
|
+
const rootDir = config.appSpecificScan ? config.appSpecificScan.rootDir : config.dir;
|
|
9197
|
+
const searchDir = config.appSpecificScan ? config.appSpecificScan.appDir : config.dir;
|
|
9197
9198
|
const extensions = config.extensions;
|
|
9198
9199
|
const extGlob = `**/*{${extensions.join(",")}}`;
|
|
9200
|
+
console.log(`
|
|
9201
|
+
\uD83D\uDD0D Finding source files in: ${searchDir}`);
|
|
9199
9202
|
const allFiles = await import_fast_glob2.default(extGlob, {
|
|
9200
|
-
cwd,
|
|
9201
|
-
ignore: [...config.ignore.folders, ...config.ignore.files]
|
|
9203
|
+
cwd: searchDir,
|
|
9204
|
+
ignore: [...config.ignore.folders, ...config.ignore.files],
|
|
9205
|
+
absolute: true
|
|
9202
9206
|
});
|
|
9203
9207
|
if (allFiles.length === 0) {
|
|
9204
9208
|
return { total: 0, used: 0, unused: 0, files: [] };
|
|
@@ -9245,8 +9249,9 @@ async function scanUnusedFiles(config) {
|
|
|
9245
9249
|
"**/api/index.ts"
|
|
9246
9250
|
];
|
|
9247
9251
|
for (const file of allFiles) {
|
|
9252
|
+
const relPath = relative(searchDir, file);
|
|
9248
9253
|
const isEntry = entryPatterns.some((pattern) => {
|
|
9249
|
-
return minimatch(
|
|
9254
|
+
return minimatch(relPath, pattern, { dot: true });
|
|
9250
9255
|
});
|
|
9251
9256
|
if (isEntry)
|
|
9252
9257
|
entryFiles.add(file);
|
|
@@ -9257,9 +9262,9 @@ async function scanUnusedFiles(config) {
|
|
|
9257
9262
|
const importRegex = /from\s+['"]([^'"]+)['"]|import\(['"]([^'"]+)['"]\)|require\(['"]([^'"]+)['"]\)/g;
|
|
9258
9263
|
while (queue.length > 0) {
|
|
9259
9264
|
const currentFile = queue.shift();
|
|
9260
|
-
const currentDir = dirname(
|
|
9265
|
+
const currentDir = dirname(currentFile);
|
|
9261
9266
|
try {
|
|
9262
|
-
const content = readFileSync2(
|
|
9267
|
+
const content = readFileSync2(currentFile, "utf-8");
|
|
9263
9268
|
let match2;
|
|
9264
9269
|
importRegex.lastIndex = 0;
|
|
9265
9270
|
while ((match2 = importRegex.exec(content)) !== null) {
|
|
@@ -9268,23 +9273,30 @@ async function scanUnusedFiles(config) {
|
|
|
9268
9273
|
continue;
|
|
9269
9274
|
let resolvedFile = null;
|
|
9270
9275
|
if (imp.startsWith(".")) {
|
|
9271
|
-
resolvedFile = resolveImport(currentDir, imp, extensions,
|
|
9276
|
+
resolvedFile = resolveImport(currentDir, imp, extensions, rootDir);
|
|
9272
9277
|
} else if (imp.startsWith("@/") || imp.startsWith("~/")) {
|
|
9273
9278
|
const aliasPath = imp.substring(2);
|
|
9274
|
-
resolvedFile = resolveImport(
|
|
9279
|
+
resolvedFile = resolveImport(rootDir, aliasPath, extensions, rootDir) || resolveImport(join2(rootDir, "src"), aliasPath, extensions, rootDir) || resolveImport(join2(rootDir, "app"), aliasPath, extensions, rootDir);
|
|
9275
9280
|
if (!resolvedFile) {
|
|
9276
|
-
const pathParts = currentFile.split(
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
|
|
9281
|
+
const pathParts = currentFile.split(sep2);
|
|
9282
|
+
const appsIndex = pathParts.lastIndexOf("apps");
|
|
9283
|
+
const packagesIndex = pathParts.lastIndexOf("packages");
|
|
9284
|
+
const index = Math.max(appsIndex, packagesIndex);
|
|
9285
|
+
if (index !== -1 && index + 1 < pathParts.length) {
|
|
9286
|
+
const projectRoot = pathParts.slice(0, index + 2).join(sep2);
|
|
9287
|
+
resolvedFile = resolveImport(projectRoot, aliasPath, extensions, rootDir) || resolveImport(join2(projectRoot, "src"), aliasPath, extensions, rootDir) || resolveImport(join2(projectRoot, "app"), aliasPath, extensions, rootDir);
|
|
9280
9288
|
}
|
|
9281
9289
|
}
|
|
9282
9290
|
}
|
|
9283
|
-
if (resolvedFile
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9291
|
+
if (resolvedFile) {
|
|
9292
|
+
const absoluteResolved = join2(rootDir, resolvedFile);
|
|
9293
|
+
const absoluteTarget = resolve(rootDir, resolvedFile);
|
|
9294
|
+
if (!visited.has(absoluteTarget) && existsSync2(absoluteTarget) && statSync(absoluteTarget).isFile()) {
|
|
9295
|
+
visited.add(absoluteTarget);
|
|
9296
|
+
usedFiles.add(absoluteTarget);
|
|
9297
|
+
queue.push(absoluteTarget);
|
|
9298
|
+
} else {
|
|
9299
|
+
usedFiles.add(absoluteTarget);
|
|
9288
9300
|
}
|
|
9289
9301
|
}
|
|
9290
9302
|
}
|
|
@@ -9293,15 +9305,15 @@ async function scanUnusedFiles(config) {
|
|
|
9293
9305
|
const unusedResults = [];
|
|
9294
9306
|
for (const file of allFiles) {
|
|
9295
9307
|
if (!usedFiles.has(file)) {
|
|
9296
|
-
const
|
|
9308
|
+
const displayPath = relative(rootDir, file);
|
|
9297
9309
|
try {
|
|
9298
|
-
const stats = statSync(
|
|
9310
|
+
const stats = statSync(file);
|
|
9299
9311
|
unusedResults.push({
|
|
9300
|
-
path:
|
|
9312
|
+
path: displayPath,
|
|
9301
9313
|
size: stats.size
|
|
9302
9314
|
});
|
|
9303
9315
|
} catch {
|
|
9304
|
-
unusedResults.push({ path:
|
|
9316
|
+
unusedResults.push({ path: displayPath, size: 0 });
|
|
9305
9317
|
}
|
|
9306
9318
|
}
|
|
9307
9319
|
}
|
|
@@ -9337,7 +9349,7 @@ function resolveImport(baseDir, impPath, extensions, rootDir) {
|
|
|
9337
9349
|
// src/scanners/unused-exports.ts
|
|
9338
9350
|
var import_fast_glob3 = __toESM(require_out4(), 1);
|
|
9339
9351
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
9340
|
-
import { join as join3 } from "node:path";
|
|
9352
|
+
import { join as join3, relative as relative2 } from "node:path";
|
|
9341
9353
|
import { Worker } from "node:worker_threads";
|
|
9342
9354
|
import { fileURLToPath } from "node:url";
|
|
9343
9355
|
import { dirname as dirname2 } from "node:path";
|
|
@@ -9440,42 +9452,66 @@ async function scanUnusedExports(config) {
|
|
|
9440
9452
|
const cwd = config.dir;
|
|
9441
9453
|
const extensions = config.extensions;
|
|
9442
9454
|
const extGlob = `**/*{${extensions.join(",")}}`;
|
|
9443
|
-
const
|
|
9444
|
-
|
|
9445
|
-
|
|
9455
|
+
const candidateCwd = config.appSpecificScan ? config.appSpecificScan.appDir : cwd;
|
|
9456
|
+
const referenceCwd = config.appSpecificScan ? config.appSpecificScan.rootDir : cwd;
|
|
9457
|
+
console.log(`
|
|
9458
|
+
\uD83D\uDD0D Finding export candidates in: ${candidateCwd}`);
|
|
9459
|
+
console.log(` \uD83C\uDF0D Checking usage in global scope: ${referenceCwd}
|
|
9460
|
+
`);
|
|
9461
|
+
const candidateFiles = await import_fast_glob3.default(extGlob, {
|
|
9462
|
+
cwd: candidateCwd,
|
|
9463
|
+
ignore: [...config.ignore.folders, ...config.ignore.files],
|
|
9464
|
+
absolute: true
|
|
9446
9465
|
});
|
|
9447
|
-
if (
|
|
9466
|
+
if (candidateFiles.length === 0) {
|
|
9448
9467
|
return { total: 0, used: 0, unused: 0, exports: [] };
|
|
9449
9468
|
}
|
|
9469
|
+
const referenceFiles = await import_fast_glob3.default(extGlob, {
|
|
9470
|
+
cwd: referenceCwd,
|
|
9471
|
+
ignore: [...config.ignore.folders, ...config.ignore.files],
|
|
9472
|
+
absolute: true
|
|
9473
|
+
});
|
|
9450
9474
|
const exportMap = new Map;
|
|
9451
9475
|
const totalContents = new Map;
|
|
9452
9476
|
let allExportsCount = 0;
|
|
9453
9477
|
const inlineExportRegex = /^export\s+(?:async\s+)?(?:const|let|var|function|type|interface|enum|class)\s+([a-zA-Z0-9_$]+)/gm;
|
|
9454
9478
|
const blockExportRegex = /^export\s*\{([^}]+)\}/gm;
|
|
9455
|
-
const USE_WORKERS =
|
|
9479
|
+
const USE_WORKERS = referenceFiles.length >= 500;
|
|
9456
9480
|
const WORKER_COUNT = 2;
|
|
9457
9481
|
if (USE_WORKERS) {
|
|
9458
|
-
console.log(`\uD83D\uDCDD Scanning ${
|
|
9459
|
-
const result = await processFilesInParallel(
|
|
9460
|
-
for (const [file, exports] of result.exportMap.entries()) {
|
|
9461
|
-
exportMap.set(file, exports);
|
|
9462
|
-
allExportsCount += exports.length;
|
|
9463
|
-
}
|
|
9482
|
+
console.log(`\uD83D\uDCDD Scanning ${candidateFiles.length} candidate files for exports & ${referenceFiles.length} files for usage...`);
|
|
9483
|
+
const result = await processFilesInParallel(referenceFiles, referenceCwd, WORKER_COUNT);
|
|
9464
9484
|
for (const [file, content] of result.contents.entries()) {
|
|
9465
9485
|
totalContents.set(file, content);
|
|
9466
9486
|
}
|
|
9487
|
+
const candidateSet = new Set(candidateFiles);
|
|
9488
|
+
for (const [file, exports] of result.exportMap.entries()) {
|
|
9489
|
+
const absoluteFile = file.startsWith("/") ? file : join3(referenceCwd, file);
|
|
9490
|
+
if (candidateSet.has(absoluteFile)) {
|
|
9491
|
+
const displayPath = relative2(config.dir, absoluteFile);
|
|
9492
|
+
const mappedExports = exports.map((e) => ({ ...e, file: displayPath }));
|
|
9493
|
+
exportMap.set(displayPath, mappedExports);
|
|
9494
|
+
allExportsCount += mappedExports.length;
|
|
9495
|
+
}
|
|
9496
|
+
}
|
|
9467
9497
|
} else {
|
|
9468
|
-
console.log(`\uD83D\uDCDD Scanning ${
|
|
9498
|
+
console.log(`\uD83D\uDCDD Scanning ${candidateFiles.length} files for exports...`);
|
|
9499
|
+
for (const file of referenceFiles) {
|
|
9500
|
+
try {
|
|
9501
|
+
const content = readFileSync3(file, "utf-8");
|
|
9502
|
+
totalContents.set(file, content);
|
|
9503
|
+
} catch {}
|
|
9504
|
+
}
|
|
9469
9505
|
let processedFiles = 0;
|
|
9470
|
-
for (const file of
|
|
9506
|
+
for (const file of candidateFiles) {
|
|
9471
9507
|
try {
|
|
9472
9508
|
processedFiles++;
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
const
|
|
9476
|
-
process.stdout.write(`\r Progress: ${processedFiles}/${
|
|
9509
|
+
const displayPath = relative2(config.dir, file);
|
|
9510
|
+
if (processedFiles % 10 === 0 || processedFiles === candidateFiles.length) {
|
|
9511
|
+
const percent = Math.round(processedFiles / candidateFiles.length * 100);
|
|
9512
|
+
process.stdout.write(`\r Progress: ${processedFiles}/${candidateFiles.length} (${percent}%)`);
|
|
9477
9513
|
}
|
|
9478
|
-
const content = readFileSync3(
|
|
9514
|
+
const content = totalContents.get(file) || readFileSync3(file, "utf-8");
|
|
9479
9515
|
totalContents.set(file, content);
|
|
9480
9516
|
const isService = file.endsWith(".service.ts") || file.endsWith(".service.tsx");
|
|
9481
9517
|
const lines = content.split(`
|
|
@@ -9485,7 +9521,7 @@ async function scanUnusedExports(config) {
|
|
|
9485
9521
|
inlineExportRegex.lastIndex = 0;
|
|
9486
9522
|
let match2;
|
|
9487
9523
|
while ((match2 = inlineExportRegex.exec(line)) !== null) {
|
|
9488
|
-
if (addExport(
|
|
9524
|
+
if (addExport(displayPath, match2[1], i + 1)) {
|
|
9489
9525
|
allExportsCount++;
|
|
9490
9526
|
}
|
|
9491
9527
|
}
|
|
@@ -9496,7 +9532,7 @@ async function scanUnusedExports(config) {
|
|
|
9496
9532
|
return parts[parts.length - 1];
|
|
9497
9533
|
});
|
|
9498
9534
|
for (const name of names) {
|
|
9499
|
-
if (addExport(
|
|
9535
|
+
if (addExport(displayPath, name, i + 1)) {
|
|
9500
9536
|
allExportsCount++;
|
|
9501
9537
|
}
|
|
9502
9538
|
}
|
|
@@ -9506,9 +9542,9 @@ async function scanUnusedExports(config) {
|
|
|
9506
9542
|
while ((match2 = classMethodRegex.exec(line)) !== null) {
|
|
9507
9543
|
const name = match2[1];
|
|
9508
9544
|
if (name && !NEST_LIFECYCLE_METHODS.has(name) && !IGNORED_EXPORT_NAMES.has(name)) {
|
|
9509
|
-
const existing = exportMap.get(
|
|
9545
|
+
const existing = exportMap.get(displayPath)?.find((e) => e.name === name);
|
|
9510
9546
|
if (!existing) {
|
|
9511
|
-
if (addExport(
|
|
9547
|
+
if (addExport(displayPath, name, i + 1)) {
|
|
9512
9548
|
allExportsCount++;
|
|
9513
9549
|
}
|
|
9514
9550
|
}
|
|
@@ -9903,7 +9939,7 @@ async function scan(config) {
|
|
|
9903
9939
|
// src/config.ts
|
|
9904
9940
|
var import_fast_glob5 = __toESM(require_out4(), 1);
|
|
9905
9941
|
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "node:fs";
|
|
9906
|
-
import { join as join5, resolve as resolve2, relative as
|
|
9942
|
+
import { join as join5, resolve as resolve2, relative as relative3, dirname as dirname3 } from "node:path";
|
|
9907
9943
|
var DEFAULT_CONFIG = {
|
|
9908
9944
|
dir: "./",
|
|
9909
9945
|
ignore: {
|
|
@@ -9977,7 +10013,7 @@ function loadConfig(options) {
|
|
|
9977
10013
|
const content = readFileSync5(configPath, "utf-8");
|
|
9978
10014
|
const config = JSON.parse(content);
|
|
9979
10015
|
const configDir = dirname3(configPath);
|
|
9980
|
-
const relDir =
|
|
10016
|
+
const relDir = relative3(cwd, configDir);
|
|
9981
10017
|
const prefixPattern = (p) => {
|
|
9982
10018
|
if (p.startsWith("**/") || p.startsWith("/") || !relDir)
|
|
9983
10019
|
return p;
|
|
@@ -10262,34 +10298,66 @@ program2.command("init").description("Create a default pruny.config.json file").
|
|
|
10262
10298
|
program2.action(async (options) => {
|
|
10263
10299
|
const startTime = Date.now();
|
|
10264
10300
|
try {
|
|
10265
|
-
const
|
|
10301
|
+
const baseConfig = loadConfig({
|
|
10266
10302
|
dir: options.dir,
|
|
10267
10303
|
config: options.config,
|
|
10268
10304
|
excludePublic: !options.public
|
|
10269
10305
|
});
|
|
10270
|
-
const absoluteDir =
|
|
10271
|
-
|
|
10306
|
+
const absoluteDir = baseConfig.dir.startsWith("/") ? baseConfig.dir : join8(process.cwd(), baseConfig.dir);
|
|
10307
|
+
baseConfig.dir = absoluteDir;
|
|
10272
10308
|
if (options.verbose)
|
|
10273
10309
|
console.log("");
|
|
10274
10310
|
console.log(source_default.bold(`
|
|
10275
10311
|
\uD83D\uDD0D Scanning for unused API routes...
|
|
10276
10312
|
`));
|
|
10277
|
-
|
|
10278
|
-
|
|
10279
|
-
|
|
10280
|
-
|
|
10281
|
-
|
|
10282
|
-
|
|
10283
|
-
|
|
10284
|
-
|
|
10285
|
-
|
|
10286
|
-
|
|
10287
|
-
|
|
10288
|
-
|
|
10289
|
-
|
|
10313
|
+
const appsDir = join8(absoluteDir, "apps");
|
|
10314
|
+
const isMonorepo = existsSync7(appsDir) && lstatSync(appsDir).isDirectory();
|
|
10315
|
+
const appsToScan = [];
|
|
10316
|
+
if (isMonorepo) {
|
|
10317
|
+
const apps = readdirSync(appsDir);
|
|
10318
|
+
for (const app of apps) {
|
|
10319
|
+
const appPath = join8(appsDir, app);
|
|
10320
|
+
if (lstatSync(appPath).isDirectory()) {
|
|
10321
|
+
appsToScan.push(app);
|
|
10322
|
+
}
|
|
10323
|
+
}
|
|
10324
|
+
console.log(source_default.bold(`
|
|
10325
|
+
\uD83C\uDFE2 Monorepo Detected. Found ${appsToScan.length} apps: ${appsToScan.join(", ")}
|
|
10290
10326
|
`));
|
|
10327
|
+
} else {
|
|
10328
|
+
appsToScan.push("root");
|
|
10329
|
+
}
|
|
10330
|
+
for (const appName of appsToScan) {
|
|
10331
|
+
const currentConfig = { ...baseConfig };
|
|
10332
|
+
let appLabel = "Root App";
|
|
10333
|
+
let appDir = absoluteDir;
|
|
10334
|
+
if (isMonorepo) {
|
|
10335
|
+
appLabel = `App: ${appName}`;
|
|
10336
|
+
appDir = join8(absoluteDir, "apps", appName);
|
|
10337
|
+
currentConfig.appSpecificScan = {
|
|
10338
|
+
appDir,
|
|
10339
|
+
rootDir: absoluteDir
|
|
10340
|
+
};
|
|
10341
|
+
}
|
|
10342
|
+
console.log(source_default.bold.magenta(`
|
|
10343
|
+
\uD83D\uDC49 Scanning ${appLabel}...`));
|
|
10344
|
+
let result = await scan(currentConfig);
|
|
10345
|
+
logScanStats(result, appLabel);
|
|
10346
|
+
if (options.filter) {
|
|
10347
|
+
filterResults(result, options.filter);
|
|
10348
|
+
}
|
|
10349
|
+
if (options.json) {
|
|
10350
|
+
console.log(JSON.stringify(result, null, 2));
|
|
10351
|
+
} else {
|
|
10352
|
+
if (options.fix) {
|
|
10353
|
+
await handleFixes(result, currentConfig, options);
|
|
10354
|
+
} else if (hasUnusedItems(result)) {
|
|
10355
|
+
console.log(source_default.dim(`\uD83D\uDCA1 Run with --fix to clean up.
|
|
10356
|
+
`));
|
|
10357
|
+
}
|
|
10358
|
+
printSummaryTable(result, appLabel);
|
|
10359
|
+
}
|
|
10291
10360
|
}
|
|
10292
|
-
printSummaryTable(result);
|
|
10293
10361
|
} catch (err) {
|
|
10294
10362
|
console.error(source_default.red("Error scanning:"), err);
|
|
10295
10363
|
process.exit(1);
|
|
@@ -10299,8 +10367,8 @@ program2.action(async (options) => {
|
|
|
10299
10367
|
⏱️ Completed in ${elapsed}s`));
|
|
10300
10368
|
});
|
|
10301
10369
|
program2.parse();
|
|
10302
|
-
function logScanStats(result) {
|
|
10303
|
-
console.log(source_default.blue.bold(
|
|
10370
|
+
function logScanStats(result, context) {
|
|
10371
|
+
console.log(source_default.blue.bold(`\uD83D\uDCCA stats for ${context}:`));
|
|
10304
10372
|
console.log(source_default.blue(` • API Routes: ${result.total}`));
|
|
10305
10373
|
if (result.publicAssets) {
|
|
10306
10374
|
console.log(source_default.blue(` • Public Assets: ${result.publicAssets.total}`));
|
|
@@ -10557,8 +10625,8 @@ async function fixUnusedExports(result, config) {
|
|
|
10557
10625
|
`));
|
|
10558
10626
|
return fixedSomething;
|
|
10559
10627
|
}
|
|
10560
|
-
function printSummaryTable(result) {
|
|
10561
|
-
console.log(source_default.bold(`\uD83D\uDCCA Summary Report
|
|
10628
|
+
function printSummaryTable(result, context) {
|
|
10629
|
+
console.log(source_default.bold(`\uD83D\uDCCA Summary Report for ${context}
|
|
10562
10630
|
`));
|
|
10563
10631
|
const summary = [];
|
|
10564
10632
|
const groupedRoutes = new Map;
|