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.
@@ -1,81 +1,117 @@
1
1
  import path from "node:path";
2
2
  import { detectRenderingType, getSegmentType, parseRoutePath, parseSpecialFileType, } from "./parser.js";
3
- export async function analyzeRoutes(files, projectPath, displayRoot) {
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
- if (!dirMap.has(dir)) {
9
- dirMap.set(dir, []);
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
- dirMap.get(dir).push(file);
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 [dir, dirFiles] of dirMap.entries()) {
18
- const routeFiles = [];
19
- for (const file of dirFiles) {
20
- const specialType = parseSpecialFileType(file.fileName);
21
- if (specialType) {
22
- const rendering = await detectRenderingType(file.fullPath);
23
- routeFiles.push({
24
- type: specialType,
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
- const routes = Array.from(routesMap.values()).sort((a, b) => a.path.localeCompare(b.path));
53
- let dynamicCount = 0;
54
- let clientCount = 0;
55
- let serverCount = 0;
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
- if (route.segmentType !== "static") {
58
- dynamicCount++;
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
- const projectName = path.basename(path.resolve(projectPath));
68
- const summary = {
97
+ return {
69
98
  routes: routes.length,
70
- dynamic: dynamicCount,
71
- client: clientCount,
72
- server: serverCount,
73
- layouts: totalLayouts,
74
- loading: totalLoading,
75
- errors: totalErrors,
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: projectName,
114
+ project: path.basename(path.resolve(projectPath)),
79
115
  router: "App Router",
80
116
  root: displayRoot,
81
117
  routes,
@@ -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 = /^\s*(?:\/\/.*?\n|\/\*[\s\S]*?\*\/)*\s*["']use client["']/m;
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
  }
@@ -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 ")} ${result.project}`);
8
- lines.push(`${pc.dim("Router ")} ${result.router}`);
9
- lines.push(`${pc.dim("Root ")} ${result.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
- badge = ` ${pc.cyan("[client]")}`;
21
+ const clientBadge = pc.cyan("[client]");
22
+ badge = ` ${clientBadge}`;
22
23
  }
23
24
  else if (route.segmentType !== "static") {
24
- badge = ` ${pc.yellow(`[${route.segmentType}]`)}`;
25
+ const segmentBadge = `[${route.segmentType}]`;
26
+ badge = ` ${pc.yellow(segmentBadge)}`;
25
27
  }
26
- lines.push(`${pc.dim(prefix)}${pc.bold(pathStr)} ${fileStr}${badge}`);
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 ")} ${result.summary.routes}`);
35
- lines.push(`${pc.dim("Dynamic ")} ${result.summary.dynamic}`);
36
- lines.push(`${pc.dim("Client ")} ${result.summary.client}`);
37
- lines.push(`${pc.dim("Server ")} ${result.summary.server}`);
38
- lines.push(`${pc.dim("Layouts ")} ${result.summary.layouts}`);
39
- lines.push(`${pc.dim("Loading ")} ${result.summary.loading}`);
40
- lines.push(`${pc.dim("Errors ")} ${result.summary.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("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-route-graph",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Visualize and inspect the structure of a Next.js App Router project from the terminal",
5
5
  "type": "module",
6
6
  "bin": {