theokit 0.50.2 → 0.51.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.
Files changed (69) hide show
  1. package/dist/{actions-virtual-module-ESKVARP4.js → actions-virtual-module-D52G2ACL.js} +3 -3
  2. package/dist/{actions-virtual-module-2STC6RV3.js → actions-virtual-module-FJIFAQUA.js} +6 -6
  3. package/dist/adapters/agent-mount.js +1 -1
  4. package/dist/{agent-7RP3VTSI.js → agent-PLEYEX37.js} +4 -4
  5. package/dist/{app-typed-client-NYZ2PLNC.js → app-typed-client-MPQDKS35.js} +6 -6
  6. package/dist/{app-typed-client-CEW2KGXR.js → app-typed-client-XPYFYUPN.js} +3 -3
  7. package/dist/{build-4LPUE2H3.js → build-MGNDNTXH.js} +6 -6
  8. package/dist/{chunk-QNGS7EVS.js → chunk-575RV3CD.js} +2 -2
  9. package/dist/{chunk-EVZKNA2W.js → chunk-6AM7HIRM.js} +2 -2
  10. package/dist/{chunk-ECTUUAMR.js → chunk-75MWRZDP.js} +8 -8
  11. package/dist/{chunk-MLWETKFA.js → chunk-DY5W5OWQ.js} +2 -2
  12. package/dist/{chunk-L4VFNFDL.js → chunk-IRASEXOG.js} +29 -1
  13. package/dist/{chunk-L4VFNFDL.js.map → chunk-IRASEXOG.js.map} +1 -1
  14. package/dist/{chunk-LLMTORUY.js → chunk-MTWZ54OI.js} +10 -10
  15. package/dist/{chunk-6J4JWHDJ.js → chunk-VHB2FSPM.js} +16 -2
  16. package/dist/chunk-VHB2FSPM.js.map +1 -0
  17. package/dist/{chunk-KUZVRJ7M.js → chunk-XLLW46MB.js} +3 -3
  18. package/dist/{chunk-3MIGBPTW.js → chunk-YJDWSM4D.js} +3 -3
  19. package/dist/{chunk-XS7EX55U.js → chunk-YYOBMOO4.js} +16 -2
  20. package/dist/chunk-YYOBMOO4.js.map +1 -0
  21. package/dist/{chunk-FD4ZDTQ6.js → chunk-Z7CJHDLS.js} +29 -1
  22. package/dist/{chunk-FD4ZDTQ6.js.map → chunk-Z7CJHDLS.js.map} +1 -1
  23. package/dist/cli/index.js +8 -8
  24. package/dist/{dev-Z4YLROZD.js → dev-2NEKDLEY.js} +8 -8
  25. package/dist/{dev-emit-MVAJAFGX.js → dev-emit-G2E2U3NP.js} +3 -3
  26. package/dist/{dev-emit-BFNMDCPB.js → dev-emit-SPSV6UU7.js} +5 -5
  27. package/dist/index.js +3 -3
  28. package/dist/{internal-api-53L6FIQ2.js → internal-api-OOJT7L6I.js} +6 -6
  29. package/dist/{internal-api-IXSWWH2N.js → internal-api-PYWIJTLF.js} +3 -3
  30. package/dist/{mcp-RKD4QQFS.js → mcp-5AWKPETM.js} +4 -4
  31. package/dist/{openapi-VFPEZSRI.js → openapi-2IBBOS3Q.js} +5 -5
  32. package/dist/{preview-LB3YL6U5.js → preview-D2RKY5ZA.js} +3 -3
  33. package/dist/{routes-BPLU3ISV.js → routes-PP5PHHLR.js} +3 -3
  34. package/dist/server/index.js +2 -2
  35. package/dist/server/scan/index.js +2 -2
  36. package/dist/{server-boundary-RBMMPWCV.js → server-boundary-ITRJTWT4.js} +3 -3
  37. package/dist/{server-boundary-ACFCBLZ4.js → server-boundary-YIRMIYAS.js} +6 -6
  38. package/dist/{start-G2JS6422.js → start-DMOVSFBL.js} +7 -7
  39. package/dist/vite-plugin/index.js +3 -3
  40. package/dist/{vite-plugin-4VM5EUQL.js → vite-plugin-6KDMU3E2.js} +8 -8
  41. package/package.json +1 -1
  42. package/dist/chunk-6J4JWHDJ.js.map +0 -1
  43. package/dist/chunk-XS7EX55U.js.map +0 -1
  44. /package/dist/{actions-virtual-module-ESKVARP4.js.map → actions-virtual-module-D52G2ACL.js.map} +0 -0
  45. /package/dist/{actions-virtual-module-2STC6RV3.js.map → actions-virtual-module-FJIFAQUA.js.map} +0 -0
  46. /package/dist/{agent-7RP3VTSI.js.map → agent-PLEYEX37.js.map} +0 -0
  47. /package/dist/{app-typed-client-NYZ2PLNC.js.map → app-typed-client-MPQDKS35.js.map} +0 -0
  48. /package/dist/{app-typed-client-CEW2KGXR.js.map → app-typed-client-XPYFYUPN.js.map} +0 -0
  49. /package/dist/{build-4LPUE2H3.js.map → build-MGNDNTXH.js.map} +0 -0
  50. /package/dist/{chunk-QNGS7EVS.js.map → chunk-575RV3CD.js.map} +0 -0
  51. /package/dist/{chunk-EVZKNA2W.js.map → chunk-6AM7HIRM.js.map} +0 -0
  52. /package/dist/{chunk-ECTUUAMR.js.map → chunk-75MWRZDP.js.map} +0 -0
  53. /package/dist/{chunk-MLWETKFA.js.map → chunk-DY5W5OWQ.js.map} +0 -0
  54. /package/dist/{chunk-LLMTORUY.js.map → chunk-MTWZ54OI.js.map} +0 -0
  55. /package/dist/{chunk-KUZVRJ7M.js.map → chunk-XLLW46MB.js.map} +0 -0
  56. /package/dist/{chunk-3MIGBPTW.js.map → chunk-YJDWSM4D.js.map} +0 -0
  57. /package/dist/{dev-Z4YLROZD.js.map → dev-2NEKDLEY.js.map} +0 -0
  58. /package/dist/{dev-emit-MVAJAFGX.js.map → dev-emit-G2E2U3NP.js.map} +0 -0
  59. /package/dist/{dev-emit-BFNMDCPB.js.map → dev-emit-SPSV6UU7.js.map} +0 -0
  60. /package/dist/{internal-api-53L6FIQ2.js.map → internal-api-OOJT7L6I.js.map} +0 -0
  61. /package/dist/{internal-api-IXSWWH2N.js.map → internal-api-PYWIJTLF.js.map} +0 -0
  62. /package/dist/{mcp-RKD4QQFS.js.map → mcp-5AWKPETM.js.map} +0 -0
  63. /package/dist/{openapi-VFPEZSRI.js.map → openapi-2IBBOS3Q.js.map} +0 -0
  64. /package/dist/{preview-LB3YL6U5.js.map → preview-D2RKY5ZA.js.map} +0 -0
  65. /package/dist/{routes-BPLU3ISV.js.map → routes-PP5PHHLR.js.map} +0 -0
  66. /package/dist/{server-boundary-RBMMPWCV.js.map → server-boundary-ITRJTWT4.js.map} +0 -0
  67. /package/dist/{server-boundary-ACFCBLZ4.js.map → server-boundary-YIRMIYAS.js.map} +0 -0
  68. /package/dist/{start-G2JS6422.js.map → start-DMOVSFBL.js.map} +0 -0
  69. /package/dist/{vite-plugin-4VM5EUQL.js.map → vite-plugin-6KDMU3E2.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/scan/action-scan.ts","../src/server/scan/match.ts","../src/core/contracts/http-methods.ts","../src/server/scan/scan.ts","../src/server/scan/detect-http-methods.ts","../src/server/scan/detect-route-policy.ts","../src/server/scan/ws-scan.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/actions/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nexport interface ActionNode {\n filePath: string\n actionPath: string\n}\n\n/**\n * Directory under `<serverDir>/actions/` reserved for ISOMORPHIC zod schemas.\n *\n * The one place in the whole server tree whose files are meant to reach the browser: the\n * `@theo/actions` virtual module emits `import { schema } from '<abs path>'` into the CLIENT\n * facade for each file here, so `<TheoForm>` can drive `zodResolver` with the same schema the\n * server validates against.\n *\n * Named once, and exported, because two rules now depend on knowing it. The scanner skips it (a\n * schema is not an executable action), and the server-only import boundary must NOT refuse it\n * (`vite-plugin/server-boundary.ts`). Restating the literal in the boundary would let the two\n * drift, and the drift would be invisible: renaming the convention here would silently turn the\n * exception into a refusal, breaking a shipped feature at build time in consumer projects only.\n */\nexport const ACTION_SCHEMAS_DIR = 'schemas'\n\n/**\n * Enriched manifest entry per plan g3-server-actions-and-useaction v1.2\n * § Phase 1 / T1.4 + ADR D4. Consumed by virtual module `@theo/actions`\n * (T3.1) and G4 devtools \"Actions\" tab (T5.1).\n */\nexport interface ActionManifestEntry {\n name: string\n filePath: string\n urlPath: string\n accept: 'form' | 'json'\n hasInput: boolean\n /**\n * P#4 plugin-forms shared-schema convention (per plan p4-plugin-forms v1.1 T1.1).\n * When present, points to an isomorphic schema file at\n * `<serverDir>/actions/schemas/<basename>.ts` exporting `export const schema = z.object(...)`.\n * Virtual module emits `import {schema} from '<schemaFilePath>'` + attaches as\n * `actions.X.__zodSchema` so client-side <TheoForm> can drive zodResolver.\n * Undefined when convention not followed (graceful degrade — TheoForm still\n * works via explicit `schema={...}` prop escape hatch).\n */\n schemaFilePath?: string\n}\n\n/**\n * EC-2: structured error for scan-time defects (name collision, reserved\n * identifier, etc.). Throw at scan time to fail loud — silent shadowing is\n * a security/correctness footgun.\n */\nexport class ActionScanError extends Error {\n readonly code: 'NAME_COLLISION' | 'RESERVED_NAME'\n readonly conflictingPaths: readonly string[]\n\n constructor(\n code: 'NAME_COLLISION' | 'RESERVED_NAME',\n message: string,\n conflictingPaths: readonly string[],\n ) {\n super(message)\n // T4.1 fix: identify class by name so serverErrorToEnvelope boundary\n // translator can route via class-name lookup (without this, runtime\n // `err.name` defaults to 'Error' and the meta.name diagnostic is wrong).\n this.name = 'ActionScanError'\n this.code = code\n this.conflictingPaths = conflictingPaths\n }\n}\n\nconst ACTION_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\nconst TEST_FILE_RE = /\\.(test|spec)\\.(ts|tsx|js|jsx)$/\nconst RESERVED_NAMES = new Set(['index', 'constructor', '__proto__', 'prototype', 'hasOwnProperty'])\n\n/**\n * Backward-compatible: original simple-shape scanner used by existing\n * consumers. Preserved verbatim; new consumers should call\n * `scanServerActionsEnriched`.\n */\nexport function scanServerActions(serverDir: string): ActionNode[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const results: ActionNode[] = []\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n let rel = relative(actionsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n results.push({\n filePath: absPath,\n actionPath: rel,\n })\n })\n return results\n}\n\n/**\n * Enriched scan: light AST detection of `accept: 'form'|'json'` + `input:`\n * presence via regex-after-comment-stripping (EC-9). Throws ActionScanError\n * on file/dir name collision (EC-2) or reserved JS identifier names.\n *\n * Output `ActionManifestEntry[]` is sorted by `name` for deterministic\n * `.theokit/actions-manifest.json` emission.\n */\nexport function scanServerActionsEnriched(serverDir: string): ActionManifestEntry[] {\n const actionsDir = join(serverDir, 'actions')\n if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {\n return []\n }\n\n const seenNames = new Set<string>()\n const entries: ActionManifestEntry[] = []\n\n // Collect first; collision-check after the full walk.\n walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {\n if (TEST_FILE_RE.test(absPath)) return\n const rel = relative(actionsDir, absPath).replace(/\\\\/g, '/')\n // P#4 plugin-forms — `schemas/` subdir holds isomorphic zod schemas\n // for the shared-schema convention (T1.1). NOT executable actions; skip.\n if (rel.startsWith(`${ACTION_SCHEMAS_DIR}/`)) return\n const name = rel.slice(0, -extname(rel).length)\n\n const basename = name.includes('/') ? (name.split('/').pop() ?? name) : name\n if (RESERVED_NAMES.has(basename)) {\n throw new ActionScanError(\n 'RESERVED_NAME',\n `Reserved JS identifier \"${basename}\" cannot be an action name (${absPath})`,\n [absPath],\n )\n }\n // File-level collision (one file appearing twice) is impossible via the\n // walker; per-export collision check happens inside the export loop below.\n const source = readFileSync(absPath, 'utf8')\n const stripped = stripComments(source)\n const accept = /\\baccept\\s*:\\s*['\"]form['\"]/.test(stripped) ? 'form' : 'json'\n const hasInput = /\\binput\\s*:\\s*z\\./.test(stripped) || /\\binput\\s*:\\s*\\w+\\(/.test(stripped)\n\n // T7.1 wire fix — extract exports so the urlPath includes the second\n // segment required by action-middleware (`/api/__actions/<file>/<export>`).\n // Each named action export becomes its own manifest entry.\n // Proxy key on the EXPORT name (so consumers write `actions.saveMemory(input)`).\n // URL keeps the runtime 2-segment shape `/api/__actions/<file>/<export>`\n // expected by action-middleware. Cross-file export collisions become a\n // scan error to surface the ambiguity early.\n const exportNames = extractActionExportNames(stripped)\n // P#4 plugin-forms shared-schema convention: check for\n // `<actionsDir>/schemas/<basename>.ts` (or .tsx/.js/.jsx).\n // Skip when actions live in subdirs (`schemas/` is flat by convention).\n const schemaFilePath = name.includes('/') ? undefined : detectSchemaFile(actionsDir, basename)\n for (const exportName of exportNames) {\n const proxyKey = exportName === 'default' ? name : exportName\n if (seenNames.has(proxyKey)) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Duplicate action proxy key \"${proxyKey}\" (two files export the same name)`,\n [absPath],\n )\n }\n seenNames.add(proxyKey)\n const entry: ActionManifestEntry = {\n name: proxyKey,\n filePath: absPath,\n urlPath: `/api/__actions/${name}/${exportName}`,\n accept,\n hasInput,\n }\n if (schemaFilePath !== undefined) {\n entry.schemaFilePath = schemaFilePath\n }\n entries.push(entry)\n }\n })\n\n // EC-2: detect file vs dir collisions (e.g., foo.ts AND foo/bar.ts).\n // After walk completes, any name that has children prefixed `name/` triggers collision.\n for (const entry of entries) {\n const childPrefix = `${entry.name}/`\n const conflictingChild = entries.find((other) => other.name.startsWith(childPrefix))\n if (conflictingChild) {\n throw new ActionScanError(\n 'NAME_COLLISION',\n `Action \"${entry.name}\" conflicts with directory of same name containing \"${conflictingChild.name}\"`,\n [entry.filePath, conflictingChild.filePath],\n )\n }\n }\n\n entries.sort((a, b) => {\n if (a.name < b.name) return -1\n if (a.name > b.name) return 1\n return 0\n })\n return entries\n}\n\n/**\n * Strip JavaScript line + block comments from source. Simple state machine\n * (does not parse strings — false positives if a comment marker appears\n * inside a string literal, but that's an acceptable trade-off for v1 vs\n * full AST parse).\n */\n/**\n * Extract action export names via regex over comment-stripped source.\n * Matches `export const <name> = defineAction(...)`, `export default\n * defineAction(...)`, and `export function <name>(...)` forms. Returns\n * `['default']` when no named action exports are found (best-effort fallback\n * for default-export shapes the regex misses).\n */\nfunction extractActionExportNames(stripped: string): string[] {\n const names = new Set<string>()\n const namedRe = /\\bexport\\s+(?:const|let|var|function\\*?)\\s+([a-zA-Z_$][\\w$]*)\\s*[=(]/g\n let m: RegExpExecArray | null\n while ((m = namedRe.exec(stripped)) !== null) {\n const name = m[1]\n if (typeof name === 'string' && name.length > 0) names.add(name)\n }\n if (/\\bexport\\s+default\\s+defineAction\\b/.test(stripped)) {\n names.add('default')\n }\n if (names.size === 0) names.add('default')\n return [...names]\n}\n\n/**\n * P#4 plugin-forms shared-schema convention helper (per plan p4-plugin-forms v1.1 T1.1).\n * Returns the resolved path to `<actionsDir>/schemas/<basename>.<ext>` if it exists.\n * Tries `.ts`, `.tsx`, `.js`, `.jsx` in that order. Returns undefined when no match.\n */\nfunction detectSchemaFile(actionsDir: string, basename: string): string | undefined {\n const schemasDir = join(actionsDir, ACTION_SCHEMAS_DIR)\n if (!existsSync(schemasDir)) return undefined\n for (const ext of ['.ts', '.tsx', '.js', '.jsx']) {\n const candidate = join(schemasDir, `${basename}${ext}`)\n if (existsSync(candidate)) return candidate\n }\n return undefined\n}\n\nfunction stripComments(source: string): string {\n let out = ''\n let i = 0\n while (i < source.length) {\n const ch = source[i]\n const next = source[i + 1]\n if (ch === '/' && next === '/') {\n // Line comment: skip until newline\n while (i < source.length && source[i] !== '\\n') i++\n continue\n }\n if (ch === '/' && next === '*') {\n // Block comment: skip until */\n i += 2\n while (i < source.length - 1 && !(source[i] === '*' && source[i + 1] === '/')) i++\n i += 2\n continue\n }\n out += ch\n i++\n }\n return out\n}\n","export interface ServerRouteNode {\n filePath: string\n routePath: string\n paramNames: string[]\n pattern: RegExp\n /** HTTP methods (uppercase) the route file exports. Optional for backward\n * compatibility with manifests generated before G1. Empty array means the\n * file has no HTTP exports (util-only); undefined means \"not detected\". */\n methods?: string[]\n}\n\nexport function compilePattern(routePath: string): {\n pattern: RegExp\n paramNames: string[]\n} {\n const paramNames: string[] = []\n // Single pass: handle both catch-all (:...name) and regular (:name) params\n const regexStr = routePath.replace(/:(?:\\.\\.\\.)?([^/]+)/g, (match: string, name: string) => {\n paramNames.push(name)\n // Catch-all matches across slashes, regular matches single segment\n return match.startsWith(':...') ? '(.+)' : '([^/]+)'\n })\n // `regexStr` is derived from a developer-authored route path (build-time\n // input, not HTTP-controlled). The `security/detect-non-literal-regexp`\n // rule cannot see this constraint — disable narrowly.\n // eslint-disable-next-line security/detect-non-literal-regexp -- route pattern from build-time scan, never HTTP input\n return { pattern: new RegExp(`^${regexStr}$`), paramNames }\n}\n\nexport function matchRoute(\n url: string,\n routes: ServerRouteNode[],\n): { route: ServerRouteNode; params: Record<string, string> } | null {\n // Strip query string and trailing slash\n let path = url.split('?')[0]\n if (path.length > 1 && path.endsWith('/')) {\n path = path.slice(0, -1)\n }\n\n for (const route of routes) {\n const match = route.pattern.exec(path)\n if (match) {\n const params: Record<string, string> = {}\n route.paramNames.forEach((name, i) => {\n params[name] = match[i + 1]\n })\n return { route, params }\n }\n }\n return null\n}\n","/**\n * Canonical HTTP method set used by the typed client codegen + Proxy runtime.\n * Kept here (under `server/scan/`) because the SCAN is the first consumer —\n * `vite-plugin/app-typed-client.ts` (Phase 2) and `client/app-client.ts` (Phase 3)\n * import from here to ensure a single source of truth.\n *\n * Lowercase variants are derived via `.toLowerCase()` at call sites; we do not\n * export them separately to avoid drift between the two lists.\n */\n\nexport const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'] as const\n\nexport type HttpMethod = (typeof HTTP_METHODS)[number]\n\nexport const HTTP_METHOD_LOWERCASE = HTTP_METHODS.map((m) => m.toLowerCase()) as readonly string[]\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/routes/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, readFileSync, statSync } from 'node:fs'\nimport { basename, extname, join, relative } from 'node:path'\n\nimport type { HttpMethod } from '../../core/contracts/http-methods.js'\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport { detectExportedHttpMethods } from './detect-http-methods.js'\nimport { detectMethodsWithDeclaredPolicy } from './detect-route-policy.js'\nimport {\n MissingRoutePolicyError,\n RedundantApiSegmentError,\n RouterConventionError,\n} from './errors.js'\nimport { createFileStampCache } from './file-stamp-cache.js'\nimport { compilePattern, type ServerRouteNode } from './match.js'\n\nconst ROUTE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\n// EC-4: Co-located unit/spec tests must be silently skipped by the scanner,\n// BEFORE the dotted-basename check fires. Matches `*.test.ts|tsx|js|jsx`\n// and `*.spec.ts|tsx|js|jsx`.\nconst TEST_OR_SPEC_RE = /\\.(test|spec)\\.[jt]sx?$/\n\nfunction isTestOrSpecFile(filePath: string): boolean {\n return TEST_OR_SPEC_RE.test(basename(filePath))\n}\n\nfunction hasDotOutsideBrackets(segment: string): boolean {\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') depth++\n else if (ch === ']') depth--\n else if (ch === '.' && depth === 0) return true\n }\n return false\n}\n\nfunction splitDottedSegmentOutsideBrackets(segment: string): string[] {\n const parts: string[] = []\n let current = ''\n let depth = 0\n for (const ch of segment) {\n if (ch === '[') {\n depth++\n current += ch\n } else if (ch === ']') {\n depth--\n current += ch\n } else if (ch === '.' && depth === 0) {\n if (current) parts.push(current)\n current = ''\n } else {\n current += ch\n }\n }\n if (current) parts.push(current)\n return parts\n}\n\nfunction buildDirectoryNestedSuggestion(filePath: string, routesDir: string): string {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/').flatMap(splitDottedSegmentOutsideBrackets)\n return `routes/${segments.join('/')}${ext}`\n}\n\n/**\n * `routes/api/…` doubles the prefix `fileToRoutePath` adds unconditionally.\n *\n * Checked on the FIRST segment only. A file merely named `api-keys.ts` or `apiary.ts` is the\n * author's to choose and resolves fine; what doubles is the DIRECTORY, because it prefixes every\n * file beneath it — and carries the same redundant segment into the generated typed client.\n */\nfunction assertNoRedundantApiSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const [first, ...rest] = rel.split('/')\n if (first !== 'api' || rest.length === 0) return\n\n const withoutExt = rel.slice(0, -extname(rel).length)\n throw new RedundantApiSegmentError({\n file: filePath,\n doubledRoutePath: `/api/${withoutExt}`,\n doubledClientChain: `client.${withoutExt.split('/').join('.')}`,\n suggestion: `routes/${rest.join('/')}`,\n })\n}\n\nfunction assertNoDottedSegment(filePath: string, routesDir: string): void {\n const rel = relative(routesDir, filePath).replace(/\\\\/g, '/')\n const ext = extname(rel)\n const withoutExt = rel.slice(0, -ext.length)\n const segments = withoutExt.split('/')\n for (const seg of segments) {\n if (hasDotOutsideBrackets(seg)) {\n throw new RouterConventionError({\n file: filePath,\n suggestion: buildDirectoryNestedSuggestion(filePath, routesDir),\n })\n }\n }\n}\n\n/**\n * ADR 0001, Decision point 5 — a route declares its policy explicitly, `public`\n * included, and absence stops meaning open.\n *\n * The refusal lives here, next to the dotted-basename refusal, because this is\n * the one place every entry point passes through: `theo build`, `theo start`,\n * `theo dev`, `theo routes` and each deployment adapter all reach routes by\n * calling `scanServerRoutes`. A gate wired into the build command instead would\n * have to be remembered by six adapters that generate their own entry file, and\n * a gate you can forget to call is a gate that reports on the routes somebody\n * remembered.\n *\n * The blast radius stops at the file system. A `RouteConfig` built in memory and\n * handed to `executeWebRequest` or `callProcedure` never passed a scanner, so it\n * never reaches this function — the runtime still treats an undeclared policy as\n * \"not declared\", exactly as `evaluateRoutePolicy` documents. Absence is refused\n * where an application DECLARES its routes, not where a caller passes one.\n */\nfunction assertEveryMethodDeclaresPolicy(\n filePath: string,\n routePath: string,\n { methods, declaredPolicy }: RouteSourceFacts,\n): void {\n if (methods.length === 0) return\n const missing = methods.filter((method) => !declaredPolicy.has(method))\n if (missing.length === 0) return\n throw new MissingRoutePolicyError({ file: filePath, routePath, methods: missing })\n}\n\n/** What the two AST passes over a route file answer, together. */\ninterface RouteSourceFacts {\n methods: HttpMethod[]\n /** The subset of `methods` that declared a `policy`. Empty when there are no methods. */\n declaredPolicy: Set<HttpMethod>\n}\n\n/**\n * Per-file facts, recomputed only when the file changes (usetheokit/theokit#417).\n *\n * `theokit dev` calls `scanServerRoutes` on EVERY request, and this function parsed each route\n * file with the TypeScript AST TWICE per call — once for the exported methods, once for the policy\n * gate, sharing the source string but not the parse. `agent-scan.ts` had solved exactly this for\n * agent files and wrote down why; routes, which an application has far more of, had neither the\n * cache nor the reasoning.\n *\n * The refusal is deliberately NOT cached — only the facts are. A scan that found a missing policy\n * once must go on refusing on every later scan, so `assertEveryMethodDeclaresPolicy` re-derives the\n * set difference (cheap, no parse) from the cached facts each time.\n */\nconst routeFactsCache = createFileStampCache<RouteSourceFacts>()\n\n/** Test seam — the module-level cache would otherwise outlive a fixture directory. */\nexport function _resetRouteScanCacheForTests(): void {\n routeFactsCache.clear()\n}\n\nfunction routeSourceFacts(absPath: string): RouteSourceFacts {\n return routeFactsCache.get(absPath, () => {\n const source = readFileSync(absPath, 'utf-8')\n const methods = detectExportedHttpMethods(absPath, source)\n // Kept lazy exactly as the early return in the assert made it: a file exporting no HTTP method\n // has nothing for the policy gate to check, and parsing it a second time to learn that was the\n // cost this cache exists to remove.\n const declaredPolicy =\n methods.length === 0\n ? new Set<HttpMethod>()\n : detectMethodsWithDeclaredPolicy(absPath, source)\n return { methods, declaredPolicy }\n })\n}\n\nfunction fileToRoutePath(filePath: string, routesDir: string): string {\n let rel = relative(routesDir, filePath)\n // Strip extension\n const ext = extname(rel)\n rel = rel.slice(0, -ext.length)\n // Normalize separators\n rel = rel.replace(/\\\\/g, '/')\n // Strip index suffix\n if (rel.endsWith('/index')) {\n rel = rel.slice(0, -6)\n } else if (rel === 'index') {\n rel = ''\n }\n // Replace [...param] with :...param (catch-all, before regular params).\n // Replace [param] with :param. Inputs are file paths bounded by the\n // OS filename limit; the bracket capture is bounded by `]`.\n rel = rel.replace(/\\[\\.\\.\\.([^\\]]+)\\]/g, ':...$1')\n // eslint-disable-next-line sonarjs/slow-regex -- bounded by `]`; input is a single filename\n rel = rel.replace(/\\[([^\\]]+)\\]/g, ':$1')\n return `/api/${rel}`\n}\n\nexport function scanServerRoutes(serverDir: string): ServerRouteNode[] {\n const routesDir = join(serverDir, 'routes')\n if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {\n return []\n }\n\n const results: ServerRouteNode[] = []\n walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {\n // EC-4: skip co-located test/spec files BEFORE the dotted-basename check\n if (isTestOrSpecFile(absPath)) return\n\n // G6 T1.1: reject dotted basenames (legacy convention that produced wrong\n // paramNames due to greedy `:(?:\\.\\.\\.)?([^/]+)` regex in compilePattern).\n assertNoRedundantApiSegment(absPath, routesDir)\n assertNoDottedSegment(absPath, routesDir)\n\n const routePath = fileToRoutePath(absPath, routesDir)\n const { pattern, paramNames } = compilePattern(routePath)\n const facts = routeSourceFacts(absPath)\n const { methods } = facts\n assertEveryMethodDeclaresPolicy(absPath, routePath, facts)\n results.push({\n filePath: absPath,\n routePath,\n paramNames,\n pattern,\n methods,\n })\n })\n\n // T1.4 / EC-2 — refuse to scan if a user route collides with the reserved\n // batch endpoint path. User must rename or disable batching.\n const conflicting = results.find((r) => r.routePath === '/api/__theo_batch__')\n if (conflicting) {\n throw new Error(\n `Server route ${conflicting.filePath} resolves to '/api/__theo_batch__' which is reserved for the batch endpoint. Rename the route or disable batching in theo.config.ts.`,\n )\n }\n\n results.sort((a, b) => compareRouteSpecificity(a.routePath, b.routePath))\n\n return results\n}\n\n/** How constrained one path segment is. Lower is more specific. */\nconst SEGMENT_STATIC = 0\nconst SEGMENT_DYNAMIC = 1\nconst SEGMENT_CATCH_ALL = 2\n\nfunction segmentSpecificity(segment: string): number {\n if (segment.startsWith(':...')) return SEGMENT_CATCH_ALL\n if (segment.startsWith(':')) return SEGMENT_DYNAMIC\n return SEGMENT_STATIC\n}\n\n/**\n * Order two route paths by specificity, most specific first.\n *\n * `matchRoute` returns on the first pattern that matches, so this order IS the\n * precedence contract — not a presentation detail of the manifest.\n *\n * Segments are compared position by position, and the first position where they\n * disagree decides: a literal beats a parameter, and a parameter beats a\n * catch-all. That is the rule the URL itself expresses, and it is the rule a\n * whole-path comparison cannot express, because a whole-path comparison reads\n * characters across segment boundaries. Comparing `/api/:resource/settings`\n * with `/api/users/:id` that way put the generic route first — `:` precedes `u`\n * in every collation — so `/api/users/settings` reached the generic handler and\n * an authorization check placed on the specific route was bypassed\n * (usetheokit/theokit#348).\n *\n * Only the segments the two paths have in common are ranked. Past that point\n * one path is a strict prefix of the other, and two such routes cannot match\n * the same URL — so there is nothing to decide, only a stable order to pick.\n *\n * The final fallback compares by code unit rather than with `localeCompare`.\n * Collation is locale-dependent, so the same route table would order\n * differently under a different `LANG` — the cross-machine divergence\n * usetheokit/theokit#346 removed from the sibling scanner. The tiebreak only\n * has to be total and stable; it does not have to be alphabetical for a human.\n */\nexport function compareRouteSpecificity(a: string, b: string): number {\n const aSegments = a.split('/')\n const bSegments = b.split('/')\n const shared = Math.min(aSegments.length, bSegments.length)\n\n for (let i = 0; i < shared; i++) {\n const difference = segmentSpecificity(aSegments[i]) - segmentSpecificity(bSegments[i])\n if (difference !== 0) return difference\n }\n\n if (a < b) return -1\n if (a > b) return 1\n return 0\n}\n","/**\n * Detect which HTTP-method named exports a route file declares.\n *\n * Uses the TypeScript compiler API (not regex) per G1 edge-case review EC-4:\n * regex over file content emits false positives for `// export const GET = ...`\n * in comments and `` `export const GET = ...` `` in template literals. AST\n * walking avoids both classes of bug.\n *\n * `typescript` ships as CommonJS with internal dynamic `require('fs')`.\n * When loaded via ESM `import`, the dynamic requires fail at module-bootstrap.\n * We use `createRequire(import.meta.url)` to keep the package on its native\n * CJS path. The type-only namespace import gives us the AST helpers shape.\n *\n * Returns the set of HTTP methods (uppercase) the file exports. Empty array\n * means the file has no HTTP exports (the route file is util-only).\n */\n\nimport { readFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\nimport { compareByCodeUnit } from '../_internal/compare-by-code-unit.js'\n\nconst require_ = createRequire(import.meta.url)\n\nconst ts = require_('typescript') as typeof TS\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts.SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\n// An AST visitor over the export forms (const / function / named export) is inherently branchy;\n// the complexity is in the grammar it walks, not in tangled logic. Splitting it would scatter one\n// cohesive dispatch across helpers for no readability gain.\n// eslint-disable-next-line complexity -- AST visitor, see above\nfunction collectFromStatement(stmt: TS.Statement, found: Set<HttpMethod>): void {\n // `export const GET = ...` / `export function GET ...` / `export async function GET ...`\n if (ts.isVariableStatement(stmt) && hasExportModifier(ts.getModifiers(stmt))) {\n for (const decl of stmt.declarationList.declarations) {\n if (ts.isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {\n found.add(decl.name.text as HttpMethod)\n }\n }\n return\n }\n\n if (\n (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) &&\n hasExportModifier(ts.getModifiers(stmt))\n ) {\n if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {\n found.add(stmt.name.text as HttpMethod)\n }\n return\n }\n\n // `export { GET }` / `export { handler as GET } from './shared'` (EC-5)\n if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {\n for (const spec of stmt.exportClause.elements) {\n // spec.name is the exported (re-)name; spec.propertyName is the original (when renamed)\n if (HTTP_METHOD_NAMES.has(spec.name.text)) {\n found.add(spec.name.text as HttpMethod)\n }\n }\n }\n}\n\nexport function detectExportedHttpMethods(filePath: string, content?: string): HttpMethod[] {\n // `filePath` is a route module the framework itself discovered by globbing the project — never\n // user input at runtime. Reading it by a computed path is the whole job of a source scanner.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- framework-controlled path\n const src = content ?? readFileSync(filePath, 'utf-8')\n const sourceFile = ts.createSourceFile(\n filePath,\n src,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts.ScriptKind.TS,\n )\n const found = new Set<HttpMethod>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, found)\n }\n // HTTP method names are ASCII, so no collation disagrees about them today.\n // Ordered by code unit anyway, so that \"build output orders by code unit\"\n // stays a rule a reader can check by grep rather than a rule with exceptions\n // each needing its own argument (usetheokit/theokit#351).\n return [...found].sort(compareByCodeUnit)\n}\n","/**\n * Detect which HTTP-method exports of a route file declare an access policy.\n *\n * The companion of `detect-http-methods.ts`, and deliberately the same shape:\n * the TypeScript AST rather than a regex, because `// policy: 'public'` in a\n * comment and `` `policy` `` in a doc string are both things a route file\n * legitimately contains, and neither declares anything.\n *\n * ADR 0001 decides that a route says who may call it, `'public'` included, and\n * that absence stops meaning open. This is the half of that decision the\n * scanner can answer: which exports said something. `scanServerRoutes` turns\n * the silence into a build error.\n *\n * ## What counts as a declaration\n *\n * Two forms, because the framework ships two ways to write a route:\n *\n * ```ts\n * export const GET = route().policy('public').handler(fn).build() // builder\n * export const GET = defineRoute({ policy: fn, handler: fn }) // object\n * ```\n *\n * Only the config position is inspected — the top level of an object literal\n * passed to a call, and `.policy(...)` links of a call chain. The handler's\n * BODY is never walked, so a handler that happens to return `{ policy: ... }`\n * does not satisfy the gate. Reading deeper would trade a false build error for\n * a false pass, and a false pass here is a route nobody protected that the gate\n * reported as protected.\n *\n * ## What it deliberately cannot see\n *\n * `export { GET } from './shared'` re-exports across a module boundary, and\n * `export const GET = makeAdminRoute(fn)` hides the config inside a helper.\n * Both come back \"not declared\", which fails the build. That direction is the\n * chosen one: the cost is an explicit declaration on a route that already had\n * one somewhere else, and the alternative cost is a route reported as declared\n * because the scanner guessed.\n */\nimport { createRequire } from 'node:module'\n\nimport type * as TS from 'typescript'\n\nimport { HTTP_METHODS, type HttpMethod } from '../../core/contracts/http-methods.js'\n\nconst require_ = createRequire(import.meta.url)\n\nconst ts = require_('typescript') as typeof TS\n\nconst HTTP_METHOD_NAMES = new Set<string>(HTTP_METHODS)\n\nconst POLICY_KEY = 'policy'\n\nfunction hasExportModifier(modifiers: readonly TS.Modifier[] | undefined): boolean {\n if (!modifiers) return false\n for (const m of modifiers) {\n if (m.kind === ts.SyntaxKind.ExportKeyword) return true\n }\n return false\n}\n\n/** Strip the wrappers that carry no runtime meaning (`(x)`, `x as T`, `x satisfies T`, `x!`). */\nfunction unwrap(expression: TS.Expression): TS.Expression {\n let current = expression\n for (;;) {\n if (\n ts.isParenthesizedExpression(current) ||\n ts.isAsExpression(current) ||\n ts.isSatisfiesExpression(current) ||\n ts.isNonNullExpression(current)\n ) {\n current = current.expression\n continue\n }\n return current\n }\n}\n\nfunction propertyNameIsPolicy(name: TS.PropertyName | undefined): boolean {\n if (name === undefined) return false\n if (ts.isIdentifier(name)) return name.text === POLICY_KEY\n if (ts.isStringLiteral(name)) return name.text === POLICY_KEY\n return false\n}\n\n/** A `policy` key at the TOP level of a config object literal. Never deeper. */\nfunction objectDeclaresPolicy(literal: TS.ObjectLiteralExpression): boolean {\n for (const property of literal.properties) {\n if (ts.isSpreadAssignment(property)) continue\n if (propertyNameIsPolicy(property.name)) return true\n }\n return false\n}\n\n/**\n * Does this initializer declare a policy?\n *\n * Walks the call chain leftwards (`.build()` -> `.handler()` -> `.policy()`) and\n * checks the arguments each call receives, which is where the object form puts\n * its config. Nothing else is visited.\n */\nfunction declaresPolicy(expression: TS.Expression): boolean {\n const expr = unwrap(expression)\n\n if (ts.isObjectLiteralExpression(expr)) return objectDeclaresPolicy(expr)\n\n if (!ts.isCallExpression(expr)) return false\n\n const callee = unwrap(expr.expression)\n\n if (ts.isPropertyAccessExpression(callee) && callee.name.text === POLICY_KEY) return true\n\n for (const argument of expr.arguments) {\n const arg = unwrap(argument)\n if (ts.isObjectLiteralExpression(arg) && objectDeclaresPolicy(arg)) return true\n }\n\n // Keep walking the chain: `route().policy(p).handler(h).build()` reaches\n // `.policy` only by stepping left through `.build` and `.handler`.\n if (ts.isPropertyAccessExpression(callee)) return declaresPolicy(callee.expression)\n\n return false\n}\n\n/** Every top-level `const x = <expr>` in the file, so `export { x as GET }` can be resolved. */\nfunction collectLocalInitializers(sourceFile: TS.SourceFile): Map<string, TS.Expression> {\n const locals = new Map<string, TS.Expression>()\n for (const stmt of sourceFile.statements) {\n if (!ts.isVariableStatement(stmt)) continue\n for (const decl of stmt.declarationList.declarations) {\n if (ts.isIdentifier(decl.name) && decl.initializer !== undefined) {\n locals.set(decl.name.text, decl.initializer)\n }\n }\n }\n return locals\n}\n\n/** `export const GET = ...` */\nfunction collectFromVariableStatement(stmt: TS.VariableStatement, declared: Set<HttpMethod>): void {\n if (!hasExportModifier(ts.getModifiers(stmt))) return\n for (const decl of stmt.declarationList.declarations) {\n if (!ts.isIdentifier(decl.name) || !HTTP_METHOD_NAMES.has(decl.name.text)) continue\n if (decl.initializer !== undefined && declaresPolicy(decl.initializer)) {\n declared.add(decl.name.text as HttpMethod)\n }\n }\n}\n\n/**\n * `export { GET }` / `export { handler as GET }` — resolvable only when the\n * declaration is in this file. A re-export with a module specifier is not.\n */\nfunction collectFromExportDeclaration(\n stmt: TS.ExportDeclaration,\n locals: Map<string, TS.Expression>,\n declared: Set<HttpMethod>,\n): void {\n if (stmt.moduleSpecifier !== undefined) return\n if (!stmt.exportClause || !ts.isNamedExports(stmt.exportClause)) return\n for (const spec of stmt.exportClause.elements) {\n if (!HTTP_METHOD_NAMES.has(spec.name.text)) continue\n const local = locals.get((spec.propertyName ?? spec.name).text)\n if (local !== undefined && declaresPolicy(local)) {\n declared.add(spec.name.text as HttpMethod)\n }\n }\n}\n\nfunction collectFromStatement(\n stmt: TS.Statement,\n locals: Map<string, TS.Expression>,\n declared: Set<HttpMethod>,\n): void {\n if (ts.isVariableStatement(stmt)) {\n collectFromVariableStatement(stmt, declared)\n return\n }\n if (ts.isExportDeclaration(stmt)) {\n collectFromExportDeclaration(stmt, locals, declared)\n }\n // `export function GET() {}` and `export class GET {}` reach neither branch,\n // on purpose: a bare function has no config object to carry a policy.\n}\n\n/**\n * The set of HTTP-method exports in this file that declare an access policy.\n *\n * `content` is passed in rather than read here so the scanner reads each route\n * file once and hands the same source to both detectors.\n */\nexport function detectMethodsWithDeclaredPolicy(\n filePath: string,\n content: string,\n): Set<HttpMethod> {\n const sourceFile = ts.createSourceFile(\n filePath,\n content,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n ts.ScriptKind.TS,\n )\n const locals = collectLocalInitializers(sourceFile)\n const declared = new Set<HttpMethod>()\n for (const stmt of sourceFile.statements) {\n collectFromStatement(stmt, locals, declared)\n }\n return declared\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks `serverDir/ws/` derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { existsSync, statSync } from 'node:fs'\nimport { extname, join, relative } from 'node:path'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nconst WS_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx'])\n\nexport interface WebSocketRouteNode {\n filePath: string\n wsPath: string\n}\n\nexport function scanWebSocketRoutes(serverDir: string): WebSocketRouteNode[] {\n const wsDir = join(serverDir, 'ws')\n if (!existsSync(wsDir) || !statSync(wsDir).isDirectory()) {\n return []\n }\n\n const results: WebSocketRouteNode[] = []\n walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {\n let rel = relative(wsDir, absPath)\n rel = rel.replace(/\\\\/g, '/')\n rel = rel.slice(0, -extname(rel).length)\n if (rel.endsWith('/index')) rel = rel.slice(0, -6)\n else if (rel === 'index') rel = ''\n results.push({\n filePath: absPath,\n wsPath: `/ws/${rel}`,\n })\n })\n return results\n}\n"],"mappings":"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","names":["basename","existsSync","readFileSync","statSync","extname","join","relative","readFileSync","readFileSync","createRequire","require_","createRequire","ts","HTTP_METHOD_NAMES","hasExportModifier","collectFromStatement","relative","extname","readFileSync","relative","extname","join","existsSync","statSync","existsSync","statSync","extname","join","relative","join","existsSync","statSync","relative","extname"]}
@@ -149,6 +149,33 @@ var MissingAgentPolicyError = class extends Error {
149
149
  this.migrationUrl = migrationUrl;
150
150
  }
151
151
  };
152
+ var RedundantApiSegmentError = class extends Error {
153
+ name = "RedundantApiSegmentError";
154
+ file;
155
+ doubledRoutePath;
156
+ suggestion;
157
+ constructor(opts) {
158
+ const message = [
159
+ `Redundant 'api' directory: routes/ is already served under /api.`,
160
+ ``,
161
+ ` File: ${opts.file}`,
162
+ ` Answers: ${opts.doubledRoutePath}`,
163
+ ` Client: ${opts.doubledClientChain}`,
164
+ ``,
165
+ `Move it up one level:`,
166
+ ``,
167
+ ` ${opts.suggestion}`,
168
+ ``,
169
+ `Both halves are wrong from one cause. The URL is not the one you would register with an`,
170
+ `identity provider or call from anywhere, and the generated typed client in`,
171
+ `.theokit/client.d.ts carries the same redundant segment.`
172
+ ].join("\n");
173
+ super(message);
174
+ this.file = opts.file;
175
+ this.doubledRoutePath = opts.doubledRoutePath;
176
+ this.suggestion = opts.suggestion;
177
+ }
178
+ };
152
179
 
153
180
  // src/server/scan/file-stamp-cache.ts
154
181
  import { statSync } from "fs";
@@ -176,6 +203,7 @@ export {
176
203
  RouterConventionError,
177
204
  MissingRoutePolicyError,
178
205
  MissingAgentPolicyError,
206
+ RedundantApiSegmentError,
179
207
  createFileStampCache
180
208
  };
181
- //# sourceMappingURL=chunk-FD4ZDTQ6.js.map
209
+ //# sourceMappingURL=chunk-Z7CJHDLS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server/_internal/compare-by-code-unit.ts","../src/server/_internal/scan-walker.ts","../src/server/scan/errors.ts","../src/server/scan/file-stamp-cache.ts"],"sourcesContent":["/**\n * Total order over strings by UTF-16 code unit — the ordering build-time output\n * must use.\n *\n * `localeCompare` is the wrong instrument here, and reads like the right one,\n * which is why it kept being chosen. With no locale argument it uses the default\n * collator, and Node derives that from `LC_ALL`/`LANG`: under `sv-SE` an `ä`\n * sorts after `z`, under `en-US` before `a`. A scanner ordering that way emits a\n * different manifest per machine — and where the order being emitted is an\n * execution order, it decides which middleware runs first\n * (usetheokit/theokit#346, usetheokit/theokit#351).\n *\n * What build output needs from a comparator is that it be total and stable.\n * Being alphabetical for a reader of one particular language is not a property\n * it needs, and is the one that costs reproducibility.\n *\n * Presentation code is the opposite case and should keep `localeCompare`: a list\n * a human reads should collate the way that human's language collates.\n *\n * @internal\n */\nexport function compareByCodeUnit(a: string, b: string): number {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks directories derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { readdirSync } from 'node:fs'\nimport type { Dirent } from 'node:fs'\nimport { extname, join, resolve } from 'node:path'\n\nimport { compareByCodeUnit } from './compare-by-code-unit.js'\n\n/**\n * Options for walkSourceFiles.\n *\n * Sequential by design — callers (route precedence) depend on insertion\n * order (EC-19 documented decision). Async-callback support is implicit\n * via JavaScript's serial await loop.\n *\n * Tested on macOS + Linux. Windows long-path support not validated (EC-20).\n */\ninterface WalkOptions {\n /** File extensions to include (e.g., new Set(['.ts', '.tsx'])). */\n extensions: ReadonlySet<string>\n /** Skip directories whose name starts with any of these (default: ['_', '.']). */\n skipPrefixes?: readonly string[]\n /**\n * Read one directory. Defaults to `readdirSync`.\n *\n * Injected for one reason: the ordering below cannot be tested against a real\n * filesystem. `readdirSync` returns whatever the filesystem returns — creation\n * order on tmpfs, hash order on ext4 with `dir_index` — and on the machine\n * this was written on those four names came back already sorted, so a\n * name-based test passed before the sort existed and proved nothing. A test\n * that agrees with the code because both read the same accidental order is\n * `B-022`, and writing one here would have been that item committed inside its\n * own fix.\n */\n readDir?: (dir: string) => Dirent[]\n}\n\n/**\n * Recursively walk `root`, invoking `onFile(absPath)` for every file matching\n * `opts.extensions`. Directories whose name starts with any `skipPrefixes`\n * char are skipped (defaults to `_` and `.`).\n *\n * Entries are emitted in UTF-16 code-unit order, files and directories in one\n * total order. Without it the walker passed the filesystem's order through to\n * six scanners — routes, actions, websockets, cron, agents, jobs — and only one\n * re-orders afterwards, so five emitted a different manifest per machine. Where\n * that order is an execution order it decides what runs first (#346, B-004).\n *\n * Replaces 3 near-identical recursive walkers in scan.ts, action-scan.ts,\n * ws-scan.ts (PV-3 — DRY consolidation). Resolves T3.1 of\n * architecture-review-remediation-plan. Six call sites consume it today, not\n * three; the count in this paragraph was true when it was written.\n *\n * Symlink loops are NOT tracked — callers must avoid them or pre-resolve\n * via `fs.realpath`. EC-11 documented but not implemented (rare in dev).\n */\nexport function walkSourceFiles(\n root: string,\n opts: WalkOptions,\n onFile: (absPath: string) => void,\n): void {\n const skipPrefixes = opts.skipPrefixes ?? ['_', '.']\n const readDir = opts.readDir ?? ((dir: string) => readdirSync(dir, { withFileTypes: true }))\n const visit = (dir: string): void => {\n let entries\n try {\n entries = readDir(dir)\n } catch {\n // Silently skip unreadable directories — caller controls discoverability\n return\n }\n // One total order over files and directories together, so a name sorts the\n // same way whichever it is. Sorting the two groups separately would emit a\n // different interleaving than a single pass, which is an ordering decision\n // nobody made (#346).\n const ordered = [...entries].sort((a, b) => compareByCodeUnit(a.name, b.name))\n for (const entry of ordered) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {\n visit(fullPath)\n } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {\n onFile(resolve(fullPath))\n }\n }\n }\n visit(root)\n}\n","import { compareByCodeUnit } from '../_internal/compare-by-code-unit.js'\n\n/**\n * Router-convention errors thrown by the server-route scanner.\n *\n *\n * theokit 0.4.0+ enforces directory-nested file-system routing\n * (`auth/[provider]/login.ts`) and REJECTS dotted-basename routes\n * (`auth.[provider].login.ts`) because the legacy regex extracted the\n * dotted basename incorrectly — `params.provider` was undefined at request\n * time. Decision recorded in the plan above (`g6-router-convention-plan.md`) and CHANGELOG 0.4.0\n * (no standalone ADR was cut for this router-convention change).\n */\n\n/**\n * Canonical migration guide URL. T4.2 establishes this as the authoritative\n * landing page. EC-3: error message uses this constant so the URL never\n * drifts from the doc location.\n */\nexport const ROUTER_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/0.3-to-0.4-router'\n\ninterface RouterConventionErrorOptions {\n /** Absolute path of the offending route file. */\n file: string\n /** Suggested directory-nested replacement path (relative, e.g. `routes/auth/[provider]/login.ts`). */\n suggestion: string\n /** Migration guide URL (defaults to `ROUTER_MIGRATION_GUIDE_URL`). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file uses the legacy\n * dotted-basename convention (`auth.[provider].login.ts`).\n *\n * The error is FAIL-FAST by design — running with a route that has wrong\n * `paramNames` produces silent 404s at request time, which is strictly\n * worse than a build-time error.\n */\nexport class RouterConventionError extends Error {\n override readonly name = 'RouterConventionError'\n readonly file: string\n readonly suggestion: string\n readonly migrationUrl: string\n\n constructor(opts: RouterConventionErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL\n const message = [\n `Router convention violation: dotted route basename is not supported in theokit 0.4+.`,\n ``,\n ` File: ${opts.file}`,\n ` Use directory-nested form: ${opts.suggestion}`,\n ``,\n `Migration guide: ${migrationUrl}`,\n `Run \\`theokit migrate router\\` to convert all dotted basenames automatically.`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.suggestion = opts.suggestion\n this.migrationUrl = migrationUrl\n }\n}\n\n/**\n * Canonical landing page for the policy-declaration migration. Held as a\n * constant for the same reason as {@link ROUTER_MIGRATION_GUIDE_URL}: the error\n * message and the doc cannot drift apart if only one of them names the URL.\n *\n * Module-local, unlike its sibling: nothing outside this file needs to name it,\n * and an export with no consumer is what `knip` exists to refuse.\n */\nconst ROUTE_POLICY_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/route-policy'\n\ninterface MissingRoutePolicyErrorOptions {\n /** Absolute path of the route file. */\n file: string\n /** The URL path the file resolves to, so the message reads like the app, not like the disk. */\n routePath: string\n /** The exported HTTP methods that declare no policy. */\n methods: string[]\n /** Migration guide URL (defaults to {@link ROUTE_POLICY_MIGRATION_GUIDE_URL}). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file exports an HTTP method that\n * declares no access policy (ADR 0001, Decision point 5).\n *\n * Absence used to mean \"not declared\", which every reader and every transport\n * had to interpret as open. The interpretation was the bug: a route nobody\n * thought about looked exactly like a route deliberately left open. This error\n * is where the two stop looking alike.\n *\n * It fires at scan time — `theo build`, `theo start`, `theo dev`, `theo routes`\n * and every deployment adapter go through the same scanner — so the answer\n * arrives before a request does, in the same place the scanner already refuses\n * a dotted basename and a collision with the reserved batch path.\n */\nexport class MissingRoutePolicyError extends Error {\n override readonly name = 'MissingRoutePolicyError'\n readonly file: string\n readonly routePath: string\n readonly methods: string[]\n readonly migrationUrl: string\n\n constructor(opts: MissingRoutePolicyErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTE_POLICY_MIGRATION_GUIDE_URL\n const methods = [...opts.methods].sort(compareByCodeUnit)\n const message = [\n `Route policy not declared: every route says who may call it (ADR 0001).`,\n ``,\n ` File: ${opts.file}`,\n ` Route: ${opts.routePath}`,\n ` Missing: ${methods.join(', ')}`,\n ``,\n `Add a policy to each method listed above:`,\n ``,\n // The import is part of the remedy, not decoration. This message shipped\n // naming `requireOwner` while no entry point exported it, so it read as\n // actionable and was not. `tests/unit/policy-gate-remedy-is-importable.test.ts`\n // asserts that every symbol named here is reachable from the path named here.\n ` import { route, requireOwner } from 'theokit/server/define'`,\n ``,\n ` export const ${methods[0] ?? 'GET'} = route()`,\n ` .policy('public') // anyone may call this`,\n ` .handler(...)`,\n ` .build()`,\n ``,\n ` export const ${methods[0] ?? 'GET'} = route()`,\n ` .policy(({ subject, params }) => requireOwner(subject, ownerOf(params.id)))`,\n ` .handler(...)`,\n ` .build()`,\n ``,\n `Writing 'public' is a decision and not a default. It states that this route`,\n `is open to anyone who can reach it, and it is greppable, so how much of the`,\n `app is open becomes a number somebody can read.`,\n ``,\n `Migration guide: ${migrationUrl}`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.routePath = opts.routePath\n this.methods = methods\n this.migrationUrl = migrationUrl\n }\n}\n\n/**\n * Canonical landing page for the agent-policy migration. Module-local for the same reason as its\n * route sibling: nothing outside this file names it, and an export with no consumer is what `knip`\n * exists to refuse.\n */\nconst AGENT_POLICY_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/agent-policy'\n\ninterface MissingAgentPolicyErrorOptions {\n /** Absolute path of the agent file. */\n file: string\n /** The URL the agent is served at, so the message reads like the app rather than like the disk. */\n agentPath: string\n /** Migration guide URL (defaults to {@link AGENT_POLICY_MIGRATION_GUIDE_URL}). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanAgents` when an agent file declares no access policy\n * (ADR 0001 Decision point 5, extended to the agent surface by usetheokit/theokit#365).\n *\n * ## Why absence had to stop meaning open HERE and not at runtime\n *\n * The agent endpoints resume a conversation the CALLER names, and they are dispatched before route\n * matching, so no route, no middleware and no `server/context.ts` ever saw those URLs. A caller\n * holding a conversation id and no credential read that conversation back.\n *\n * There is no safe runtime default for that. Refusing every caller-named session id would break\n * multi-turn chat, which is the base case; admitting them is the defect. So the decision moves to\n * where a person can answer it once, in the file that owns the agent — the same place and the same\n * reasoning as the route gate, which is why this error reads like its sibling.\n *\n * ## What it costs\n *\n * Every existing application with an agent fails its next build until it adds one line. That is the\n * half of ADR 0001 the ADR itself called breaking, and the trade it names: a build error that\n * points at a file beats a silent 200 that hands over somebody else's conversation.\n */\nexport class MissingAgentPolicyError extends Error {\n override readonly name = 'MissingAgentPolicyError'\n readonly file: string\n readonly agentPath: string\n readonly migrationUrl: string\n\n constructor(opts: MissingAgentPolicyErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? AGENT_POLICY_MIGRATION_GUIDE_URL\n const message = [\n `Agent policy not declared: every agent says who may run it (ADR 0001).`,\n ``,\n ` File: ${opts.file}`,\n ` Agent: ${opts.agentPath}`,\n ``,\n `This one declaration covers every endpoint the agent exposes - the run, the`,\n `thread routes, the pending-approval listing, the approve route and MCP - because`,\n `they all reach the same conversation and the same paused tools.`,\n ``,\n `Add ONE of these to the file above:`,\n ``,\n ` export const policy = 'public' // anyone who can reach it may run it`,\n ``,\n ` import { requireOwner } from 'theokit/server/define'`,\n ``,\n ` // subject <- what server/context.ts put on ctx.subject`,\n ` // params <- { agent, endpoint, sessionId?, approvalId? }`,\n ` // body <- the parsed chat body, on the endpoints that have one`,\n ` export const policy = ({ subject, params }) =>`,\n ` requireOwner(subject, ownerOf(params.sessionId))`,\n ``,\n `Writing 'public' is a decision and not a default. The endpoint resumes whatever`,\n `conversation the CALLER names, so 'public' means any caller holding an id may read`,\n `and continue that conversation - a capability model, which is legitimate when it is`,\n `the choice somebody made and greppable once it is written down.`,\n ``,\n `Migration guide: ${migrationUrl}`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.agentPath = opts.agentPath\n this.migrationUrl = migrationUrl\n }\n}\n","/**\n * Memoise per source file, invalidated by the file's own stamp.\n *\n * ## Why this exists\n *\n * `theokit dev` re-scans the routes and agents directories on EVERY request, so any work that\n * parses a file with the TypeScript AST turns a build-time check into per-request latency.\n * `agent-scan.ts` recognised this and carried its own `Map` keyed on `path:mtimeMs:size`;\n * `scan.ts` — with far more files — carried neither the cache nor the reasoning, and the\n * route-policy gate then added a SECOND full parse of every route file on top of the one\n * `detectExportedHttpMethods` had been paying since before it (usetheokit/theokit#417).\n *\n * Lifting the mechanism here rather than copying it is the point: two hand-rolled caches keyed\n * \"the same way\" are two chances for the keys to stop being the same.\n *\n * ## Why the stamp and not a watcher\n *\n * `mtimeMs` and `size` together come from the `statSync` the scanner is doing anyway, so an edit\n * invalidates the entry without anyone remembering to, and a `theokit build` — one process, one\n * scan — never notices the cache exists.\n *\n * The known limit, stated rather than discovered: a rewrite that preserves BOTH mtime and size is\n * invisible. That is a deliberate trade every mtime cache makes, and the alternative (hashing the\n * contents) reads the file to avoid reading the file.\n */\nimport { statSync } from 'node:fs'\n\nexport interface FileStampCache<T> {\n /** The memoised value for `filePath`, computing it when the file's stamp has changed. */\n get: (filePath: string, compute: () => T) => T\n /** Test seam — a module-level cache would otherwise outlive a fixture directory. */\n clear: () => void\n}\n\nexport function createFileStampCache<T>(): FileStampCache<T> {\n const entries = new Map<string, T>()\n\n return {\n get(filePath, compute) {\n // A path the scanner itself discovered by globbing the project, never caller input at runtime.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- see above\n const stat = statSync(filePath)\n const key = `${filePath}:${String(stat.mtimeMs)}:${String(stat.size)}`\n\n const hit = entries.get(key)\n // `has` rather than a truthiness check on the value: `false`, `0` and `''` are all legitimate\n // results to cache, and a truthiness test would recompute them on every call — which is the\n // defect this file exists to remove, wearing a subtler hat.\n if (hit !== undefined || entries.has(key)) return hit as T\n\n const value = compute()\n entries.set(key, value)\n return value\n },\n clear() {\n entries.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/server/_internal/compare-by-code-unit.ts","../src/server/_internal/scan-walker.ts","../src/server/scan/errors.ts","../src/server/scan/file-stamp-cache.ts"],"sourcesContent":["/**\n * Total order over strings by UTF-16 code unit — the ordering build-time output\n * must use.\n *\n * `localeCompare` is the wrong instrument here, and reads like the right one,\n * which is why it kept being chosen. With no locale argument it uses the default\n * collator, and Node derives that from `LC_ALL`/`LANG`: under `sv-SE` an `ä`\n * sorts after `z`, under `en-US` before `a`. A scanner ordering that way emits a\n * different manifest per machine — and where the order being emitted is an\n * execution order, it decides which middleware runs first\n * (usetheokit/theokit#346, usetheokit/theokit#351).\n *\n * What build output needs from a comparator is that it be total and stable.\n * Being alphabetical for a reader of one particular language is not a property\n * it needs, and is the one that costs reproducibility.\n *\n * Presentation code is the opposite case and should keep `localeCompare`: a list\n * a human reads should collate the way that human's language collates.\n *\n * @internal\n */\nexport function compareByCodeUnit(a: string, b: string): number {\n if (a < b) return -1\n if (a > b) return 1\n return 0\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: walks directories derived from cwd.\n * No HTTP input ever reaches these fs calls.\n */\nimport { readdirSync } from 'node:fs'\nimport type { Dirent } from 'node:fs'\nimport { extname, join, resolve } from 'node:path'\n\nimport { compareByCodeUnit } from './compare-by-code-unit.js'\n\n/**\n * Options for walkSourceFiles.\n *\n * Sequential by design — callers (route precedence) depend on insertion\n * order (EC-19 documented decision). Async-callback support is implicit\n * via JavaScript's serial await loop.\n *\n * Tested on macOS + Linux. Windows long-path support not validated (EC-20).\n */\ninterface WalkOptions {\n /** File extensions to include (e.g., new Set(['.ts', '.tsx'])). */\n extensions: ReadonlySet<string>\n /** Skip directories whose name starts with any of these (default: ['_', '.']). */\n skipPrefixes?: readonly string[]\n /**\n * Read one directory. Defaults to `readdirSync`.\n *\n * Injected for one reason: the ordering below cannot be tested against a real\n * filesystem. `readdirSync` returns whatever the filesystem returns — creation\n * order on tmpfs, hash order on ext4 with `dir_index` — and on the machine\n * this was written on those four names came back already sorted, so a\n * name-based test passed before the sort existed and proved nothing. A test\n * that agrees with the code because both read the same accidental order is\n * `B-022`, and writing one here would have been that item committed inside its\n * own fix.\n */\n readDir?: (dir: string) => Dirent[]\n}\n\n/**\n * Recursively walk `root`, invoking `onFile(absPath)` for every file matching\n * `opts.extensions`. Directories whose name starts with any `skipPrefixes`\n * char are skipped (defaults to `_` and `.`).\n *\n * Entries are emitted in UTF-16 code-unit order, files and directories in one\n * total order. Without it the walker passed the filesystem's order through to\n * six scanners — routes, actions, websockets, cron, agents, jobs — and only one\n * re-orders afterwards, so five emitted a different manifest per machine. Where\n * that order is an execution order it decides what runs first (#346, B-004).\n *\n * Replaces 3 near-identical recursive walkers in scan.ts, action-scan.ts,\n * ws-scan.ts (PV-3 — DRY consolidation). Resolves T3.1 of\n * architecture-review-remediation-plan. Six call sites consume it today, not\n * three; the count in this paragraph was true when it was written.\n *\n * Symlink loops are NOT tracked — callers must avoid them or pre-resolve\n * via `fs.realpath`. EC-11 documented but not implemented (rare in dev).\n */\nexport function walkSourceFiles(\n root: string,\n opts: WalkOptions,\n onFile: (absPath: string) => void,\n): void {\n const skipPrefixes = opts.skipPrefixes ?? ['_', '.']\n const readDir = opts.readDir ?? ((dir: string) => readdirSync(dir, { withFileTypes: true }))\n const visit = (dir: string): void => {\n let entries\n try {\n entries = readDir(dir)\n } catch {\n // Silently skip unreadable directories — caller controls discoverability\n return\n }\n // One total order over files and directories together, so a name sorts the\n // same way whichever it is. Sorting the two groups separately would emit a\n // different interleaving than a single pass, which is an ordering decision\n // nobody made (#346).\n const ordered = [...entries].sort((a, b) => compareByCodeUnit(a.name, b.name))\n for (const entry of ordered) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {\n visit(fullPath)\n } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {\n onFile(resolve(fullPath))\n }\n }\n }\n visit(root)\n}\n","import { compareByCodeUnit } from '../_internal/compare-by-code-unit.js'\n\n/**\n * Router-convention errors thrown by the server-route scanner.\n *\n *\n * theokit 0.4.0+ enforces directory-nested file-system routing\n * (`auth/[provider]/login.ts`) and REJECTS dotted-basename routes\n * (`auth.[provider].login.ts`) because the legacy regex extracted the\n * dotted basename incorrectly — `params.provider` was undefined at request\n * time. Decision recorded in the plan above (`g6-router-convention-plan.md`) and CHANGELOG 0.4.0\n * (no standalone ADR was cut for this router-convention change).\n */\n\n/**\n * Canonical migration guide URL. T4.2 establishes this as the authoritative\n * landing page. EC-3: error message uses this constant so the URL never\n * drifts from the doc location.\n */\nexport const ROUTER_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/0.3-to-0.4-router'\n\ninterface RouterConventionErrorOptions {\n /** Absolute path of the offending route file. */\n file: string\n /** Suggested directory-nested replacement path (relative, e.g. `routes/auth/[provider]/login.ts`). */\n suggestion: string\n /** Migration guide URL (defaults to `ROUTER_MIGRATION_GUIDE_URL`). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file uses the legacy\n * dotted-basename convention (`auth.[provider].login.ts`).\n *\n * The error is FAIL-FAST by design — running with a route that has wrong\n * `paramNames` produces silent 404s at request time, which is strictly\n * worse than a build-time error.\n */\nexport class RouterConventionError extends Error {\n override readonly name = 'RouterConventionError'\n readonly file: string\n readonly suggestion: string\n readonly migrationUrl: string\n\n constructor(opts: RouterConventionErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTER_MIGRATION_GUIDE_URL\n const message = [\n `Router convention violation: dotted route basename is not supported in theokit 0.4+.`,\n ``,\n ` File: ${opts.file}`,\n ` Use directory-nested form: ${opts.suggestion}`,\n ``,\n `Migration guide: ${migrationUrl}`,\n `Run \\`theokit migrate router\\` to convert all dotted basenames automatically.`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.suggestion = opts.suggestion\n this.migrationUrl = migrationUrl\n }\n}\n\n/**\n * Canonical landing page for the policy-declaration migration. Held as a\n * constant for the same reason as {@link ROUTER_MIGRATION_GUIDE_URL}: the error\n * message and the doc cannot drift apart if only one of them names the URL.\n *\n * Module-local, unlike its sibling: nothing outside this file needs to name it,\n * and an export with no consumer is what `knip` exists to refuse.\n */\nconst ROUTE_POLICY_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/route-policy'\n\ninterface MissingRoutePolicyErrorOptions {\n /** Absolute path of the route file. */\n file: string\n /** The URL path the file resolves to, so the message reads like the app, not like the disk. */\n routePath: string\n /** The exported HTTP methods that declare no policy. */\n methods: string[]\n /** Migration guide URL (defaults to {@link ROUTE_POLICY_MIGRATION_GUIDE_URL}). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file exports an HTTP method that\n * declares no access policy (ADR 0001, Decision point 5).\n *\n * Absence used to mean \"not declared\", which every reader and every transport\n * had to interpret as open. The interpretation was the bug: a route nobody\n * thought about looked exactly like a route deliberately left open. This error\n * is where the two stop looking alike.\n *\n * It fires at scan time — `theo build`, `theo start`, `theo dev`, `theo routes`\n * and every deployment adapter go through the same scanner — so the answer\n * arrives before a request does, in the same place the scanner already refuses\n * a dotted basename and a collision with the reserved batch path.\n */\nexport class MissingRoutePolicyError extends Error {\n override readonly name = 'MissingRoutePolicyError'\n readonly file: string\n readonly routePath: string\n readonly methods: string[]\n readonly migrationUrl: string\n\n constructor(opts: MissingRoutePolicyErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? ROUTE_POLICY_MIGRATION_GUIDE_URL\n const methods = [...opts.methods].sort(compareByCodeUnit)\n const message = [\n `Route policy not declared: every route says who may call it (ADR 0001).`,\n ``,\n ` File: ${opts.file}`,\n ` Route: ${opts.routePath}`,\n ` Missing: ${methods.join(', ')}`,\n ``,\n `Add a policy to each method listed above:`,\n ``,\n // The import is part of the remedy, not decoration. This message shipped\n // naming `requireOwner` while no entry point exported it, so it read as\n // actionable and was not. `tests/unit/policy-gate-remedy-is-importable.test.ts`\n // asserts that every symbol named here is reachable from the path named here.\n ` import { route, requireOwner } from 'theokit/server/define'`,\n ``,\n ` export const ${methods[0] ?? 'GET'} = route()`,\n ` .policy('public') // anyone may call this`,\n ` .handler(...)`,\n ` .build()`,\n ``,\n ` export const ${methods[0] ?? 'GET'} = route()`,\n ` .policy(({ subject, params }) => requireOwner(subject, ownerOf(params.id)))`,\n ` .handler(...)`,\n ` .build()`,\n ``,\n `Writing 'public' is a decision and not a default. It states that this route`,\n `is open to anyone who can reach it, and it is greppable, so how much of the`,\n `app is open becomes a number somebody can read.`,\n ``,\n `Migration guide: ${migrationUrl}`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.routePath = opts.routePath\n this.methods = methods\n this.migrationUrl = migrationUrl\n }\n}\n\n/**\n * Canonical landing page for the agent-policy migration. Module-local for the same reason as its\n * route sibling: nothing outside this file names it, and an export with no consumer is what `knip`\n * exists to refuse.\n */\nconst AGENT_POLICY_MIGRATION_GUIDE_URL = 'https://theokit.dev/migration/agent-policy'\n\ninterface MissingAgentPolicyErrorOptions {\n /** Absolute path of the agent file. */\n file: string\n /** The URL the agent is served at, so the message reads like the app rather than like the disk. */\n agentPath: string\n /** Migration guide URL (defaults to {@link AGENT_POLICY_MIGRATION_GUIDE_URL}). */\n migrationUrl?: string\n}\n\n/**\n * Thrown by `scanAgents` when an agent file declares no access policy\n * (ADR 0001 Decision point 5, extended to the agent surface by usetheokit/theokit#365).\n *\n * ## Why absence had to stop meaning open HERE and not at runtime\n *\n * The agent endpoints resume a conversation the CALLER names, and they are dispatched before route\n * matching, so no route, no middleware and no `server/context.ts` ever saw those URLs. A caller\n * holding a conversation id and no credential read that conversation back.\n *\n * There is no safe runtime default for that. Refusing every caller-named session id would break\n * multi-turn chat, which is the base case; admitting them is the defect. So the decision moves to\n * where a person can answer it once, in the file that owns the agent — the same place and the same\n * reasoning as the route gate, which is why this error reads like its sibling.\n *\n * ## What it costs\n *\n * Every existing application with an agent fails its next build until it adds one line. That is the\n * half of ADR 0001 the ADR itself called breaking, and the trade it names: a build error that\n * points at a file beats a silent 200 that hands over somebody else's conversation.\n */\nexport class MissingAgentPolicyError extends Error {\n override readonly name = 'MissingAgentPolicyError'\n readonly file: string\n readonly agentPath: string\n readonly migrationUrl: string\n\n constructor(opts: MissingAgentPolicyErrorOptions) {\n const migrationUrl = opts.migrationUrl ?? AGENT_POLICY_MIGRATION_GUIDE_URL\n const message = [\n `Agent policy not declared: every agent says who may run it (ADR 0001).`,\n ``,\n ` File: ${opts.file}`,\n ` Agent: ${opts.agentPath}`,\n ``,\n `This one declaration covers every endpoint the agent exposes - the run, the`,\n `thread routes, the pending-approval listing, the approve route and MCP - because`,\n `they all reach the same conversation and the same paused tools.`,\n ``,\n `Add ONE of these to the file above:`,\n ``,\n ` export const policy = 'public' // anyone who can reach it may run it`,\n ``,\n ` import { requireOwner } from 'theokit/server/define'`,\n ``,\n ` // subject <- what server/context.ts put on ctx.subject`,\n ` // params <- { agent, endpoint, sessionId?, approvalId? }`,\n ` // body <- the parsed chat body, on the endpoints that have one`,\n ` export const policy = ({ subject, params }) =>`,\n ` requireOwner(subject, ownerOf(params.sessionId))`,\n ``,\n `Writing 'public' is a decision and not a default. The endpoint resumes whatever`,\n `conversation the CALLER names, so 'public' means any caller holding an id may read`,\n `and continue that conversation - a capability model, which is legitimate when it is`,\n `the choice somebody made and greppable once it is written down.`,\n ``,\n `Migration guide: ${migrationUrl}`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.agentPath = opts.agentPath\n this.migrationUrl = migrationUrl\n }\n}\n\ninterface RedundantApiSegmentErrorOptions {\n /** Absolute path of the offending route file. */\n file: string\n /** The path it resolves to today, with the doubled prefix. */\n doubledRoutePath: string\n /** Where the file belongs, relative and ready to copy. */\n suggestion: string\n /** The typed-client chain it produces today, with the redundant segment. */\n doubledClientChain: string\n}\n\n/**\n * Thrown by `scanServerRoutes` when a route file sits under `routes/api/`.\n *\n * `routes/` is already served under `/api`, so the directory doubles the prefix — and it does so\n * in two places at once, which is why this is an error rather than a lint.\n *\n * The URL is the visible half: `routes/api/auth/callback.ts` answers at `/api/api/auth/callback`,\n * which is not the redirect URI anybody registered with an identity provider. The second half\n * survives a reader's attention: `.theokit/client.d.ts` mirrors the file tree into the typed\n * client, so the same file produces `client.api.auth.callback.get()` — an `api` segment that reads\n * as a typo and is not one.\n *\n * Refusing rather than silently stripping the segment is deliberate. Collapsing would swap one\n * silent behaviour for another, and would let `routes/api/foo.ts` and `routes/foo.ts` resolve to\n * one URL — a collision needing its own error anyway.\n *\n * The `/api` prefix itself is not the defect. It is the boundary between what the server answers\n * and what the SPA answers, and three framework namespaces live under it (`/api/__actions/`,\n * `/api/agents/`, `/api/__theo_batch__`).\n */\nexport class RedundantApiSegmentError extends Error {\n override readonly name = 'RedundantApiSegmentError'\n readonly file: string\n readonly doubledRoutePath: string\n readonly suggestion: string\n\n constructor(opts: RedundantApiSegmentErrorOptions) {\n const message = [\n `Redundant 'api' directory: routes/ is already served under /api.`,\n ``,\n ` File: ${opts.file}`,\n ` Answers: ${opts.doubledRoutePath}`,\n ` Client: ${opts.doubledClientChain}`,\n ``,\n `Move it up one level:`,\n ``,\n ` ${opts.suggestion}`,\n ``,\n `Both halves are wrong from one cause. The URL is not the one you would register with an`,\n `identity provider or call from anywhere, and the generated typed client in`,\n `.theokit/client.d.ts carries the same redundant segment.`,\n ].join('\\n')\n super(message)\n this.file = opts.file\n this.doubledRoutePath = opts.doubledRoutePath\n this.suggestion = opts.suggestion\n }\n}\n","/**\n * Memoise per source file, invalidated by the file's own stamp.\n *\n * ## Why this exists\n *\n * `theokit dev` re-scans the routes and agents directories on EVERY request, so any work that\n * parses a file with the TypeScript AST turns a build-time check into per-request latency.\n * `agent-scan.ts` recognised this and carried its own `Map` keyed on `path:mtimeMs:size`;\n * `scan.ts` — with far more files — carried neither the cache nor the reasoning, and the\n * route-policy gate then added a SECOND full parse of every route file on top of the one\n * `detectExportedHttpMethods` had been paying since before it (usetheokit/theokit#417).\n *\n * Lifting the mechanism here rather than copying it is the point: two hand-rolled caches keyed\n * \"the same way\" are two chances for the keys to stop being the same.\n *\n * ## Why the stamp and not a watcher\n *\n * `mtimeMs` and `size` together come from the `statSync` the scanner is doing anyway, so an edit\n * invalidates the entry without anyone remembering to, and a `theokit build` — one process, one\n * scan — never notices the cache exists.\n *\n * The known limit, stated rather than discovered: a rewrite that preserves BOTH mtime and size is\n * invisible. That is a deliberate trade every mtime cache makes, and the alternative (hashing the\n * contents) reads the file to avoid reading the file.\n */\nimport { statSync } from 'node:fs'\n\nexport interface FileStampCache<T> {\n /** The memoised value for `filePath`, computing it when the file's stamp has changed. */\n get: (filePath: string, compute: () => T) => T\n /** Test seam — a module-level cache would otherwise outlive a fixture directory. */\n clear: () => void\n}\n\nexport function createFileStampCache<T>(): FileStampCache<T> {\n const entries = new Map<string, T>()\n\n return {\n get(filePath, compute) {\n // A path the scanner itself discovered by globbing the project, never caller input at runtime.\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- see above\n const stat = statSync(filePath)\n const key = `${filePath}:${String(stat.mtimeMs)}:${String(stat.size)}`\n\n const hit = entries.get(key)\n // `has` rather than a truthiness check on the value: `false`, `0` and `''` are all legitimate\n // results to cache, and a truthiness test would recompute them on every call — which is the\n // defect this file exists to remove, wearing a subtler hat.\n if (hit !== undefined || entries.has(key)) return hit as T\n\n const value = compute()\n entries.set(key, value)\n return value\n },\n clear() {\n entries.clear()\n },\n }\n}\n"],"mappings":"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","names":[]}
package/dist/cli/index.js CHANGED
@@ -15,12 +15,12 @@ function cliVersion() {
15
15
  // src/cli/index.ts
16
16
  var cli = cac("theokit");
17
17
  cli.command("dev", "Start development server").option("--port <port>", "Port number").action(async (options) => {
18
- const { devCommand } = await import("../dev-Z4YLROZD.js");
18
+ const { devCommand } = await import("../dev-2NEKDLEY.js");
19
19
  await devCommand({ port: options.port ? Number(options.port) : void 0 });
20
20
  });
21
21
  cli.command("build", "Build for production").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
22
22
  try {
23
- const { buildCommand } = await import("../build-4LPUE2H3.js");
23
+ const { buildCommand } = await import("../build-MGNDNTXH.js");
24
24
  await buildCommand({ target: options.target });
25
25
  } catch (err) {
26
26
  const msg = err instanceof Error ? err.message : String(err);
@@ -32,7 +32,7 @@ cli.command("build", "Build for production").option("--target <target>", "Deploy
32
32
  });
33
33
  cli.command("start", "Start production server").option("--port <port>", "Port number").action(async (options) => {
34
34
  try {
35
- const { startCommand } = await import("../start-G2JS6422.js");
35
+ const { startCommand } = await import("../start-DMOVSFBL.js");
36
36
  await startCommand({ port: options.port ? Number(options.port) : void 0 });
37
37
  } catch (err) {
38
38
  const msg = err instanceof Error ? err.message : String(err);
@@ -44,7 +44,7 @@ cli.command("start", "Start production server").option("--port <port>", "Port nu
44
44
  });
45
45
  cli.command("preview", "Build for production, then serve it \u2014 one step (B-030)").option("--port <port>", "Port number").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
46
46
  try {
47
- const { previewCommand } = await import("../preview-LB3YL6U5.js");
47
+ const { previewCommand } = await import("../preview-D2RKY5ZA.js");
48
48
  await previewCommand({
49
49
  port: options.port ? Number(options.port) : void 0,
50
50
  target: options.target
@@ -77,7 +77,7 @@ cli.command(
77
77
  "Run an agent in the terminal (stream + tool calls + approval)"
78
78
  ).action(async (name, message) => {
79
79
  try {
80
- const { agentCommand } = await import("../agent-7RP3VTSI.js");
80
+ const { agentCommand } = await import("../agent-PLEYEX37.js");
81
81
  const { sawError } = await agentCommand(name, message);
82
82
  if (sawError) process.exit(1);
83
83
  } catch (err) {
@@ -108,7 +108,7 @@ cli.command("agent sessions gc", "Collect old transcripts for this project (dry
108
108
  });
109
109
  cli.command("mcp <agent>", "Serve an agent as an MCP server over stdio (for desktop MCP clients)").action(async (agent) => {
110
110
  try {
111
- const { mcpCommand } = await import("../mcp-RKD4QQFS.js");
111
+ const { mcpCommand } = await import("../mcp-5AWKPETM.js");
112
112
  await mcpCommand(agent);
113
113
  } catch (err) {
114
114
  const msg = err instanceof Error ? err.message : String(err);
@@ -120,7 +120,7 @@ cli.command("mcp <agent>", "Serve an agent as an MCP server over stdio (for desk
120
120
  });
121
121
  cli.command("routes", "List all routes, actions, and WebSocket endpoints").action(async () => {
122
122
  try {
123
- const { routesCommand } = await import("../routes-BPLU3ISV.js");
123
+ const { routesCommand } = await import("../routes-PP5PHHLR.js");
124
124
  await routesCommand();
125
125
  } catch (err) {
126
126
  const msg = err instanceof Error ? err.message : String(err);
@@ -173,7 +173,7 @@ cli.command(
173
173
  "Generate <distDir>/openapi.json from route schemas (opt-in via config.openapi)"
174
174
  ).option("--dry-run", "Print the document to stdout without writing to disk (EC-3)").action(async (options) => {
175
175
  try {
176
- const { openapiCommand } = await import("../openapi-VFPEZSRI.js");
176
+ const { openapiCommand } = await import("../openapi-2IBBOS3Q.js");
177
177
  await openapiCommand({ dryRun: Boolean(options.dryRun) });
178
178
  } catch (err) {
179
179
  const msg = err instanceof Error ? err.message : String(err);
@@ -11,23 +11,23 @@ import {
11
11
  } from "./chunk-KTMQUYED.js";
12
12
  import {
13
13
  theoPluginAsync
14
- } from "./chunk-LLMTORUY.js";
15
- import "./chunk-MLWETKFA.js";
14
+ } from "./chunk-MTWZ54OI.js";
15
+ import "./chunk-DY5W5OWQ.js";
16
16
  import "./chunk-2CVV6CNN.js";
17
17
  import "./chunk-3PWQQWT6.js";
18
18
  import "./chunk-FDOOBTXJ.js";
19
19
  import "./chunk-47MM2JUK.js";
20
- import "./chunk-QNGS7EVS.js";
20
+ import "./chunk-575RV3CD.js";
21
21
  import "./chunk-5YPXXSYY.js";
22
22
  import "./chunk-MWWFXYW3.js";
23
23
  import "./chunk-RPET332G.js";
24
24
  import "./chunk-GSIOWE5B.js";
25
25
  import "./chunk-C27VRUVN.js";
26
26
  import "./chunk-7UZZHCRN.js";
27
- import "./chunk-KUZVRJ7M.js";
28
- import "./chunk-EVZKNA2W.js";
29
- import "./chunk-XS7EX55U.js";
30
- import "./chunk-FD4ZDTQ6.js";
27
+ import "./chunk-XLLW46MB.js";
28
+ import "./chunk-6AM7HIRM.js";
29
+ import "./chunk-YYOBMOO4.js";
30
+ import "./chunk-Z7CJHDLS.js";
31
31
  import {
32
32
  loadConfig
33
33
  } from "./chunk-QTHVHNJJ.js";
@@ -123,4 +123,4 @@ export {
123
123
  devCommand,
124
124
  startDevServer
125
125
  };
126
- //# sourceMappingURL=dev-Z4YLROZD.js.map
126
+ //# sourceMappingURL=dev-2NEKDLEY.js.map
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  generateManifest
3
- } from "./chunk-6J4JWHDJ.js";
3
+ } from "./chunk-VHB2FSPM.js";
4
4
  import "./chunk-DV34VG3N.js";
5
- import "./chunk-L4VFNFDL.js";
5
+ import "./chunk-IRASEXOG.js";
6
6
  import "./chunk-5K4TDLRG.js";
7
7
  import "./chunk-ZWN3AODA.js";
8
8
  import "./chunk-XTHN6JBX.js";
@@ -339,4 +339,4 @@ export {
339
339
  _resetInFlightForTests,
340
340
  reEmitOpenApi
341
341
  };
342
- //# sourceMappingURL=dev-emit-MVAJAFGX.js.map
342
+ //# sourceMappingURL=dev-emit-G2E2U3NP.js.map
@@ -6,10 +6,10 @@ import {
6
6
  } from "./chunk-47VNWTL4.js";
7
7
  import {
8
8
  generateManifest
9
- } from "./chunk-KUZVRJ7M.js";
10
- import "./chunk-EVZKNA2W.js";
11
- import "./chunk-XS7EX55U.js";
12
- import "./chunk-FD4ZDTQ6.js";
9
+ } from "./chunk-XLLW46MB.js";
10
+ import "./chunk-6AM7HIRM.js";
11
+ import "./chunk-YYOBMOO4.js";
12
+ import "./chunk-Z7CJHDLS.js";
13
13
 
14
14
  // src/vite-plugin/openapi-emit/dev-emit.ts
15
15
  var inFlight = false;
@@ -41,4 +41,4 @@ export {
41
41
  _resetInFlightForTests,
42
42
  reEmitOpenApi
43
43
  };
44
- //# sourceMappingURL=dev-emit-BFNMDCPB.js.map
44
+ //# sourceMappingURL=dev-emit-SPSV6UU7.js.map
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  scanRoutes,
9
9
  theoConfigSchema,
10
10
  theoPlugin
11
- } from "./chunk-ECTUUAMR.js";
11
+ } from "./chunk-75MWRZDP.js";
12
12
  import "./chunk-VQPHPQ5L.js";
13
13
  import "./chunk-SSYDEKWF.js";
14
14
  import "./chunk-IQRL5KZL.js";
@@ -25,9 +25,9 @@ import "./chunk-CHTAWXC2.js";
25
25
  import "./chunk-VSLTKX5L.js";
26
26
  import "./chunk-TNI4MARH.js";
27
27
  import "./chunk-OQCRCXCP.js";
28
- import "./chunk-6J4JWHDJ.js";
28
+ import "./chunk-VHB2FSPM.js";
29
29
  import "./chunk-DV34VG3N.js";
30
- import "./chunk-L4VFNFDL.js";
30
+ import "./chunk-IRASEXOG.js";
31
31
  import "./chunk-BX6EUYHU.js";
32
32
  import "./chunk-BPYMC7SU.js";
33
33
  import "./chunk-IRQGAC4L.js";
@@ -16,7 +16,7 @@ import {
16
16
  handleCspReport,
17
17
  handleCsrfReadiness,
18
18
  mountAgent
19
- } from "./chunk-QNGS7EVS.js";
19
+ } from "./chunk-575RV3CD.js";
20
20
  import {
21
21
  resolveProvider
22
22
  } from "./chunk-5YPXXSYY.js";
@@ -38,10 +38,10 @@ import {
38
38
  import "./chunk-7UZZHCRN.js";
39
39
  import {
40
40
  generateManifest
41
- } from "./chunk-KUZVRJ7M.js";
41
+ } from "./chunk-XLLW46MB.js";
42
42
  import {
43
43
  scanAgents
44
- } from "./chunk-EVZKNA2W.js";
44
+ } from "./chunk-6AM7HIRM.js";
45
45
  import {
46
46
  ACTION_SCHEMAS_DIR,
47
47
  matchRoute,
@@ -49,8 +49,8 @@ import {
49
49
  scanServerActionsEnriched,
50
50
  scanServerRoutes,
51
51
  scanWebSocketRoutes
52
- } from "./chunk-XS7EX55U.js";
53
- import "./chunk-FD4ZDTQ6.js";
52
+ } from "./chunk-YYOBMOO4.js";
53
+ import "./chunk-Z7CJHDLS.js";
54
54
  import "./chunk-U5PWXS3J.js";
55
55
  import "./chunk-VI7ZANB3.js";
56
56
  import "./chunk-VQIUTCK2.js";
@@ -87,4 +87,4 @@ export {
87
87
  scanWebSocketRoutes,
88
88
  sendError
89
89
  };
90
- //# sourceMappingURL=internal-api-53L6FIQ2.js.map
90
+ //# sourceMappingURL=internal-api-OOJT7L6I.js.map
@@ -41,11 +41,11 @@ import {
41
41
  scanServerActionsEnriched,
42
42
  scanServerRoutes,
43
43
  scanWebSocketRoutes
44
- } from "./chunk-6J4JWHDJ.js";
44
+ } from "./chunk-VHB2FSPM.js";
45
45
  import "./chunk-DV34VG3N.js";
46
46
  import {
47
47
  scanAgents
48
- } from "./chunk-L4VFNFDL.js";
48
+ } from "./chunk-IRASEXOG.js";
49
49
  import "./chunk-BX6EUYHU.js";
50
50
  import {
51
51
  CSP_REPORT_PATH,
@@ -106,4 +106,4 @@ export {
106
106
  scanWebSocketRoutes,
107
107
  sendError
108
108
  };
109
- //# sourceMappingURL=internal-api-IXSWWH2N.js.map
109
+ //# sourceMappingURL=internal-api-PYWIJTLF.js.map
@@ -2,7 +2,7 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  createAgentSsrLoader
5
- } from "./chunk-3MIGBPTW.js";
5
+ } from "./chunk-YJDWSM4D.js";
6
6
  import {
7
7
  extractAppResources,
8
8
  handleMcpJsonRpc
@@ -10,8 +10,8 @@ import {
10
10
  import "./chunk-5YPXXSYY.js";
11
11
  import {
12
12
  scanAgents
13
- } from "./chunk-EVZKNA2W.js";
14
- import "./chunk-FD4ZDTQ6.js";
13
+ } from "./chunk-6AM7HIRM.js";
14
+ import "./chunk-Z7CJHDLS.js";
15
15
  import "./chunk-3QPVEJ7D.js";
16
16
 
17
17
  // src/cli/commands/mcp.ts
@@ -75,4 +75,4 @@ async function mcpCommand(name, deps = {}) {
75
75
  export {
76
76
  mcpCommand
77
77
  };
78
- //# sourceMappingURL=mcp-RKD4QQFS.js.map
78
+ //# sourceMappingURL=mcp-5AWKPETM.js.map
@@ -6,10 +6,10 @@ import {
6
6
  } from "./chunk-47VNWTL4.js";
7
7
  import {
8
8
  generateManifest
9
- } from "./chunk-KUZVRJ7M.js";
10
- import "./chunk-EVZKNA2W.js";
11
- import "./chunk-XS7EX55U.js";
12
- import "./chunk-FD4ZDTQ6.js";
9
+ } from "./chunk-XLLW46MB.js";
10
+ import "./chunk-6AM7HIRM.js";
11
+ import "./chunk-YYOBMOO4.js";
12
+ import "./chunk-Z7CJHDLS.js";
13
13
  import {
14
14
  loadConfig
15
15
  } from "./chunk-QTHVHNJJ.js";
@@ -84,4 +84,4 @@ function emitOpenApiInMemory(manifest, config) {
84
84
  export {
85
85
  openapiCommand
86
86
  };
87
- //# sourceMappingURL=openapi-VFPEZSRI.js.map
87
+ //# sourceMappingURL=openapi-2IBBOS3Q.js.map
@@ -4,8 +4,8 @@ import "tsx/esm";
4
4
  // src/cli/commands/preview.ts
5
5
  async function defaultSteps() {
6
6
  const [{ buildCommand }, { startCommand }] = await Promise.all([
7
- import("./build-4LPUE2H3.js"),
8
- import("./start-G2JS6422.js")
7
+ import("./build-MGNDNTXH.js"),
8
+ import("./start-DMOVSFBL.js")
9
9
  ]);
10
10
  return {
11
11
  build: (opts) => buildCommand(opts),
@@ -20,4 +20,4 @@ async function previewCommand(options = {}, steps) {
20
20
  export {
21
21
  previewCommand
22
22
  };
23
- //# sourceMappingURL=preview-LB3YL6U5.js.map
23
+ //# sourceMappingURL=preview-D2RKY5ZA.js.map
@@ -7,8 +7,8 @@ import {
7
7
  scanServerActions,
8
8
  scanServerRoutes,
9
9
  scanWebSocketRoutes
10
- } from "./chunk-XS7EX55U.js";
11
- import "./chunk-FD4ZDTQ6.js";
10
+ } from "./chunk-YYOBMOO4.js";
11
+ import "./chunk-Z7CJHDLS.js";
12
12
  import {
13
13
  loadConfig
14
14
  } from "./chunk-QTHVHNJJ.js";
@@ -64,4 +64,4 @@ async function routesCommand() {
64
64
  export {
65
65
  routesCommand
66
66
  };
67
- //# sourceMappingURL=routes-BPLU3ISV.js.map
67
+ //# sourceMappingURL=routes-PP5PHHLR.js.map
@@ -158,9 +158,9 @@ import {
158
158
  scanServerRoutes,
159
159
  scanWebSocketRoutes,
160
160
  writeManifest
161
- } from "../chunk-6J4JWHDJ.js";
161
+ } from "../chunk-VHB2FSPM.js";
162
162
  import "../chunk-DV34VG3N.js";
163
- import "../chunk-L4VFNFDL.js";
163
+ import "../chunk-IRASEXOG.js";
164
164
  import {
165
165
  scanMiddlewares
166
166
  } from "../chunk-BX6EUYHU.js";
@@ -17,9 +17,9 @@ import {
17
17
  scanServerRoutes,
18
18
  scanWebSocketRoutes,
19
19
  writeManifest
20
- } from "../../chunk-6J4JWHDJ.js";
20
+ } from "../../chunk-VHB2FSPM.js";
21
21
  import "../../chunk-DV34VG3N.js";
22
- import "../../chunk-L4VFNFDL.js";
22
+ import "../../chunk-IRASEXOG.js";
23
23
  import {
24
24
  scanMiddlewares
25
25
  } from "../../chunk-BX6EUYHU.js";