theokit 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/actions-virtual-module-E6PU47GV.js +175 -0
  2. package/dist/actions-virtual-module-E6PU47GV.js.map +1 -0
  3. package/dist/actions-virtual-module-E7IELVHL.js +174 -0
  4. package/dist/actions-virtual-module-E7IELVHL.js.map +1 -0
  5. package/dist/app-typed-client-RZMVWHJ3.js +289 -0
  6. package/dist/app-typed-client-RZMVWHJ3.js.map +1 -0
  7. package/dist/app-typed-client-YIBP6JWH.js +289 -0
  8. package/dist/app-typed-client-YIBP6JWH.js.map +1 -0
  9. package/dist/{aws-lambda-PONVF3UX.js → aws-lambda-XYCGZH3I.js} +2 -2
  10. package/dist/{build-IQQYCA3B.js → build-NCGIV4LL.js} +41 -14
  11. package/dist/build-NCGIV4LL.js.map +1 -0
  12. package/dist/build-request-body-preview-EOP2Y4F3.js +52 -0
  13. package/dist/build-request-body-preview-EOP2Y4F3.js.map +1 -0
  14. package/dist/build-request-body-preview-II2235E6.js +54 -0
  15. package/dist/build-request-body-preview-II2235E6.js.map +1 -0
  16. package/dist/{bun-DITMZMKL.js → bun-K73TQEWC.js} +2 -2
  17. package/dist/{chunk-GQSTRXXB.js → chunk-2BQYEBCK.js} +77 -68
  18. package/dist/chunk-2BQYEBCK.js.map +1 -0
  19. package/dist/chunk-7DHGV77J.js +140 -0
  20. package/dist/chunk-7DHGV77J.js.map +1 -0
  21. package/dist/{chunk-WQCPKA2L.js → chunk-C27AZN46.js} +570 -165
  22. package/dist/chunk-C27AZN46.js.map +1 -0
  23. package/dist/chunk-ERAZNHHR.js +73 -0
  24. package/dist/chunk-ERAZNHHR.js.map +1 -0
  25. package/dist/{chunk-ZK5JCE5J.js → chunk-EXJWO2HB.js} +201 -43
  26. package/dist/chunk-EXJWO2HB.js.map +1 -0
  27. package/dist/chunk-F3TG5FJV.js +97 -0
  28. package/dist/chunk-F3TG5FJV.js.map +1 -0
  29. package/dist/{chunk-OK2PJOLK.js → chunk-KJVEJILQ.js} +7 -3
  30. package/dist/chunk-KJVEJILQ.js.map +1 -0
  31. package/dist/{chunk-JBC7PFV4.js → chunk-M23FP2AS.js} +2 -2
  32. package/dist/chunk-M23FP2AS.js.map +1 -0
  33. package/dist/{chunk-FRBYJANT.js → chunk-M6MYZSD2.js} +195 -41
  34. package/dist/chunk-M6MYZSD2.js.map +1 -0
  35. package/dist/{chunk-S3JBIFKS.js → chunk-MG7H3LAW.js} +2 -2
  36. package/dist/chunk-MOT42NAH.js +165 -0
  37. package/dist/chunk-MOT42NAH.js.map +1 -0
  38. package/dist/{chunk-OEB3OIYP.js → chunk-OJHO2X7C.js} +497 -168
  39. package/dist/chunk-OJHO2X7C.js.map +1 -0
  40. package/dist/chunk-QLGES2HC.js +189 -0
  41. package/dist/chunk-QLGES2HC.js.map +1 -0
  42. package/dist/{chunk-JZ5WEG3S.js → chunk-SEVYARIU.js} +9 -5
  43. package/dist/chunk-SEVYARIU.js.map +1 -0
  44. package/dist/chunk-WSJKACWB.js +17 -0
  45. package/dist/chunk-WSJKACWB.js.map +1 -0
  46. package/dist/{chunk-3Q36SEDK.js → chunk-ZBWROIIO.js} +43 -2
  47. package/dist/{chunk-3Q36SEDK.js.map → chunk-ZBWROIIO.js.map} +1 -1
  48. package/dist/{chunk-Y5KE6YFP.js → chunk-ZJW5FFYJ.js} +3 -3
  49. package/dist/chunk-ZJW5FFYJ.js.map +1 -0
  50. package/dist/chunk-ZOXNJV2O.js +358 -0
  51. package/dist/chunk-ZOXNJV2O.js.map +1 -0
  52. package/dist/cli/index.js +22 -7
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/client/index.d.ts +31 -1
  55. package/dist/client/index.js +100 -0
  56. package/dist/client/index.js.map +1 -1
  57. package/dist/{cloudflare-RBANU3SE.js → cloudflare-LCAOTRYZ.js} +2 -2
  58. package/dist/{deno-deploy-J7LI6HNL.js → deno-deploy-ZN4RE5HF.js} +2 -2
  59. package/dist/{dev-VN6WS3CM.js → dev-WDXC74CK.js} +15 -7
  60. package/dist/dev-WDXC74CK.js.map +1 -0
  61. package/dist/dev-emit-3CHM73TV.js +390 -0
  62. package/dist/dev-emit-3CHM73TV.js.map +1 -0
  63. package/dist/dev-emit-I3CKOHML.js +44 -0
  64. package/dist/dev-emit-I3CKOHML.js.map +1 -0
  65. package/dist/devtools/entry.js +545 -181
  66. package/dist/devtools/entry.js.map +1 -1
  67. package/dist/{dispatcher-IK3FVMX5.js → dispatcher-AQJGI3HU.js} +2 -2
  68. package/dist/dispatcher-E6QV32BO.js +83 -0
  69. package/dist/dispatcher-E6QV32BO.js.map +1 -0
  70. package/dist/{dist-Q5VBFWVL.js → dist-R2Q7GVDW.js} +10357 -12068
  71. package/dist/dist-R2Q7GVDW.js.map +1 -0
  72. package/dist/{generate-SIO4JSKJ.js → generate-DT4IIAHF.js} +88 -22
  73. package/dist/generate-DT4IIAHF.js.map +1 -0
  74. package/dist/index.d.ts +108 -1
  75. package/dist/index.js +5 -2
  76. package/dist/index.js.map +1 -1
  77. package/dist/{info-B3UOXFUF.js → info-VM7EK4JC.js} +3 -3
  78. package/dist/{netlify-IFY6ZGFV.js → netlify-5VQ22Z2P.js} +2 -2
  79. package/dist/{node-KWZI3CKV.js → node-3APTLGWB.js} +2 -2
  80. package/dist/openapi-CBASKC7W.js +84 -0
  81. package/dist/openapi-CBASKC7W.js.map +1 -0
  82. package/dist/registry-5QFTLOL2.js +25 -0
  83. package/dist/{routes-CAGFZKUK.js → routes-7O4NUDYS.js} +9 -7
  84. package/dist/{routes-CAGFZKUK.js.map → routes-7O4NUDYS.js.map} +1 -1
  85. package/dist/{schema-Cxq2qqwh.d.ts → schema-CjVly9c_.d.ts} +43 -1
  86. package/dist/{schema-MVB2FVK6.js → schema-JAKF23GR.js} +2 -2
  87. package/dist/server/cost/index.js +1 -1
  88. package/dist/server/index.d.ts +219 -47
  89. package/dist/server/index.js +35 -116
  90. package/dist/server/index.js.map +1 -1
  91. package/dist/{start-ZGWW5MOL.js → start-5JTFWF4T.js} +20 -14
  92. package/dist/start-5JTFWF4T.js.map +1 -0
  93. package/dist/{static-BH6JQNM2.js → static-TZBVBDTB.js} +4 -4
  94. package/dist/{upgrade-readiness-OHL36FQL.js → upgrade-readiness-Q46KTG2J.js} +6 -1
  95. package/dist/upgrade-readiness-Q46KTG2J.js.map +1 -0
  96. package/dist/{vercel-RHADE5IP.js → vercel-MWW24ABC.js} +2 -2
  97. package/dist/vite-plugin/index.d.ts +25 -1
  98. package/dist/vite-plugin/index.js +5 -2
  99. package/dist/{vite-plugin-WA3G45YY.js → vite-plugin-4WZ3C2AC.js} +7 -6
  100. package/package.json +4 -2
  101. package/dist/build-IQQYCA3B.js.map +0 -1
  102. package/dist/chunk-FRBYJANT.js.map +0 -1
  103. package/dist/chunk-GQSTRXXB.js.map +0 -1
  104. package/dist/chunk-JBC7PFV4.js.map +0 -1
  105. package/dist/chunk-JZ5WEG3S.js.map +0 -1
  106. package/dist/chunk-OEB3OIYP.js.map +0 -1
  107. package/dist/chunk-OK2PJOLK.js.map +0 -1
  108. package/dist/chunk-WQCPKA2L.js.map +0 -1
  109. package/dist/chunk-Y5KE6YFP.js.map +0 -1
  110. package/dist/chunk-ZK5JCE5J.js.map +0 -1
  111. package/dist/dev-VN6WS3CM.js.map +0 -1
  112. package/dist/dist-Q5VBFWVL.js.map +0 -1
  113. package/dist/generate-SIO4JSKJ.js.map +0 -1
  114. package/dist/registry-BB3UYAOG.js +0 -25
  115. package/dist/start-ZGWW5MOL.js.map +0 -1
  116. package/dist/upgrade-readiness-OHL36FQL.js.map +0 -1
  117. /package/dist/{aws-lambda-PONVF3UX.js.map → aws-lambda-XYCGZH3I.js.map} +0 -0
  118. /package/dist/{bun-DITMZMKL.js.map → bun-K73TQEWC.js.map} +0 -0
  119. /package/dist/{chunk-S3JBIFKS.js.map → chunk-MG7H3LAW.js.map} +0 -0
  120. /package/dist/{cloudflare-RBANU3SE.js.map → cloudflare-LCAOTRYZ.js.map} +0 -0
  121. /package/dist/{deno-deploy-J7LI6HNL.js.map → deno-deploy-ZN4RE5HF.js.map} +0 -0
  122. /package/dist/{dispatcher-IK3FVMX5.js.map → dispatcher-AQJGI3HU.js.map} +0 -0
  123. /package/dist/{info-B3UOXFUF.js.map → info-VM7EK4JC.js.map} +0 -0
  124. /package/dist/{netlify-IFY6ZGFV.js.map → netlify-5VQ22Z2P.js.map} +0 -0
  125. /package/dist/{node-KWZI3CKV.js.map → node-3APTLGWB.js.map} +0 -0
  126. /package/dist/{registry-BB3UYAOG.js.map → registry-5QFTLOL2.js.map} +0 -0
  127. /package/dist/{schema-MVB2FVK6.js.map → schema-JAKF23GR.js.map} +0 -0
  128. /package/dist/{static-BH6JQNM2.js.map → static-TZBVBDTB.js.map} +0 -0
  129. /package/dist/{vercel-RHADE5IP.js.map → vercel-MWW24ABC.js.map} +0 -0
  130. /package/dist/{vite-plugin-WA3G45YY.js.map → vite-plugin-4WZ3C2AC.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vite-plugin/app-typed-client.ts"],"sourcesContent":["/**\n * Phase 2 + Phase 4 of G1 (g1-client-codegen-plan.md):\n *\n * 1. `generateClientDts(manifest, dtsOutPath)` — pure function emitting the\n * `.theo/client.d.ts` shape (declared module `@theo/client`).\n * 2. `appTypedClientPlugin(opts)` — Vite plugin wrapper that calls (1),\n * writes the file atomically, and rewrites on route file changes.\n *\n * Implementation notes:\n * - Pure (1) is unit-testable without Vite or a filesystem (see G1-P2 tests).\n * - EC-2: kebab-case segments → camelCase; unsafe chars → bracket-access.\n * - EC-3: collision between HTTP method name and sub-segment name → method\n * wins; sub-segment ganha suffix `_` + warning comment.\n * - EC-6: write é `writeFileSync(tmp) + renameSync(tmp, final)` (POSIX atomic).\n * - EC-8: imports normalizam `\\\\` → `/`.\n */\n\nimport { mkdirSync, readFileSync, renameSync, writeFileSync, existsSync } from 'node:fs'\nimport { dirname, isAbsolute, posix, relative, resolve } from 'node:path'\n\nimport type { Plugin, ViteDevServer } from 'vite'\n\nimport { HTTP_METHODS, type HttpMethod } from '../core/contracts/http-methods.js'\nimport {\n generateManifest,\n type ManifestRoute,\n type TheoManifest,\n} from '../server/scan/manifest.js'\n\nconst FILE_HEADER = `// AUTO-GENERATED by theokit — do not edit.\n// Source: theokit/packages/theo/src/vite-plugin/app-typed-client.ts (G1 plan).\n// To refresh: \\`pnpm dev\\` or \\`pnpm build\\` regenerates this file on every route change.\n\n/* eslint-disable */\n`\n\ninterface TreeNode {\n /** sub-namespaces — keyed by segment name (already normalized). */\n children: Map<string, TreeNode>\n /** HTTP methods declared at this path → import alias. */\n methods: Map<HttpMethod, string>\n /** Whether any descendant declares dynamic params (for params type construction). */\n paramNamesAccumulated: string[]\n}\n\nfunction makeNode(): TreeNode {\n return { children: new Map(), methods: new Map(), paramNamesAccumulated: [] }\n}\n\n/**\n * Normalize a path segment to a valid TS identifier when possible.\n * EC-2: kebab-case → camelCase; pure dynamic (`:name`) → identifier `name`;\n * starts with digit or unsafe char → emit as bracket-access key.\n */\nfunction normalizeSegment(raw: string): { identifier: string; bracket: boolean } {\n // Dynamic segment: leading `:` (e.g. `:id` or `:...slug`).\n let s = raw\n if (s.startsWith(':...')) s = s.slice(4)\n else if (s.startsWith(':')) s = s.slice(1)\n\n // kebab → camel\n const camel = s.replace(/-([a-z0-9])/gi, (_, c: string) => c.toUpperCase())\n\n // Valid TS identifier (cannot start with digit, must be [a-zA-Z_$][a-zA-Z0-9_$]*)\n if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {\n return { identifier: camel, bracket: false }\n }\n // Fallback: bracket-access (keep raw form intact for JSON-string key)\n return { identifier: s, bracket: true }\n}\n\nfunction importPathFor(dtsOutPath: string, routeFileAbs: string): string {\n let rel = relative(dirname(dtsOutPath), routeFileAbs)\n // EC-8: normalize Windows path separators.\n rel = rel.replace(/\\\\/g, '/')\n // Strip extension (`.ts` / `.tsx`); TS module resolution likes extensionless.\n rel = rel.replace(/\\.[jt]sx?$/, '')\n if (!rel.startsWith('.') && !rel.startsWith('/')) {\n rel = './' + rel\n }\n return rel\n}\n\n/**\n * Convert a route path (e.g. `/api/users/:id/posts`) into the tree segments\n * we want under the `client` root. Strips the `/api/` prefix and splits.\n */\nfunction routePathSegments(routePath: string): string[] {\n let p = routePath\n if (p.startsWith('/api/')) p = p.slice(5)\n else if (p.startsWith('/api')) p = p.slice(4)\n if (p.startsWith('/')) p = p.slice(1)\n if (p === '') return []\n return p.split('/').filter(Boolean)\n}\n\nfunction paramNamesFromRoute(routePath: string): string[] {\n const out: string[] = []\n const re = /:(?:\\.\\.\\.)?([^/]+)/g\n let m: RegExpExecArray | null\n while ((m = re.exec(routePath)) !== null) {\n out.push(m[1])\n }\n return out\n}\n\ninterface BuildContext {\n root: TreeNode\n /** Sequential index per route file for aliasing imports. */\n aliasCounter: { value: number }\n /** Lines emitted at the top of the declared module (one per route file). */\n importLines: string[]\n /** Diagnostic warnings rendered as block comments inside the declared module. */\n warnings: string[]\n dtsOutPath: string\n serverDir: string\n}\n\nfunction buildOneRoute(route: ManifestRoute, ctx: BuildContext): void {\n if (!route.methods || route.methods.length === 0) return\n // Build alias prefix once per route file (`_r0`, `_r1`, ...).\n const idx = ctx.aliasCounter.value++\n // Resolve absolute path for the route file (manifest stores it relative to serverDir).\n const absRouteFile = isAbsolute(route.filePath)\n ? route.filePath\n : resolve(ctx.serverDir, route.filePath)\n const importPath = importPathFor(ctx.dtsOutPath, absRouteFile)\n const aliases = route.methods\n .filter((m): m is HttpMethod => (HTTP_METHODS as readonly string[]).includes(m))\n .map((method) => ({ method, alias: `_r${idx}_${method}` }))\n if (aliases.length === 0) return\n const importClause = aliases.map((a) => `${a.method} as ${a.alias}`).join(', ')\n ctx.importLines.push(` import type { ${importClause} } from '${importPath}'`)\n\n // Walk segments + register methods at the leaf node.\n const segments = routePathSegments(route.routePath)\n let node = ctx.root\n const tokens: Array<{ token: string; bracket: boolean }> = []\n for (const seg of segments) {\n const norm = normalizeSegment(seg)\n tokens.push({ token: norm.identifier, bracket: norm.bracket })\n let child = node.children.get(norm.identifier)\n if (child === undefined) {\n child = makeNode()\n node.children.set(norm.identifier, child)\n }\n node = child\n }\n // Accumulated param names for typed params (e.g. `{ id: string }`).\n node.paramNamesAccumulated = paramNamesFromRoute(route.routePath)\n\n // Register each HTTP method (collision detection runs in a post-pass).\n for (const { method, alias } of aliases) {\n if (node.methods.has(method)) {\n ctx.warnings.push(\n `// WARNING: duplicate ${method} export at /${segments.join('/')} (route file declared twice). Keeping last definition.`,\n )\n }\n node.methods.set(method, alias)\n }\n}\n\n/**\n * EC-3: walk the tree post-build and resolve any node where a method's\n * lowercase name clashes with a child segment. HTTP method wins (more common);\n * the offending child gets a `_` suffix + a warning entry.\n */\nfunction resolveCollisions(node: TreeNode, pathPrefix: string, warnings: string[]): void {\n const methodLowerNames = new Set<string>()\n for (const method of node.methods.keys()) {\n methodLowerNames.add(method.toLowerCase())\n }\n const renames: Array<{ from: string; to: string }> = []\n for (const childName of node.children.keys()) {\n if (methodLowerNames.has(childName)) {\n renames.push({ from: childName, to: `${childName}_` })\n }\n }\n for (const { from, to } of renames) {\n const old = node.children.get(from)\n if (old !== undefined) {\n node.children.delete(from)\n node.children.set(to, old)\n warnings.push(\n `WARNING (EC-3): path collision — method '${from}' clashed with sub-namespace at ${pathPrefix}/${from}; sub-namespace renamed to '${to}'.`,\n )\n }\n }\n // Recurse into all children (including the renamed ones).\n for (const [name, child] of node.children) {\n resolveCollisions(child, `${pathPrefix}/${name}`, warnings)\n }\n}\n\nfunction renderTreeNode(node: TreeNode, indent: string, isRoot: boolean): string {\n const lines: string[] = []\n if (isRoot) {\n lines.push('{')\n } else {\n lines.push('{')\n }\n // Methods first\n for (const [method, alias] of node.methods) {\n const lower = method.toLowerCase()\n // Routes with path params (`:id`, `:name`) emit a `{ params: {...} }` object\n // intersected with TheoFetchOptions. Without the outer `{...}` wrap the\n // generated signature `(opts: params: {...} & ...)` is invalid TS syntax\n // (parser reads `params:` as a parameter label, then `{...}` as type — invalid).\n const paramsField = node.paramNamesAccumulated.length > 0\n ? `{ params: { ${node.paramNamesAccumulated.map((p) => `${p}: string`).join('; ')} } } & `\n : ''\n lines.push(\n `${indent} ${lower}: (opts${node.paramNamesAccumulated.length === 0 ? '?' : ''}: ${paramsField}import('theokit/client').TheoFetchOptions<typeof ${alias}>) => Promise<import('theokit/client').InferResponse<typeof ${alias}>>`,\n )\n }\n // Children\n for (const [name, child] of node.children) {\n const propKey = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name.replace(/'/g, \"\\\\'\")}'`\n lines.push(`${indent} ${propKey}: ${renderTreeNode(child, indent + ' ', false)}`)\n }\n lines.push(`${indent}}`)\n return lines.join('\\n')\n}\n\nexport interface GenerateClientDtsOptions {\n manifest: TheoManifest\n /** Absolute path to the .d.ts that will be emitted. Used to compute import paths. */\n dtsOutPath: string\n /** Absolute serverDir; manifest's filePath entries are relative to this. */\n serverDir: string\n}\n\nexport function generateClientDts({\n manifest,\n dtsOutPath,\n serverDir,\n}: GenerateClientDtsOptions): string {\n const ctx: BuildContext = {\n root: makeNode(),\n aliasCounter: { value: 0 },\n importLines: [],\n warnings: [],\n dtsOutPath,\n serverDir,\n }\n for (const route of manifest.routes) {\n buildOneRoute(route, ctx)\n }\n // EC-3: resolve collisions in a post-build pass (collisions materialize\n // when two routes are both processed, not during a single route's build).\n resolveCollisions(ctx.root, '', ctx.warnings)\n\n const importsBlock =\n ctx.importLines.length > 0 ? ctx.importLines.join('\\n') + '\\n\\n' : '\\n'\n const warningsBlock =\n ctx.warnings.length > 0\n ? ' // ' + ctx.warnings.join('\\n // ') + '\\n\\n'\n : ''\n\n // Empty manifest (no routes with methods) → emit a stub interface so consumers\n // still get `client.X` autocomplete-friendly errors instead of \"Module not found\".\n const treeBody =\n ctx.root.children.size === 0 && ctx.root.methods.size === 0\n ? '{\\n // No routes detected — add `server/routes/*.ts` to populate this client.\\n }'\n : renderTreeNode(ctx.root, ' ', /* isRoot */ true)\n\n return `${FILE_HEADER}\ndeclare module '@theo/client' {\n${importsBlock}${warningsBlock} export interface AppClient ${treeBody}\n export const client: AppClient\n}\n`\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Phase 4 — Vite plugin\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface AppTypedClientPluginOptions {\n /** Project root (cwd). */\n cwd: string\n /** Absolute path to the user's `server/` dir. */\n serverDir: string\n /** Absolute path to the `.theo/` output dir (where `client.d.ts` lands). */\n distDir: string\n}\n\nconst VIRTUAL_APP_CLIENT_ID = '@theo/client'\nconst RESOLVED_APP_CLIENT_ID = '\\0@theo/client'\n\n/** Atomic write via tmp + rename (EC-6). Skips when content is unchanged. */\nfunction writeFileIfChanged(filePath: string, content: string): { changed: boolean } {\n if (existsSync(filePath)) {\n try {\n const current = readFileSync(filePath, 'utf-8')\n if (current === content) return { changed: false }\n } catch {\n // fall through to write\n }\n }\n mkdirSync(dirname(filePath), { recursive: true })\n const tmp = `${filePath}.${process.pid}.tmp`\n writeFileSync(tmp, content)\n renameSync(tmp, filePath)\n return { changed: true }\n}\n\nfunction emitClientDts(opts: AppTypedClientPluginOptions): { changed: boolean; path: string } {\n if (!existsSync(opts.serverDir)) {\n // No server dir — emit empty stub to avoid \"module not found\".\n const stubPath = posix.join(\n opts.distDir.replace(/\\\\/g, '/'),\n 'client.d.ts',\n )\n return {\n changed: writeFileIfChanged(\n stubPath,\n generateClientDts({\n manifest: {\n version: 1,\n generatedAt: new Date().toISOString(),\n routes: [],\n actions: [],\n websockets: [],\n },\n dtsOutPath: stubPath,\n serverDir: opts.serverDir,\n }),\n ).changed,\n path: stubPath,\n }\n }\n const manifest = generateManifest(opts.serverDir)\n const dtsOutPath = posix.join(opts.distDir.replace(/\\\\/g, '/'), 'client.d.ts')\n const content = generateClientDts({\n manifest,\n dtsOutPath,\n serverDir: opts.serverDir,\n })\n const { changed } = writeFileIfChanged(dtsOutPath, content)\n return { changed, path: dtsOutPath }\n}\n\nexport function appTypedClientPlugin(opts: AppTypedClientPluginOptions): Plugin {\n let debounceTimer: NodeJS.Timeout | undefined\n let viteServer: ViteDevServer | undefined\n\n function scheduleEmit(): void {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n try {\n emitClientDts(opts)\n // Invalidate the virtual module so re-imports pick up potential new shape.\n if (viteServer) {\n const mod = viteServer.moduleGraph.getModuleById(RESOLVED_APP_CLIENT_ID)\n if (mod) viteServer.moduleGraph.invalidateModule(mod)\n }\n } catch (err) {\n // Surface but don't crash dev server.\n // eslint-disable-next-line no-console\n console.error('[theokit:app-typed-client] codegen error:', err)\n }\n }, 100)\n }\n\n return {\n name: 'theokit:app-typed-client',\n enforce: 'post',\n configResolved() {\n try {\n emitClientDts(opts)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[theokit:app-typed-client] initial codegen error:', err)\n }\n },\n buildEnd() {\n try {\n emitClientDts(opts)\n } catch (err) {\n // eslint-disable-next-line no-console\n console.error('[theokit:app-typed-client] buildEnd codegen error:', err)\n }\n },\n configureServer(server) {\n viteServer = server\n const routesGlob = posix.join(opts.serverDir.replace(/\\\\/g, '/'), 'routes')\n const onFile = (file: string): void => {\n const normalized = file.replace(/\\\\/g, '/')\n if (normalized.startsWith(routesGlob)) scheduleEmit()\n }\n server.watcher.on('add', onFile)\n server.watcher.on('change', onFile)\n server.watcher.on('unlink', onFile)\n },\n resolveId(id) {\n if (id === VIRTUAL_APP_CLIENT_ID) return RESOLVED_APP_CLIENT_ID\n return null\n },\n load(id) {\n if (id === RESOLVED_APP_CLIENT_ID) {\n return [\n `import { createAppClient } from 'theokit/client'`,\n `export const client = createAppClient()`,\n `export default client`,\n ``,\n ].join('\\n')\n }\n return null\n },\n }\n}\n\nexport { VIRTUAL_APP_CLIENT_ID, RESOLVED_APP_CLIENT_ID, emitClientDts }\n"],"mappings":"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","names":[]}
@@ -2,7 +2,7 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  nodeAdapter
5
- } from "./chunk-Y5KE6YFP.js";
5
+ } from "./chunk-ZJW5FFYJ.js";
6
6
  import {
7
7
  assertServicesUnsupported,
8
8
  readManifest
@@ -170,4 +170,4 @@ export {
170
170
  renderAwsLambdaEntry,
171
171
  responseToLambdaResultV2
172
172
  };
173
- //# sourceMappingURL=aws-lambda-PONVF3UX.js.map
173
+ //# sourceMappingURL=aws-lambda-XYCGZH3I.js.map
@@ -1,24 +1,32 @@
1
1
  #!/usr/bin/env node
2
2
  import "tsx/esm";
3
3
  import {
4
- generateManifest,
5
- writeManifest as writeManifest2
6
- } from "./chunk-JZ5WEG3S.js";
4
+ emitOpenApi,
5
+ loadRoutesForOpenApi
6
+ } from "./chunk-ZOXNJV2O.js";
7
+ import {
8
+ preflightNodeAndBindings
9
+ } from "./chunk-F3TG5FJV.js";
7
10
  import {
8
11
  validateProjectStructure
9
12
  } from "./chunk-XMS5VRIK.js";
