@12-apps/mcp 3.1.0 → 3.2.1
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/ADOPTING.md +32 -0
- package/dist/chunk-7QVYU63E.js +7 -0
- package/dist/chunk-7QVYU63E.js.map +1 -0
- package/dist/chunk-FYEVBTDU.js +162 -0
- package/dist/chunk-FYEVBTDU.js.map +1 -0
- package/dist/chunk-HAZOPC6U.js +229 -0
- package/dist/chunk-HAZOPC6U.js.map +1 -0
- package/dist/chunk-UIILEGAC.js +1247 -0
- package/dist/chunk-UIILEGAC.js.map +1 -0
- package/dist/chunk-WJJNKKNS.js +63 -0
- package/dist/chunk-WJJNKKNS.js.map +1 -0
- package/dist/coverage-gate/index.d.ts +129 -0
- package/dist/coverage-gate/index.js +163 -0
- package/dist/coverage-gate/index.js.map +1 -0
- package/dist/create-api-mcp-oauth-CwVXKK-A.d.ts +647 -0
- package/dist/generate/index.d.ts +91 -0
- package/dist/generate/index.js +101 -0
- package/dist/generate/index.js.map +1 -0
- package/dist/generate-Dx3cK8th.d.ts +184 -0
- package/dist/guide-DV5MQbCg.d.ts +135 -0
- package/dist/hono/index.d.ts +28 -0
- package/dist/hono/index.js +25 -0
- package/dist/hono/index.js.map +1 -0
- package/dist/index.d.ts +382 -0
- package/dist/index.js +331 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth/index.d.ts +446 -0
- package/dist/oauth/index.js +279 -0
- package/dist/oauth/index.js.map +1 -0
- package/dist/react/index.d.ts +239 -0
- package/dist/react/index.js +1183 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +31 -10
- package/prisma/migrations/20260812150000_add_mcp_oauth_tables/migration.sql +6 -6
- package/src/index.ts +13 -0
- package/src/server/jsonrpc.ts +167 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "../chunk-7QVYU63E.js";
|
|
4
|
+
|
|
5
|
+
// src/coverage-gate/index.ts
|
|
6
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
7
|
+
import { exportedActionsOf, segmentPrefixMatch, walkActionFiles } from "@12-apps/rbac/coverage";
|
|
8
|
+
|
|
9
|
+
// src/coverage-gate/route-methods.ts
|
|
10
|
+
import { readFileSync } from "fs";
|
|
11
|
+
import { relative } from "path";
|
|
12
|
+
import { exportedNamesOf, urlPathOf, walkRouteFiles } from "@12-apps/rbac/coverage";
|
|
13
|
+
var HTTP_METHODS = [
|
|
14
|
+
"GET",
|
|
15
|
+
"HEAD",
|
|
16
|
+
"POST",
|
|
17
|
+
"PUT",
|
|
18
|
+
"PATCH",
|
|
19
|
+
"DELETE",
|
|
20
|
+
"OPTIONS"
|
|
21
|
+
];
|
|
22
|
+
function exportedMethodsOf(source) {
|
|
23
|
+
return exportedNamesOf(source, { syncFunctions: true, accept: isHttpMethod });
|
|
24
|
+
}
|
|
25
|
+
__name(exportedMethodsOf, "exportedMethodsOf");
|
|
26
|
+
function isHttpMethod(name) {
|
|
27
|
+
return HTTP_METHODS.includes(name);
|
|
28
|
+
}
|
|
29
|
+
__name(isHttpMethod, "isHttpMethod");
|
|
30
|
+
function collectRouteMethods(appDir, webRoot) {
|
|
31
|
+
return walkRouteFiles(appDir).flatMap((file) => {
|
|
32
|
+
const urlPath = urlPathOf(file, appDir);
|
|
33
|
+
return exportedMethodsOf(readFileSync(file, "utf8")).map((method) => ({
|
|
34
|
+
urlPath,
|
|
35
|
+
method,
|
|
36
|
+
file: relative(webRoot, file)
|
|
37
|
+
}));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
__name(collectRouteMethods, "collectRouteMethods");
|
|
41
|
+
|
|
42
|
+
// src/coverage-gate/index.ts
|
|
43
|
+
function readJson(path) {
|
|
44
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
45
|
+
}
|
|
46
|
+
__name(readJson, "readJson");
|
|
47
|
+
function routeFailures(ctx) {
|
|
48
|
+
const failures = [];
|
|
49
|
+
const infraPrefixes = Object.keys(ctx.exclusions.routes);
|
|
50
|
+
const routeMethods = collectRouteMethods(ctx.appDir, ctx.webRoot);
|
|
51
|
+
const registered = new Set(
|
|
52
|
+
ctx.endpoints.map((endpoint) => `${endpoint.method.toUpperCase()} ${endpoint.path}`)
|
|
53
|
+
);
|
|
54
|
+
const covered = routeMethods.filter(({ urlPath }) => !segmentPrefixMatch(urlPath, infraPrefixes));
|
|
55
|
+
for (const { urlPath, method, file } of covered) {
|
|
56
|
+
if (!registered.has(`${method} ${urlPath}`)) {
|
|
57
|
+
failures.push(
|
|
58
|
+
`unregistered route: ${method} ${urlPath} (${file}) \u2014 add a registry entry, or (human-authorized, for infra only) a routes prefix in the exclusions file`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const served = new Set(
|
|
63
|
+
routeMethods.map(({ method, urlPath }) => `${method} ${urlPath}`)
|
|
64
|
+
);
|
|
65
|
+
for (const endpoint of ctx.endpoints) {
|
|
66
|
+
if (!served.has(`${endpoint.method.toUpperCase()} ${endpoint.path}`)) {
|
|
67
|
+
failures.push(
|
|
68
|
+
`registry entry without a route: ${endpoint.operationId} (${endpoint.method.toUpperCase()} ${endpoint.path}) \u2014 the manifest advertises a tool no route serves`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return { failures, routeMethodCount: covered.length };
|
|
73
|
+
}
|
|
74
|
+
__name(routeFailures, "routeFailures");
|
|
75
|
+
function unaccountedActions(ctx, actions, mapped) {
|
|
76
|
+
const failures = [];
|
|
77
|
+
for (const action of actions) {
|
|
78
|
+
const isMapped = action in mapped;
|
|
79
|
+
const isExcluded = action in ctx.exclusions.actions;
|
|
80
|
+
if (!isMapped && !isExcluded) {
|
|
81
|
+
failures.push(
|
|
82
|
+
`unmapped server action: ${action} \u2014 map it to a registry operationId in the action map, or (human-authorized) add it to the exclusions file with a reason`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
if (isMapped && isExcluded) {
|
|
86
|
+
failures.push(
|
|
87
|
+
`action both mapped and excluded: ${action} \u2014 remove it from one of the two files`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return failures;
|
|
92
|
+
}
|
|
93
|
+
__name(unaccountedActions, "unaccountedActions");
|
|
94
|
+
function staleActionEntries(ctx, actions, mapped) {
|
|
95
|
+
const failures = [];
|
|
96
|
+
const operationIds = new Set(ctx.endpoints.map((endpoint) => endpoint.operationId));
|
|
97
|
+
for (const [action, operationId] of Object.entries(mapped)) {
|
|
98
|
+
if (!actions.has(action)) {
|
|
99
|
+
failures.push(`stale action-map entry: ${action} \u2014 the action no longer exists`);
|
|
100
|
+
}
|
|
101
|
+
if (!operationIds.has(operationId)) {
|
|
102
|
+
failures.push(`action-map points at unknown operationId: ${action} \u2192 ${operationId}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
for (const action of Object.keys(ctx.exclusions.actions)) {
|
|
106
|
+
if (!actions.has(action)) {
|
|
107
|
+
failures.push(`stale action exclusion: ${action} \u2014 the action no longer exists`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return failures;
|
|
111
|
+
}
|
|
112
|
+
__name(staleActionEntries, "staleActionEntries");
|
|
113
|
+
function actionFailures(ctx) {
|
|
114
|
+
const mapped = ctx.actionMap?.mapped ?? {};
|
|
115
|
+
const actions = new Set(
|
|
116
|
+
walkActionFiles(ctx.appDir).flatMap((file) => exportedActionsOf(readFileSync2(file, "utf8")))
|
|
117
|
+
);
|
|
118
|
+
return {
|
|
119
|
+
failures: [
|
|
120
|
+
...unaccountedActions(ctx, actions, mapped),
|
|
121
|
+
...staleActionEntries(ctx, actions, mapped)
|
|
122
|
+
],
|
|
123
|
+
actionCount: actions.size
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
__name(actionFailures, "actionFailures");
|
|
127
|
+
function runMcpCoverage(options) {
|
|
128
|
+
const ctx = {
|
|
129
|
+
appDir: options.appDir,
|
|
130
|
+
webRoot: options.webRoot ?? options.appDir,
|
|
131
|
+
endpoints: options.endpoints,
|
|
132
|
+
exclusions: readJson(options.exclusionsPath),
|
|
133
|
+
actionMap: options.actionMapPath ? readJson(options.actionMapPath) : null
|
|
134
|
+
};
|
|
135
|
+
const routes = routeFailures(ctx);
|
|
136
|
+
const actions = actionFailures(ctx);
|
|
137
|
+
return {
|
|
138
|
+
failures: [...routes.failures, ...actions.failures],
|
|
139
|
+
routeMethodCount: routes.routeMethodCount,
|
|
140
|
+
actionCount: actions.actionCount
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
__name(runMcpCoverage, "runMcpCoverage");
|
|
144
|
+
function mcpCoverageCli(options) {
|
|
145
|
+
const { failures, routeMethodCount, actionCount } = runMcpCoverage(options);
|
|
146
|
+
if (failures.length > 0) {
|
|
147
|
+
console.error(`[mcp:coverage] ${failures.length} violation(s):`);
|
|
148
|
+
for (const failure of failures) console.error(` \u2717 ${failure}`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
console.log(
|
|
152
|
+
`[mcp:coverage] OK \u2014 ${routeMethodCount} route method(s) registered, ${actionCount} action(s) mapped/excluded.`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
__name(mcpCoverageCli, "mcpCoverageCli");
|
|
156
|
+
export {
|
|
157
|
+
HTTP_METHODS,
|
|
158
|
+
collectRouteMethods,
|
|
159
|
+
exportedMethodsOf,
|
|
160
|
+
mcpCoverageCli,
|
|
161
|
+
runMcpCoverage
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/coverage-gate/index.ts","../../src/coverage-gate/route-methods.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\n\nimport { exportedActionsOf, segmentPrefixMatch, walkActionFiles } from \"@12-apps/rbac/coverage\";\n\nimport { collectRouteMethods } from \"./route-methods\";\n\n/**\n * `@12-apps/mcp/coverage` — the MCP route/action coverage gate (12-23), moved out\n * of the origin host's `apps/web/scripts/mcp/coverage.ts` so a host's own script is a\n * one-line re-export and the CI workflow that shells out to the consumer's\n * `mcp:coverage` package script (`12-apps/ci`'s `mcp-contract.yml`) keeps working\n * unchanged.\n *\n * `mcp:check` only proves the REGISTRY matches the committed manifest; nothing\n * stops a new route file, or a new server action, from shipping outside the\n * agent-exposable surface. This gate closes both:\n *\n * 1. **Route coverage** — every HTTP method exported by a route file must be\n * registered in the host's MCP registry (or its path listed under `routes` in\n * the exclusions file), and every registry entry must map back to a real route\n * file exporting that method. A tool the manifest advertises but no route\n * serves is a promise an agent cannot cash.\n * 2. **Action coverage** — every exported server action must be mapped to a\n * registry operationId in the action map, or listed under `actions` in the\n * exclusions file with a reason. New actions fail until mapped; stale entries\n * fail until pruned, so neither file can rot.\n *\n * The exclusions file is the ONLY escape hatch, and keeping it a separate,\n * human-protected file is the point: an agent cannot silently exclude a new\n * route/action — it has to justify to a human why the capability is not exposed.\n */\n\n/** One registry entry, as the host's MCP registry describes an endpoint. */\nexport interface McpRegistryEndpoint {\n method: string;\n /** URL path in `{param}` form — the same shape the scan produces. */\n path: string;\n operationId: string;\n}\n\n/** The protected exclusions file: every deliberate gate escape hatch. */\nexport interface McpCoverageExclusions {\n /** Server actions kept off the surface, name → reason. */\n actions: Record<string, string>;\n /** Route path prefixes kept off the surface, prefix → reason. */\n routes: Record<string, string>;\n}\n\n/** The action map: server action name → registry operationId. */\nexport interface McpActionMap {\n mapped: Record<string, string>;\n}\n\nexport interface McpCoverageOptions {\n /** The framework routes folder (the WHOLE `app`, never `app/api`). */\n appDir: string;\n /** Root for relative paths in failure messages. Default: `appDir`. */\n webRoot?: string;\n /** The host's registry entries (its `endpoints` array). */\n endpoints: readonly McpRegistryEndpoint[];\n /** Path to the exclusions JSON ({@link McpCoverageExclusions}). */\n exclusionsPath: string;\n /**\n * Path to the action-map JSON ({@link McpActionMap}). Omit for a host with no\n * server actions at all — action coverage is then vacuous rather than a crash on\n * a file that was never written.\n */\n actionMapPath?: string;\n}\n\nexport interface McpCoverageResult {\n failures: string[];\n routeMethodCount: number;\n actionCount: number;\n}\n\ninterface GateContext {\n appDir: string;\n webRoot: string;\n endpoints: readonly McpRegistryEndpoint[];\n exclusions: McpCoverageExclusions;\n actionMap: McpActionMap | null;\n}\n\nfunction readJson<T>(path: string): T {\n return JSON.parse(readFileSync(path, \"utf8\")) as T;\n}\n\n/** Route coverage — every served method registered, every registry entry served. */\nfunction routeFailures(ctx: GateContext): { failures: string[]; routeMethodCount: number } {\n const failures: string[] = [];\n const infraPrefixes = Object.keys(ctx.exclusions.routes);\n const routeMethods = collectRouteMethods(ctx.appDir, ctx.webRoot);\n const registered = new Set(\n ctx.endpoints.map((endpoint) => `${endpoint.method.toUpperCase()} ${endpoint.path}`),\n );\n\n // NOTE which way this loop fails, because it is the opposite of the instinct a\n // gate invites: a method the SCAN misses is simply absent from `covered`, so no\n // violation is raised at all and that route ships unregistered. Under-detection\n // here is fail-OPEN — which is why `exportedNamesOf` blanks comments and strings\n // before it looks for an export head instead of trusting raw source. (The loop\n // after this one is the merely noisy half: a REGISTERED entry whose route was\n // missed reports `registry entry without a route`.)\n const covered = routeMethods.filter(({ urlPath }) => !segmentPrefixMatch(urlPath, infraPrefixes));\n for (const { urlPath, method, file } of covered) {\n if (!registered.has(`${method} ${urlPath}`)) {\n failures.push(\n `unregistered route: ${method} ${urlPath} (${file}) — add a registry entry, or ` +\n `(human-authorized, for infra only) a routes prefix in the exclusions file`,\n );\n }\n }\n\n const served = new Set(\n routeMethods.map(({ method, urlPath }) => `${method} ${urlPath}`),\n );\n for (const endpoint of ctx.endpoints) {\n if (!served.has(`${endpoint.method.toUpperCase()} ${endpoint.path}`)) {\n failures.push(\n `registry entry without a route: ${endpoint.operationId} ` +\n `(${endpoint.method.toUpperCase()} ${endpoint.path}) — the manifest advertises a tool no route serves`,\n );\n }\n }\n\n return { failures, routeMethodCount: covered.length };\n}\n\n/**\n * Every action the host actually exports must be accounted for — mapped to a\n * registry operationId, or excluded with a reason. Neither silently.\n */\nfunction unaccountedActions(\n ctx: GateContext,\n actions: Set<string>,\n mapped: Record<string, string>,\n): string[] {\n const failures: string[] = [];\n for (const action of actions) {\n const isMapped = action in mapped;\n const isExcluded = action in ctx.exclusions.actions;\n if (!isMapped && !isExcluded) {\n failures.push(\n `unmapped server action: ${action} — map it to a registry operationId in the action ` +\n `map, or (human-authorized) add it to the exclusions file with a reason`,\n );\n }\n if (isMapped && isExcluded) {\n failures.push(\n `action both mapped and excluded: ${action} — remove it from one of the two files`,\n );\n }\n }\n return failures;\n}\n\n/**\n * The other direction, and the one that keeps both files from rotting: an entry\n * naming an action that no longer exists, or an operationId the registry does not\n * have. Adding the mapping is never enough — a stale line must go.\n */\nfunction staleActionEntries(\n ctx: GateContext,\n actions: Set<string>,\n mapped: Record<string, string>,\n): string[] {\n const failures: string[] = [];\n const operationIds = new Set(ctx.endpoints.map((endpoint) => endpoint.operationId));\n for (const [action, operationId] of Object.entries(mapped)) {\n if (!actions.has(action)) {\n failures.push(`stale action-map entry: ${action} — the action no longer exists`);\n }\n if (!operationIds.has(operationId)) {\n failures.push(`action-map points at unknown operationId: ${action} → ${operationId}`);\n }\n }\n for (const action of Object.keys(ctx.exclusions.actions)) {\n if (!actions.has(action)) {\n failures.push(`stale action exclusion: ${action} — the action no longer exists`);\n }\n }\n return failures;\n}\n\n/** Action coverage — every action mapped or excluded, and neither file stale. */\nfunction actionFailures(ctx: GateContext): { failures: string[]; actionCount: number } {\n const mapped = ctx.actionMap?.mapped ?? {};\n const actions = new Set(\n walkActionFiles(ctx.appDir).flatMap((file) => exportedActionsOf(readFileSync(file, \"utf8\"))),\n );\n\n return {\n failures: [\n ...unaccountedActions(ctx, actions, mapped),\n ...staleActionEntries(ctx, actions, mapped),\n ],\n actionCount: actions.size,\n };\n}\n\n/** Run the gate and return every violation (empty = green). */\nexport function runMcpCoverage(options: McpCoverageOptions): McpCoverageResult {\n const ctx: GateContext = {\n appDir: options.appDir,\n webRoot: options.webRoot ?? options.appDir,\n endpoints: options.endpoints,\n exclusions: readJson<McpCoverageExclusions>(options.exclusionsPath),\n actionMap: options.actionMapPath ? readJson<McpActionMap>(options.actionMapPath) : null,\n };\n const routes = routeFailures(ctx);\n const actions = actionFailures(ctx);\n return {\n failures: [...routes.failures, ...actions.failures],\n routeMethodCount: routes.routeMethodCount,\n actionCount: actions.actionCount,\n };\n}\n\n/**\n * The CLI face: print the verdict and exit non-zero on violations. A host's\n * `scripts/mcp/coverage.ts` is then one import + one call, and the CI workflow that\n * runs `pnpm mcp:coverage` needs no change at all.\n */\nexport function mcpCoverageCli(options: McpCoverageOptions): void {\n const { failures, routeMethodCount, actionCount } = runMcpCoverage(options);\n if (failures.length > 0) {\n console.error(`[mcp:coverage] ${failures.length} violation(s):`);\n for (const failure of failures) console.error(` ✗ ${failure}`);\n process.exit(1);\n }\n console.log(\n `[mcp:coverage] OK — ${routeMethodCount} route method(s) registered, ` +\n `${actionCount} action(s) mapped/excluded.`,\n );\n}\n\nexport {\n collectRouteMethods,\n exportedMethodsOf,\n HTTP_METHODS,\n type RouteMethod,\n} from \"./route-methods\";\n","import { readFileSync } from \"node:fs\";\nimport { relative } from \"node:path\";\n\nimport { exportedNamesOf, urlPathOf, walkRouteFiles } from \"@12-apps/rbac/coverage\";\n\n/**\n * The route-METHOD half of the surface scan (12-23) — what `mcp:coverage` needs\n * on top of what `rbac:coverage` already ships.\n *\n * The WALK is imported from `@12-apps/rbac/coverage` rather than copied — the file\n * walk (`walkRouteFiles`), the URL mapping (`urlPathOf`) AND the export-head\n * parser (`exportedNamesOf`) — and that is deliberate: both gates assert a\n * COMPLETENESS property over the same two surfaces (`app/**` route files and\n * `*actions.ts` modules), and the origin host's own comment on the shared scanner says\n * why they must share it — \"so the two gates can never disagree about what the\n * surface is\". Two copies would agree on the day they were written and drift\n * silently after, in the direction of not looking. What is left here is the one\n * thing that genuinely differs: the GRAMMAR (see {@link exportedMethodsOf}).\n *\n * THE SCAN ROOT IS THE WHOLE `app` FOLDER, never `app/api`: a completeness gate\n * rooted below the surface it claims to cover does not fail when it misses\n * something, it simply never looks. Three OAuth/JWKS discovery routes shipped\n * unregistered for exactly as long as the walk was rooted at `app/api`.\n *\n * Detection is over SOURCE, with no TS compiler: fast, dependency-free, and it\n * matches how the framework itself keys routes off file paths plus exported names.\n */\n\n/** Every method a route file can serve — the scan must see them all. */\nexport const HTTP_METHODS = [\n \"GET\",\n \"HEAD\",\n \"POST\",\n \"PUT\",\n \"PATCH\",\n \"DELETE\",\n \"OPTIONS\",\n] as const;\n\n/** One exported HTTP handler discovered on a route file. */\nexport interface RouteMethod {\n /** URL path with `[param]` → `{param}` (e.g. `/api/checkout/{id}`). */\n urlPath: string;\n /** The HTTP method exported (GET/POST/…). */\n method: string;\n /** The route file, relative to the web root. */\n file: string;\n}\n\n/**\n * Exported HTTP methods, across every form the app router serves:\n * `export const GET`, `export function GET`, `export async function GET`,\n * `export const { GET, POST } = handlers`, `export { handler as GET }`. For brace\n * lists the exported name is the last identifier of each item (after `as`, or\n * after `:` for destructuring renames). `export type { … }` never matches — a type\n * is never a handler.\n *\n * The two grammar knobs are the whole difference from `exportedActionsOf`, and both\n * are load-bearing: a route handler may be a SYNC `export function` (a server\n * action may not — it must be async), and only the seven HTTP methods count, where\n * every runtime export of a use-server module is an action.\n *\n * The shared walk is a linear hand-parse, not a regex: the `\\s+`-joined patterns\n * this gate first shipped with backtracked polynomially on adversarial input\n * (CodeQL js/polynomial-redos), and a COMPLETENESS gate must stay O(n) on whatever\n * source it is pointed at — it is run over files a contributor supplies.\n */\nexport function exportedMethodsOf(source: string): string[] {\n return exportedNamesOf(source, { syncFunctions: true, accept: isHttpMethod });\n}\n\nfunction isHttpMethod(name: string): boolean {\n return (HTTP_METHODS as readonly string[]).includes(name);\n}\n\n/** Every exported HTTP handler across all route files under `appDir`. */\nexport function collectRouteMethods(appDir: string, webRoot: string): RouteMethod[] {\n return walkRouteFiles(appDir).flatMap((file) => {\n const urlPath = urlPathOf(file, appDir);\n return exportedMethodsOf(readFileSync(file, \"utf8\")).map((method) => ({\n urlPath,\n method,\n file: relative(webRoot, file),\n }));\n });\n}\n"],"mappings":";;;;;AAAA,SAAS,gBAAAA,qBAAoB;AAE7B,SAAS,mBAAmB,oBAAoB,uBAAuB;;;ACFvE,SAAS,oBAAoB;AAC7B,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB,WAAW,sBAAsB;AA0BpD,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AA8BO,SAAS,kBAAkB,QAA0B;AAC1D,SAAO,gBAAgB,QAAQ,EAAE,eAAe,MAAM,QAAQ,aAAa,CAAC;AAC9E;AAFgB;AAIhB,SAAS,aAAa,MAAuB;AAC3C,SAAQ,aAAmC,SAAS,IAAI;AAC1D;AAFS;AAKF,SAAS,oBAAoB,QAAgB,SAAgC;AAClF,SAAO,eAAe,MAAM,EAAE,QAAQ,CAAC,SAAS;AAC9C,UAAM,UAAU,UAAU,MAAM,MAAM;AACtC,WAAO,kBAAkB,aAAa,MAAM,MAAM,CAAC,EAAE,IAAI,CAAC,YAAY;AAAA,MACpE;AAAA,MACA;AAAA,MACA,MAAM,SAAS,SAAS,IAAI;AAAA,IAC9B,EAAE;AAAA,EACJ,CAAC;AACH;AATgB;;;ADQhB,SAAS,SAAY,MAAiB;AACpC,SAAO,KAAK,MAAMC,cAAa,MAAM,MAAM,CAAC;AAC9C;AAFS;AAKT,SAAS,cAAc,KAAoE;AACzF,QAAM,WAAqB,CAAC;AAC5B,QAAM,gBAAgB,OAAO,KAAK,IAAI,WAAW,MAAM;AACvD,QAAM,eAAe,oBAAoB,IAAI,QAAQ,IAAI,OAAO;AAChE,QAAM,aAAa,IAAI;AAAA,IACrB,IAAI,UAAU,IAAI,CAAC,aAAa,GAAG,SAAS,OAAO,YAAY,CAAC,IAAI,SAAS,IAAI,EAAE;AAAA,EACrF;AASA,QAAM,UAAU,aAAa,OAAO,CAAC,EAAE,QAAQ,MAAM,CAAC,mBAAmB,SAAS,aAAa,CAAC;AAChG,aAAW,EAAE,SAAS,QAAQ,KAAK,KAAK,SAAS;AAC/C,QAAI,CAAC,WAAW,IAAI,GAAG,MAAM,IAAI,OAAO,EAAE,GAAG;AAC3C,eAAS;AAAA,QACP,uBAAuB,MAAM,IAAI,OAAO,KAAK,IAAI;AAAA,MAEnD;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,IAAI;AAAA,IACjB,aAAa,IAAI,CAAC,EAAE,QAAQ,QAAQ,MAAM,GAAG,MAAM,IAAI,OAAO,EAAE;AAAA,EAClE;AACA,aAAW,YAAY,IAAI,WAAW;AACpC,QAAI,CAAC,OAAO,IAAI,GAAG,SAAS,OAAO,YAAY,CAAC,IAAI,SAAS,IAAI,EAAE,GAAG;AACpE,eAAS;AAAA,QACP,mCAAmC,SAAS,WAAW,KACjD,SAAS,OAAO,YAAY,CAAC,IAAI,SAAS,IAAI;AAAA,MACtD;AAAA,IACF;AAAA,EACF;AAEA,SAAO,EAAE,UAAU,kBAAkB,QAAQ,OAAO;AACtD;AAtCS;AA4CT,SAAS,mBACP,KACA,SACA,QACU;AACV,QAAM,WAAqB,CAAC;AAC5B,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,UAAU;AAC3B,UAAM,aAAa,UAAU,IAAI,WAAW;AAC5C,QAAI,CAAC,YAAY,CAAC,YAAY;AAC5B,eAAS;AAAA,QACP,2BAA2B,MAAM;AAAA,MAEnC;AAAA,IACF;AACA,QAAI,YAAY,YAAY;AAC1B,eAAS;AAAA,QACP,oCAAoC,MAAM;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAtBS;AA6BT,SAAS,mBACP,KACA,SACA,QACU;AACV,QAAM,WAAqB,CAAC;AAC5B,QAAM,eAAe,IAAI,IAAI,IAAI,UAAU,IAAI,CAAC,aAAa,SAAS,WAAW,CAAC;AAClF,aAAW,CAAC,QAAQ,WAAW,KAAK,OAAO,QAAQ,MAAM,GAAG;AAC1D,QAAI,CAAC,QAAQ,IAAI,MAAM,GAAG;AACxB,eAAS,KAAK,2BAA2B,MAAM,qCAAgC;AAAA,IACjF;AACA,QAAI,CAAC,aAAa,IAAI,WAAW,GAAG;AAClC,eAAS,KAAK,6CAA6C,MAAM,WAAM,WAAW,EAAE;AAAA,IACtF;AAAA,EACF;AACA,aAAW,UAAU,OAAO,KAAK,IAAI,WAAW,OAAO,GAAG;AACxD,QAAI,CAAC,QAAQ,IAAI,MAAM,GAAG;AACxB,eAAS,KAAK,2BAA2B,MAAM,qCAAgC;AAAA,IACjF;AAAA,EACF;AACA,SAAO;AACT;AArBS;AAwBT,SAAS,eAAe,KAA+D;AACrF,QAAM,SAAS,IAAI,WAAW,UAAU,CAAC;AACzC,QAAM,UAAU,IAAI;AAAA,IAClB,gBAAgB,IAAI,MAAM,EAAE,QAAQ,CAAC,SAAS,kBAAkBA,cAAa,MAAM,MAAM,CAAC,CAAC;AAAA,EAC7F;AAEA,SAAO;AAAA,IACL,UAAU;AAAA,MACR,GAAG,mBAAmB,KAAK,SAAS,MAAM;AAAA,MAC1C,GAAG,mBAAmB,KAAK,SAAS,MAAM;AAAA,IAC5C;AAAA,IACA,aAAa,QAAQ;AAAA,EACvB;AACF;AAbS;AAgBF,SAAS,eAAe,SAAgD;AAC7E,QAAM,MAAmB;AAAA,IACvB,QAAQ,QAAQ;AAAA,IAChB,SAAS,QAAQ,WAAW,QAAQ;AAAA,IACpC,WAAW,QAAQ;AAAA,IACnB,YAAY,SAAgC,QAAQ,cAAc;AAAA,IAClE,WAAW,QAAQ,gBAAgB,SAAuB,QAAQ,aAAa,IAAI;AAAA,EACrF;AACA,QAAM,SAAS,cAAc,GAAG;AAChC,QAAM,UAAU,eAAe,GAAG;AAClC,SAAO;AAAA,IACL,UAAU,CAAC,GAAG,OAAO,UAAU,GAAG,QAAQ,QAAQ;AAAA,IAClD,kBAAkB,OAAO;AAAA,IACzB,aAAa,QAAQ;AAAA,EACvB;AACF;AAfgB;AAsBT,SAAS,eAAe,SAAmC;AAChE,QAAM,EAAE,UAAU,kBAAkB,YAAY,IAAI,eAAe,OAAO;AAC1E,MAAI,SAAS,SAAS,GAAG;AACvB,YAAQ,MAAM,kBAAkB,SAAS,MAAM,gBAAgB;AAC/D,eAAW,WAAW,SAAU,SAAQ,MAAM,YAAO,OAAO,EAAE;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,UAAQ;AAAA,IACN,4BAAuB,gBAAgB,gCAClC,WAAW;AAAA,EAClB;AACF;AAXgB;","names":["readFileSync","readFileSync"]}
|