@zauso-ai/capstan-router 0.1.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/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +14 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +29 -0
- package/dist/manifest.js.map +1 -0
- package/dist/matcher.d.ts +15 -0
- package/dist/matcher.d.ts.map +1 -0
- package/dist/matcher.js +141 -0
- package/dist/matcher.js.map +1 -0
- package/dist/scanner.d.ts +6 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +257 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RouteManifest } from "./types.js";
|
|
2
|
+
export interface AgentApiRoute {
|
|
3
|
+
method: string;
|
|
4
|
+
path: string;
|
|
5
|
+
filePath: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Extract API route information from a full RouteManifest for use by the
|
|
9
|
+
* agent surface layer. Each API route is expanded into one entry per HTTP method.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateRouteManifest(manifest: RouteManifest): {
|
|
12
|
+
apiRoutes: AgentApiRoute[];
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,aAAa,GACtB;IAAE,SAAS,EAAE,aAAa,EAAE,CAAA;CAAE,CA0BhC"}
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract API route information from a full RouteManifest for use by the
|
|
3
|
+
* agent surface layer. Each API route is expanded into one entry per HTTP method.
|
|
4
|
+
*/
|
|
5
|
+
export function generateRouteManifest(manifest) {
|
|
6
|
+
const apiRoutes = [];
|
|
7
|
+
for (const route of manifest.routes) {
|
|
8
|
+
if (route.type !== "api") {
|
|
9
|
+
continue;
|
|
10
|
+
}
|
|
11
|
+
const methods = route.methods ?? ["GET"];
|
|
12
|
+
for (const method of methods) {
|
|
13
|
+
apiRoutes.push({
|
|
14
|
+
method,
|
|
15
|
+
path: route.urlPattern,
|
|
16
|
+
filePath: route.filePath,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
// Sort for deterministic output
|
|
21
|
+
apiRoutes.sort((a, b) => {
|
|
22
|
+
const pathCmp = a.path.localeCompare(b.path);
|
|
23
|
+
if (pathCmp !== 0)
|
|
24
|
+
return pathCmp;
|
|
25
|
+
return a.method.localeCompare(b.method);
|
|
26
|
+
});
|
|
27
|
+
return { apiRoutes };
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAAuB;IAEvB,MAAM,SAAS,GAAoB,EAAE,CAAC;IAEtC,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,KAAK,CAAC,IAAI,KAAK,KAAK,EAAE,CAAC;YACzB,SAAS;QACX,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;QACzC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,SAAS,CAAC,IAAI,CAAC;gBACb,MAAM;gBACN,IAAI,EAAE,KAAK,CAAC,UAAU;gBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACtB,MAAM,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,OAAO,KAAK,CAAC;YAAE,OAAO,OAAO,CAAC;QAClC,OAAO,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,SAAS,EAAE,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MatchedRoute, RouteManifest } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Match a URL path and HTTP method against the route manifest.
|
|
4
|
+
*
|
|
5
|
+
* Returns the best matching route (most specific) along with extracted parameters,
|
|
6
|
+
* or null if no route matches.
|
|
7
|
+
*
|
|
8
|
+
* Matching priority:
|
|
9
|
+
* 1. Static segments are preferred over dynamic segments
|
|
10
|
+
* 2. Dynamic segments are preferred over catch-all
|
|
11
|
+
* 3. Page routes match any method (they serve GET by convention)
|
|
12
|
+
* 4. API routes must match the given HTTP method
|
|
13
|
+
*/
|
|
14
|
+
export declare function matchRoute(manifest: RouteManifest, method: string, urlPath: string): MatchedRoute | null;
|
|
15
|
+
//# sourceMappingURL=matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA+E9D;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CACxB,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,YAAY,GAAG,IAAI,CA0ErB"}
|
package/dist/matcher.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a URL path by removing trailing slashes (except for root "/")
|
|
3
|
+
* and collapsing repeated slashes.
|
|
4
|
+
*/
|
|
5
|
+
function normalizePath(urlPath) {
|
|
6
|
+
// Collapse repeated slashes
|
|
7
|
+
let normalized = urlPath.replace(/\/+/g, "/");
|
|
8
|
+
// Remove trailing slash (unless it IS the root)
|
|
9
|
+
if (normalized.length > 1 && normalized.endsWith("/")) {
|
|
10
|
+
normalized = normalized.slice(0, -1);
|
|
11
|
+
}
|
|
12
|
+
// Ensure leading slash
|
|
13
|
+
if (!normalized.startsWith("/")) {
|
|
14
|
+
normalized = "/" + normalized;
|
|
15
|
+
}
|
|
16
|
+
return normalized;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Attempt to match a URL path against a single route pattern.
|
|
20
|
+
* Returns extracted params and a specificity score, or null if no match.
|
|
21
|
+
*
|
|
22
|
+
* Specificity is measured as the number of static segments — more static segments
|
|
23
|
+
* means a more specific match. Catch-all routes get the lowest specificity.
|
|
24
|
+
*/
|
|
25
|
+
function tryMatch(pattern, urlPath) {
|
|
26
|
+
const patternParts = pattern === "/" ? [""] : pattern.split("/");
|
|
27
|
+
const urlParts = urlPath === "/" ? [""] : urlPath.split("/");
|
|
28
|
+
const params = {};
|
|
29
|
+
let staticSegments = 0;
|
|
30
|
+
for (let i = 0; i < patternParts.length; i++) {
|
|
31
|
+
const patternPart = patternParts[i];
|
|
32
|
+
// Catch-all: matches the rest of the URL
|
|
33
|
+
if (patternPart === "*") {
|
|
34
|
+
// Collect remaining URL segments (everything from this position onward)
|
|
35
|
+
const rest = urlParts.slice(i).join("/");
|
|
36
|
+
// For catch-all stored in params, we need the param name from the route.
|
|
37
|
+
// We store under a generic key here; the caller maps it to the real name.
|
|
38
|
+
params["*"] = rest;
|
|
39
|
+
// Catch-all gets lowest specificity bonus
|
|
40
|
+
return { params, specificity: staticSegments };
|
|
41
|
+
}
|
|
42
|
+
// If we've run out of URL segments but still have pattern segments, no match
|
|
43
|
+
if (i >= urlParts.length) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const urlPart = urlParts[i];
|
|
47
|
+
if (patternPart.startsWith(":")) {
|
|
48
|
+
// Dynamic segment
|
|
49
|
+
const paramName = patternPart.slice(1);
|
|
50
|
+
params[paramName] = decodeURIComponent(urlPart);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Static segment — must match exactly
|
|
54
|
+
if (patternPart !== urlPart) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
staticSegments++;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// If there are remaining URL segments that weren't consumed, no match
|
|
61
|
+
if (urlParts.length > patternParts.length) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
return { params, specificity: staticSegments };
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Match a URL path and HTTP method against the route manifest.
|
|
68
|
+
*
|
|
69
|
+
* Returns the best matching route (most specific) along with extracted parameters,
|
|
70
|
+
* or null if no route matches.
|
|
71
|
+
*
|
|
72
|
+
* Matching priority:
|
|
73
|
+
* 1. Static segments are preferred over dynamic segments
|
|
74
|
+
* 2. Dynamic segments are preferred over catch-all
|
|
75
|
+
* 3. Page routes match any method (they serve GET by convention)
|
|
76
|
+
* 4. API routes must match the given HTTP method
|
|
77
|
+
*/
|
|
78
|
+
export function matchRoute(manifest, method, urlPath) {
|
|
79
|
+
const normalizedPath = normalizePath(urlPath);
|
|
80
|
+
const upperMethod = method.toUpperCase();
|
|
81
|
+
let best = null;
|
|
82
|
+
for (const route of manifest.routes) {
|
|
83
|
+
// Skip layout and middleware entries — they are not directly routable
|
|
84
|
+
if (route.type === "layout" || route.type === "middleware") {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// For API routes, check that the method is supported
|
|
88
|
+
if (route.type === "api" && route.methods && !route.methods.includes(upperMethod)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const result = tryMatch(route.urlPattern, normalizedPath);
|
|
92
|
+
if (result === null) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
// Resolve catch-all param name: replace the generic "*" key with the actual param name
|
|
96
|
+
if ("*" in result.params && route.params.length > 0) {
|
|
97
|
+
const catchAllParamName = route.params[route.params.length - 1];
|
|
98
|
+
result.params[catchAllParamName] = result.params["*"];
|
|
99
|
+
delete result.params["*"];
|
|
100
|
+
}
|
|
101
|
+
const candidate = {
|
|
102
|
+
match: { route, params: result.params },
|
|
103
|
+
specificity: result.specificity,
|
|
104
|
+
isCatchAll: route.isCatchAll,
|
|
105
|
+
};
|
|
106
|
+
if (best === null) {
|
|
107
|
+
best = candidate;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
// Determine if this candidate is better than the current best.
|
|
111
|
+
//
|
|
112
|
+
// Priority order:
|
|
113
|
+
// 1. Non-catch-all beats catch-all
|
|
114
|
+
// 2. Higher specificity (more static segments) beats lower
|
|
115
|
+
// 3. At equal specificity and catch-all status, prefer the route type
|
|
116
|
+
// that best matches the HTTP method:
|
|
117
|
+
// - For GET requests, prefer page routes over api routes
|
|
118
|
+
// - For non-GET requests, prefer api routes over page routes
|
|
119
|
+
if (!candidate.isCatchAll && best.isCatchAll) {
|
|
120
|
+
best = candidate;
|
|
121
|
+
}
|
|
122
|
+
else if (candidate.isCatchAll === best.isCatchAll) {
|
|
123
|
+
if (candidate.specificity > best.specificity) {
|
|
124
|
+
best = candidate;
|
|
125
|
+
}
|
|
126
|
+
else if (candidate.specificity === best.specificity) {
|
|
127
|
+
const preferApi = upperMethod !== "GET";
|
|
128
|
+
const candidateIsApi = candidate.match.route.type === "api";
|
|
129
|
+
const bestIsApi = best.match.route.type === "api";
|
|
130
|
+
if (preferApi && candidateIsApi && !bestIsApi) {
|
|
131
|
+
best = candidate;
|
|
132
|
+
}
|
|
133
|
+
else if (!preferApi && !candidateIsApi && bestIsApi) {
|
|
134
|
+
best = candidate;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return best?.match ?? null;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.js","sourceRoot":"","sources":["../src/matcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RouteManifest } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Scan a routes directory and produce a RouteManifest describing every route file found.
|
|
4
|
+
*/
|
|
5
|
+
export declare function scanRoutes(routesDir: string): Promise<RouteManifest>;
|
|
6
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,aAAa,EAAa,MAAM,YAAY,CAAC;AAoKvE;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA2H1E"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { readdir, stat } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Determine the route type from a filename.
|
|
5
|
+
* Returns null if the file is not a recognized route file.
|
|
6
|
+
*/
|
|
7
|
+
function classifyFile(filename) {
|
|
8
|
+
if (filename === "_layout.tsx")
|
|
9
|
+
return "layout";
|
|
10
|
+
if (filename === "_middleware.ts")
|
|
11
|
+
return "middleware";
|
|
12
|
+
if (filename.endsWith(".page.tsx"))
|
|
13
|
+
return "page";
|
|
14
|
+
if (filename.endsWith(".api.ts"))
|
|
15
|
+
return "api";
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Convert a filename to its URL segment contribution.
|
|
20
|
+
*
|
|
21
|
+
* index.page.tsx -> "" (index routes map to the directory itself)
|
|
22
|
+
* about.page.tsx -> "about"
|
|
23
|
+
* [id].page.tsx -> ":id"
|
|
24
|
+
* [...rest].page.tsx -> "*"
|
|
25
|
+
* index.api.ts -> ""
|
|
26
|
+
* [id].api.ts -> ":id"
|
|
27
|
+
*/
|
|
28
|
+
function fileToSegment(filename) {
|
|
29
|
+
// Strip the suffix to get the base name
|
|
30
|
+
let base;
|
|
31
|
+
if (filename.endsWith(".page.tsx")) {
|
|
32
|
+
base = filename.slice(0, -".page.tsx".length);
|
|
33
|
+
}
|
|
34
|
+
else if (filename.endsWith(".api.ts")) {
|
|
35
|
+
base = filename.slice(0, -".api.ts".length);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
return { segment: "", params: [], isCatchAll: false };
|
|
39
|
+
}
|
|
40
|
+
if (base === "index") {
|
|
41
|
+
return { segment: "", params: [], isCatchAll: false };
|
|
42
|
+
}
|
|
43
|
+
// Catch-all: [...rest]
|
|
44
|
+
const catchAllMatch = base.match(/^\[\.\.\.(\w+)\]$/);
|
|
45
|
+
if (catchAllMatch) {
|
|
46
|
+
return { segment: "*", params: [catchAllMatch[1]], isCatchAll: true };
|
|
47
|
+
}
|
|
48
|
+
// Dynamic segment: [param]
|
|
49
|
+
const dynamicMatch = base.match(/^\[(\w+)\]$/);
|
|
50
|
+
if (dynamicMatch) {
|
|
51
|
+
return { segment: `:${dynamicMatch[1]}`, params: [dynamicMatch[1]], isCatchAll: false };
|
|
52
|
+
}
|
|
53
|
+
// Static segment
|
|
54
|
+
return { segment: base, params: [], isCatchAll: false };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Convert a directory path relative to the routes root into URL segments.
|
|
58
|
+
* Each directory named as a dynamic segment (e.g. `[orgId]`) is converted.
|
|
59
|
+
*/
|
|
60
|
+
function dirToSegments(relativeDir) {
|
|
61
|
+
if (relativeDir === "" || relativeDir === ".") {
|
|
62
|
+
return { segments: [], params: [] };
|
|
63
|
+
}
|
|
64
|
+
const parts = relativeDir.split(path.sep);
|
|
65
|
+
const segments = [];
|
|
66
|
+
const params = [];
|
|
67
|
+
for (const part of parts) {
|
|
68
|
+
const catchAllMatch = part.match(/^\[\.\.\.(\w+)\]$/);
|
|
69
|
+
if (catchAllMatch) {
|
|
70
|
+
segments.push("*");
|
|
71
|
+
params.push(catchAllMatch[1]);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const dynamicMatch = part.match(/^\[(\w+)\]$/);
|
|
75
|
+
if (dynamicMatch) {
|
|
76
|
+
segments.push(`:${dynamicMatch[1]}`);
|
|
77
|
+
params.push(dynamicMatch[1]);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
segments.push(part);
|
|
81
|
+
}
|
|
82
|
+
return { segments, params };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Collect all _layout.tsx files from the routes root down to the given directory,
|
|
86
|
+
* ordered from outermost to innermost.
|
|
87
|
+
*/
|
|
88
|
+
function collectLayouts(routesDir, relativeDir) {
|
|
89
|
+
const layouts = [];
|
|
90
|
+
const parts = relativeDir === "" || relativeDir === "." ? [] : relativeDir.split(path.sep);
|
|
91
|
+
// Check the root directory first
|
|
92
|
+
layouts.push(path.join(routesDir, "_layout.tsx"));
|
|
93
|
+
// Then each nested directory
|
|
94
|
+
let current = routesDir;
|
|
95
|
+
for (const part of parts) {
|
|
96
|
+
current = path.join(current, part);
|
|
97
|
+
layouts.push(path.join(current, "_layout.tsx"));
|
|
98
|
+
}
|
|
99
|
+
return layouts;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Collect all _middleware.ts files from the routes root down to the given directory,
|
|
103
|
+
* ordered from outermost to innermost.
|
|
104
|
+
*/
|
|
105
|
+
function collectMiddlewares(routesDir, relativeDir) {
|
|
106
|
+
const middlewares = [];
|
|
107
|
+
const parts = relativeDir === "" || relativeDir === "." ? [] : relativeDir.split(path.sep);
|
|
108
|
+
middlewares.push(path.join(routesDir, "_middleware.ts"));
|
|
109
|
+
let current = routesDir;
|
|
110
|
+
for (const part of parts) {
|
|
111
|
+
current = path.join(current, part);
|
|
112
|
+
middlewares.push(path.join(current, "_middleware.ts"));
|
|
113
|
+
}
|
|
114
|
+
return middlewares;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Recursively walk a directory, returning all files as paths relative to the root.
|
|
118
|
+
*/
|
|
119
|
+
async function walkDir(dir, root) {
|
|
120
|
+
const files = [];
|
|
121
|
+
let entries;
|
|
122
|
+
try {
|
|
123
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// Directory doesn't exist or can't be read
|
|
127
|
+
return files;
|
|
128
|
+
}
|
|
129
|
+
for (const entry of entries) {
|
|
130
|
+
const fullPath = path.join(dir, entry.name);
|
|
131
|
+
if (entry.isDirectory()) {
|
|
132
|
+
const nested = await walkDir(fullPath, root);
|
|
133
|
+
files.push(...nested);
|
|
134
|
+
}
|
|
135
|
+
else if (entry.isFile()) {
|
|
136
|
+
files.push(path.relative(root, fullPath));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return files;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Filter layout/middleware candidate paths to only those that actually exist as
|
|
143
|
+
* files discovered during the scan.
|
|
144
|
+
*/
|
|
145
|
+
function filterExisting(candidates, existingAbsolute) {
|
|
146
|
+
return candidates.filter((p) => existingAbsolute.has(p));
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Scan a routes directory and produce a RouteManifest describing every route file found.
|
|
150
|
+
*/
|
|
151
|
+
export async function scanRoutes(routesDir) {
|
|
152
|
+
const resolvedRoot = path.resolve(routesDir);
|
|
153
|
+
// Verify the directory exists
|
|
154
|
+
try {
|
|
155
|
+
const s = await stat(resolvedRoot);
|
|
156
|
+
if (!s.isDirectory()) {
|
|
157
|
+
return {
|
|
158
|
+
routes: [],
|
|
159
|
+
scannedAt: new Date().toISOString(),
|
|
160
|
+
rootDir: resolvedRoot,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return {
|
|
166
|
+
routes: [],
|
|
167
|
+
scannedAt: new Date().toISOString(),
|
|
168
|
+
rootDir: resolvedRoot,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const relativePaths = await walkDir(resolvedRoot, resolvedRoot);
|
|
172
|
+
// Build a set of all absolute paths for existence checks
|
|
173
|
+
const absolutePathSet = new Set(relativePaths.map((rp) => path.join(resolvedRoot, rp)));
|
|
174
|
+
const routes = [];
|
|
175
|
+
for (const relPath of relativePaths) {
|
|
176
|
+
const filename = path.basename(relPath);
|
|
177
|
+
const routeType = classifyFile(filename);
|
|
178
|
+
if (routeType === null) {
|
|
179
|
+
// Not a recognized route file — skip
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const relativeDir = path.dirname(relPath);
|
|
183
|
+
const absoluteFilePath = path.join(resolvedRoot, relPath);
|
|
184
|
+
// Build URL pattern
|
|
185
|
+
const dirInfo = dirToSegments(relativeDir);
|
|
186
|
+
if (routeType === "layout" || routeType === "middleware") {
|
|
187
|
+
// Layouts and middlewares don't get their own URL pattern —
|
|
188
|
+
// they are referenced by other routes. But we still include them
|
|
189
|
+
// in the manifest so they can be discovered.
|
|
190
|
+
const urlParts = dirInfo.segments;
|
|
191
|
+
const urlPattern = "/" + urlParts.join("/");
|
|
192
|
+
routes.push({
|
|
193
|
+
filePath: absoluteFilePath,
|
|
194
|
+
type: routeType,
|
|
195
|
+
urlPattern: urlPattern === "/" ? "/" : urlPattern.replace(/\/$/, ""),
|
|
196
|
+
layouts: [],
|
|
197
|
+
middlewares: [],
|
|
198
|
+
params: dirInfo.params,
|
|
199
|
+
isCatchAll: false,
|
|
200
|
+
});
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
// Page or API route
|
|
204
|
+
const fileInfo = fileToSegment(filename);
|
|
205
|
+
const allParams = [...dirInfo.params, ...fileInfo.params];
|
|
206
|
+
const urlParts = [...dirInfo.segments];
|
|
207
|
+
if (fileInfo.segment !== "") {
|
|
208
|
+
urlParts.push(fileInfo.segment);
|
|
209
|
+
}
|
|
210
|
+
const urlPattern = "/" + urlParts.join("/");
|
|
211
|
+
// Collect parent layouts and middlewares (only those that actually exist on disk)
|
|
212
|
+
const layoutCandidates = collectLayouts(resolvedRoot, relativeDir);
|
|
213
|
+
const middlewareCandidates = collectMiddlewares(resolvedRoot, relativeDir);
|
|
214
|
+
const layouts = filterExisting(layoutCandidates, absolutePathSet);
|
|
215
|
+
const middlewares = filterExisting(middlewareCandidates, absolutePathSet);
|
|
216
|
+
const entry = {
|
|
217
|
+
filePath: absoluteFilePath,
|
|
218
|
+
type: routeType,
|
|
219
|
+
urlPattern: urlPattern === "/" ? "/" : urlPattern.replace(/\/$/, ""),
|
|
220
|
+
layouts,
|
|
221
|
+
middlewares,
|
|
222
|
+
params: allParams,
|
|
223
|
+
isCatchAll: fileInfo.isCatchAll,
|
|
224
|
+
};
|
|
225
|
+
if (routeType === "api") {
|
|
226
|
+
// API routes support all standard HTTP methods by default.
|
|
227
|
+
// The actual exported methods are determined at runtime.
|
|
228
|
+
entry.methods = ["GET", "POST", "PUT", "DELETE", "PATCH"];
|
|
229
|
+
}
|
|
230
|
+
routes.push(entry);
|
|
231
|
+
}
|
|
232
|
+
// Sort for deterministic output:
|
|
233
|
+
// 1. By URL pattern alphabetically
|
|
234
|
+
// 2. By type (layout < middleware < page < api)
|
|
235
|
+
// 3. By file path as tiebreaker
|
|
236
|
+
const typeOrder = {
|
|
237
|
+
layout: 0,
|
|
238
|
+
middleware: 1,
|
|
239
|
+
page: 2,
|
|
240
|
+
api: 3,
|
|
241
|
+
};
|
|
242
|
+
routes.sort((a, b) => {
|
|
243
|
+
const patternCmp = a.urlPattern.localeCompare(b.urlPattern);
|
|
244
|
+
if (patternCmp !== 0)
|
|
245
|
+
return patternCmp;
|
|
246
|
+
const typeCmp = typeOrder[a.type] - typeOrder[b.type];
|
|
247
|
+
if (typeCmp !== 0)
|
|
248
|
+
return typeCmp;
|
|
249
|
+
return a.filePath.localeCompare(b.filePath);
|
|
250
|
+
});
|
|
251
|
+
return {
|
|
252
|
+
routes,
|
|
253
|
+
scannedAt: new Date().toISOString(),
|
|
254
|
+
rootDir: resolvedRoot,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type RouteType = "page" | "api" | "layout" | "middleware";
|
|
2
|
+
export interface RouteEntry {
|
|
3
|
+
/** Absolute file path */
|
|
4
|
+
filePath: string;
|
|
5
|
+
/** Route type determined by file suffix */
|
|
6
|
+
type: RouteType;
|
|
7
|
+
/** URL path pattern, e.g. "/tickets/:id" */
|
|
8
|
+
urlPattern: string;
|
|
9
|
+
/** HTTP methods this route handles (for api routes) */
|
|
10
|
+
methods?: string[];
|
|
11
|
+
/** Parent layout file paths (from outermost to innermost) */
|
|
12
|
+
layouts: string[];
|
|
13
|
+
/** Middleware file paths (from outermost to innermost) */
|
|
14
|
+
middlewares: string[];
|
|
15
|
+
/** Dynamic parameter names */
|
|
16
|
+
params: string[];
|
|
17
|
+
/** Whether this is a catch-all route */
|
|
18
|
+
isCatchAll: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface RouteManifest {
|
|
21
|
+
routes: RouteEntry[];
|
|
22
|
+
/** Timestamp of last scan */
|
|
23
|
+
scannedAt: string;
|
|
24
|
+
/** Root directory that was scanned */
|
|
25
|
+
rootDir: string;
|
|
26
|
+
}
|
|
27
|
+
export interface MatchedRoute {
|
|
28
|
+
route: RouteEntry;
|
|
29
|
+
params: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,YAAY,CAAC;AAEjE,MAAM,WAAW,UAAU;IACzB,yBAAyB;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,IAAI,EAAE,SAAS,CAAC;IAChB,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,0DAA0D;IAC1D,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,8BAA8B;IAC9B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,wCAAwC;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,6BAA6B;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zauso-ai/capstan-router",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc -p tsconfig.json",
|
|
15
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"description": "File-based routing for Capstan — .page.tsx, .api.ts, _layout.tsx, _middleware.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"dist"
|
|
20
|
+
],
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/barry3406/capstan.git",
|
|
24
|
+
"directory": "packages/router"
|
|
25
|
+
},
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"author": "barry3406",
|
|
28
|
+
"homepage": "https://github.com/barry3406/capstan",
|
|
29
|
+
"keywords": [
|
|
30
|
+
"capstan",
|
|
31
|
+
"ai-agent",
|
|
32
|
+
"full-stack",
|
|
33
|
+
"framework",
|
|
34
|
+
"mcp",
|
|
35
|
+
"a2a",
|
|
36
|
+
"typescript"
|
|
37
|
+
],
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "public"
|
|
40
|
+
}
|
|
41
|
+
}
|