10
- import {
11
- walkSourceFiles
12
- } from "./chunk-GQSTRXXB.js";
13
13
  import {
14
14
  loadConfig,
15
15
  loadEnv
16
- } from "./chunk-S3JBIFKS.js";
17
- import "./chunk-3Q36SEDK.js";
16
+ } from "./chunk-MG7H3LAW.js";
17
+ import "./chunk-ZBWROIIO.js";
18
18
  import {
19
19
  buildManifest,
20
20
  writeManifest
21
21
  } from "./chunk-WZ7CPVQB.js";
22
+ import {
23
+ generateManifest,
24
+ writeManifest as writeManifest2
25
+ } from "./chunk-SEVYARIU.js";
26
+ import "./chunk-QLGES2HC.js";
27
+ import {
28
+ walkSourceFiles
29
+ } from "./chunk-MOT42NAH.js";
22
30
  import "./chunk-KT3MWNZG.js";
23
31
 
24
32
  // src/cli/commands/build.ts
@@ -446,6 +454,7 @@ async function cleanOutDir(opts) {
446
454
  var CRON_NA_TARGETS = /* @__PURE__ */ new Set(["bun", "netlify", "static"]);
447
455
  async function buildCommand(options) {
448
456
  const cwd = process.cwd();
457
+ preflightNodeAndBindings(cwd);
449
458
  loadEnv({ cwd, mode: "production" });
450
459
  const config = await loadConfig(cwd);
451
460
  validateProjectStructure(cwd);
@@ -487,21 +496,39 @@ async function buildCommand(options) {
487
496
  ` \u2713 Services manifest: ${String(servicesManifest.services.length)} service(s) (${servicesManifest.services.map((s) => s.name).join(", ")})`
488
497
  );
489
498
  }
499
+ if (config.openapi !== void 0) {
500
+ const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes });
501
+ const devResult = emitOpenApi({
502
+ manifest: hydrated,
503
+ config: { ...config.openapi, outDir: distDir }
504
+ });
505
+ console.log(` \u2713 OpenAPI: ${String(hydrated.length)} ops \u2192 ${devResult.path}`);
506
+ }
490
507
  await runAdapterBuild(target, config, cwd);
