pruny 1.40.0 → 1.41.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/README.md +3 -3
- package/dist/index.js +14 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@ npx pruny
|
|
|
17
17
|
| Scanner | What it finds |
|
|
18
18
|
| :------ | :------------ |
|
|
19
19
|
| **API Routes** | Unused Next.js `route.ts` handlers and NestJS controller methods |
|
|
20
|
-
| **Broken Links** | `<Link>`, `router.push()`, `redirect()` pointing to pages that don't exist |
|
|
20
|
+
| **Broken Links** | `<Link>`, `router.push()`, `redirect()`, `href: "/path"` in arrays/objects pointing to pages that don't exist |
|
|
21
21
|
| **Unused Exports** | Named exports and class methods not imported anywhere |
|
|
22
22
|
| **Unused Files** | Source files not reachable from any entry point |
|
|
23
23
|
| **Unused Services** | NestJS service methods never called by controllers or other services |
|
|
@@ -100,7 +100,7 @@ For example, if your file structure is:
|
|
|
100
100
|
app/(code)/tenant_sites/[domain]/view_seat/page.tsx
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
And your components reference `/view_seat` (resolved at runtime via subdomain), Pruny recognizes this as a valid route and will **not** report it as a broken link.
|
|
103
|
+
And your components reference `/view_seat` (resolved at runtime via subdomain), Pruny recognizes this as a valid route and will **not** report it as a broken link. The matched tail must contain at least one literal segment (e.g., `view_seat`) — fully-dynamic tails like `[token]` alone won't match arbitrary paths.
|
|
104
104
|
|
|
105
105
|
If auto-detection doesn't cover your case, use `ignore.links` in config:
|
|
106
106
|
|
|
@@ -140,7 +140,7 @@ This scans all monorepo apps and exits with code 1 if any issues are found. Comb
|
|
|
140
140
|
## How It Works
|
|
141
141
|
|
|
142
142
|
1. **Route Detection**: Finds all `app/api/**/route.ts` (Next.js) and `*.controller.ts` (NestJS) files
|
|
143
|
-
2. **Link Detection**: Finds `<Link>`, `router.push()`, `redirect()`, `href
|
|
143
|
+
2. **Link Detection**: Finds `<Link>`, `router.push()`, `redirect()`, `href: "/path"` in arrays/config objects, `<a>` tags, `revalidatePath()`, and `pathname ===` comparisons — validates all against known page routes. The summary table always shows an "Internal Links" row when links are scanned, so you can see the feature is active
|
|
144
144
|
3. **Reference Scanning**: Searches the entire codebase for string references to routes, exports, and assets
|
|
145
145
|
4. **Dynamic Route Matching**: Understands `[id]`, `[...slug]`, `[[...slug]]` dynamic segments
|
|
146
146
|
5. **Fix Mode**: Removes unused methods, exports, and files with a cascading second pass to catch newly dead code
|
package/dist/index.js
CHANGED
|
@@ -15905,6 +15905,9 @@ function matchesDynamicSuffix(refSegments, routeSegments) {
|
|
|
15905
15905
|
if (!prefix.some((s) => /^\[.+\]$/.test(s)))
|
|
15906
15906
|
return false;
|
|
15907
15907
|
const tail = routeSegments.slice(prefixLen);
|
|
15908
|
+
const hasLiteralInTail = tail.some((s) => !/^\[/.test(s));
|
|
15909
|
+
if (!hasLiteralInTail)
|
|
15910
|
+
return false;
|
|
15908
15911
|
return matchSegments(refSegments, tail);
|
|
15909
15912
|
}
|
|
15910
15913
|
function matchSegments(refSegments, routeSegments) {
|
|
@@ -15939,7 +15942,7 @@ async function scanBrokenLinks(config) {
|
|
|
15939
15942
|
ignore: [...config.ignore.folders, "**/node_modules/**", "**/_*/**"]
|
|
15940
15943
|
});
|
|
15941
15944
|
if (pageFiles.length === 0) {
|
|
15942
|
-
return { total: 0, links: [] };
|
|
15945
|
+
return { total: 0, scanned: 0, links: [] };
|
|
15943
15946
|
}
|
|
15944
15947
|
const knownRoutes = new Set;
|
|
15945
15948
|
const routeSegmentsList = [];
|
|
@@ -15965,6 +15968,7 @@ async function scanBrokenLinks(config) {
|
|
|
15965
15968
|
absolute: true
|
|
15966
15969
|
});
|
|
15967
15970
|
const brokenMap = new Map;
|
|
15971
|
+
const allLinkPaths = new Set;
|
|
15968
15972
|
for (const file of sourceFiles) {
|
|
15969
15973
|
try {
|
|
15970
15974
|
const content = readFileSync9(file, "utf-8");
|
|
@@ -15980,6 +15984,7 @@ async function scanBrokenLinks(config) {
|
|
|
15980
15984
|
const cleaned = cleanPath(rawPath);
|
|
15981
15985
|
if (!cleaned || cleaned === "/")
|
|
15982
15986
|
continue;
|
|
15987
|
+
allLinkPaths.add(cleaned);
|
|
15983
15988
|
if (!matchesRoute(cleaned, knownRoutes, routeSegmentsList)) {
|
|
15984
15989
|
const ignorePatterns = [
|
|
15985
15990
|
...config.ignore.links || [],
|
|
@@ -16012,6 +16017,7 @@ async function scanBrokenLinks(config) {
|
|
|
16012
16017
|
links.sort((a, b) => b.references.length - a.references.length);
|
|
16013
16018
|
return {
|
|
16014
16019
|
total: links.length,
|
|
16020
|
+
scanned: allLinkPaths.size,
|
|
16015
16021
|
links
|
|
16016
16022
|
};
|
|
16017
16023
|
}
|
|
@@ -17589,12 +17595,14 @@ function printSummaryTable(result, context) {
|
|
|
17589
17595
|
Unused: result.missingAssets.total
|
|
17590
17596
|
});
|
|
17591
17597
|
}
|
|
17592
|
-
if (result.brokenLinks && result.brokenLinks.
|
|
17598
|
+
if (result.brokenLinks && result.brokenLinks.scanned > 0) {
|
|
17599
|
+
const broken = result.brokenLinks.total;
|
|
17600
|
+
const valid = result.brokenLinks.scanned - broken;
|
|
17593
17601
|
summary.push({
|
|
17594
|
-
Category: source_default.red.bold("
|
|
17595
|
-
Total: result.brokenLinks.
|
|
17596
|
-
Used:
|
|
17597
|
-
Unused:
|
|
17602
|
+
Category: broken > 0 ? source_default.red.bold("Internal Links") : "Internal Links",
|
|
17603
|
+
Total: result.brokenLinks.scanned,
|
|
17604
|
+
Used: valid,
|
|
17605
|
+
Unused: broken
|
|
17598
17606
|
});
|
|
17599
17607
|
}
|
|
17600
17608
|
if (result.unusedFiles)
|