next-route-graph 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Next Route Map Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom me the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,367 @@
1
+ # next-route-graph
2
+
3
+ > Visualize and inspect the structure of a Next.js App Router project directly from your terminal.
4
+
5
+ `next-route-graph` is a lightweight, fast, zero-config CLI tool for Next.js developers.
6
+
7
+ It scans your local App Router structure and provides useful information about routes, dynamic segments, rendering types, layouts, route handlers, and special Next.js files — without requiring any configuration or network requests.
8
+
9
+ ## Features
10
+
11
+ - ⚡ **Zero-config** — Works out of the box with no configuration files.
12
+ - 🚫 **Zero-network** — Analyzes your project locally without sending files or data anywhere.
13
+ - 🎯 **App Router focused** — Automatically detects both `app/` and `src/app/`.
14
+ - 📁 **Dynamic route support** — Detects dynamic, catch-all, and optional catch-all segments.
15
+ - 🧩 **Advanced routing support** — Recognizes Route Groups, Parallel Routes, and Intercepting Routes.
16
+ - 🖥️ **Rendering detection** — Identifies Server and Client components.
17
+ - 📄 **Special files** — Detects Next.js App Router special files and route handlers.
18
+ - 🔍 **Search & filter** — Quickly find routes matching a specific query.
19
+ - 📊 **Multiple output formats** — Human-readable tree, flat route list, or machine-readable JSON.
20
+ - 🚀 **Fast & lightweight** — No Next.js runtime or project configuration required.
21
+
22
+ ## Installation
23
+
24
+ ### Run with npx
25
+
26
+ The easiest way to use `next-route-graph`:
27
+
28
+ ```bash
29
+ npx next-route-graph
30
+ ```
31
+
32
+ ### Install globally
33
+
34
+ You can also install it globally:
35
+
36
+ ```bash
37
+ npm install -g next-route-graph
38
+ ```
39
+
40
+ Then run:
41
+
42
+ ```bash
43
+ next-route-graph
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ Run the CLI from the root of your Next.js project:
49
+
50
+ ```bash
51
+ npx next-route-graph
52
+ ```
53
+
54
+ The CLI automatically detects your App Router directory:
55
+
56
+ ```text
57
+ app/
58
+ ```
59
+
60
+ or:
61
+
62
+ ```text
63
+ src/app/
64
+ ```
65
+
66
+ You can also provide a custom project path:
67
+
68
+ ```bash
69
+ npx next-route-graph ./my-next-app
70
+ ```
71
+
72
+ ## Example
73
+
74
+ For a project with the following structure:
75
+
76
+ ```text
77
+ src/app/
78
+ ├── [locale]/
79
+ │ ├── about/
80
+ │ │ └── page.tsx
81
+ │ ├── concepts/
82
+ │ │ ├── [category]/
83
+ │ │ │ └── [slug]/
84
+ │ │ │ └── page.tsx
85
+ │ │ └── page.tsx
86
+ │ └── page.tsx
87
+ ├── api/
88
+ │ └── route.ts
89
+ └── layout.tsx
90
+ ```
91
+
92
+ `next-route-graph` produces a route overview such as:
93
+
94
+ ```text
95
+ Project my-next-app
96
+ Router App Router
97
+ Root src/app
98
+
99
+ Routes
100
+ ────────────────────────────────────────
101
+ ├── /[locale] page.tsx [dynamic]
102
+ ├── /[locale]/about page.tsx [dynamic]
103
+ ├── /[locale]/concepts page.tsx [dynamic]
104
+ ├── /[locale]/concepts/[category] page.tsx [dynamic]
105
+ ├── /[locale]/concepts/[category]/[slug] page.tsx [dynamic]
106
+ └── /api route.ts
107
+
108
+ Summary
109
+ ────────────────────────────────────────
110
+ Routes 6
111
+ Dynamic 5
112
+ Client 0
113
+ Server 6
114
+ Layouts 1
115
+ Loading 0
116
+ Errors 0
117
+
118
+ ✓ Analysis completed in 18ms
119
+ ```
120
+
121
+ ## CLI Options
122
+
123
+ ### `--flat`
124
+
125
+ Output only route paths as a flat list.
126
+
127
+ ```bash
128
+ npx next-route-graph --flat
129
+ ```
130
+
131
+ Example:
132
+
133
+ ```text
134
+ /[locale]
135
+ /[locale]/about
136
+ /[locale]/concepts
137
+ /[locale]/concepts/[category]
138
+ /api
139
+ ```
140
+
141
+ This format is useful when you only need the route paths without additional metadata.
142
+
143
+ ### `--json`
144
+
145
+ Output the complete analysis as machine-readable JSON.
146
+
147
+ ```bash
148
+ npx next-route-graph --json
149
+ ```
150
+
151
+ This is useful for scripts, automation, CI workflows, and other development tools.
152
+
153
+ ### `--search <query>`
154
+
155
+ Filter routes using a search query.
156
+
157
+ ```bash
158
+ npx next-route-graph --search blog
159
+ ```
160
+
161
+ For example:
162
+
163
+ ```text
164
+ /blog
165
+ /blog/[slug]
166
+ /blog/archive
167
+ ```
168
+
169
+ Search can be combined with other output modes:
170
+
171
+ ```bash
172
+ npx next-route-graph --search blog --flat
173
+ ```
174
+
175
+ or:
176
+
177
+ ```bash
178
+ npx next-route-graph --search blog --json
179
+ ```
180
+
181
+ ### `<projectPath>`
182
+
183
+ Analyze a specific Next.js project instead of the current directory.
184
+
185
+ ```bash
186
+ npx next-route-graph ./my-next-app
187
+ ```
188
+
189
+ ## Command Reference
190
+
191
+ | Command / Option | Description |
192
+ | -------------------------------- | ----------------------------------- |
193
+ | `next-route-graph` | Analyze the current Next.js project |
194
+ | `next-route-graph <projectPath>` | Analyze a specific project |
195
+ | `--flat` | Output only route paths |
196
+ | `--json` | Output machine-readable JSON |
197
+ | `--search <query>` | Filter routes by search string |
198
+ | `--help` | Show CLI help |
199
+ | `--version` | Show the installed version |
200
+
201
+ ## Supported Next.js Routing Features
202
+
203
+ `next-route-graph` understands common Next.js App Router conventions, including:
204
+
205
+ ### Static Routes
206
+
207
+ ```text
208
+ /about
209
+ /dashboard
210
+ /settings/profile
211
+ ```
212
+
213
+ ### Dynamic Routes
214
+
215
+ ```text
216
+ /[id]
217
+ /products/[productId]
218
+ ```
219
+
220
+ ### Catch-all Routes
221
+
222
+ ```text
223
+ /docs/[...slug]
224
+ ```
225
+
226
+ ### Optional Catch-all Routes
227
+
228
+ ```text
229
+ /docs/[[...slug]]
230
+ ```
231
+
232
+ ### Route Groups
233
+
234
+ ```text
235
+ /(marketing)/about
236
+ ```
237
+
238
+ ### Parallel Routes
239
+
240
+ ```text
241
+ /dashboard/@analytics
242
+ ```
243
+
244
+ ### Intercepting Routes
245
+
246
+ ```text
247
+ (.)photo
248
+ (..)photo
249
+ (...)photo
250
+ ```
251
+
252
+ ### Route Handlers
253
+
254
+ ```text
255
+ /api/users/route.ts
256
+ ```
257
+
258
+ ### Special App Router Files
259
+
260
+ The analyzer detects relevant Next.js App Router files such as:
261
+
262
+ ```text
263
+ layout.tsx
264
+ page.tsx
265
+ loading.tsx
266
+ error.tsx
267
+ not-found.tsx
268
+ route.ts
269
+ ```
270
+
271
+ ## Output Formats
272
+
273
+ `next-route-graph` currently provides three output modes.
274
+
275
+ ### Tree
276
+
277
+ The default output provides a human-readable overview of the project.
278
+
279
+ ```bash
280
+ npx next-route-graph
281
+ ```
282
+
283
+ ### Flat
284
+
285
+ Outputs only route paths.
286
+
287
+ ```bash
288
+ npx next-route-graph --flat
289
+ ```
290
+
291
+ ### JSON
292
+
293
+ Outputs structured data for programmatic use.
294
+
295
+ ```bash
296
+ npx next-route-graph --json
297
+ ```
298
+
299
+ ## Why next-route-graph?
300
+
301
+ Next.js App Router projects can become difficult to navigate as the number of routes, layouts, dynamic segments, and special files grows.
302
+
303
+ `next-route-graph` gives you a quick overview of the routing structure directly from your terminal.
304
+
305
+ No configuration.
306
+
307
+ No network requests.
308
+
309
+ No Next.js runtime.
310
+
311
+ Just run it and inspect your routes.
312
+
313
+ ## Development
314
+
315
+ Clone the repository:
316
+
317
+ ```bash
318
+ git clone https://github.com/Nima-sltn/next-route-graph.git
319
+ cd next-route-graph
320
+ ```
321
+
322
+ Install dependencies:
323
+
324
+ ```bash
325
+ npm install
326
+ ```
327
+
328
+ Run tests:
329
+
330
+ ```bash
331
+ npm test
332
+ ```
333
+
334
+ Run type checking:
335
+
336
+ ```bash
337
+ npm run typecheck
338
+ ```
339
+
340
+ Build the project:
341
+
342
+ ```bash
343
+ npm run build
344
+ ```
345
+
346
+ ## Contributing
347
+
348
+ Contributions, bug reports, feature requests, and improvements are welcome.
349
+
350
+ If you find an issue or have an idea for improving `next-route-graph`, feel free to open an issue or submit a pull request.
351
+
352
+ Before submitting a pull request, please make sure that:
353
+
354
+ - Tests are passing.
355
+ - Type checking passes.
356
+ - The project builds successfully.
357
+
358
+ ## License
359
+
360
+ MIT © 2026 Next Route Map Contributors
361
+
362
+ See the [LICENSE](./LICENSE) file for the full license text.
363
+
364
+ ## Links
365
+
366
+ - **npm:** https://www.npmjs.com/package/next-route-graph
367
+ - **GitHub:** https://github.com/Nima-sltn/next-route-graph
package/dist/cli.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import pc from "picocolors";
4
+ import { analyzeRoutes } from "./core/analyzer.js";
5
+ import { detectAppRouter } from "./core/detector.js";
6
+ import { scanAppDir } from "./core/scanner.js";
7
+ import { renderFlat } from "./render/flat.js";
8
+ import { renderJson } from "./render/json.js";
9
+ import { renderTree } from "./render/tree.js";
10
+ const program = new Command();
11
+ program
12
+ .name("next-route-graph")
13
+ .description("Inspect and visualize Next.js App Router project routes")
14
+ .version("1.0.0")
15
+ .argument("[projectPath]", "Path to the Next.js project root", ".")
16
+ .option("--flat", "Output only flat route paths")
17
+ .option("--json", "Output machine-readable JSON")
18
+ .option("--search <query>", "Filter routes by search string")
19
+ .action(async (projectPath, options) => {
20
+ try {
21
+ const { appDir, displayRoot } = detectAppRouter(projectPath);
22
+ const scannedFiles = await scanAppDir(appDir);
23
+ let analysisResult = await analyzeRoutes(scannedFiles, projectPath, displayRoot);
24
+ if (options.search) {
25
+ const search = options.search;
26
+ analysisResult.routes = analysisResult.routes.filter((route) => route.path.includes(search));
27
+ analysisResult.summary = {
28
+ ...analysisResult.summary,
29
+ routes: analysisResult.routes.length,
30
+ dynamic: analysisResult.routes.filter((route) => route.segmentType === "dynamic").length,
31
+ client: analysisResult.routes.filter((route) => route.rendering === "client").length,
32
+ server: analysisResult.routes.filter((route) => route.rendering === "server").length,
33
+ };
34
+ }
35
+ if (options.json) {
36
+ console.log(renderJson(analysisResult));
37
+ }
38
+ else if (options.flat) {
39
+ const flatOutput = renderFlat(analysisResult.routes);
40
+ if (flatOutput) {
41
+ console.log(flatOutput);
42
+ }
43
+ }
44
+ else {
45
+ console.log(renderTree(analysisResult));
46
+ }
47
+ }
48
+ catch (error) {
49
+ if (options.json) {
50
+ console.error(JSON.stringify({ error: error.message }));
51
+ }
52
+ else {
53
+ console.error(pc.red(`✗ ${error.message}`));
54
+ }
55
+ process.exit(1);
56
+ }
57
+ });
58
+ program.parse(process.argv);
@@ -0,0 +1,85 @@
1
+ import path from "node:path";
2
+ import { detectRenderingType, getSegmentType, parseRoutePath, parseSpecialFileType, } from "./parser.js";
3
+ export async function analyzeRoutes(files, projectPath, displayRoot) {
4
+ const startTime = Date.now();
5
+ const dirMap = new Map();
6
+ for (const file of files) {
7
+ const dir = path.dirname(file.relativePath).replace(/\\/g, "/");
8
+ if (!dirMap.has(dir)) {
9
+ dirMap.set(dir, []);
10
+ }
11
+ dirMap.get(dir).push(file);
12
+ }
13
+ const routesMap = new Map();
14
+ let totalLayouts = 0;
15
+ let totalLoading = 0;
16
+ 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
+ });
50
+ }
51
+ }
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;
56
+ 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
+ }
66
+ }
67
+ const projectName = path.basename(path.resolve(projectPath));
68
+ const summary = {
69
+ routes: routes.length,
70
+ dynamic: dynamicCount,
71
+ client: clientCount,
72
+ server: serverCount,
73
+ layouts: totalLayouts,
74
+ loading: totalLoading,
75
+ errors: totalErrors,
76
+ };
77
+ return {
78
+ project: projectName,
79
+ router: "App Router",
80
+ root: displayRoot,
81
+ routes,
82
+ summary,
83
+ executionTimeMs: Date.now() - startTime,
84
+ };
85
+ }
@@ -0,0 +1,28 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ export function detectAppRouter(projectDir) {
4
+ const absoluteDir = path.resolve(projectDir);
5
+ if (!fs.existsSync(absoluteDir)) {
6
+ throw new Error("The specified project path does not exist.");
7
+ }
8
+ const srcAppPath = path.join(absoluteDir, "src", "app");
9
+ const appPath = path.join(absoluteDir, "app");
10
+ const hasSrcApp = fs.existsSync(srcAppPath) && fs.statSync(srcAppPath).isDirectory();
11
+ const hasApp = fs.existsSync(appPath) && fs.statSync(appPath).isDirectory();
12
+ if (hasSrcApp && hasApp) {
13
+ throw new Error(`Could not determine the App Router root.\n\nFound both:\n ./app\n ./src/app\n\nPlease keep only one App Router root for analysis.`);
14
+ }
15
+ if (hasSrcApp) {
16
+ return {
17
+ appDir: srcAppPath,
18
+ displayRoot: "src/app",
19
+ };
20
+ }
21
+ if (hasApp) {
22
+ return {
23
+ appDir: appPath,
24
+ displayRoot: "app",
25
+ };
26
+ }
27
+ throw new Error(`No Next.js App Router directory found.\n\nExpected:\n ./app\n ./src/app`);
28
+ }
@@ -0,0 +1,78 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export const SPECIAL_FILES = new Set([
4
+ "page",
5
+ "layout",
6
+ "loading",
7
+ "error",
8
+ "not-found",
9
+ "template",
10
+ "default",
11
+ "route",
12
+ ]);
13
+ export function parseSpecialFileType(fileName) {
14
+ const baseName = path.parse(fileName).name;
15
+ if (SPECIAL_FILES.has(baseName)) {
16
+ return baseName;
17
+ }
18
+ return null;
19
+ }
20
+ export async function detectRenderingType(filePath) {
21
+ try {
22
+ const content = await fs.readFile(filePath, "utf-8");
23
+ const firstLines = content.slice(0, 500);
24
+ const clientDirectiveRegex = /^\s*(?:\/\/.*?\n|\/\*[\s\S]*?\*\/)*\s*["']use client["']/m;
25
+ if (clientDirectiveRegex.test(firstLines)) {
26
+ return "client";
27
+ }
28
+ }
29
+ catch {
30
+ // Default to server if unreadable
31
+ }
32
+ return "server";
33
+ }
34
+ export function parseRoutePath(relativePath) {
35
+ const parts = relativePath.split("/");
36
+ // Remove the file name at the end
37
+ parts.pop();
38
+ const urlSegments = [];
39
+ for (const part of parts) {
40
+ if (!part)
41
+ continue;
42
+ // Route groups are ignored in URL matching
43
+ if (part.startsWith("(") && part.endsWith(")")) {
44
+ const inner = part.slice(1, -1);
45
+ // Exclude intercepting routes which also start with '('
46
+ if (!inner.startsWith(".") && inner !== "...") {
47
+ continue;
48
+ }
49
+ }
50
+ // Parallel routes are ignored in URL pathing
51
+ if (part.startsWith("@")) {
52
+ continue;
53
+ }
54
+ // Intercepting routes clean-up for display path
55
+ let cleanedPart = part;
56
+ if (cleanedPart.startsWith("(.)") ||
57
+ cleanedPart.startsWith("(..)") ||
58
+ cleanedPart.startsWith("(...)") ||
59
+ cleanedPart.startsWith("(..)(..)")) {
60
+ cleanedPart = cleanedPart.replace(/^\(\.\.\.\)|\(\.\.\)|\(\.\)/, "");
61
+ }
62
+ urlSegments.push(cleanedPart);
63
+ }
64
+ const routePath = "/" + urlSegments.join("/");
65
+ return routePath === "/" ? "/" : routePath.replace(/\/+/g, "/");
66
+ }
67
+ export function getSegmentType(routePath) {
68
+ if (routePath.includes("[[...")) {
69
+ return "optional-catch-all";
70
+ }
71
+ if (routePath.includes("[...")) {
72
+ return "catch-all";
73
+ }
74
+ if (routePath.includes("[")) {
75
+ return "dynamic";
76
+ }
77
+ return "static";
78
+ }
@@ -0,0 +1,45 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ const ALLOWED_EXTENSIONS = new Set([".ts", ".tsx", ".js", ".jsx"]);
4
+ const IGNORE_DIRS = new Set([
5
+ "node_modules",
6
+ ".git",
7
+ ".next",
8
+ "dist",
9
+ "build",
10
+ "coverage",
11
+ ]);
12
+ export async function scanAppDir(rootDir, currentDir = rootDir) {
13
+ let files = [];
14
+ let entries;
15
+ try {
16
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
17
+ }
18
+ catch {
19
+ throw new Error("Unable to read the App Router directory.");
20
+ }
21
+ for (const entry of entries) {
22
+ const fullPath = path.join(currentDir, entry.name);
23
+ if (entry.isDirectory()) {
24
+ if (!IGNORE_DIRS.has(entry.name)) {
25
+ const subFiles = await scanAppDir(rootDir, fullPath);
26
+ files = files.concat(subFiles);
27
+ }
28
+ }
29
+ else if (entry.isFile()) {
30
+ const ext = path.extname(entry.name);
31
+ if (ALLOWED_EXTENSIONS.has(ext)) {
32
+ const relativePath = path
33
+ .relative(rootDir, fullPath)
34
+ .replace(/\\/g, "/");
35
+ files.push({
36
+ relativePath,
37
+ fullPath,
38
+ fileName: entry.name,
39
+ extension: ext,
40
+ });
41
+ }
42
+ }
43
+ }
44
+ return files;
45
+ }
@@ -0,0 +1,3 @@
1
+ export function renderFlat(routes) {
2
+ return routes.map((r) => r.path).join("\n");
3
+ }
@@ -0,0 +1,16 @@
1
+ export function renderJson(result) {
2
+ const output = {
3
+ project: result.project,
4
+ router: "app",
5
+ root: result.root,
6
+ routes: result.routes.map((r) => ({
7
+ path: r.path,
8
+ type: r.type,
9
+ file: r.file,
10
+ segmentType: r.segmentType,
11
+ rendering: r.rendering,
12
+ })),
13
+ summary: result.summary,
14
+ };
15
+ return JSON.stringify(output, null, 2);
16
+ }
@@ -0,0 +1,45 @@
1
+ import pc from "picocolors";
2
+ export function renderTree(result) {
3
+ const lines = [];
4
+ lines.push("");
5
+ lines.push(pc.bold("next-route-graph"));
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}`);
10
+ lines.push("");
11
+ lines.push(pc.bold("Routes"));
12
+ lines.push(pc.dim("────────────────────────────────────────"));
13
+ const routes = result.routes;
14
+ routes.forEach((route, index) => {
15
+ const isLast = index === routes.length - 1;
16
+ const prefix = isLast ? "└── " : "├── ";
17
+ const pathStr = route.path.padEnd(28, " ");
18
+ const fileStr = pc.dim(route.file.split("/").pop() || "");
19
+ let badge = "";
20
+ if (route.rendering === "client") {
21
+ badge = ` ${pc.cyan("[client]")}`;
22
+ }
23
+ else if (route.segmentType !== "static") {
24
+ badge = ` ${pc.yellow(`[${route.segmentType}]`)}`;
25
+ }
26
+ lines.push(`${pc.dim(prefix)}${pc.bold(pathStr)} ${fileStr}${badge}`);
27
+ });
28
+ if (routes.length === 0) {
29
+ lines.push(pc.dim(" (No routes found)"));
30
+ }
31
+ lines.push("");
32
+ lines.push(pc.bold("Summary"));
33
+ 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}`);
41
+ lines.push("");
42
+ lines.push(pc.green(`✓ Analysis completed in ${result.executionTimeMs}ms`));
43
+ lines.push("");
44
+ return lines.join("\n");
45
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "next-route-graph",
3
+ "version": "1.0.0",
4
+ "description": "Visualize and inspect the structure of a Next.js App Router project from the terminal",
5
+ "type": "module",
6
+ "bin": {
7
+ "next-route-graph": "dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "engines": {
13
+ "node": ">=18"
14
+ },
15
+ "scripts": {
16
+ "dev": "tsx src/cli.ts",
17
+ "build": "tsc -p tsconfig.build.json",
18
+ "typecheck": "tsc --noEmit",
19
+ "test": "tsx --test tests/**/*.test.ts",
20
+ "prepublishOnly": "npm run typecheck && npm test && npm run build"
21
+ },
22
+ "keywords": [
23
+ "nextjs",
24
+ "app-router",
25
+ "routes",
26
+ "cli",
27
+ "visualization",
28
+ "route-map",
29
+ "typescript"
30
+ ],
31
+ "author": "Next Route Map Contributors",
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "commander": "^12.1.0",
35
+ "picocolors": "^1.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.19.43",
39
+ "tsx": "^4.7.0",
40
+ "typescript": "^5.3.3"
41
+ }
42
+ }