508
+ if (config.openapi !== void 0) {
509
+ const distOut = resolve(cwd, "dist");
510
+ const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes });
511
+ const distResult = emitOpenApi({
512
+ manifest: hydrated,
513
+ config: { ...config.openapi, outDir: distOut }
514
+ });
515
+ console.log(` \u2713 OpenAPI (dist): ${distResult.path}`);
516
+ }
491
517
  const ssrNote = config.ssr ? " (SSR)" : "";
492
518
  console.log(`
493
519
  \u2713 Build complete \u2192 ${target}${ssrNote}
494
520
  `);
495
521
  }
496
522
  async function runAdapterBuild(target, config, cwd) {
497
- const { theoPlugin } = await import("./vite-plugin-WA3G45YY.js");
523
+ const { theoPluginAsync } = await import("./vite-plugin-4WZ3C2AC.js");
498
524
  const { default: react } = await import("@vitejs/plugin-react");
499
525
  const ctx = {
500
- // `react()` may return Plugin or Plugin[] depending on version; flatten so the
501
- // contract returns a flat Plugin[] as declared in AdapterBuildContext.
502
- makeVitePlugins: (opts) => [react(), theoPlugin(opts)].flat()
526
+ // `react()` may return Plugin or Plugin[] depending on version; spread the
527
+ // async chain so the contract returns a flat Plugin[] (AdapterBuildContext
528
+ // type updated to `Plugin[] | Promise<Plugin[]>`).
529
+ makeVitePlugins: async (opts) => [react(), ...await theoPluginAsync(opts)].flat()
503
530
  };
504
- const { resolveAdapter } = await import("./registry-BB3UYAOG.js");
531
+ const { resolveAdapter } = await import("./registry-5QFTLOL2.js");
505
532
  const adapter = await resolveAdapter(target);
506
533
  await adapter.build(config, cwd, ctx);
507
534
  }
