next-route-graph 1.0.2 → 1.2.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/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +74 -0
- package/README.md +169 -134
- package/SECURITY.md +19 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +126 -44
- package/dist/core/analyzer.d.ts +16 -0
- package/dist/core/analyzer.d.ts.map +1 -0
- package/dist/core/analyzer.js +113 -29
- package/dist/core/detector.d.ts +19 -0
- package/dist/core/detector.d.ts.map +1 -0
- package/dist/core/detector.js +31 -3
- package/dist/core/inspect.d.ts +6 -0
- package/dist/core/inspect.d.ts.map +1 -0
- package/dist/core/inspect.js +21 -0
- package/dist/core/linter.d.ts +11 -0
- package/dist/core/linter.d.ts.map +1 -0
- package/dist/core/linter.js +194 -0
- package/dist/core/parser.d.ts +95 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +242 -15
- package/dist/core/scanner.d.ts +12 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +32 -6
- package/dist/errors/index.d.ts +35 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +65 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/preview.d.ts +4 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +25 -0
- package/dist/render/dot.d.ts +13 -0
- package/dist/render/dot.d.ts.map +1 -0
- package/dist/render/dot.js +66 -0
- package/dist/render/flat.d.ts +13 -0
- package/dist/render/flat.d.ts.map +1 -0
- package/dist/render/flat.js +14 -2
- package/dist/render/graph.d.ts +13 -0
- package/dist/render/graph.d.ts.map +1 -0
- package/dist/render/graph.js +53 -0
- package/dist/render/html.d.ts +8 -0
- package/dist/render/html.d.ts.map +1 -0
- package/dist/render/html.js +718 -0
- package/dist/render/index.d.ts +20 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +37 -0
- package/dist/render/json.d.ts +13 -0
- package/dist/render/json.d.ts.map +1 -0
- package/dist/render/json.js +12 -14
- package/dist/render/markdown.d.ts +13 -0
- package/dist/render/markdown.d.ts.map +1 -0
- package/dist/render/markdown.js +59 -0
- package/dist/render/mermaid.d.ts +13 -0
- package/dist/render/mermaid.d.ts.map +1 -0
- package/dist/render/mermaid.js +64 -0
- package/dist/render/renderer.interface.d.ts +8 -0
- package/dist/render/renderer.interface.d.ts.map +1 -0
- package/dist/render/renderer.interface.js +1 -0
- package/dist/render/svg.d.ts +8 -0
- package/dist/render/svg.d.ts.map +1 -0
- package/dist/render/svg.js +76 -0
- package/dist/render/tree.d.ts +13 -0
- package/dist/render/tree.d.ts.map +1 -0
- package/dist/render/tree.js +57 -7
- package/dist/types/index.d.ts +132 -0
- package/dist/types/index.d.ts.map +1 -0
- package/docs/ARCHITECTURE.md +71 -0
- package/docs/NEW_FEATURES.md +16 -0
- package/docs/adr/0001-offline-reports-and-shared-route-graph.md +29 -0
- package/docs/route-report.png +0 -0
- package/package.json +71 -52
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error hierarchy for next-route-graph
|
|
3
|
+
*/
|
|
4
|
+
export declare abstract class NextRouteGraphError extends Error {
|
|
5
|
+
abstract readonly code: string;
|
|
6
|
+
readonly hint?: string;
|
|
7
|
+
constructor(message: string, hint?: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class ProjectNotFoundError extends NextRouteGraphError {
|
|
10
|
+
readonly code = "ERR_PROJECT_NOT_FOUND";
|
|
11
|
+
constructor(projectPath?: string);
|
|
12
|
+
}
|
|
13
|
+
export declare class AmbiguousRouterError extends NextRouteGraphError {
|
|
14
|
+
readonly code = "ERR_AMBIGUOUS_ROUTER";
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
export declare class NoAppRouterError extends NextRouteGraphError {
|
|
18
|
+
readonly code = "ERR_NO_APP_ROUTER";
|
|
19
|
+
constructor();
|
|
20
|
+
}
|
|
21
|
+
export declare class RouteDirectoryReadError extends NextRouteGraphError {
|
|
22
|
+
readonly code = "ERR_DIRECTORY_READ";
|
|
23
|
+
constructor(dirPath?: string, cause?: unknown);
|
|
24
|
+
}
|
|
25
|
+
export declare class RouteFileReadError extends NextRouteGraphError {
|
|
26
|
+
readonly code = "ERR_FILE_READ";
|
|
27
|
+
constructor(filePath: string, cause: unknown);
|
|
28
|
+
}
|
|
29
|
+
export declare class RouteLintError extends NextRouteGraphError {
|
|
30
|
+
readonly code = "ERR_ROUTE_LINT";
|
|
31
|
+
readonly errorCount: number;
|
|
32
|
+
readonly warningCount: number;
|
|
33
|
+
constructor(errorCount: number, warningCount?: number);
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/errors/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,8BAAsB,mBAAoB,SAAQ,KAAK;IACrD,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEX,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAM3C;AAED,qBAAa,oBAAqB,SAAQ,mBAAmB;IAC3D,QAAQ,CAAC,IAAI,2BAA2B;gBAE5B,WAAW,CAAC,EAAE,MAAM;CAQjC;AAED,qBAAa,oBAAqB,SAAQ,mBAAmB;IAC3D,QAAQ,CAAC,IAAI,0BAA0B;;CAQxC;AAED,qBAAa,gBAAiB,SAAQ,mBAAmB;IACvD,QAAQ,CAAC,IAAI,uBAAuB;;CAQrC;AAED,qBAAa,uBAAwB,SAAQ,mBAAmB;IAC9D,QAAQ,CAAC,IAAI,wBAAwB;gBAEzB,OAAO,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAW9C;AAED,qBAAa,kBAAmB,SAAQ,mBAAmB;IACzD,QAAQ,CAAC,IAAI,mBAAmB;gBAEpB,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAO7C;AAED,qBAAa,cAAe,SAAQ,mBAAmB;IACrD,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;gBAElB,UAAU,EAAE,MAAM,EAAE,YAAY,GAAE,MAAU;CAWzD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error hierarchy for next-route-graph
|
|
3
|
+
*/
|
|
4
|
+
export class NextRouteGraphError extends Error {
|
|
5
|
+
hint;
|
|
6
|
+
constructor(message, hint) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = this.constructor.name;
|
|
9
|
+
this.hint = hint;
|
|
10
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class ProjectNotFoundError extends NextRouteGraphError {
|
|
14
|
+
code = "ERR_PROJECT_NOT_FOUND";
|
|
15
|
+
constructor(projectPath) {
|
|
16
|
+
super(projectPath
|
|
17
|
+
? `The specified project path does not exist: "${projectPath}"`
|
|
18
|
+
: "The specified project path does not exist.", "Please verify the directory path exists and you have read permissions.");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class AmbiguousRouterError extends NextRouteGraphError {
|
|
22
|
+
code = "ERR_AMBIGUOUS_ROUTER";
|
|
23
|
+
constructor() {
|
|
24
|
+
super(`Could not determine the App Router root. Found both "./app" and "./src/app".`, "Next.js only supports one App Router root. Please keep either ./app or ./src/app.");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class NoAppRouterError extends NextRouteGraphError {
|
|
28
|
+
code = "ERR_NO_APP_ROUTER";
|
|
29
|
+
constructor() {
|
|
30
|
+
super(`No Next.js App Router directory found.\n\nExpected:\n ./app\n ./src/app`, "Expected to find an ./app or ./src/app directory containing App Router files.");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export class RouteDirectoryReadError extends NextRouteGraphError {
|
|
34
|
+
code = "ERR_DIRECTORY_READ";
|
|
35
|
+
constructor(dirPath, cause) {
|
|
36
|
+
super("Unable to read the App Router directory.", dirPath
|
|
37
|
+
? `Ensure you have filesystem read permissions for "${dirPath}".`
|
|
38
|
+
: "Ensure you have filesystem read permissions for this folder.");
|
|
39
|
+
if (cause) {
|
|
40
|
+
this.cause = cause;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class RouteFileReadError extends NextRouteGraphError {
|
|
45
|
+
code = "ERR_FILE_READ";
|
|
46
|
+
constructor(filePath, cause) {
|
|
47
|
+
super(`Unable to read route file: "${filePath}"`, "Check that the file still exists and is readable, then scan again.");
|
|
48
|
+
this.cause = cause;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export class RouteLintError extends NextRouteGraphError {
|
|
52
|
+
code = "ERR_ROUTE_LINT";
|
|
53
|
+
errorCount;
|
|
54
|
+
warningCount;
|
|
55
|
+
constructor(errorCount, warningCount = 0) {
|
|
56
|
+
const details = [];
|
|
57
|
+
if (errorCount > 0)
|
|
58
|
+
details.push(`${errorCount} error(s)`);
|
|
59
|
+
if (warningCount > 0)
|
|
60
|
+
details.push(`${warningCount} warning(s)`);
|
|
61
|
+
super(`Route validation failed with ${details.join(" and ") || "issues"}.`, "Fix the reported route issues. Use a normal scan without --check or --strict to inspect without failing validation.");
|
|
62
|
+
this.errorCount = errorCount;
|
|
63
|
+
this.warningCount = warningCount;
|
|
64
|
+
}
|
|
65
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* next-route-graph
|
|
3
|
+
*
|
|
4
|
+
* Inspect, lint, and visualize Next.js App Router project routes.
|
|
5
|
+
* Programmatic SDK entry point.
|
|
6
|
+
*/
|
|
7
|
+
export { analyzeRoutes } from "./core/analyzer.js";
|
|
8
|
+
export { inspectProject, filterRoutes } from "./core/inspect.js";
|
|
9
|
+
export { buildRouteGraph, type RouteGraphNode } from "./render/graph.js";
|
|
10
|
+
export { detectAppRouter, type DetectionResult } from "./core/detector.js";
|
|
11
|
+
export { lintRoutes } from "./core/linter.js";
|
|
12
|
+
export { detectHttpMethods, detectRenderingType, detectRouteConfig, detectServerActions, extractRouteMetadata, getPrologueDirectives, getSegmentType, HTTP_METHODS, parseRoutePath, parseSpecialFileType, SPECIAL_FILES, stripComments, } from "./core/parser.js";
|
|
13
|
+
export { scanAppDir } from "./core/scanner.js";
|
|
14
|
+
export { AmbiguousRouterError, NextRouteGraphError, NoAppRouterError, ProjectNotFoundError, RouteDirectoryReadError, RouteFileReadError, RouteLintError, } from "./errors/index.js";
|
|
15
|
+
export { DotRenderer, FlatRenderer, getRenderer, HtmlRenderer, JsonRenderer, MarkdownRenderer, MermaidRenderer, renderDot, renderFlat, renderHtml, renderJson, renderMarkdown, renderMermaid, renderTree, TreeRenderer, SvgRenderer, renderSvg, } from "./render/index.js";
|
|
16
|
+
export type { OutputRenderer } from "./render/index.js";
|
|
17
|
+
export type { AnalysisResult, CLIOptions, DiscoveredFile, HttpMethod, LintIssue, LintResult, LintSeverity, OutputFormat, RenderingType, RouteEntry, RouteFile, SegmentType, SpecialFileType, SummaryStats, } from "./types/index.js";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/C,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,uBAAuB,EACvB,kBAAkB,EAClB,cAAc,GACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,WAAW,EACX,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,UAAU,EACV,UAAU,EACV,UAAU,EACV,cAAc,EACd,aAAa,EACb,UAAU,EACV,YAAY,EACZ,WAAW,EACX,SAAS,GACV,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,YAAY,EACV,cAAc,EACd,UAAU,EACV,cAAc,EACd,UAAU,EACV,SAAS,EACT,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,UAAU,EACV,SAAS,EACT,WAAW,EACX,eAAe,EACf,YAAY,GACb,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* next-route-graph
|
|
3
|
+
*
|
|
4
|
+
* Inspect, lint, and visualize Next.js App Router project routes.
|
|
5
|
+
* Programmatic SDK entry point.
|
|
6
|
+
*/
|
|
7
|
+
export { analyzeRoutes } from "./core/analyzer.js";
|
|
8
|
+
export { inspectProject, filterRoutes } from "./core/inspect.js";
|
|
9
|
+
export { buildRouteGraph } from "./render/graph.js";
|
|
10
|
+
export { detectAppRouter } from "./core/detector.js";
|
|
11
|
+
export { lintRoutes } from "./core/linter.js";
|
|
12
|
+
export { detectHttpMethods, detectRenderingType, detectRouteConfig, detectServerActions, extractRouteMetadata, getPrologueDirectives, getSegmentType, HTTP_METHODS, parseRoutePath, parseSpecialFileType, SPECIAL_FILES, stripComments, } from "./core/parser.js";
|
|
13
|
+
export { scanAppDir } from "./core/scanner.js";
|
|
14
|
+
// Domain Errors
|
|
15
|
+
export { AmbiguousRouterError, NextRouteGraphError, NoAppRouterError, ProjectNotFoundError, RouteDirectoryReadError, RouteFileReadError, RouteLintError, } from "./errors/index.js";
|
|
16
|
+
// Renderers & Strategy Pattern
|
|
17
|
+
export { DotRenderer, FlatRenderer, getRenderer, HtmlRenderer, JsonRenderer, MarkdownRenderer, MermaidRenderer, renderDot, renderFlat, renderHtml, renderJson, renderMarkdown, renderMermaid, renderTree, TreeRenderer, SvgRenderer, renderSvg, } from "./render/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,iFAAiF;AACjF,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAoBtB"}
|
package/dist/preview.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
/** Serve a single in-memory report on loopback. No project files are exposed. */
|
|
3
|
+
export async function startPreview(html, port) {
|
|
4
|
+
const server = http.createServer((req, res) => {
|
|
5
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
6
|
+
res.setHeader("Cache-Control", "no-store");
|
|
7
|
+
if (req.method !== "GET" && req.method !== "HEAD") {
|
|
8
|
+
res.writeHead(405, { Allow: "GET, HEAD" });
|
|
9
|
+
res.end();
|
|
10
|
+
}
|
|
11
|
+
else if (req.url !== "/") {
|
|
12
|
+
res.writeHead(404);
|
|
13
|
+
res.end();
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
17
|
+
res.end(req.method === "HEAD" ? undefined : html);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
await new Promise((resolve, reject) => {
|
|
21
|
+
server.once("error", reject);
|
|
22
|
+
server.listen(port, "127.0.0.1", resolve);
|
|
23
|
+
});
|
|
24
|
+
return server;
|
|
25
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AnalysisResult } from "../types/index.js";
|
|
2
|
+
import type { OutputRenderer } from "./renderer.interface.js";
|
|
3
|
+
/**
|
|
4
|
+
* Generates a Graphviz DOT representation of the routes.
|
|
5
|
+
*
|
|
6
|
+
* @param result - AnalysisResult
|
|
7
|
+
* @returns Graphviz DOT diagram string
|
|
8
|
+
*/
|
|
9
|
+
export declare function renderDot(result: AnalysisResult): string;
|
|
10
|
+
export declare class DotRenderer implements OutputRenderer {
|
|
11
|
+
render(result: AnalysisResult): string;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=dot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../../src/render/dot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAa9D;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAsDxD;AAED,qBAAa,WAAY,YAAW,cAAc;IAChD,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM;CAGvC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { buildRouteGraph } from "./graph.js";
|
|
2
|
+
/**
|
|
3
|
+
* Escapes backslashes and double quotes for Graphviz DOT format.
|
|
4
|
+
*/
|
|
5
|
+
function escapeDot(str) {
|
|
6
|
+
return str
|
|
7
|
+
.replace(/\\/g, "\\\\")
|
|
8
|
+
.replace(/"/g, '\\"')
|
|
9
|
+
.replace(/\r/g, "\\r")
|
|
10
|
+
.replace(/\n/g, "\\n");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generates a Graphviz DOT representation of the routes.
|
|
14
|
+
*
|
|
15
|
+
* @param result - AnalysisResult
|
|
16
|
+
* @returns Graphviz DOT diagram string
|
|
17
|
+
*/
|
|
18
|
+
export function renderDot(result) {
|
|
19
|
+
const lines = [];
|
|
20
|
+
const graphName = `${escapeDot(result.project)}Routes`;
|
|
21
|
+
lines.push(`digraph "${graphName}" {`);
|
|
22
|
+
lines.push(" rankdir=TB;");
|
|
23
|
+
lines.push(' node [shape=box, style="rounded,filled", fontname="Helvetica", fillcolor="#f8fafc", color="#cbd5e1"];');
|
|
24
|
+
lines.push(' edge [color="#94a3b8"];');
|
|
25
|
+
lines.push("");
|
|
26
|
+
const routes = result.routes;
|
|
27
|
+
if (routes.length === 0) {
|
|
28
|
+
lines.push(' empty [label="(No routes discovered)"];');
|
|
29
|
+
lines.push("}");
|
|
30
|
+
return lines.join("\n");
|
|
31
|
+
}
|
|
32
|
+
const nodes = buildRouteGraph(routes);
|
|
33
|
+
nodes.forEach((node) => {
|
|
34
|
+
const route = node.routes[0];
|
|
35
|
+
const id = node.id;
|
|
36
|
+
const labels = [node.path];
|
|
37
|
+
for (const endpoint of node.routes) {
|
|
38
|
+
labels.push(endpoint.file);
|
|
39
|
+
if (endpoint.methods?.length)
|
|
40
|
+
labels.push(`[${endpoint.methods.join(", ")}]`);
|
|
41
|
+
else if (endpoint.rendering === "client")
|
|
42
|
+
labels.push("[client]");
|
|
43
|
+
else if (endpoint.segmentType !== "static")
|
|
44
|
+
labels.push(`[${endpoint.segmentType}]`);
|
|
45
|
+
}
|
|
46
|
+
const labelText = labels.map(escapeDot).join("\\n");
|
|
47
|
+
const color = route?.rendering === "client"
|
|
48
|
+
? 'fillcolor="#ecfeff", color="#06b6d4"'
|
|
49
|
+
: route?.type === "route"
|
|
50
|
+
? 'fillcolor="#fdf4ff", color="#c026d3"'
|
|
51
|
+
: 'fillcolor="#f8fafc", color="#64748b"';
|
|
52
|
+
lines.push(` ${id} [label="${labelText}", ${color}];`);
|
|
53
|
+
});
|
|
54
|
+
lines.push("");
|
|
55
|
+
nodes.forEach((node) => {
|
|
56
|
+
if (node.parentId)
|
|
57
|
+
lines.push(` ${node.parentId} -> ${node.id};`);
|
|
58
|
+
});
|
|
59
|
+
lines.push("}");
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
62
|
+
export class DotRenderer {
|
|
63
|
+
render(result) {
|
|
64
|
+
return renderDot(result);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AnalysisResult, RouteEntry } from "../types/index.js";
|
|
2
|
+
import type { OutputRenderer } from "./renderer.interface.js";
|
|
3
|
+
/**
|
|
4
|
+
* Formats routes as a flat list of URL paths.
|
|
5
|
+
*
|
|
6
|
+
* @param input - Array of route entries or full AnalysisResult
|
|
7
|
+
* @returns Newline-separated route paths
|
|
8
|
+
*/
|
|
9
|
+
export declare function renderFlat(input: RouteEntry[] | AnalysisResult): string;
|
|
10
|
+
export declare class FlatRenderer implements OutputRenderer {
|
|
11
|
+
render(result: AnalysisResult): string;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=flat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flat.d.ts","sourceRoot":"","sources":["../../src/render/flat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAE9D;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,GAAG,cAAc,GAAG,MAAM,CAGvE;AAED,qBAAa,YAAa,YAAW,cAAc;IACjD,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM;CAGvC"}
|
package/dist/render/flat.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Formats routes as a flat list of URL paths.
|
|
3
|
+
*
|
|
4
|
+
* @param input - Array of route entries or full AnalysisResult
|
|
5
|
+
* @returns Newline-separated route paths
|
|
6
|
+
*/
|
|
7
|
+
export function renderFlat(input) {
|
|
8
|
+
const routes = Array.isArray(input) ? input : input.routes;
|
|
9
|
+
return routes.map((route) => route.path).join("\n");
|
|
10
|
+
}
|
|
11
|
+
export class FlatRenderer {
|
|
12
|
+
render(result) {
|
|
13
|
+
return renderFlat(result);
|
|
14
|
+
}
|
|
3
15
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RouteEntry } from "../types/index.js";
|
|
2
|
+
export interface RouteGraphNode {
|
|
3
|
+
id: string;
|
|
4
|
+
path: string;
|
|
5
|
+
depth: number;
|
|
6
|
+
parentId?: string;
|
|
7
|
+
routes: RouteEntry[];
|
|
8
|
+
}
|
|
9
|
+
/** URL hierarchy, including non-endpoint segments. Duplicate URLs retain every source. */
|
|
10
|
+
export declare function buildRouteGraph(routes: RouteEntry[]): RouteGraphNode[];
|
|
11
|
+
/** Escape text and attributes in HTML and XML, including untrusted source filenames. */
|
|
12
|
+
export declare function escapeMarkup(value: string): string;
|
|
13
|
+
//# sourceMappingURL=graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.d.ts","sourceRoot":"","sources":["../../src/render/graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,0FAA0F;AAC1F,wBAAgB,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CA0CtE;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlD"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/** URL hierarchy, including non-endpoint segments. Duplicate URLs retain every source. */
|
|
2
|
+
export function buildRouteGraph(routes) {
|
|
3
|
+
if (routes.length === 0)
|
|
4
|
+
return [];
|
|
5
|
+
const nodes = new Map();
|
|
6
|
+
nodes.set("/", { id: "root", path: "/", depth: 0, routes: [] });
|
|
7
|
+
for (const route of routes) {
|
|
8
|
+
let parent = nodes.get("/");
|
|
9
|
+
const segments = route.path.split("/").filter(Boolean);
|
|
10
|
+
for (let depth = 1; depth <= segments.length; depth++) {
|
|
11
|
+
const path = "/" + segments.slice(0, depth).join("/");
|
|
12
|
+
let node = nodes.get(path);
|
|
13
|
+
if (!node) {
|
|
14
|
+
node = {
|
|
15
|
+
id: `segment_${nodes.size}`,
|
|
16
|
+
path,
|
|
17
|
+
depth,
|
|
18
|
+
parentId: parent.id,
|
|
19
|
+
routes: [],
|
|
20
|
+
};
|
|
21
|
+
nodes.set(path, node);
|
|
22
|
+
}
|
|
23
|
+
parent = node;
|
|
24
|
+
}
|
|
25
|
+
parent.routes.push(route);
|
|
26
|
+
}
|
|
27
|
+
// A preorder keeps connecting lines clear even when SDK input is unsorted.
|
|
28
|
+
const children = new Map();
|
|
29
|
+
for (const node of nodes.values()) {
|
|
30
|
+
const siblings = children.get(node.parentId ?? "") ?? [];
|
|
31
|
+
siblings.push(node);
|
|
32
|
+
children.set(node.parentId ?? "", siblings);
|
|
33
|
+
}
|
|
34
|
+
const ordered = [];
|
|
35
|
+
const visit = (parentId) => {
|
|
36
|
+
for (const node of (children.get(parentId) ?? []).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) {
|
|
37
|
+
ordered.push(node);
|
|
38
|
+
visit(node.id);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
visit("");
|
|
42
|
+
return ordered;
|
|
43
|
+
}
|
|
44
|
+
/** Escape text and attributes in HTML and XML, including untrusted source filenames. */
|
|
45
|
+
export function escapeMarkup(value) {
|
|
46
|
+
return value
|
|
47
|
+
.replace(/&/g, "&")
|
|
48
|
+
.replace(/</g, "<")
|
|
49
|
+
.replace(/>/g, ">")
|
|
50
|
+
.replace(/"/g, """)
|
|
51
|
+
.replace(/'/g, "'")
|
|
52
|
+
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, "");
|
|
53
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AnalysisResult } from "../types/index.js";
|
|
2
|
+
import type { OutputRenderer } from "./renderer.interface.js";
|
|
3
|
+
/** Self-contained report. All source data is escaped; no project code is executed. */
|
|
4
|
+
export declare function renderHtml(result: AnalysisResult): string;
|
|
5
|
+
export declare class HtmlRenderer implements OutputRenderer {
|
|
6
|
+
render(result: AnalysisResult): string;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=html.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"html.d.ts","sourceRoot":"","sources":["../../src/render/html.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAOxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAK9D,sFAAsF;AACtF,wBAAgB,UAAU,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CA+dzD;AAkOD,qBAAa,YAAa,YAAW,cAAc;IACjD,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM;CAGvC"}
|