next-route-graph 1.0.0 → 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/README.md +43 -276
- package/dist/core/analyzer.js +96 -60
- package/dist/core/parser.js +3 -10
- package/dist/render/tree.js +16 -13
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -1,95 +1,52 @@
|
|
|
1
|
+
````md
|
|
1
2
|
# next-route-graph
|
|
2
3
|
|
|
3
|
-
> Visualize and inspect
|
|
4
|
+
> Visualize and inspect your Next.js App Router structure from the terminal.
|
|
4
5
|
|
|
5
|
-
`next-route-graph` is a lightweight,
|
|
6
|
+
`next-route-graph` is a lightweight, zero-config CLI that scans your `app/` or `src/app/` directory and turns your routing structure into a clear overview.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
Useful for quickly understanding unfamiliar projects, documenting routes in a GitHub README, onboarding developers, or giving AI coding tools a structured view of your project's routing architecture.
|
|
9
|
+
|
|
10
|
+
No configuration. No network requests. No Next.js runtime.
|
|
8
11
|
|
|
9
12
|
## Features
|
|
10
13
|
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
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.
|
|
14
|
+
- Detects static, dynamic, catch-all, and optional catch-all routes
|
|
15
|
+
- Supports Route Groups, Parallel Routes, and Intercepting Routes
|
|
16
|
+
- Detects Server/Client rendering
|
|
17
|
+
- Detects layouts, route handlers, and special App Router files
|
|
18
|
+
- Tree, flat, and JSON output
|
|
19
|
+
- Route search and filtering
|
|
20
|
+
- Lightweight and fast
|
|
21
21
|
|
|
22
22
|
## Installation
|
|
23
23
|
|
|
24
|
-
### Run with npx
|
|
25
|
-
|
|
26
|
-
The easiest way to use `next-route-graph`:
|
|
27
|
-
|
|
28
24
|
```bash
|
|
29
25
|
npx next-route-graph
|
|
30
26
|
```
|
|
27
|
+
````
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
You can also install it globally:
|
|
29
|
+
Or install globally:
|
|
35
30
|
|
|
36
31
|
```bash
|
|
37
32
|
npm install -g next-route-graph
|
|
38
33
|
```
|
|
39
34
|
|
|
40
|
-
Then run:
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
next-route-graph
|
|
44
|
-
```
|
|
45
|
-
|
|
46
35
|
## Usage
|
|
47
36
|
|
|
48
|
-
Run
|
|
37
|
+
Run from your Next.js project:
|
|
49
38
|
|
|
50
39
|
```bash
|
|
51
40
|
npx next-route-graph
|
|
52
41
|
```
|
|
53
42
|
|
|
54
|
-
|
|
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:
|
|
43
|
+
Analyze another project:
|
|
67
44
|
|
|
68
45
|
```bash
|
|
69
46
|
npx next-route-graph ./my-next-app
|
|
70
47
|
```
|
|
71
48
|
|
|
72
|
-
|
|
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:
|
|
49
|
+
Example output:
|
|
93
50
|
|
|
94
51
|
```text
|
|
95
52
|
Project my-next-app
|
|
@@ -100,268 +57,78 @@ Routes
|
|
|
100
57
|
────────────────────────────────────────
|
|
101
58
|
├── /[locale] page.tsx [dynamic]
|
|
102
59
|
├── /[locale]/about page.tsx [dynamic]
|
|
103
|
-
├── /[locale]/
|
|
104
|
-
├── /[locale]/
|
|
105
|
-
├── /[locale]/concepts/[category]/[slug] page.tsx [dynamic]
|
|
60
|
+
├── /[locale]/blog page.tsx [dynamic]
|
|
61
|
+
├── /[locale]/blog/[slug] page.tsx [dynamic]
|
|
106
62
|
└── /api route.ts
|
|
107
63
|
|
|
108
64
|
Summary
|
|
109
65
|
────────────────────────────────────────
|
|
110
|
-
Routes
|
|
111
|
-
Dynamic
|
|
66
|
+
Routes 5
|
|
67
|
+
Dynamic 4
|
|
112
68
|
Client 0
|
|
113
|
-
Server
|
|
69
|
+
Server 5
|
|
114
70
|
Layouts 1
|
|
115
|
-
Loading 0
|
|
116
|
-
Errors 0
|
|
117
|
-
|
|
118
|
-
✓ Analysis completed in 18ms
|
|
119
71
|
```
|
|
120
72
|
|
|
121
|
-
## CLI
|
|
73
|
+
## CLI
|
|
74
|
+
|
|
75
|
+
```text
|
|
76
|
+
next-route-graph
|
|
77
|
+
next-route-graph <projectPath>
|
|
78
|
+
next-route-graph --flat
|
|
79
|
+
next-route-graph --json
|
|
80
|
+
next-route-graph --search <query>
|
|
81
|
+
next-route-graph --help
|
|
82
|
+
next-route-graph --version
|
|
83
|
+
```
|
|
122
84
|
|
|
123
85
|
### `--flat`
|
|
124
86
|
|
|
125
|
-
|
|
87
|
+
Show only route paths:
|
|
126
88
|
|
|
127
89
|
```bash
|
|
128
90
|
npx next-route-graph --flat
|
|
129
91
|
```
|
|
130
92
|
|
|
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
93
|
### `--json`
|
|
144
94
|
|
|
145
|
-
Output
|
|
95
|
+
Output structured JSON for scripts, automation, AI tools, or CI:
|
|
146
96
|
|
|
147
97
|
```bash
|
|
148
98
|
npx next-route-graph --json
|
|
149
99
|
```
|
|
150
100
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
### `--search <query>`
|
|
101
|
+
### `--search`
|
|
154
102
|
|
|
155
|
-
Filter routes
|
|
103
|
+
Filter routes:
|
|
156
104
|
|
|
157
105
|
```bash
|
|
158
106
|
npx next-route-graph --search blog
|
|
159
107
|
```
|
|
160
108
|
|
|
161
|
-
|
|
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:
|
|
109
|
+
Options can be combined:
|
|
176
110
|
|
|
177
111
|
```bash
|
|
178
112
|
npx next-route-graph --search blog --json
|
|
179
113
|
```
|
|
180
114
|
|
|
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
115
|
## Development
|
|
314
116
|
|
|
315
|
-
Clone the repository:
|
|
316
|
-
|
|
317
117
|
```bash
|
|
318
118
|
git clone https://github.com/Nima-sltn/next-route-graph.git
|
|
319
119
|
cd next-route-graph
|
|
320
|
-
```
|
|
321
|
-
|
|
322
|
-
Install dependencies:
|
|
323
|
-
|
|
324
|
-
```bash
|
|
325
120
|
npm install
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
Run tests:
|
|
329
|
-
|
|
330
|
-
```bash
|
|
331
121
|
npm test
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
Run type checking:
|
|
335
|
-
|
|
336
|
-
```bash
|
|
337
122
|
npm run typecheck
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
Build the project:
|
|
341
|
-
|
|
342
|
-
```bash
|
|
343
123
|
npm run build
|
|
344
124
|
```
|
|
345
125
|
|
|
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
126
|
## License
|
|
359
127
|
|
|
360
|
-
MIT © 2026
|
|
128
|
+
MIT © 2026 Nima Soltanian
|
|
361
129
|
|
|
362
|
-
|
|
130
|
+
[GitHub](https://github.com/Nima-sltn/next-route-graph) · [npm](https://www.npmjs.com/package/next-route-graph)
|
|
363
131
|
|
|
364
|
-
|
|
132
|
+
```
|
|
365
133
|
|
|
366
|
-
|
|
367
|
-
- **GitHub:** https://github.com/Nima-sltn/next-route-graph
|
|
134
|
+
```
|
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("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "next-route-graph",
|
|
3
|
-
"version": "1.0.
|
|
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": {
|
|
@@ -28,8 +28,18 @@
|
|
|
28
28
|
"route-map",
|
|
29
29
|
"typescript"
|
|
30
30
|
],
|
|
31
|
-
"author":
|
|
31
|
+
"author": {
|
|
32
|
+
"name": "Nima Soltanian",
|
|
33
|
+
"url": "https://github.com/Nima-sltn"
|
|
34
|
+
},
|
|
32
35
|
"license": "MIT",
|
|
36
|
+
"repository": {
|
|
37
|
+
"type": "git",
|
|
38
|
+
"url": "https://github.com/Nima-sltn/next-route-graph.git"
|
|
39
|
+
},
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/Nima-sltn/next-route-graph/issues"
|
|
42
|
+
},
|
|
33
43
|
"dependencies": {
|
|
34
44
|
"commander": "^12.1.0",
|
|
35
45
|
"picocolors": "^1.1.0"
|