@@ -568,4 +595,4 @@ function relativize3(absPath, root) {
568
595
  export {
569
596
  buildCommand
570
597
  };
571
- //# sourceMappingURL=build-IQQYCA3B.js.map
598
+ //# sourceMappingURL=build-NCGIV4LL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/build.ts","../src/adapters/types.ts","../src/server/cron/adapter-translators.ts","../src/server/_internal/atomic-write.ts","../src/server/cron/cron-manifest.ts","../src/server/cron/cron-scan.ts","../src/server/jobs/job-manifest.ts","../src/server/jobs/job-scan.ts","../src/cli/cleanup/cleanup.ts"],"sourcesContent":["import { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\n// T1.1 (architecture-medium-deferrals) — nodeAdapter no longer static-imported.\n// All adapters dispatch via `adapterRegistry` (lazy-imported within runAdapterBuild).\nimport { VALID_TARGETS, type BuildTarget, type AdapterBuildContext } from '../../adapters/types.js'\nimport { loadConfig } from '../../config/load-config.js'\nimport { loadEnv } from '../../config/load-env.js'\nimport { validateProjectStructure } from '../../core/validate-structure.js'\nimport {\n ExistingConfigUnparseableError,\n translateCronToAws,\n translateCronToCloudflare,\n translateCronToDeno,\n translateCronToVercel,\n} from '../../server/cron/adapter-translators.js'\nimport { writeCronManifest } from '../../server/cron/cron-manifest.js'\nimport { scanCrons } from '../../server/cron/cron-scan.js'\nimport { writeJobManifest } from '../../server/jobs/job-manifest.js'\nimport { scanJobs } from '../../server/jobs/job-scan.js'\nimport { generateManifest, writeManifest } from '../../server/scan/manifest.js'\nimport {\n buildManifest as buildServicesManifest,\n writeManifest as writeServicesManifest,\n} from '../../services/index.js'\n// G2 T2.2 — OpenAPI emit. Opt-in via `config.openapi`. Dual output:\n// 1. <distDir>/openapi.json (pre-Vite, dev surface + manifests sibling)\n// 2. dist/openapi.json (post-Vite, build artifact)\n// The dist emit awaits runAdapterBuild — if Vite fails, the second emit\n// never runs (EC-2 absorbed: no stale dist artifact).\nimport { emitOpenApi } from '../../vite-plugin/openapi-emit/emit.js'\nimport { loadRoutesForOpenApi } from '../../vite-plugin/openapi-emit/load-routes.js'\nimport { cleanOutDir } from '../cleanup/cleanup.js'\nimport { preflightNodeAndBindings } from '../preflight-node-version.js'\n\n// Adapters that do NOT support cron triggers natively. Build still\n// succeeds with crons declared, but emits a warning + skip note.\nconst CRON_NA_TARGETS = new Set<BuildTarget>(['bun', 'netlify', 'static'])\n\nexport async function buildCommand(options?: { target?: string }): Promise<void> {\n const cwd = process.cwd()\n // Preflight (FIRST — BEFORE anything that touches native bindings).\n preflightNodeAndBindings(cwd)\n // Phase 1 (T1.2) — Load .env BEFORE config load.\n loadEnv({ cwd, mode: 'production' })\n\n const config = await loadConfig(cwd)\n validateProjectStructure(cwd)\n\n // T2.2 — Clean .theo/ at build start (Astro pattern). Skip .git*.\n const distDirAbs = resolve(cwd, config.distDir)\n await cleanOutDir({ dir: distDirAbs })\n\n const target = (options?.target ?? 'node') as BuildTarget\n\n if (!VALID_TARGETS.includes(target)) {\n throw new Error(\n `Invalid build target \"${target}\". Available targets: ${VALID_TARGETS.join(', ')}`,\n )\n }\n\n // EC-201 — cross-reference note when config.adapters[] diverges from\n // the --target flag. --target is authoritative per ADR D2.\n const configAdapters = (config as { adapters?: readonly string[] }).adapters\n if (configAdapters && configAdapters.length > 0) {\n const otherAdapters = configAdapters.filter((a) => a !== target)\n if (otherAdapters.length > 0) {\n console.log(\n `\\n Note: theo.config.ts.adapters lists [${otherAdapters.join(', ')}]; ` +\n `this build translates for ${target} only. ` +\n `Run \\`theokit build --target=<adapter>\\` separately for each (cross-reference).`,\n )\n }\n }\n\n console.log(`\\n Building for ${target}...\\n`)\n\n // Manifests emit BEFORE adapter bundling (Vite). Why: manifests are\n // fast + deterministic + don't depend on Vite. If Vite fails (missing\n // dep, malformed entry), the user still gets manifests for diagnostics.\n const serverDir = resolve(cwd, config.serverDir)\n const distDir = distDirAbs\n const manifest = generateManifest(serverDir)\n writeManifest(manifest, distDir)\n\n const totalEndpoints =\n manifest.routes.length + manifest.actions.length + manifest.websockets.length\n console.log(\n ` ✓ Manifest: ${manifest.routes.length} routes, ${manifest.actions.length} actions, ${manifest.websockets.length} ws (${totalEndpoints} total)`,\n )\n\n // T1.1 — cron scan + manifest + per-target adapter translation\n await emitCronArtifacts({ cwd, serverDir, distDir, target })\n\n // T1.2 — job scan + manifest (no per-target translation needed)\n await emitJobArtifacts({ cwd, serverDir, distDir })\n\n // Wave 2 (T1.2) — services manifest at <cwd>/.theo/services.json. Always\n // emit (empty array when services: {} is empty) so adapters can rely on\n // the file existing. Topological order preserved by buildServicesManifest.\n const servicesManifest = buildServicesManifest(config.services)\n writeServicesManifest(cwd, servicesManifest)\n if (servicesManifest.services.length > 0) {\n console.log(\n ` ✓ Services manifest: ${String(servicesManifest.services.length)} service(s) ` +\n `(${servicesManifest.services.map((s) => s.name).join(', ')})`,\n )\n }\n\n // G2 T2.2 — OpenAPI dev-surface emit (pre-Vite, sibling of manifests).\n // Opt-in: only when config.openapi is defined. Best-effort: a route load\n // failure produces a warning, not a build abort.\n if (config.openapi !== undefined) {\n const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes })\n const devResult = emitOpenApi({\n manifest: hydrated,\n config: { ...config.openapi, outDir: distDir },\n })\n console.log(` ✓ OpenAPI: ${String(hydrated.length)} ops → ${devResult.path}`)\n }\n\n // Now run the adapter-specific bundling (Vite + adapter-specific work).\n await runAdapterBuild(target, config, cwd)\n\n // G2 T2.2 — OpenAPI build-artifact emit (post-Vite, EC-2 gated on success).\n // If runAdapterBuild threw, execution never reaches this point — no stale\n // dist/openapi.json is written.\n if (config.openapi !== undefined) {\n const distOut = resolve(cwd, 'dist')\n const hydrated = await loadRoutesForOpenApi({ serverDir, routes: manifest.routes })\n const distResult = emitOpenApi({\n manifest: hydrated,\n config: { ...config.openapi, outDir: distOut },\n })\n console.log(` ✓ OpenAPI (dist): ${distResult.path}`)\n }\n\n const ssrNote = config.ssr ? ' (SSR)' : ''\n console.log(`\\n ✓ Build complete → ${target}${ssrNote}\\n`)\n}\n\nasync function runAdapterBuild(\n target: BuildTarget,\n config: Awaited<ReturnType<typeof loadConfig>>,\n cwd: string,\n): Promise<void> {\n // T1.1 (architecture-cleanup) — CLI composes the Vite Plugin[] and INJECTS it into\n // the adapter via ctx.makeVitePlugins. This inverts the previous `adapters → vite-plugin`\n // edge — adapters no longer import from vite-plugin/ directly.\n //\n // theoPlugin AND `@vitejs/plugin-react` are dynamically imported so the deps are\n // materialized only when needed (adapter is `node`). This keeps the CLI's startup\n // path independent of optional build-time deps (so e.g. `theokit build --target=static`\n // does not need react installed). dep-cruiser's per-module rule allows `cli → vite-plugin`.\n //\n // 0.2.2 regression fix: switched sync `theoPlugin()` → `theoPluginAsync()` so the\n // returned Plugin[] includes the @theo/actions virtual module + typed-client +\n // services + @usetheo/ui auto-chain. Without async, build-time Rollup couldn't\n // resolve `@theo/actions` even though dev-time Vite (which already used async)\n // could. See https://github.com/usetheo/theokit/commits — 0.2.2 changelog.\n const { theoPluginAsync } = await import('../../vite-plugin/index.js')\n const { default: react } = await import('@vitejs/plugin-react')\n const ctx: AdapterBuildContext = {\n // `react()` may return Plugin or Plugin[] depending on version; spread the\n // async chain so the contract returns a flat Plugin[] (AdapterBuildContext\n // type updated to `Plugin[] | Promise<Plugin[]>`).\n makeVitePlugins: async (opts) => [react(), ...(await theoPluginAsync(opts))].flat(),\n }\n\n // T1.1 (architecture-medium-deferrals, ADR D1) — Adapter Registry replaces\n // the previous 9-case switch. New adapters add 1 line in `adapters/registry.ts`;\n // CLI is closed for modification (OCP).\n const { resolveAdapter } = await import('../../adapters/registry.js')\n const adapter = await resolveAdapter(target)\n await adapter.build(config, cwd, ctx)\n}\n\nasync function emitCronArtifacts(opts: {\n cwd: string\n serverDir: string\n distDir: string\n target: BuildTarget\n}): Promise<void> {\n const cronsDir = resolve(opts.serverDir, 'crons')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- known-internal path under project's own serverDir\n const cronNodes = existsSync(cronsDir) ? await scanCrons(cronsDir) : []\n const manifestPath = resolve(opts.distDir, 'crons.json')\n writeCronManifest(manifestPath, cronNodes, opts.cwd)\n\n console.log(` ✓ Crons: ${cronNodes.length} declared`)\n\n if (cronNodes.length === 0) return\n\n // EC-201: target-specific translation. N/A targets emit a warning + skip.\n if (CRON_NA_TARGETS.has(opts.target)) {\n console.log(\n ` ⚠ Cron not supported by target \"${opts.target}\" — declared crons skipped. ` +\n `See docs/concepts/crons.md for supported targets.`,\n )\n return\n }\n\n const manifestEntries = cronNodes.map((n) => ({\n name: n.name,\n filePath: relativize(n.filePath, opts.cwd),\n schedule: n.schedule,\n concurrency: n.concurrency,\n }))\n\n try {\n switch (opts.target) {\n case 'vercel':\n translateCronToVercel(resolve(opts.cwd, 'vercel.json'), manifestEntries)\n console.log(` ✓ Cron → vercel.json crons[] (${cronNodes.length} entries)`)\n break\n case 'cloudflare':\n translateCronToCloudflare(resolve(opts.cwd, 'wrangler.toml'), manifestEntries)\n console.log(` ✓ Cron → wrangler.toml [triggers] (${cronNodes.length})`)\n break\n case 'aws-lambda':\n translateCronToAws(resolve(opts.cwd, 'serverless.yml'), manifestEntries)\n console.log(` ✓ Cron → serverless.yml functions (${cronNodes.length})`)\n break\n case 'deno-deploy':\n translateCronToDeno(resolve(opts.distDir, 'crons-entry.ts'), manifestEntries)\n console.log(` ✓ Cron → ${opts.distDir}/crons-entry.ts (Deno.cron)`)\n break\n case 'node':\n console.log(` ✓ Cron → in-process scheduler (theokit start)`)\n break\n }\n } catch (err) {\n if (err instanceof ExistingConfigUnparseableError) throw err\n throw err\n }\n}\n\nasync function emitJobArtifacts(opts: {\n cwd: string\n serverDir: string\n distDir: string\n}): Promise<void> {\n const jobsDir = resolve(opts.serverDir, 'jobs')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- known-internal path under project's own serverDir\n const jobNodes = existsSync(jobsDir) ? await scanJobs(jobsDir) : []\n const manifestPath = resolve(opts.distDir, 'jobs.json')\n writeJobManifest(manifestPath, jobNodes, opts.cwd)\n console.log(` ✓ Jobs: ${jobNodes.length} declared`)\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","import type { Plugin } from 'vite'\n\nimport type { TheoConfig } from '../config/schema.js'\n\n/**\n * Build context injected by the CLI into adapter.build.\n *\n * - `makeVitePlugins` — optional factory provided by CLI for adapters\n * that drive `viteBuild()` directly (currently: `nodeAdapter`).\n * When omitted, adapters that need Vite must fail with an actionable\n * error. This inverts the previous direct edge `adapters → vite-plugin`\n * per ADR-0001 v3 (T1.1 of the architecture-cleanup plan).\n *\n * The factory MAY return a Promise — `theokit build` uses\n * `theoPluginAsync` to wire the full Plugin[] chain (actions virtual\n * module + typed client + services + @usetheo/ui auto-chain) so adapters\n * MUST `await` the result. See `cli/commands/build.ts` for the canonical\n * invocation.\n */\nexport interface AdapterBuildContext {\n makeVitePlugins?: (opts: { root: string; ssr?: boolean }) => Plugin[] | Promise<Plugin[]>\n}\n\nexport interface DeployAdapter {\n name: string\n build(config: TheoConfig, cwd: string, ctx?: AdapterBuildContext): Promise<void>\n}\n\nexport type BuildTarget =\n | 'node'\n | 'vercel'\n | 'cloudflare'\n | 'static'\n | 'bun'\n | 'deno-deploy'\n | 'netlify'\n | 'aws-lambda'\n | 'theo-cloud'\n\nexport const VALID_TARGETS: BuildTarget[] = [\n 'node',\n 'vercel',\n 'cloudflare',\n 'static',\n 'bun',\n 'deno-deploy',\n 'netlify',\n 'aws-lambda',\n 'theo-cloud',\n]\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time adapter translators: caller-controlled paths only.\n */\nimport { existsSync, readFileSync } from 'node:fs'\n\nimport { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { CronManifestEntry } from './cron-manifest.js'\n\n/**\n * Thrown when an existing platform-config file (vercel.json, wrangler.toml,\n * serverless.yml) cannot be parsed. Caller must fix the file before\n * re-running `theokit build`. We NEVER silently overwrite a user's config.\n */\nexport class ExistingConfigUnparseableError extends Error {\n readonly code = 'EXISTING_CONFIG_UNPARSEABLE'\n constructor(\n public readonly filePath: string,\n public readonly parseError: string,\n ) {\n super(\n `Existing config \"${filePath}\" could not be parsed: ${parseError}. ` +\n 'Fix or remove the file before re-running build — TheoKit never silently overwrites user configuration.',\n )\n this.name = 'ExistingConfigUnparseableError'\n }\n}\n\n// ──────────────────────────────────────────────────────────\n// Vercel — vercel.json crons[]\n// ──────────────────────────────────────────────────────────\n\ninterface VercelJson {\n crons?: { path: string; schedule: string }[]\n [key: string]: unknown\n}\n\n/**\n * Translate a TheoKit cron manifest into `vercel.json crons[]`.\n *\n * EC-105: existing fields (functions, headers, redirects, rewrites, env,\n * etc.) are preserved verbatim — ONLY the `crons[]` slice is replaced.\n */\nexport function translateCronToVercel(\n vercelJsonPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n let existing: VercelJson = {}\n if (existsSync(vercelJsonPath)) {\n const raw = readFileSync(vercelJsonPath, 'utf8')\n try {\n existing = JSON.parse(raw) as VercelJson\n } catch (err) {\n throw new ExistingConfigUnparseableError(\n vercelJsonPath,\n err instanceof Error ? err.message : String(err),\n )\n }\n }\n const merged: VercelJson = {\n ...existing,\n crons: crons.map((c) => ({\n path: `/api/__crons/${c.name}`,\n schedule: c.schedule,\n })),\n }\n writeAtomic(vercelJsonPath, JSON.stringify(merged, null, 2))\n}\n\n// ──────────────────────────────────────────────────────────\n// Cloudflare — wrangler.toml [triggers] crons\n// ──────────────────────────────────────────────────────────\n\n/**\n * Translate a TheoKit cron manifest into `wrangler.toml [triggers] crons`.\n *\n * EC-105: regex-based mutation that preserves comments + other sections\n * verbatim. Replaces only the `[triggers]` block (or appends if absent).\n */\nexport function translateCronToCloudflare(\n wranglerTomlPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n const schedules = crons.map((c) => `\"${c.schedule}\"`).join(', ')\n const triggersBlock = `[triggers]\\ncrons = [${schedules}]\\n`\n\n if (!existsSync(wranglerTomlPath)) {\n writeAtomic(wranglerTomlPath, triggersBlock)\n return\n }\n\n const existing = readFileSync(wranglerTomlPath, 'utf8')\n\n // Replace existing [triggers] section if present. Strategy: split into\n // line array, find the [triggers] header line, drop subsequent lines\n // until the next section header (line starting with `[`) or EOF.\n const lines = existing.split('\\n')\n const triggersIdx = lines.findIndex((l) => l.trim() === '[triggers]')\n\n let next: string\n if (triggersIdx !== -1) {\n let endIdx = lines.length\n for (let i = triggersIdx + 1; i < lines.length; i++) {\n if (lines[i].trim().startsWith('[')) {\n endIdx = i\n break\n }\n }\n const before = lines.slice(0, triggersIdx).join('\\n')\n const after = lines.slice(endIdx).join('\\n')\n next = `${before}\\n${triggersBlock.trimEnd()}\\n${after}`.replace(/\\n{3,}/g, '\\n\\n')\n } else {\n next = existing.endsWith('\\n')\n ? `${existing}\\n${triggersBlock}`\n : `${existing}\\n\\n${triggersBlock}`\n }\n writeAtomic(wranglerTomlPath, next)\n}\n\n// ──────────────────────────────────────────────────────────\n// AWS Lambda — serverless.yml functions.<fn>.events: schedule\n// ──────────────────────────────────────────────────────────\n\n/**\n * Convert TheoKit's 5-field UTC cron to AWS EventBridge's 6-field format.\n * EventBridge requires `?` in EITHER day-of-month OR day-of-week (not both `*`).\n *\n * Algorithm:\n * - If DOM is \"*\" and DOW is \"*\" → insert ? in DOW (default)\n * - If DOM is \"*\" and DOW is specific → insert ? in DOM\n * - If DOM is specific and DOW is \"*\" → insert ? in DOW\n * - Append \"*\" year field at end.\n */\nexport function convertToAwsCron(schedule: string): string {\n const parts = schedule.trim().split(/\\s+/)\n if (parts.length !== 5) {\n throw new Error(`Invalid 5-field schedule for AWS conversion: \"${schedule}\"`)\n }\n const [minute, hour, dom, month, dow] = parts\n let awsDom = dom\n let awsDow = dow\n if (dom === '*' && dow === '*') {\n awsDow = '?'\n } else if (dom === '*') {\n awsDom = '?'\n } else if (dow === '*') {\n awsDow = '?'\n }\n return `cron(${minute} ${hour} ${awsDom} ${month} ${awsDow} *)`\n}\n\n/**\n * Translate a TheoKit cron manifest into a `serverless.yml` functions\n * map with `events: - schedule: cron(...)` entries.\n *\n * EC-105: appends to `functions:` block, preserving all existing\n * functions/sections. Cron functions are named `cron_<name>` to avoid\n * collision with user-declared functions.\n */\nexport function translateCronToAws(\n serverlessYmlPath: string,\n crons: readonly CronManifestEntry[],\n): void {\n const cronFunctionsYaml = crons\n .map(\n (c) => ` cron_${c.name}:\n handler: .theo/server/crons/${c.name}.handler\n events:\n - schedule: ${convertToAwsCron(c.schedule)}`,\n )\n .join('\\n')\n\n const block = `\\nfunctions:\\n${cronFunctionsYaml}\\n`\n\n if (!existsSync(serverlessYmlPath)) {\n // Minimal serverless.yml scaffold\n writeAtomic(\n serverlessYmlPath,\n `service: theokit-app\\nprovider:\\n name: aws\\n runtime: nodejs22.x\\n${block}`,\n )\n return\n }\n\n const existing = readFileSync(serverlessYmlPath, 'utf8')\n\n // Look for an existing top-level `functions:` block; if present, append our\n // cron entries under it (don't replace user-declared functions).\n const functionsRe = /^functions:\\s*\\n/m\n let next: string\n if (functionsRe.test(existing)) {\n next = existing.replace(functionsRe, (match) => `${match}${cronFunctionsYaml}\\n`)\n } else {\n next = existing.endsWith('\\n') ? `${existing}${block}` : `${existing}\\n${block}`\n }\n writeAtomic(serverlessYmlPath, next)\n}\n\n// ──────────────────────────────────────────────────────────\n// Deno Deploy — Deno.cron registrations\n// ──────────────────────────────────────────────────────────\n\n/**\n * Emit a Deno entry file that registers each cron via `Deno.cron`.\n *\n * Unlike Vercel/CF/AWS, Deno.cron is an in-process runtime API — the\n * entry file is a managed artifact (overwritten each build), not a user\n * config (so EC-105 doesn't apply here).\n */\nexport function translateCronToDeno(entryPath: string, crons: readonly CronManifestEntry[]): void {\n const lines = crons.map(\n (c) =>\n `Deno.cron(\"${c.name}\", \"${c.schedule}\", async () => {\n const mod = await import(\"../${c.filePath}\");\n const def = mod.default;\n await def.handler({\n traceId: crypto.randomUUID().replace(/-/g, \"\"),\n scheduledAt: new Date(),\n signal: AbortSignal.timeout(60_000),\n });\n});`,\n )\n const content = `// AUTOGENERATED by TheoKit cron build — DO NOT EDIT.\n// Source: server/crons/*.ts → .theo/crons.json → this file.\n${lines.join('\\n\\n')}\n`\n writeAtomic(entryPath, content)\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time atomic write: caller-controlled paths only. No HTTP input\n * reaches these fs calls.\n */\nimport { randomBytes } from 'node:crypto'\nimport { mkdirSync, renameSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\n\n/**\n * Write `content` to `path` atomically via the tmp + rename pattern.\n *\n * Two concurrent calls to `writeAtomic(path, ...)` are guaranteed to\n * leave `path` containing valid content from ONE of the calls (never\n * truncated, never interleaved). POSIX rename is atomic on the same\n * filesystem.\n *\n * EC-106 (jobs-crons-webhooks-cost-tracking-plan) — shared helper for\n * `.theo/crons.json` and `.theo/jobs.json` manifest writes so a\n * concurrent dev-server scan + build manifest emit never produces\n * partial JSON.\n *\n * @param path destination path\n * @param content bytes (UTF-8 string) to write\n */\nexport function writeAtomic(path: string, content: string): void {\n mkdirSync(dirname(path), { recursive: true })\n // Include a random suffix so two concurrent writes don't trample each\n // other's tmp file. crypto.randomBytes is non-blocking + collision-safe\n // for the tiny entropy we need (8 hex chars = 32 bits).\n const tmp = `${path}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`\n writeFileSync(tmp, content, 'utf8')\n renameSync(tmp, path)\n}\n","import { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { CronNode } from './cron-scan.js'\n\nexport const CRON_MANIFEST_SCHEMA_VERSION = 1 as const\n\nexport interface CronManifestEntry {\n readonly name: string\n readonly filePath: string\n readonly schedule: string\n readonly concurrency: 'forbid' | 'allow'\n}\n\nexport interface CronManifest {\n readonly schemaVersion: typeof CRON_MANIFEST_SCHEMA_VERSION\n readonly generatedAt: string\n readonly crons: readonly CronManifestEntry[]\n}\n\n/**\n * Build a `CronManifest` from scanned `CronNode[]`. Filepaths are kept\n * relative to the caller's project root for portability.\n */\nexport function buildCronManifest(nodes: readonly CronNode[], projectRoot?: string): CronManifest {\n const crons: CronManifestEntry[] = nodes.map((n) => ({\n name: n.name,\n filePath: projectRoot ? relativize(n.filePath, projectRoot) : n.filePath,\n schedule: n.schedule,\n concurrency: n.concurrency,\n }))\n return {\n schemaVersion: CRON_MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n crons,\n }\n}\n\n/**\n * Write the cron manifest to `path` atomically (EC-106).\n *\n * Accepts either a pre-built `CronManifest` OR an array of `CronNode[]`\n * (which gets converted via `buildCronManifest`). The atomic-write\n * helper guarantees `path` always contains valid JSON, even under\n * concurrent writes (e.g., dev-server rescan + build).\n */\nexport function writeCronManifest(\n path: string,\n input: readonly CronNode[] | CronManifest,\n projectRoot?: string,\n): void {\n const manifest: CronManifest = isManifest(input) ? input : buildCronManifest(input, projectRoot)\n writeAtomic(path, JSON.stringify(manifest, null, 2))\n}\n\nfunction isManifest(value: unknown): value is CronManifest {\n return typeof value === 'object' && value !== null && 'schemaVersion' in value && 'crons' in value\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: caller-controlled directory paths only. No HTTP\n * input ever reaches these fs calls.\n */\nimport { existsSync } from 'node:fs'\nimport { basename } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport type { CronConcurrencyPolicy, CronDefinition } from './cron-types.js'\n\n/**\n * One discovered cron from build-time scan. The handler is intentionally\n * NOT included — manifest is platform-neutral and consumed by adapters\n * that emit static config. Runtime dispatch loads the handler at fire time.\n */\nexport interface CronNode {\n readonly name: string\n readonly filePath: string\n readonly schedule: string\n readonly concurrency: CronConcurrencyPolicy\n}\n\nexport class DuplicateCronNameError extends Error {\n readonly code = 'DUPLICATE_CRON_NAME'\n constructor(\n public readonly cronName: string,\n public readonly filePaths: readonly string[],\n ) {\n super(\n `Duplicate cron name \"${cronName}\" defined in: ${filePaths.join(', ')}. ` +\n 'Cron names must be unique across server/crons/.',\n )\n this.name = 'DuplicateCronNameError'\n }\n}\n\nconst CRON_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs'])\n\ninterface CronModule {\n default?: unknown\n}\n\nfunction isCronDefinition(value: unknown): value is CronDefinition {\n if (typeof value !== 'object' || value === null) return false\n const def = value as Record<string, unknown>\n return (\n typeof def.name === 'string' &&\n typeof def.schedule === 'string' &&\n typeof def.handler === 'function' &&\n (def.concurrency === 'forbid' || def.concurrency === 'allow')\n )\n}\n\n/**\n * Scan a directory for cron definition files and return the discovered\n * `CronNode[]` sorted by name. Throws `DuplicateCronNameError` on name\n * collision and `Error` on missing default export.\n *\n * Sequential by design — module imports are awaited one-by-one to keep\n * error messages anchored to the file that failed.\n */\nexport async function scanCrons(cronsDir: string): Promise<CronNode[]> {\n if (!existsSync(cronsDir)) return []\n\n const filePaths: string[] = []\n walkSourceFiles(cronsDir, { extensions: CRON_EXTENSIONS }, (p) => {\n const base = basename(p)\n // Skip private helpers (`_helper.ts`) and OS junk (`.DS_Store`).\n if (base.startsWith('_') || base.startsWith('.')) return\n filePaths.push(p)\n })\n\n const nodes: CronNode[] = []\n for (const filePath of filePaths) {\n let mod: CronModule\n try {\n mod = (await import(pathToFileURL(filePath).href)) as CronModule\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to import cron file \"${filePath}\": ${reason}`)\n }\n const exported = mod.default\n if (!isCronDefinition(exported)) {\n throw new Error(\n `Cron file \"${filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineCron(name, { schedule, handler })`.',\n )\n }\n nodes.push({\n name: exported.name,\n filePath,\n schedule: exported.schedule,\n concurrency: exported.concurrency,\n })\n }\n\n // Dup-name detection\n const byName = new Map<string, string[]>()\n for (const node of nodes) {\n const bucket = byName.get(node.name) ?? []\n bucket.push(node.filePath)\n byName.set(node.name, bucket)\n }\n for (const [name, paths] of byName) {\n if (paths.length > 1) {\n throw new DuplicateCronNameError(name, paths)\n }\n }\n\n return nodes.sort((a, b) => a.name.localeCompare(b.name))\n}\n","import { writeAtomic } from '../_internal/atomic-write.js'\n\nimport type { JobNode } from './job-scan.js'\n\nexport const JOB_MANIFEST_SCHEMA_VERSION = 1 as const\n\nexport interface JobManifestEntry {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport interface JobManifest {\n readonly schemaVersion: typeof JOB_MANIFEST_SCHEMA_VERSION\n readonly generatedAt: string\n readonly jobs: readonly JobManifestEntry[]\n}\n\nexport function buildJobManifest(nodes: readonly JobNode[], projectRoot?: string): JobManifest {\n const jobs: JobManifestEntry[] = nodes.map((n) => ({\n name: n.name,\n filePath: projectRoot ? relativize(n.filePath, projectRoot) : n.filePath,\n maxAttempts: n.maxAttempts,\n hasInputSchema: n.hasInputSchema,\n }))\n return {\n schemaVersion: JOB_MANIFEST_SCHEMA_VERSION,\n generatedAt: new Date().toISOString(),\n jobs,\n }\n}\n\nexport function writeJobManifest(\n path: string,\n input: readonly JobNode[] | JobManifest,\n projectRoot?: string,\n): void {\n const manifest: JobManifest = isManifest(input) ? input : buildJobManifest(input, projectRoot)\n writeAtomic(path, JSON.stringify(manifest, null, 2))\n}\n\nfunction isManifest(value: unknown): value is JobManifest {\n return typeof value === 'object' && value !== null && 'schemaVersion' in value && 'jobs' in value\n}\n\nfunction relativize(absPath: string, root: string): string {\n if (absPath.startsWith(root)) {\n const trimmed = absPath.slice(root.length)\n return trimmed.startsWith('/') ? trimmed.slice(1) : trimmed\n }\n return absPath\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Build-time scanner: caller-controlled directory paths only.\n */\nimport { existsSync } from 'node:fs'\nimport { basename } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport { walkSourceFiles } from '../_internal/scan-walker.js'\n\nimport type { JobDefinition } from './job-types.js'\n\nexport interface JobNode {\n readonly name: string\n readonly filePath: string\n readonly maxAttempts: number\n readonly hasInputSchema: boolean\n}\n\nexport class DuplicateJobNameError extends Error {\n readonly code = 'DUPLICATE_JOB_NAME'\n constructor(\n public readonly jobName: string,\n public readonly filePaths: readonly string[],\n ) {\n super(\n `Duplicate job name \"${jobName}\" defined in: ${filePaths.join(', ')}. ` +\n 'Job names must be unique across server/jobs/.',\n )\n this.name = 'DuplicateJobNameError'\n }\n}\n\nconst JOB_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.mjs'])\n\ninterface JobModule {\n default?: unknown\n}\n\nfunction isJobDefinition(value: unknown): value is JobDefinition {\n if (typeof value !== 'object' || value === null) return false\n const def = value as Record<string, unknown>\n return (\n typeof def.name === 'string' &&\n typeof def.handler === 'function' &&\n typeof def.maxAttempts === 'number' &&\n typeof def.hasInputSchema === 'boolean'\n )\n}\n\nexport async function scanJobs(jobsDir: string): Promise<JobNode[]> {\n if (!existsSync(jobsDir)) return []\n\n const filePaths: string[] = []\n walkSourceFiles(jobsDir, { extensions: JOB_EXTENSIONS }, (p) => {\n const base = basename(p)\n if (base.startsWith('_') || base.startsWith('.')) return\n filePaths.push(p)\n })\n\n const nodes: JobNode[] = []\n for (const filePath of filePaths) {\n let mod: JobModule\n try {\n mod = (await import(pathToFileURL(filePath).href)) as JobModule\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err)\n throw new Error(`Failed to import job file \"${filePath}\": ${reason}`)\n }\n const exported = mod.default\n if (!isJobDefinition(exported)) {\n throw new Error(\n `Job file \"${filePath}\" is missing a valid default export. ` +\n 'Expected `export default defineJob(name, { handler })`.',\n )\n }\n nodes.push({\n name: exported.name,\n filePath,\n maxAttempts: exported.maxAttempts,\n hasInputSchema: exported.hasInputSchema,\n })\n }\n\n const byName = new Map<string, string[]>()\n for (const node of nodes) {\n const bucket = byName.get(node.name) ?? []\n bucket.push(node.filePath)\n byName.set(node.name, bucket)\n }\n for (const [name, paths] of byName) {\n if (paths.length > 1) {\n throw new DuplicateJobNameError(name, paths)\n }\n }\n\n return nodes.sort((a, b) => a.name.localeCompare(b.name))\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Cleanup helpers. Paths are caller-controlled (CLI/config); EC-3 path-safety\n * guard rejects absolute paths outside cwd to prevent catastrophic data loss.\n */\n/**\n * State cleanup utilities (Phase 2 of\n * docs/plans/framework-zero-config-polish-plan.md).\n *\n * - `cleanOutDir` — Astro pattern: empty a directory except a skip list,\n * used at `theokit build` start.\n * - `gcAgentRegistry` — Nuxt LRU pattern: delete oldest agent dirs when\n * count exceeds cap, used at `theokit dev` startup.\n *\n * EC-3 (MUST FIX, CRITICAL): cleanOutDir refuses to wipe anything outside\n * the current cwd. Prevents catastrophic `distDir: '/'` data loss.\n * EC-9 (SHOULD TEST): gcAgentRegistry handles dirs with mtime=0 (Docker overlay).\n * EC-11 (SHOULD TEST): cleanOutDir normalizes trailing-slash in skip basenames.\n * EC-12 (SHOULD TEST): cleanOutDir catches EROFS and continues.\n */\n\nimport { promises as fs } from 'node:fs'\nimport { basename, resolve as resolvePath, sep } from 'node:path'\n\nimport type {\n CleanOutDirOptions,\n CleanOutDirResult,\n GcAgentRegistryOptions,\n GcAgentRegistryResult,\n} from './cleanup-types.js'\n\nconst DEFAULT_SKIP = ['.git', '.gitkeep', '.gitignore']\n// DEFAULT_MAX_AGENTS removed in Phase 7 — gcAgentRegistry is a tombstone.\n\nfunction normalizeSkipName(name: string): string {\n // EC-11 — strip trailing slash + leading `./`\n return basename(name.replace(/\\/$/, ''))\n}\n\n/**\n * Empty a directory, preserving entries in the skip list.\n *\n * Throws if `opts.dir` is not inside `process.cwd()` (EC-3 path safety).\n * Returns `{deleted:0, kept:0}` when the directory does not exist.\n */\nexport async function cleanOutDir(opts: CleanOutDirOptions): Promise<CleanOutDirResult> {\n const resolvedDir = resolvePath(opts.dir)\n const resolvedCwd = resolvePath(process.cwd())\n\n // EC-3 — path safety guard. CRITICAL.\n if (resolvedDir === resolvedCwd) {\n throw new Error(\n `cleanOutDir refused to wipe ${resolvedDir} — must be a child of cwd, not cwd itself`,\n )\n }\n if (!resolvedDir.startsWith(resolvedCwd + sep)) {\n throw new Error(\n `cleanOutDir refused to wipe ${resolvedDir} — must be inside cwd (${resolvedCwd})`,\n )\n }\n\n const skip = new Set((opts.skip ?? DEFAULT_SKIP).map(normalizeSkipName))\n\n let entries: { name: string }[]\n try {\n entries = await fs.readdir(resolvedDir, { withFileTypes: true })\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === 'ENOENT') return { deleted: 0, kept: 0 }\n throw err\n }\n\n let deleted = 0\n let kept = 0\n for (const entry of entries) {\n const name = entry.name\n if (skip.has(name)) {\n kept++\n continue\n }\n const fullPath = resolvePath(resolvedDir, name)\n try {\n await fs.rm(fullPath, { recursive: true, force: true, maxRetries: 3 })\n deleted++\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code\n if (code === 'ENOENT') {\n deleted++ // already gone\n continue\n }\n // EC-12 — EROFS or other rm failure: warn + continue, don't crash.\n console.warn(\n `[theokit] cleanOutDir could not remove ${fullPath} (${code ?? 'unknown'}); skipping`,\n )\n kept++\n }\n }\n\n return { deleted, kept }\n}\n\n/**\n * Phase 7 — TOMBSTONE for backward-compat. The SDK v1.1.0's `Agent.registry`\n * handles GC natively; this function is a no-op + emits a deprecation warning\n * ONCE per process (EC-10).\n *\n * Will be deleted entirely in TheoKit 0.4.0. Marked deprecated via runtime\n * warning rather than `@deprecated` JSDoc tag to avoid the eslint\n * sonarjs/deprecation flagging our own internal symbols.\n */\nlet warnedOnce = false\nexport async function gcAgentRegistry(\n _opts: GcAgentRegistryOptions,\n): Promise<GcAgentRegistryResult> {\n if (!warnedOnce) {\n warnedOnce = true\n console.warn(\n '[theokit] gcAgentRegistry is deprecated; SDK Agent.registry handles GC natively (configure via theo.config.ts > agents.registry)',\n )\n }\n return Promise.resolve({ deleted: 0, kept: 0 })\n}\n\n/**\n * @internal — testing helper. Resets the module-scoped warnedOnce flag.\n */\nexport function __resetGcDeprecationWarnedForTests(): void {\n warnedOnce = false\n}\n"],"mappings":"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","names":["existsSync","existsSync","existsSync","relativize","isManifest","existsSync","basename","pathToFileURL","existsSync","basename","pathToFileURL","basename","writeManifest","existsSync","relativize"]}
@@ -0,0 +1,52 @@
1
+ import "./chunk-DGUM43GV.js";
2
+
3
+ // src/devtools/server-side/build-request-body-preview.ts
4
+ var DEFAULT_MAX_BYTES = 4096;
5
+ function buildRequestBodyPreview(body, options = {}) {
6
+ if (body === void 0 || body === null) return void 0;
7
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
8
+ if (typeof body === "string") {
9
+ return finalize(body, body.length, maxBytes);
10
+ }
11
+ if (body instanceof Uint8Array) {
12
+ return {
13
+ preview: `<binary ${String(body.byteLength)} bytes>`,
14
+ length: body.byteLength,
15
+ truncated: false
16
+ };
17
+ }
18
+ if (typeof body === "object") {
19
+ try {
20
+ const json = JSON.stringify(body, null, 2);
21
+ if (typeof json !== "string") {
22
+ return { preview: "<unserializable>", length: 0, truncated: false };
23
+ }
24
+ return finalize(json, json.length, maxBytes);
25
+ } catch {
26
+ return {
27
+ preview: "<unserializable: circular or non-JSON value>",
28
+ length: 0,
29
+ truncated: false
30
+ };
31
+ }
32
+ }
33
+ if (typeof body === "number" || typeof body === "boolean" || typeof body === "bigint") {
34
+ const s = String(body);
35
+ return finalize(s, s.length, maxBytes);
36
+ }
37
+ if (typeof body === "symbol") {
38
+ const s = body.toString();
39
+ return finalize(s, s.length, maxBytes);
40
+ }
41
+ return { preview: "<unserializable>", length: 0, truncated: false };
42
+ }
43
+ function finalize(source, totalLength, maxBytes) {
44
+ if (source.length <= maxBytes) {
45
+ return { preview: source, length: totalLength, truncated: false };
46
+ }
47
+ return { preview: source.slice(0, maxBytes), length: totalLength, truncated: true };
48
+ }
49
+ export {
50
+ buildRequestBodyPreview
51
+ };
52
+ //# sourceMappingURL=build-request-body-preview-EOP2Y4F3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/devtools/server-side/build-request-body-preview.ts"],"sourcesContent":["/**\n * Pure helper: build a devtools-safe body preview from an arbitrary\n * request body value (parsed object, raw string, Uint8Array, etc.).\n *\n * Caps preview at `maxBytes` (default 4 KB) — large bodies still\n * surface a truncated tail so the devtools tab is useful for forms\n * and small JSON; binary uploads degrade to a marker line.\n *\n * Used by `broadcastRequest` to enrich the per-request payload sent\n * to the devtools UI. Pure / side-effect free / no Node-only APIs\n * so it runs in any environment (vitest, Node, Vite SSR, deno, edge).\n *\n * NEVER use dangerouslySetInnerHTML in any devtools component — see plan EC-20.\n */\n\nconst DEFAULT_MAX_BYTES = 4096\n\nexport interface BodyPreview {\n /** Preview string (already truncated to <= maxBytes). */\n readonly preview: string\n /** Total length of the source body in bytes (or chars for strings). */\n readonly length: number\n /** Whether the preview was truncated. */\n readonly truncated: boolean\n}\n\nexport interface BuildBodyPreviewOptions {\n maxBytes?: number\n}\n\nexport function buildRequestBodyPreview(\n body: unknown,\n options: BuildBodyPreviewOptions = {},\n): BodyPreview | undefined {\n if (body === undefined || body === null) return undefined\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES\n\n if (typeof body === 'string') {\n return finalize(body, body.length, maxBytes)\n }\n\n if (body instanceof Uint8Array) {\n return {\n preview: `<binary ${String(body.byteLength)} bytes>`,\n length: body.byteLength,\n truncated: false,\n }\n }\n\n if (typeof body === 'object') {\n try {\n const json = JSON.stringify(body, null, 2)\n // JSON.stringify returns undefined for some inputs (functions / symbols\n // at top level); falsy/empty-string also unhelpful — surface a marker.\n if (typeof json !== 'string') {\n return { preview: '<unserializable>', length: 0, truncated: false }\n }\n return finalize(json, json.length, maxBytes)\n } catch {\n // Circular reference, BigInt, or other JSON.stringify failure.\n return {\n preview: '<unserializable: circular or non-JSON value>',\n length: 0,\n truncated: false,\n }\n }\n }\n\n // Primitives only: explicit dispatch keeps this pure (no Object-default\n // [object Object] stringification surprises) and lint-clean.\n if (typeof body === 'number' || typeof body === 'boolean' || typeof body === 'bigint') {\n const s = String(body)\n return finalize(s, s.length, maxBytes)\n }\n if (typeof body === 'symbol') {\n const s = body.toString()\n return finalize(s, s.length, maxBytes)\n }\n // function or any remaining exotic — surface a marker without stringifying.\n return { preview: '<unserializable>', length: 0, truncated: false }\n}\n\nfunction finalize(source: string, totalLength: number, maxBytes: number): BodyPreview {\n if (source.length <= maxBytes) {\n return { preview: source, length: totalLength, truncated: false }\n }\n return { preview: source.slice(0, maxBytes), length: totalLength, truncated: true }\n}\n"],"mappings":";;;AAeA,IAAM,oBAAoB;AAenB,SAAS,wBACd,MACA,UAAmC,CAAC,GACX;AACzB,MAAI,SAAS,UAAa,SAAS,KAAM,QAAO;AAChD,QAAM,WAAW,QAAQ,YAAY;AAErC,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,SAAS,MAAM,KAAK,QAAQ,QAAQ;AAAA,EAC7C;AAEA,MAAI,gBAAgB,YAAY;AAC9B,WAAO;AAAA,MACL,SAAS,WAAW,OAAO,KAAK,UAAU,CAAC;AAAA,MAC3C,QAAQ,KAAK;AAAA,MACb,WAAW;AAAA,IACb;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,UAAU;AAC5B,QAAI;AACF,YAAM,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AAGzC,UAAI,OAAO,SAAS,UAAU;AAC5B,eAAO,EAAE,SAAS,oBAAoB,QAAQ,GAAG,WAAW,MAAM;AAAA,MACpE;AACA,aAAO,SAAS,MAAM,KAAK,QAAQ,QAAQ;AAAA,IAC7C,QAAQ;AAEN,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAIA,MAAI,OAAO,SAAS,YAAY,OAAO,SAAS,aAAa,OAAO,SAAS,UAAU;AACrF,UAAM,IAAI,OAAO,IAAI;AACrB,WAAO,SAAS,GAAG,EAAE,QAAQ,QAAQ;AAAA,EACvC;AACA,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,IAAI,KAAK,SAAS;AACxB,WAAO,SAAS,GAAG,EAAE,QAAQ,QAAQ;AAAA,EACvC;AAEA,SAAO,EAAE,SAAS,oBAAoB,QAAQ,GAAG,WAAW,MAAM;AACpE;AAEA,SAAS,SAAS,QAAgB,aAAqB,UAA+B;AACpF,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,QAAQ,QAAQ,aAAa,WAAW,MAAM;AAAA,EAClE;AACA,SAAO,EAAE,SAAS,OAAO,MAAM,GAAG,QAAQ,GAAG,QAAQ,aAAa,WAAW,KAAK;AACpF;","names":[]}
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import "tsx/esm";
3
+ import "./chunk-KT3MWNZG.js";
4
+
5
+ // src/devtools/server-side/build-request-body-preview.ts
6
+ var DEFAULT_MAX_BYTES = 4096;
7
+ function buildRequestBodyPreview(body, options = {}) {
8
+ if (body === void 0 || body === null) return void 0;
9
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
10
+ if (typeof body === "string") {
11
+ return finalize(body, body.length, maxBytes);
12
+ }
13
+ if (body instanceof Uint8Array) {
14
+ return {
15
+ preview: `<binary ${String(body.byteLength)} bytes>`,
16
+ length: body.byteLength,
17
+ truncated: false
18
+ };
19
+ }
20
+ if (typeof body === "object") {
21
+ try {
22
+ const json = JSON.stringify(body, null, 2);
23
+ if (typeof json !== "string") {
24
+ return { preview: "<unserializable>", length: 0, truncated: false };
25
+ }
26
+ return finalize(json, json.length, maxBytes);
27
+ } catch {
28
+ return {
29
+ preview: "<unserializable: circular or non-JSON value>",
30
+ length: 0,
31
+ truncated: false
32
+ };
33
+ }
34
+ }
35
+ if (typeof body === "number" || typeof body === "boolean" || typeof body === "bigint") {
36
+ const s = String(body);
37
+ return finalize(s, s.length, maxBytes);
38
+ }
39
+ if (typeof body === "symbol") {
40
+ const s = body.toString();
41
+ return finalize(s, s.length, maxBytes);
42
+ }
43
+ return { preview: "<unserializable>", length: 0, truncated: false };
44
+ }
45
+ function finalize(source, totalLength, maxBytes) {
46
+ if (source.length <= maxBytes) {
47
+ return { preview: source, length: totalLength, truncated: false };
48
+ }
49
+ return { preview: source.slice(0, maxBytes), length: totalLength, truncated: true };
50
+ }
51
+ export {
52
+ buildRequestBodyPreview
53
+ };
54
+ //# sourceMappingURL=build-request-body-preview-II2235E6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/devtools/server-side/build-request-body-preview.ts"],"sourcesContent":["/**\n * Pure helper: build a devtools-safe body preview from an arbitrary\n * request body value (parsed object, raw string, Uint8Array, etc.).\n *\n * Caps preview at `maxBytes` (default 4 KB) — large bodies still\n * surface a truncated tail so the devtools tab is useful for forms\n * and small JSON; binary uploads degrade to a marker line.\n *\n * Used by `broadcastRequest` to enrich the per-request payload sent\n * to the devtools UI. Pure / side-effect free / no Node-only APIs\n * so it runs in any environment (vitest, Node, Vite SSR, deno, edge).\n *\n * NEVER use dangerouslySetInnerHTML in any devtools component — see plan EC-20.\n */\n\nconst DEFAULT_MAX_BYTES = 4096\n\nexport interface BodyPreview {\n /** Preview string (already truncated to <= maxBytes). */\n readonly preview: string\n /** Total length of the source body in bytes (or chars for strings). */\n readonly length: number\n /** Whether the preview was truncated. */\n readonly truncated: boolean\n}\n\nexport interface BuildBodyPreviewOptions {\n maxBytes?: number\n}\n\nexport function buildRequestBodyPreview(\n body: unknown,\n options: BuildBodyPreviewOptions = {},\n): BodyPreview | undefined {\n if (body === undefined || body === null) return undefined\n const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES\n\n if (typeof body === 'string') {\n return finalize(body, body.length, maxBytes)\n }\n\n if (body instanceof Uint8Array) {\n return {\n preview: `<binary ${String(body.byteLength)} bytes>`,\n length: body.byteLength,\n truncated: false,\n }\n }\n\n if (typeof body === 'object') {\n try {\n const json = JSON.stringify(body, null, 2)\n // JSON.stringify returns undefined for some inputs (functions / symbols\n // at top level); falsy/empty-string also unhelpful — surface a marker.\n if (typeof json !== 'string') {\n return { preview: '<unserializable>', length: 0, truncated: false }\n }\n return finalize(json, json.length, maxBytes)\n } catch {\n // Circular reference, BigInt, or other JSON.stringify failure.\n return {\n preview: '<unserializable: circular or non-JSON value>',\n length: 0,\n truncated: false,\n }\n }\n }\n\n // Primitives only: explicit dispatch keeps this pure (no Object-default\n // [object Object] stringification surprises) and lint-clean.\n if (typeof body === 'number' || typeof body === 'boolean' || typeof body === 'bigint') {\n const s = String(body)\n return finalize(s, s.length, maxBytes)\n }\n if (typeof body === 'symbol') {\n const s = body.toString()\n return finalize(s, s.length, maxBytes)\n }\n // function or any remaining exotic — surface a marker without stringifying.\n return { preview: '<unserializable>', length: 0, truncated: false }\n}\n\nfunction finalize(source: string, totalLength: number, maxBytes: number): BodyPreview {\n if (source.length <= maxBytes) {\n return { preview: source, length: totalLength, truncated: false }\n }\n return { preview: source.slice(0, maxBytes), length: totalLength, truncated: true }\n}\n"],"mappings":";;;;;AAeA,IAAM,oBAAoB;AAenB,SAAS,wBACd,MACA,UAAmC,CAAC,GACX;AACzB,MAAI,SAAS,UAAa,SAAS,KAAM,QAAO;AAChD,QAAM,WAAW,QAAQ,YAAY;AAErC,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,SAAS,MAAM,KAAK,QAAQ,QAAQ;AAAA,EAC7C;AAEA,MAAI,gBAAgB,YAAY;AAC9B,WAAO;AAAA,MACL,SAAS,WAAW,OAAO,KAAK,UAAU,CAAC;AAAA,MAC3C,QAAQ,KAAK;AAAA,MACb,WAAW;AAAA,IACb;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,UAAU;AAC5B,QAAI;AACF,YAAM,OAAO,KAAK,UAAU,MAAM,MAAM,CAAC;AAGzC,UAAI,OAAO,SAAS,UAAU;AAC5B,eAAO,EAAE,SAAS,oBAAoB,QAAQ,GAAG,WAAW,MAAM;AAAA,MACpE;AACA,aAAO,SAAS,MAAM,KAAK,QAAQ,QAAQ;AAAA,IAC7C,QAAQ;AAEN,aAAO;AAAA,QACL,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,WAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAIA,MAAI,OAAO,SAAS,YAAY,OAAO,SAAS,aAAa,OAAO,SAAS,UAAU;AACrF,UAAM,IAAI,OAAO,IAAI;AACrB,WAAO,SAAS,GAAG,EAAE,QAAQ,QAAQ;AAAA,EACvC;AACA,MAAI,OAAO,SAAS,UAAU;AAC5B,UAAM,IAAI,KAAK,SAAS;AACxB,WAAO,SAAS,GAAG,EAAE,QAAQ,QAAQ;AAAA,EACvC;AAEA,SAAO,EAAE,SAAS,oBAAoB,QAAQ,GAAG,WAAW,MAAM;AACpE;AAEA,SAAS,SAAS,QAAgB,aAAqB,UAA+B;AACpF,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,QAAQ,QAAQ,aAAa,WAAW,MAAM;AAAA,EAClE;AACA,SAAO,EAAE,SAAS,OAAO,MAAM,GAAG,QAAQ,GAAG,QAAQ,aAAa,WAAW,KAAK;AACpF;","names":[]}
@@ -2,7 +2,7 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  nodeAdapter
5
- } from "./chunk-Y5KE6YFP.js";
5
+ } from "./chunk-ZJW5FFYJ.js";
6
6
  import {
7
7
  assertServicesUnsupported,
8
8
  readManifest
@@ -137,4 +137,4 @@ export {
137
137
  bunAdapter,
138
138
  renderBunEntry
139
139
  };
140
- //# sourceMappingURL=bun-DITMZMKL.js.map
140
+ //# sourceMappingURL=bun-K73TQEWC.js.map
@@ -1,57 +1,9 @@
1
- #!/usr/bin/env node
2
- import "tsx/esm";
3
-
4
- // src/server/scan/action-scan.ts
5
- import { existsSync, statSync } from "fs";
6
- import { extname as extname2, join as join2, relative } from "path";
7
-
8
- // src/server/_internal/scan-walker.ts
9
- import { readdirSync } from "fs";
10
- import { extname, join, resolve } from "path";
11
- function walkSourceFiles(root, opts, onFile) {
12
- const skipPrefixes = opts.skipPrefixes ?? ["_", "."];
13
- const visit = (dir) => {
14
- let entries;
15
- try {
16
- entries = readdirSync(dir, { withFileTypes: true });
17
- } catch {
18
- return;
19
- }
20
- for (const entry of entries) {
21
- const fullPath = join(dir, entry.name);
22
- if (entry.isDirectory() && !skipPrefixes.some((p) => entry.name.startsWith(p))) {
23
- visit(fullPath);
24
- } else if (entry.isFile() && opts.extensions.has(extname(entry.name))) {
25
- onFile(resolve(fullPath));
26
- }
27
- }
28
- };
29
- visit(root);
30
- }
31
-
32
- // src/server/scan/action-scan.ts
33
- var ACTION_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
34
- function scanServerActions(serverDir) {
35
- const actionsDir = join2(serverDir, "actions");
36
- if (!existsSync(actionsDir) || !statSync(actionsDir).isDirectory()) {
37
- return [];
38
- }
39
- const results = [];
40
- walkSourceFiles(actionsDir, { extensions: ACTION_EXTENSIONS }, (absPath) => {
41
- let rel = relative(actionsDir, absPath);
42
- rel = rel.replace(/\\/g, "/");
43
- rel = rel.slice(0, -extname2(rel).length);
44
- results.push({
45
- filePath: absPath,
46
- actionPath: rel
47
- });
48
- });
49
- return results;
50
- }
51
-
52
- // src/server/scan/scan.ts
53
- import { existsSync as existsSync2, statSync as statSync2 } from "fs";
54
- import { extname as extname3, join as join3, relative as relative2 } from "path";
1
+ import {
2
+ walkSourceFiles
3
+ } from "./chunk-X2VVCJ4V.js";
4
+ import {
5
+ HTTP_METHODS
6
+ } from "./chunk-WSJKACWB.js";
55
7
 
56
8
  // src/server/scan/match.ts
57
9
  function compilePattern(routePath) {
@@ -80,11 +32,68 @@ function matchRoute(url, routes) {
80
32
  return null;
81
33
  }
82
34
 
35
+ // src/server/scan/scan.ts
36
+ import { existsSync, statSync } from "fs";
37
+ import { extname, join, relative } from "path";
38
+
39
+ // src/server/scan/detect-http-methods.ts
40
+ import { readFileSync } from "fs";
41
+ import { createRequire } from "module";
42
+ var require_ = createRequire(import.meta.url);
43
+ var ts = require_("typescript");
44
+ var HTTP_METHOD_NAMES = new Set(HTTP_METHODS);
45
+ function hasExportModifier(modifiers) {
46
+ if (!modifiers) return false;
47
+ for (const m of modifiers) {
48
+ if (m.kind === ts.SyntaxKind.ExportKeyword) return true;
49
+ }
50
+ return false;
51
+ }
52
+ function collectFromStatement(stmt, found) {
53
+ if (ts.isVariableStatement(stmt) && hasExportModifier(ts.getModifiers(stmt))) {
54
+ for (const decl of stmt.declarationList.declarations) {
55
+ if (ts.isIdentifier(decl.name) && HTTP_METHOD_NAMES.has(decl.name.text)) {
56
+ found.add(decl.name.text);
57
+ }
58
+ }
59
+ return;
60
+ }
61
+ if ((ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) && hasExportModifier(ts.getModifiers(stmt))) {
62
+ if (stmt.name && HTTP_METHOD_NAMES.has(stmt.name.text)) {
63
+ found.add(stmt.name.text);
64
+ }
65
+ return;
66
+ }
67
+ if (ts.isExportDeclaration(stmt) && stmt.exportClause && ts.isNamedExports(stmt.exportClause)) {
68
+ for (const spec of stmt.exportClause.elements) {
69
+ if (HTTP_METHOD_NAMES.has(spec.name.text)) {
70
+ found.add(spec.name.text);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ function detectExportedHttpMethods(filePath, content) {
76
+ const src = content ?? readFileSync(filePath, "utf-8");
77
+ const sourceFile = ts.createSourceFile(
78
+ filePath,
79
+ src,
80
+ ts.ScriptTarget.Latest,
81
+ /* setParentNodes */
82
+ false,
83
+ ts.ScriptKind.TS
84
+ );
85
+ const found = /* @__PURE__ */ new Set();
86
+ for (const stmt of sourceFile.statements) {
87
+ collectFromStatement(stmt, found);
88
+ }
89
+ return [...found].sort();
90
+ }
91
+
83
92
  // src/server/scan/scan.ts
84
93
  var ROUTE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
85
94
  function fileToRoutePath(filePath, routesDir) {
86
- let rel = relative2(routesDir, filePath);
87
- const ext = extname3(rel);
95
+ let rel = relative(routesDir, filePath);
96
+ const ext = extname(rel);
88
97
  rel = rel.slice(0, -ext.length);
89
98
  rel = rel.replace(/\\/g, "/");
90
99
  if (rel.endsWith("/index")) {
@@ -97,19 +106,21 @@ function fileToRoutePath(filePath, routesDir) {
97
106
  return `/api/${rel}`;
98
107
  }
99
108
  function scanServerRoutes(serverDir) {
100
- const routesDir = join3(serverDir, "routes");
101
- if (!existsSync2(routesDir) || !statSync2(routesDir).isDirectory()) {
109
+ const routesDir = join(serverDir, "routes");
110
+ if (!existsSync(routesDir) || !statSync(routesDir).isDirectory()) {
102
111
  return [];
103
112
  }
104
113
  const results = [];
105
114
  walkSourceFiles(routesDir, { extensions: ROUTE_EXTENSIONS }, (absPath) => {
106
115
  const routePath = fileToRoutePath(absPath, routesDir);
107
116
  const { pattern, paramNames } = compilePattern(routePath);
117
+ const methods = detectExportedHttpMethods(absPath);
108
118
  results.push({
109
119
  filePath: absPath,
110
120
  routePath,
111
121
  paramNames,
112
- pattern
122
+ pattern,
123
+ methods
113
124
  });
114
125
  });
115
126
  const conflicting = results.find((r) => r.routePath === "/api/__theo_batch__");
@@ -134,19 +145,19 @@ function scanServerRoutes(serverDir) {
134
145
  }
135
146
 
136
147
  // src/server/scan/ws-scan.ts
137
- import { existsSync as existsSync3, statSync as statSync3 } from "fs";
138
- import { extname as extname4, join as join4, relative as relative3 } from "path";
148
+ import { existsSync as existsSync2, statSync as statSync2 } from "fs";
149
+ import { extname as extname2, join as join2, relative as relative2 } from "path";
139
150
  var WS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
140
151
  function scanWebSocketRoutes(serverDir) {
141
- const wsDir = join4(serverDir, "ws");
142
- if (!existsSync3(wsDir) || !statSync3(wsDir).isDirectory()) {
152
+ const wsDir = join2(serverDir, "ws");
153
+ if (!existsSync2(wsDir) || !statSync2(wsDir).isDirectory()) {
143
154
  return [];
144
155
  }
145
156
  const results = [];
146
157
  walkSourceFiles(wsDir, { extensions: WS_EXTENSIONS }, (absPath) => {
147
- let rel = relative3(wsDir, absPath);
158
+ let rel = relative2(wsDir, absPath);
148
159
  rel = rel.replace(/\\/g, "/");
149
- rel = rel.slice(0, -extname4(rel).length);
160
+ rel = rel.slice(0, -extname2(rel).length);
150
161
  if (rel.endsWith("/index")) rel = rel.slice(0, -6);
151
162
  else if (rel === "index") rel = "";
152
163
  results.push({
@@ -158,11 +169,9 @@ function scanWebSocketRoutes(serverDir) {
158
169
  }
159
170
 
160
171
  export {
161
- walkSourceFiles,
162
- scanServerActions,
163
172
  compilePattern,
164
173
  matchRoute,
165
174
  scanServerRoutes,
166
175
  scanWebSocketRoutes
167
176
  };
168
- //# sourceMappingURL=chunk-GQSTRXXB.js.map
177
+ //# sourceMappingURL=chunk-2BQYEBCK.js.map