pruny 1.36.0 → 1.37.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 +305 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,15 +5,29 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
@@ -12553,8 +12567,8 @@ import { rmSync, existsSync as existsSync9, readdirSync, lstatSync, writeFileSyn
|
|
|
12553
12567
|
import { dirname as dirname5, join as join10, relative as relative5, resolve as resolve3 } from "node:path";
|
|
12554
12568
|
|
|
12555
12569
|
// src/scanner.ts
|
|
12556
|
-
var
|
|
12557
|
-
import { existsSync as existsSync6, readFileSync as
|
|
12570
|
+
var import_fast_glob10 = __toESM(require_out4(), 1);
|
|
12571
|
+
import { existsSync as existsSync6, readFileSync as readFileSync9 } from "node:fs";
|
|
12558
12572
|
import { join as join7 } from "node:path";
|
|
12559
12573
|
|
|
12560
12574
|
// src/patterns.ts
|
|
@@ -15715,6 +15729,176 @@ async function scanUnusedServices(config) {
|
|
|
15715
15729
|
return { total: methods.length, methods };
|
|
15716
15730
|
}
|
|
15717
15731
|
|
|
15732
|
+
// src/scanners/broken-links.ts
|
|
15733
|
+
var import_fast_glob9 = __toESM(require_out4(), 1);
|
|
15734
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
15735
|
+
var LINK_PATTERNS = [
|
|
15736
|
+
/<Link\s+[^>]*href\s*=\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
|
|
15737
|
+
/router\.(push|replace)\s*\(\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
|
|
15738
|
+
/(?:redirect|permanentRedirect)\s*\(\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
|
|
15739
|
+
/href\s*:\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
|
|
15740
|
+
/<a\s+[^>]*href\s*=\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
|
|
15741
|
+
/revalidatePath\s*\(\s*['"`](\/[^'"`\s{}$]+)['"`]/g,
|
|
15742
|
+
/pathname\s*===?\s*['"`](\/[^'"`\s{}$]+)['"`]/g
|
|
15743
|
+
];
|
|
15744
|
+
function extractPath(match2) {
|
|
15745
|
+
if (match2[2] && match2[2].startsWith("/"))
|
|
15746
|
+
return match2[2];
|
|
15747
|
+
if (match2[1] && match2[1].startsWith("/"))
|
|
15748
|
+
return match2[1];
|
|
15749
|
+
return null;
|
|
15750
|
+
}
|
|
15751
|
+
function shouldSkipPath(path2) {
|
|
15752
|
+
if (/^https?:\/\//.test(path2))
|
|
15753
|
+
return true;
|
|
15754
|
+
if (/^mailto:/.test(path2))
|
|
15755
|
+
return true;
|
|
15756
|
+
if (/^tel:/.test(path2))
|
|
15757
|
+
return true;
|
|
15758
|
+
if (path2 === "#" || path2.startsWith("#"))
|
|
15759
|
+
return true;
|
|
15760
|
+
if (path2.startsWith("/api/") || path2 === "/api")
|
|
15761
|
+
return true;
|
|
15762
|
+
if (path2 === "/_next" || path2.startsWith("/_next/"))
|
|
15763
|
+
return true;
|
|
15764
|
+
return false;
|
|
15765
|
+
}
|
|
15766
|
+
function cleanPath(path2) {
|
|
15767
|
+
return path2.replace(/[?#].*$/, "").replace(/\/$/, "") || "/";
|
|
15768
|
+
}
|
|
15769
|
+
function filePathToRoute(filePath) {
|
|
15770
|
+
let path2 = filePath.replace(/^src\//, "").replace(/^apps\/[^/]+\//, "").replace(/^packages\/[^/]+\//, "");
|
|
15771
|
+
path2 = path2.replace(/^app\//, "").replace(/^pages\//, "");
|
|
15772
|
+
path2 = path2.replace(/\/page\.(ts|tsx|js|jsx|md|mdx)$/, "");
|
|
15773
|
+
path2 = path2.replace(/\.(ts|tsx|js|jsx)$/, "");
|
|
15774
|
+
path2 = path2.replace(/\/index$/, "");
|
|
15775
|
+
const segments = path2.split("/").filter((segment) => {
|
|
15776
|
+
if (/^\([^.)][^)]*\)$/.test(segment))
|
|
15777
|
+
return false;
|
|
15778
|
+
if (segment.startsWith("@"))
|
|
15779
|
+
return false;
|
|
15780
|
+
if (/^\(\.+\)/.test(segment))
|
|
15781
|
+
return false;
|
|
15782
|
+
return true;
|
|
15783
|
+
});
|
|
15784
|
+
return "/" + segments.join("/");
|
|
15785
|
+
}
|
|
15786
|
+
function matchesRoute(refPath, routes, routeSegments) {
|
|
15787
|
+
const cleaned = cleanPath(refPath);
|
|
15788
|
+
if (routes.has(cleaned))
|
|
15789
|
+
return true;
|
|
15790
|
+
const refSegments = cleaned.split("/").filter(Boolean);
|
|
15791
|
+
for (const routeSeg of routeSegments) {
|
|
15792
|
+
if (matchSegments(refSegments, routeSeg))
|
|
15793
|
+
return true;
|
|
15794
|
+
}
|
|
15795
|
+
return false;
|
|
15796
|
+
}
|
|
15797
|
+
function matchSegments(refSegments, routeSegments) {
|
|
15798
|
+
let ri = 0;
|
|
15799
|
+
let si = 0;
|
|
15800
|
+
while (ri < refSegments.length && si < routeSegments.length) {
|
|
15801
|
+
const routeSeg = routeSegments[si];
|
|
15802
|
+
if (/^\[\[?\.\.\./.test(routeSeg))
|
|
15803
|
+
return true;
|
|
15804
|
+
if (/^\[.+\]$/.test(routeSeg)) {
|
|
15805
|
+
ri++;
|
|
15806
|
+
si++;
|
|
15807
|
+
continue;
|
|
15808
|
+
}
|
|
15809
|
+
if (refSegments[ri].toLowerCase() !== routeSeg.toLowerCase())
|
|
15810
|
+
return false;
|
|
15811
|
+
ri++;
|
|
15812
|
+
si++;
|
|
15813
|
+
}
|
|
15814
|
+
return ri === refSegments.length && si === routeSegments.length;
|
|
15815
|
+
}
|
|
15816
|
+
async function scanBrokenLinks(config) {
|
|
15817
|
+
const appDir = config.appSpecificScan ? config.appSpecificScan.appDir : config.dir;
|
|
15818
|
+
const pagePatterns = [
|
|
15819
|
+
"app/**/page.{ts,tsx,js,jsx,md,mdx}",
|
|
15820
|
+
"src/app/**/page.{ts,tsx,js,jsx,md,mdx}",
|
|
15821
|
+
"pages/**/*.{ts,tsx,js,jsx}",
|
|
15822
|
+
"src/pages/**/*.{ts,tsx,js,jsx}"
|
|
15823
|
+
];
|
|
15824
|
+
const pageFiles = await import_fast_glob9.default(pagePatterns, {
|
|
15825
|
+
cwd: appDir,
|
|
15826
|
+
ignore: [...config.ignore.folders, "**/node_modules/**", "**/_*/**"]
|
|
15827
|
+
});
|
|
15828
|
+
if (pageFiles.length === 0) {
|
|
15829
|
+
return { total: 0, links: [] };
|
|
15830
|
+
}
|
|
15831
|
+
const knownRoutes = new Set;
|
|
15832
|
+
const routeSegmentsList = [];
|
|
15833
|
+
knownRoutes.add("/");
|
|
15834
|
+
for (const file of pageFiles) {
|
|
15835
|
+
const route = filePathToRoute(file);
|
|
15836
|
+
knownRoutes.add(route);
|
|
15837
|
+
const segments = route.split("/").filter(Boolean);
|
|
15838
|
+
if (segments.some((s) => s.startsWith("["))) {
|
|
15839
|
+
routeSegmentsList.push(segments);
|
|
15840
|
+
}
|
|
15841
|
+
}
|
|
15842
|
+
if (process.env.DEBUG_PRUNY) {
|
|
15843
|
+
console.log(`[DEBUG] Known routes: ${Array.from(knownRoutes).join(", ")}`);
|
|
15844
|
+
}
|
|
15845
|
+
const refDir = config.appSpecificScan ? config.appSpecificScan.rootDir : config.dir;
|
|
15846
|
+
const ignore = [...config.ignore.folders, ...config.ignore.files, "**/node_modules/**"];
|
|
15847
|
+
const extensions = config.extensions;
|
|
15848
|
+
const globPattern = `**/*{${extensions.join(",")}}`;
|
|
15849
|
+
const sourceFiles = await import_fast_glob9.default(globPattern, {
|
|
15850
|
+
cwd: refDir,
|
|
15851
|
+
ignore,
|
|
15852
|
+
absolute: true
|
|
15853
|
+
});
|
|
15854
|
+
const brokenMap = new Map;
|
|
15855
|
+
for (const file of sourceFiles) {
|
|
15856
|
+
try {
|
|
15857
|
+
const content = readFileSync8(file, "utf-8");
|
|
15858
|
+
for (const pattern of LINK_PATTERNS) {
|
|
15859
|
+
pattern.lastIndex = 0;
|
|
15860
|
+
let match2;
|
|
15861
|
+
while ((match2 = pattern.exec(content)) !== null) {
|
|
15862
|
+
const rawPath = extractPath(match2);
|
|
15863
|
+
if (!rawPath)
|
|
15864
|
+
continue;
|
|
15865
|
+
if (shouldSkipPath(rawPath))
|
|
15866
|
+
continue;
|
|
15867
|
+
const cleaned = cleanPath(rawPath);
|
|
15868
|
+
if (!cleaned || cleaned === "/")
|
|
15869
|
+
continue;
|
|
15870
|
+
if (!matchesRoute(cleaned, knownRoutes, routeSegmentsList)) {
|
|
15871
|
+
const isIgnored = config.ignore.routes.some((ignorePath) => {
|
|
15872
|
+
const pattern2 = ignorePath.replace(/\*/g, ".*");
|
|
15873
|
+
return new RegExp(`^${pattern2}$`).test(cleaned);
|
|
15874
|
+
});
|
|
15875
|
+
if (isIgnored)
|
|
15876
|
+
continue;
|
|
15877
|
+
const lineNumber = content.substring(0, match2.index).split(`
|
|
15878
|
+
`).length;
|
|
15879
|
+
if (!brokenMap.has(cleaned)) {
|
|
15880
|
+
brokenMap.set(cleaned, new Set);
|
|
15881
|
+
}
|
|
15882
|
+
brokenMap.get(cleaned).add(`${file}:${lineNumber}`);
|
|
15883
|
+
}
|
|
15884
|
+
}
|
|
15885
|
+
}
|
|
15886
|
+
} catch (_e) {}
|
|
15887
|
+
}
|
|
15888
|
+
const links = [];
|
|
15889
|
+
for (const [path2, refs] of brokenMap.entries()) {
|
|
15890
|
+
links.push({
|
|
15891
|
+
path: path2,
|
|
15892
|
+
references: Array.from(refs).sort()
|
|
15893
|
+
});
|
|
15894
|
+
}
|
|
15895
|
+
links.sort((a, b) => b.references.length - a.references.length);
|
|
15896
|
+
return {
|
|
15897
|
+
total: links.length,
|
|
15898
|
+
links
|
|
15899
|
+
};
|
|
15900
|
+
}
|
|
15901
|
+
|
|
15718
15902
|
// src/scanner.ts
|
|
15719
15903
|
function extractRoutePath(filePath) {
|
|
15720
15904
|
let path2 = filePath.replace(/^src\//, "").replace(/^apps\/[^/]+\//, "").replace(/^packages\/[^/]+\//, "");
|
|
@@ -15820,19 +16004,19 @@ function extractNestMethodName(content) {
|
|
|
15820
16004
|
return "";
|
|
15821
16005
|
}
|
|
15822
16006
|
function shouldIgnore(path2, ignorePatterns) {
|
|
15823
|
-
const
|
|
16007
|
+
const cleanPath2 = path2.replace(/\\/g, "/").replace(/^\//, "").replace(/^\.\//, "");
|
|
15824
16008
|
return ignorePatterns.some((pattern) => {
|
|
15825
16009
|
let cleanPattern = pattern.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
15826
16010
|
const isAbsolute4 = cleanPattern.startsWith("/");
|
|
15827
16011
|
if (isAbsolute4)
|
|
15828
16012
|
cleanPattern = cleanPattern.substring(1);
|
|
15829
|
-
if (minimatch(
|
|
16013
|
+
if (minimatch(cleanPath2, cleanPattern))
|
|
15830
16014
|
return true;
|
|
15831
16015
|
const folderPattern = cleanPattern.endsWith("/") ? cleanPattern : cleanPattern + "/";
|
|
15832
|
-
if (
|
|
16016
|
+
if (cleanPath2.startsWith(folderPattern))
|
|
15833
16017
|
return true;
|
|
15834
16018
|
if (!isAbsolute4 && !cleanPattern.includes("/") && !cleanPattern.includes("*")) {
|
|
15835
|
-
if (
|
|
16019
|
+
if (cleanPath2.endsWith("/" + cleanPattern) || cleanPath2 === cleanPattern)
|
|
15836
16020
|
return true;
|
|
15837
16021
|
}
|
|
15838
16022
|
return false;
|
|
@@ -15849,9 +16033,9 @@ async function detectGlobalPrefix(appDir) {
|
|
|
15849
16033
|
const mainTsAltPath = join7(appDir, "main.ts");
|
|
15850
16034
|
let content;
|
|
15851
16035
|
if (existsSync6(mainTsPath)) {
|
|
15852
|
-
content =
|
|
16036
|
+
content = readFileSync9(mainTsPath, "utf-8");
|
|
15853
16037
|
} else if (existsSync6(mainTsAltPath)) {
|
|
15854
|
-
content =
|
|
16038
|
+
content = readFileSync9(mainTsAltPath, "utf-8");
|
|
15855
16039
|
} else {
|
|
15856
16040
|
return "";
|
|
15857
16041
|
}
|
|
@@ -15913,7 +16097,7 @@ function getVercelCronPaths(dir) {
|
|
|
15913
16097
|
return [];
|
|
15914
16098
|
}
|
|
15915
16099
|
try {
|
|
15916
|
-
const content =
|
|
16100
|
+
const content = readFileSync9(vercelPath, "utf-8");
|
|
15917
16101
|
const config = JSON.parse(content);
|
|
15918
16102
|
if (!config.crons) {
|
|
15919
16103
|
return [];
|
|
@@ -15949,13 +16133,13 @@ async function scan(config) {
|
|
|
15949
16133
|
if (prefix)
|
|
15950
16134
|
detectedGlobalPrefix = prefix;
|
|
15951
16135
|
}
|
|
15952
|
-
const nextFiles = await
|
|
16136
|
+
const nextFiles = await import_fast_glob10.default(activeNextPatterns, {
|
|
15953
16137
|
cwd: scanCwd,
|
|
15954
16138
|
ignore: config.ignore.folders
|
|
15955
16139
|
});
|
|
15956
16140
|
const nextRoutes = nextFiles.map((file) => {
|
|
15957
16141
|
const fullPath = join7(scanCwd, file);
|
|
15958
|
-
const content =
|
|
16142
|
+
const content = readFileSync9(fullPath, "utf-8");
|
|
15959
16143
|
const { methods, methodLines } = extractExportedMethods(content);
|
|
15960
16144
|
return {
|
|
15961
16145
|
type: "nextjs",
|
|
@@ -15969,13 +16153,13 @@ async function scan(config) {
|
|
|
15969
16153
|
};
|
|
15970
16154
|
});
|
|
15971
16155
|
const nestPatterns = ["**/*.controller.ts"];
|
|
15972
|
-
const nestFiles = await
|
|
16156
|
+
const nestFiles = await import_fast_glob10.default(nestPatterns, {
|
|
15973
16157
|
cwd: scanCwd,
|
|
15974
16158
|
ignore: config.ignore.folders
|
|
15975
16159
|
});
|
|
15976
16160
|
const nestRoutes = nestFiles.flatMap((file) => {
|
|
15977
16161
|
const fullPath = join7(scanCwd, file);
|
|
15978
|
-
const content =
|
|
16162
|
+
const content = readFileSync9(fullPath, "utf-8");
|
|
15979
16163
|
const relativePathFromRoot = fullPath.replace(config.appSpecificScan ? config.appSpecificScan.rootDir + "/" : cwd + "/", "");
|
|
15980
16164
|
return extractNestRoutes(relativePathFromRoot, content, detectedGlobalPrefix);
|
|
15981
16165
|
});
|
|
@@ -15995,7 +16179,7 @@ async function scan(config) {
|
|
|
15995
16179
|
}
|
|
15996
16180
|
const referenceScanCwd = config.appSpecificScan ? config.appSpecificScan.rootDir : cwd;
|
|
15997
16181
|
const extGlob = `**/*{${config.extensions.join(",")}}`;
|
|
15998
|
-
const sourceFiles = await
|
|
16182
|
+
const sourceFiles = await import_fast_glob10.default(extGlob, {
|
|
15999
16183
|
cwd: referenceScanCwd,
|
|
16000
16184
|
ignore: [...config.ignore.folders, ...config.ignore.files]
|
|
16001
16185
|
});
|
|
@@ -16004,7 +16188,7 @@ async function scan(config) {
|
|
|
16004
16188
|
for (const file of sourceFiles) {
|
|
16005
16189
|
const filePath = join7(referenceScanCwd, file);
|
|
16006
16190
|
try {
|
|
16007
|
-
const content =
|
|
16191
|
+
const content = readFileSync9(filePath, "utf-8");
|
|
16008
16192
|
const refs = extractApiReferences(content);
|
|
16009
16193
|
if (refs.length > 0) {
|
|
16010
16194
|
fileReferences.set(file, refs);
|
|
@@ -16051,6 +16235,7 @@ async function scan(config) {
|
|
|
16051
16235
|
routes,
|
|
16052
16236
|
publicAssets,
|
|
16053
16237
|
missingAssets: await scanMissingAssets(config),
|
|
16238
|
+
brokenLinks: await scanBrokenLinks(config),
|
|
16054
16239
|
unusedFiles,
|
|
16055
16240
|
unusedExports: await scanUnusedExports(config).then((result) => {
|
|
16056
16241
|
const filtered = result.exports.filter((exp) => !exp.file.endsWith(".controller.ts") && !exp.file.endsWith(".controller.tsx"));
|
|
@@ -16062,8 +16247,8 @@ async function scan(config) {
|
|
|
16062
16247
|
}
|
|
16063
16248
|
|
|
16064
16249
|
// src/config.ts
|
|
16065
|
-
var
|
|
16066
|
-
import { existsSync as existsSync7, readFileSync as
|
|
16250
|
+
var import_fast_glob11 = __toESM(require_out4(), 1);
|
|
16251
|
+
import { existsSync as existsSync7, readFileSync as readFileSync10 } from "node:fs";
|
|
16067
16252
|
import { join as join8, resolve as resolve2, relative as relative4, dirname as dirname4 } from "node:path";
|
|
16068
16253
|
var DEFAULT_CONFIG = {
|
|
16069
16254
|
dir: "./",
|
|
@@ -16086,7 +16271,7 @@ var DEFAULT_CONFIG = {
|
|
|
16086
16271
|
};
|
|
16087
16272
|
function loadConfig(options) {
|
|
16088
16273
|
const cwd = options.dir || "./";
|
|
16089
|
-
const configFiles =
|
|
16274
|
+
const configFiles = import_fast_glob11.default.sync(["**/pruny.config.json", "**/.prunyrc.json", "**/.prunyrc"], {
|
|
16090
16275
|
cwd,
|
|
16091
16276
|
ignore: DEFAULT_CONFIG.ignore.folders,
|
|
16092
16277
|
absolute: true
|
|
@@ -16112,7 +16297,7 @@ function loadConfig(options) {
|
|
|
16112
16297
|
let excludePublic = options.excludePublic ?? false;
|
|
16113
16298
|
for (const configPath of configFiles) {
|
|
16114
16299
|
try {
|
|
16115
|
-
const content =
|
|
16300
|
+
const content = readFileSync10(configPath, "utf-8");
|
|
16116
16301
|
const config = JSON.parse(content);
|
|
16117
16302
|
const configDir = dirname4(configPath);
|
|
16118
16303
|
const relDir = relative4(cwd, configDir);
|
|
@@ -16159,7 +16344,7 @@ function parseGitIgnore(dir) {
|
|
|
16159
16344
|
if (!existsSync7(gitIgnorePath))
|
|
16160
16345
|
return [];
|
|
16161
16346
|
try {
|
|
16162
|
-
const content =
|
|
16347
|
+
const content = readFileSync10(gitIgnorePath, "utf-8");
|
|
16163
16348
|
return content.split(`
|
|
16164
16349
|
`).map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((pattern) => {
|
|
16165
16350
|
if (pattern.startsWith("/") || pattern.startsWith("**/"))
|
|
@@ -16434,6 +16619,10 @@ function filterResults(result, filterPattern) {
|
|
|
16434
16619
|
result.unusedExports.total = result.unusedExports.exports.length;
|
|
16435
16620
|
result.unusedExports.unused = result.unusedExports.exports.length;
|
|
16436
16621
|
}
|
|
16622
|
+
if (result.brokenLinks) {
|
|
16623
|
+
result.brokenLinks.links = result.brokenLinks.links.filter((l) => matchesFilter(l.path, filter2));
|
|
16624
|
+
result.brokenLinks.total = result.brokenLinks.links.length;
|
|
16625
|
+
}
|
|
16437
16626
|
result.total = result.routes.length;
|
|
16438
16627
|
result.used = result.routes.filter((r) => r.used).length;
|
|
16439
16628
|
result.unused = result.routes.filter((r) => !r.used).length;
|
|
@@ -16511,6 +16700,17 @@ function printDetailedReport(result) {
|
|
|
16511
16700
|
}
|
|
16512
16701
|
console.log("");
|
|
16513
16702
|
}
|
|
16703
|
+
if (result.brokenLinks && result.brokenLinks.total > 0) {
|
|
16704
|
+
console.log(source_default.red.bold(`\uD83D\uDD17 Broken Internal Links:
|
|
16705
|
+
`));
|
|
16706
|
+
for (const link of result.brokenLinks.links) {
|
|
16707
|
+
console.log(source_default.red(` ${link.path}`));
|
|
16708
|
+
for (const ref of link.references) {
|
|
16709
|
+
console.log(source_default.dim(` → ${ref}`));
|
|
16710
|
+
}
|
|
16711
|
+
}
|
|
16712
|
+
console.log("");
|
|
16713
|
+
}
|
|
16514
16714
|
if (!hasUnusedItems(result)) {
|
|
16515
16715
|
console.log(source_default.green(`✅ Everything is used! Clean as a whistle.
|
|
16516
16716
|
`));
|
|
@@ -16524,10 +16724,11 @@ function countIssues(result) {
|
|
|
16524
16724
|
const partialRoutes = result.routes.filter((r) => r.used && r.unusedMethods.length > 0).length;
|
|
16525
16725
|
const unusedAssets = result.publicAssets ? result.publicAssets.unused : 0;
|
|
16526
16726
|
const missingAssets = result.missingAssets ? result.missingAssets.total : 0;
|
|
16727
|
+
const brokenLinks = result.brokenLinks ? result.brokenLinks.total : 0;
|
|
16527
16728
|
const unusedFiles = result.unusedFiles ? result.unusedFiles.unused : 0;
|
|
16528
16729
|
const unusedExports = result.unusedExports ? result.unusedExports.unused : 0;
|
|
16529
16730
|
const unusedServices = result.unusedServices ? result.unusedServices.total : 0;
|
|
16530
|
-
return unusedRoutes + partialRoutes + unusedAssets + missingAssets + unusedFiles + unusedExports + unusedServices;
|
|
16731
|
+
return unusedRoutes + partialRoutes + unusedAssets + missingAssets + brokenLinks + unusedFiles + unusedExports + unusedServices;
|
|
16531
16732
|
}
|
|
16532
16733
|
async function handleFixes(result, config, options, showBack) {
|
|
16533
16734
|
const gitRoot = findGitRoot(config.dir);
|
|
@@ -16605,6 +16806,11 @@ Analyzing cascading impact...`));
|
|
|
16605
16806
|
const title = count > 0 ? `⚠ Missing Assets (Broken Links) (${count})` : `✅ Missing Assets (0) - All good!`;
|
|
16606
16807
|
choices.push({ title, value: "missing-assets" });
|
|
16607
16808
|
}
|
|
16809
|
+
if (result.brokenLinks) {
|
|
16810
|
+
const count = result.brokenLinks.total;
|
|
16811
|
+
const title = count > 0 ? `\uD83D\uDD17 Broken Internal Links (${count})` : `✅ Internal Links (0) - All good!`;
|
|
16812
|
+
choices.push({ title, value: "broken-links" });
|
|
16813
|
+
}
|
|
16608
16814
|
if (showBack) {
|
|
16609
16815
|
choices.push({ title: source_default.cyan("← Back"), value: "back" });
|
|
16610
16816
|
}
|
|
@@ -16671,7 +16877,11 @@ Analyzing cascading impact...`));
|
|
|
16671
16877
|
const dryRunReport = {
|
|
16672
16878
|
uniqueFiles: new Set(targetRoutes.map((r) => r.filePath)).size,
|
|
16673
16879
|
routes: [],
|
|
16674
|
-
exports: []
|
|
16880
|
+
exports: [],
|
|
16881
|
+
files: [],
|
|
16882
|
+
assets: [],
|
|
16883
|
+
missingAssets: [],
|
|
16884
|
+
brokenLinks: []
|
|
16675
16885
|
};
|
|
16676
16886
|
if (selected === "routes" || selected === "dry-run-json" || action === "dry-run") {
|
|
16677
16887
|
dryRunReport.routes = targetRoutes.map((r) => ({
|
|
@@ -16718,6 +16928,38 @@ Analyzing cascading impact...`));
|
|
|
16718
16928
|
}));
|
|
16719
16929
|
dryRunReport.uniqueFiles = new Set(servicesList.map((m) => m.file)).size;
|
|
16720
16930
|
}
|
|
16931
|
+
if (selected === "files") {
|
|
16932
|
+
const filesList = result.unusedFiles?.files || [];
|
|
16933
|
+
dryRunReport.files = filesList.map((f) => ({
|
|
16934
|
+
path: f.path,
|
|
16935
|
+
size: f.size
|
|
16936
|
+
}));
|
|
16937
|
+
dryRunReport.uniqueFiles = filesList.length;
|
|
16938
|
+
}
|
|
16939
|
+
if (selected === "assets") {
|
|
16940
|
+
const assetsList = result.publicAssets?.assets.filter((a) => !a.used) || [];
|
|
16941
|
+
dryRunReport.assets = assetsList.map((a) => ({
|
|
16942
|
+
path: a.relativePath,
|
|
16943
|
+
references: a.references
|
|
16944
|
+
}));
|
|
16945
|
+
dryRunReport.uniqueFiles = assetsList.length;
|
|
16946
|
+
}
|
|
16947
|
+
if (selected === "missing-assets") {
|
|
16948
|
+
const missingList = result.missingAssets?.assets || [];
|
|
16949
|
+
dryRunReport.missingAssets = missingList.map((a) => ({
|
|
16950
|
+
path: a.path,
|
|
16951
|
+
references: a.references
|
|
16952
|
+
}));
|
|
16953
|
+
dryRunReport.uniqueFiles = missingList.length;
|
|
16954
|
+
}
|
|
16955
|
+
if (selected === "broken-links") {
|
|
16956
|
+
const brokenList = result.brokenLinks?.links || [];
|
|
16957
|
+
dryRunReport.brokenLinks = brokenList.map((l) => ({
|
|
16958
|
+
path: l.path,
|
|
16959
|
+
references: l.references
|
|
16960
|
+
}));
|
|
16961
|
+
dryRunReport.uniqueFiles = brokenList.length;
|
|
16962
|
+
}
|
|
16721
16963
|
const reportPath = join10(process.cwd(), "pruny-dry-run.json");
|
|
16722
16964
|
writeFileSync3(reportPath, JSON.stringify(dryRunReport, null, 2));
|
|
16723
16965
|
console.log(source_default.green(`
|
|
@@ -16726,6 +16968,25 @@ Analyzing cascading impact...`));
|
|
|
16726
16968
|
}
|
|
16727
16969
|
const selectedList = options.cleanup ? options.cleanup.split(",").map((s) => s.trim()) : [selected];
|
|
16728
16970
|
let fixedSomething = false;
|
|
16971
|
+
if (selectedList.includes("broken-links")) {
|
|
16972
|
+
if (result.brokenLinks && result.brokenLinks.total > 0) {
|
|
16973
|
+
console.log(source_default.yellow.bold(`
|
|
16974
|
+
\uD83D\uDD17 Broken Internal Links Detected:`));
|
|
16975
|
+
console.log(source_default.gray(" (These links point to pages that don't exist. Please fix or remove them:)"));
|
|
16976
|
+
for (const link of result.brokenLinks.links) {
|
|
16977
|
+
console.log(source_default.red.bold(`
|
|
16978
|
+
❌ ${link.path}`));
|
|
16979
|
+
for (const ref of link.references) {
|
|
16980
|
+
console.log(source_default.gray(` ➜ ${ref}`));
|
|
16981
|
+
}
|
|
16982
|
+
}
|
|
16983
|
+
console.log(source_default.yellow(`
|
|
16984
|
+
Create the missing pages or update the links to valid routes.`));
|
|
16985
|
+
} else {
|
|
16986
|
+
console.log(source_default.green(`
|
|
16987
|
+
✅ No broken internal links found! All links are valid.`));
|
|
16988
|
+
}
|
|
16989
|
+
}
|
|
16729
16990
|
if (selectedList.includes("missing-assets")) {
|
|
16730
16991
|
if (result.missingAssets && result.missingAssets.total > 0) {
|
|
16731
16992
|
console.log(source_default.yellow.bold(`
|
|
@@ -17108,6 +17369,14 @@ function printSummaryTable(result, context) {
|
|
|
17108
17369
|
Unused: result.missingAssets.total
|
|
17109
17370
|
});
|
|
17110
17371
|
}
|
|
17372
|
+
if (result.brokenLinks && result.brokenLinks.total > 0) {
|
|
17373
|
+
summary.push({
|
|
17374
|
+
Category: source_default.red.bold("\uD83D\uDD17 Broken Links"),
|
|
17375
|
+
Total: result.brokenLinks.total,
|
|
17376
|
+
Used: "-",
|
|
17377
|
+
Unused: result.brokenLinks.total
|
|
17378
|
+
});
|
|
17379
|
+
}
|
|
17111
17380
|
if (result.unusedFiles)
|
|
17112
17381
|
summary.push({ Category: "Code Files (.ts/.js)", Total: result.unusedFiles.used + result.unusedFiles.unused, Used: result.unusedFiles.used, Unused: result.unusedFiles.unused });
|
|
17113
17382
|
if (result.unusedExports)
|
|
@@ -17143,6 +17412,19 @@ function printSummaryTable(result, context) {
|
|
|
17143
17412
|
console.log(source_default.yellow(`
|
|
17144
17413
|
These files are referenced in code but don't exist. Update the links or create the files.`));
|
|
17145
17414
|
}
|
|
17415
|
+
if (result.brokenLinks && result.brokenLinks.total > 0) {
|
|
17416
|
+
console.log(source_default.red.bold(`
|
|
17417
|
+
\uD83D\uDD17 Broken Internal Links:
|
|
17418
|
+
`));
|
|
17419
|
+
for (const link of result.brokenLinks.links) {
|
|
17420
|
+
console.log(source_default.red(` ✗ ${link.path}`));
|
|
17421
|
+
for (const ref of link.references) {
|
|
17422
|
+
console.log(source_default.dim(` → ${ref}`));
|
|
17423
|
+
}
|
|
17424
|
+
}
|
|
17425
|
+
console.log(source_default.yellow(`
|
|
17426
|
+
These links point to pages/routes that don't exist. Create the pages or fix the links.`));
|
|
17427
|
+
}
|
|
17146
17428
|
}
|
|
17147
17429
|
function printConsolidatedTable(allResults) {
|
|
17148
17430
|
console.log(source_default.bold(`\uD83D\uDCCA Monorepo Summary
|