next-route-graph 1.0.1 → 1.0.2
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/core/analyzer.js +96 -60
- package/dist/core/parser.js +3 -10
- package/dist/render/tree.js +16 -13
- package/package.json +1 -1
package/dist/core/analyzer.js
CHANGED
|
@@ -1,81 +1,117 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { detectRenderingType, getSegmentType, parseRoutePath, parseSpecialFileType, } from "./parser.js";
|
|
3
|
-
|
|
4
|
-
const startTime = Date.now();
|
|
3
|
+
function groupFilesByDirectory(files) {
|
|
5
4
|
const dirMap = new Map();
|
|
6
5
|
for (const file of files) {
|
|
7
6
|
const dir = path.dirname(file.relativePath).replace(/\\/g, "/");
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
const dirFiles = dirMap.get(dir);
|
|
8
|
+
if (dirFiles) {
|
|
9
|
+
dirFiles.push(file);
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
dirMap.set(dir, [file]);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return dirMap;
|
|
16
|
+
}
|
|
17
|
+
async function processDirectoryFiles(dirFiles) {
|
|
18
|
+
const routeFiles = [];
|
|
19
|
+
let layouts = 0;
|
|
20
|
+
let loading = 0;
|
|
21
|
+
let errors = 0;
|
|
22
|
+
for (const file of dirFiles) {
|
|
23
|
+
const specialType = parseSpecialFileType(file.fileName);
|
|
24
|
+
if (!specialType) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const rendering = await detectRenderingType(file.fullPath);
|
|
28
|
+
routeFiles.push({
|
|
29
|
+
type: specialType,
|
|
30
|
+
fileName: file.fileName,
|
|
31
|
+
relativePath: file.relativePath,
|
|
32
|
+
rendering,
|
|
33
|
+
});
|
|
34
|
+
if (specialType === "layout") {
|
|
35
|
+
layouts++;
|
|
10
36
|
}
|
|
11
|
-
|
|
37
|
+
if (specialType === "loading") {
|
|
38
|
+
loading++;
|
|
39
|
+
}
|
|
40
|
+
if (specialType === "error") {
|
|
41
|
+
errors++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
routeFiles,
|
|
46
|
+
layouts,
|
|
47
|
+
loading,
|
|
48
|
+
errors,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createRouteEntry(routeFiles) {
|
|
52
|
+
const pageFile = routeFiles.find((file) => file.type === "page");
|
|
53
|
+
const routeHandlerFile = routeFiles.find((file) => file.type === "route");
|
|
54
|
+
const primaryFile = pageFile ?? routeHandlerFile;
|
|
55
|
+
if (!primaryFile) {
|
|
56
|
+
return null;
|
|
12
57
|
}
|
|
58
|
+
const computedPath = parseRoutePath(primaryFile.relativePath);
|
|
59
|
+
return {
|
|
60
|
+
path: computedPath,
|
|
61
|
+
type: pageFile ? "page" : "route",
|
|
62
|
+
file: primaryFile.relativePath,
|
|
63
|
+
segmentType: getSegmentType(computedPath),
|
|
64
|
+
rendering: primaryFile.rendering,
|
|
65
|
+
files: routeFiles,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function analyzeDirectories(dirMap) {
|
|
13
69
|
const routesMap = new Map();
|
|
14
70
|
let totalLayouts = 0;
|
|
15
71
|
let totalLoading = 0;
|
|
16
72
|
let totalErrors = 0;
|
|
17
|
-
for (const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
fileName: file.fileName,
|
|
26
|
-
relativePath: file.relativePath,
|
|
27
|
-
rendering,
|
|
28
|
-
});
|
|
29
|
-
if (specialType === "layout")
|
|
30
|
-
totalLayouts++;
|
|
31
|
-
if (specialType === "loading")
|
|
32
|
-
totalLoading++;
|
|
33
|
-
if (specialType === "error")
|
|
34
|
-
totalErrors++;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const pageFile = routeFiles.find((f) => f.type === "page");
|
|
38
|
-
const routeHandlerFile = routeFiles.find((f) => f.type === "route");
|
|
39
|
-
if (pageFile || routeHandlerFile) {
|
|
40
|
-
const primaryFile = (pageFile || routeHandlerFile);
|
|
41
|
-
const computedPath = parseRoutePath(primaryFile.relativePath);
|
|
42
|
-
routesMap.set(computedPath, {
|
|
43
|
-
path: computedPath,
|
|
44
|
-
type: pageFile ? "page" : "route",
|
|
45
|
-
file: primaryFile.relativePath,
|
|
46
|
-
segmentType: getSegmentType(computedPath),
|
|
47
|
-
rendering: primaryFile.rendering,
|
|
48
|
-
files: routeFiles,
|
|
49
|
-
});
|
|
73
|
+
for (const dirFiles of dirMap.values()) {
|
|
74
|
+
const result = await processDirectoryFiles(dirFiles);
|
|
75
|
+
totalLayouts += result.layouts;
|
|
76
|
+
totalLoading += result.loading;
|
|
77
|
+
totalErrors += result.errors;
|
|
78
|
+
const route = createRouteEntry(result.routeFiles);
|
|
79
|
+
if (route) {
|
|
80
|
+
routesMap.set(route.path, route);
|
|
50
81
|
}
|
|
51
82
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
83
|
+
return {
|
|
84
|
+
routesMap,
|
|
85
|
+
totalLayouts,
|
|
86
|
+
totalLoading,
|
|
87
|
+
totalErrors,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function getRoutesSummary(routes, layouts, loading, errors) {
|
|
91
|
+
let dynamic = 0;
|
|
92
|
+
let client = 0;
|
|
56
93
|
for (const route of routes) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
if (route.rendering === "client") {
|
|
61
|
-
clientCount++;
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
serverCount++;
|
|
65
|
-
}
|
|
94
|
+
dynamic += route.segmentType !== "static" ? 1 : 0;
|
|
95
|
+
client += route.rendering === "client" ? 1 : 0;
|
|
66
96
|
}
|
|
67
|
-
|
|
68
|
-
const summary = {
|
|
97
|
+
return {
|
|
69
98
|
routes: routes.length,
|
|
70
|
-
dynamic
|
|
71
|
-
client
|
|
72
|
-
server:
|
|
73
|
-
layouts
|
|
74
|
-
loading
|
|
75
|
-
errors
|
|
99
|
+
dynamic,
|
|
100
|
+
client,
|
|
101
|
+
server: routes.length - client,
|
|
102
|
+
layouts,
|
|
103
|
+
loading,
|
|
104
|
+
errors,
|
|
76
105
|
};
|
|
106
|
+
}
|
|
107
|
+
export async function analyzeRoutes(files, projectPath, displayRoot) {
|
|
108
|
+
const startTime = Date.now();
|
|
109
|
+
const dirMap = groupFilesByDirectory(files);
|
|
110
|
+
const { routesMap, totalLayouts, totalLoading, totalErrors } = await analyzeDirectories(dirMap);
|
|
111
|
+
const routes = Array.from(routesMap.values()).sort((a, b) => a.path.localeCompare(b.path));
|
|
112
|
+
const summary = getRoutesSummary(routes, totalLayouts, totalLoading, totalErrors);
|
|
77
113
|
return {
|
|
78
|
-
project:
|
|
114
|
+
project: path.basename(path.resolve(projectPath)),
|
|
79
115
|
router: "App Router",
|
|
80
116
|
root: displayRoot,
|
|
81
117
|
routes,
|
package/dist/core/parser.js
CHANGED
|
@@ -21,43 +21,36 @@ export async function detectRenderingType(filePath) {
|
|
|
21
21
|
try {
|
|
22
22
|
const content = await fs.readFile(filePath, "utf-8");
|
|
23
23
|
const firstLines = content.slice(0, 500);
|
|
24
|
-
const clientDirectiveRegex =
|
|
24
|
+
const clientDirectiveRegex = /^(?:(?:\s*\/\/.*?\n)|(?:\s*\/\*[\s\S]*?\*\/)|(?:\s*))["']use client["']/m;
|
|
25
25
|
if (clientDirectiveRegex.test(firstLines)) {
|
|
26
26
|
return "client";
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
catch {
|
|
30
|
-
// Default to server if unreadable
|
|
31
|
-
}
|
|
29
|
+
catch { }
|
|
32
30
|
return "server";
|
|
33
31
|
}
|
|
34
32
|
export function parseRoutePath(relativePath) {
|
|
35
33
|
const parts = relativePath.split("/");
|
|
36
|
-
// Remove the file name at the end
|
|
37
34
|
parts.pop();
|
|
38
35
|
const urlSegments = [];
|
|
39
36
|
for (const part of parts) {
|
|
40
37
|
if (!part)
|
|
41
38
|
continue;
|
|
42
|
-
// Route groups are ignored in URL matching
|
|
43
39
|
if (part.startsWith("(") && part.endsWith(")")) {
|
|
44
40
|
const inner = part.slice(1, -1);
|
|
45
|
-
// Exclude intercepting routes which also start with '('
|
|
46
41
|
if (!inner.startsWith(".") && inner !== "...") {
|
|
47
42
|
continue;
|
|
48
43
|
}
|
|
49
44
|
}
|
|
50
|
-
// Parallel routes are ignored in URL pathing
|
|
51
45
|
if (part.startsWith("@")) {
|
|
52
46
|
continue;
|
|
53
47
|
}
|
|
54
|
-
// Intercepting routes clean-up for display path
|
|
55
48
|
let cleanedPart = part;
|
|
56
49
|
if (cleanedPart.startsWith("(.)") ||
|
|
57
50
|
cleanedPart.startsWith("(..)") ||
|
|
58
51
|
cleanedPart.startsWith("(...)") ||
|
|
59
52
|
cleanedPart.startsWith("(..)(..)")) {
|
|
60
|
-
cleanedPart = cleanedPart.replace(
|
|
53
|
+
cleanedPart = cleanedPart.replace(/^(?:\(\.\.\.\)|\(\.\.\)|\(\.\))/, "");
|
|
61
54
|
}
|
|
62
55
|
urlSegments.push(cleanedPart);
|
|
63
56
|
}
|
package/dist/render/tree.js
CHANGED
|
@@ -4,9 +4,9 @@ export function renderTree(result) {
|
|
|
4
4
|
lines.push("");
|
|
5
5
|
lines.push(pc.bold("next-route-graph"));
|
|
6
6
|
lines.push("");
|
|
7
|
-
lines.push(`${pc.dim("Project ")}
|
|
8
|
-
lines.push(`${pc.dim("Router ")}
|
|
9
|
-
lines.push(`${pc.dim("Root ")}
|
|
7
|
+
lines.push(`${pc.dim("Project ")}${result.project}`);
|
|
8
|
+
lines.push(`${pc.dim("Router ")}${result.router}`);
|
|
9
|
+
lines.push(`${pc.dim("Root ")}${result.root}`);
|
|
10
10
|
lines.push("");
|
|
11
11
|
lines.push(pc.bold("Routes"));
|
|
12
12
|
lines.push(pc.dim("────────────────────────────────────────"));
|
|
@@ -18,12 +18,15 @@ export function renderTree(result) {
|
|
|
18
18
|
const fileStr = pc.dim(route.file.split("/").pop() || "");
|
|
19
19
|
let badge = "";
|
|
20
20
|
if (route.rendering === "client") {
|
|
21
|
-
|
|
21
|
+
const clientBadge = pc.cyan("[client]");
|
|
22
|
+
badge = ` ${clientBadge}`;
|
|
22
23
|
}
|
|
23
24
|
else if (route.segmentType !== "static") {
|
|
24
|
-
|
|
25
|
+
const segmentBadge = `[${route.segmentType}]`;
|
|
26
|
+
badge = ` ${pc.yellow(segmentBadge)}`;
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
const routeLine = `${pc.dim(prefix)}${pc.bold(pathStr)} ${fileStr}${badge}`;
|
|
29
|
+
lines.push(routeLine);
|
|
27
30
|
});
|
|
28
31
|
if (routes.length === 0) {
|
|
29
32
|
lines.push(pc.dim(" (No routes found)"));
|
|
@@ -31,13 +34,13 @@ export function renderTree(result) {
|
|
|
31
34
|
lines.push("");
|
|
32
35
|
lines.push(pc.bold("Summary"));
|
|
33
36
|
lines.push(pc.dim("────────────────────────────────────────"));
|
|
34
|
-
lines.push(`${pc.dim("Routes
|
|
35
|
-
lines.push(`${pc.dim("Dynamic
|
|
36
|
-
lines.push(`${pc.dim("Client
|
|
37
|
-
lines.push(`${pc.dim("Server
|
|
38
|
-
lines.push(`${pc.dim("Layouts
|
|
39
|
-
lines.push(`${pc.dim("Loading
|
|
40
|
-
lines.push(`${pc.dim("Errors
|
|
37
|
+
lines.push(`${pc.dim("Routes ")}${result.summary.routes}`);
|
|
38
|
+
lines.push(`${pc.dim("Dynamic ")}${result.summary.dynamic}`);
|
|
39
|
+
lines.push(`${pc.dim("Client ")}${result.summary.client}`);
|
|
40
|
+
lines.push(`${pc.dim("Server ")}${result.summary.server}`);
|
|
41
|
+
lines.push(`${pc.dim("Layouts ")}${result.summary.layouts}`);
|
|
42
|
+
lines.push(`${pc.dim("Loading ")}${result.summary.loading}`);
|
|
43
|
+
lines.push(`${pc.dim("Errors ")}${result.summary.errors}`);
|
|
41
44
|
lines.push("");
|
|
42
45
|
lines.push(pc.green(`✓ Analysis completed in ${result.executionTimeMs}ms`));
|
|
43
46
|
lines.push("");
|