theokit 0.48.2 → 0.48.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +2 -2
- package/dist/{actions-virtual-module-2VVLVXHY.js → actions-virtual-module-GB5FWUPS.js} +6 -6
- package/dist/{actions-virtual-module-Z5LXAX7E.js → actions-virtual-module-YQXRSSMW.js} +2 -2
- package/dist/add-3VLFUPF4.js +0 -0
- package/dist/{agent-Y3JKRROD.js → agent-OAFIXOR5.js} +2 -2
- package/dist/{agents-typed-client-X56CQ6FX.js → agents-typed-client-D4OA2TXB.js} +12 -4
- package/dist/agents-typed-client-D4OA2TXB.js.map +1 -0
- package/dist/{agents-typed-client-TNHCUU7Q.js → agents-typed-client-INR2E7B3.js} +12 -4
- package/dist/agents-typed-client-INR2E7B3.js.map +1 -0
- package/dist/{app-typed-client-N4I4RBLT.js → app-typed-client-5QDNHANF.js} +6 -6
- package/dist/{app-typed-client-NI3CM2FA.js → app-typed-client-GHO3EWZK.js} +2 -2
- package/dist/{aws-lambda-7SUTORXM.js → aws-lambda-CYNDGU5O.js} +3 -3
- package/dist/body-parser-web-TIOTXPDW.js +0 -0
- package/dist/broadcast-LUMOJIJT.js +0 -0
- package/dist/{build-SJQDRD2B.js → build-QYEU5OOZ.js} +13 -12
- package/dist/build-QYEU5OOZ.js.map +1 -0
- package/dist/build-request-body-preview-XPZ2GYMG.js +0 -0
- package/dist/{bun-TLYZCEGE.js → bun-33RD6K7O.js} +3 -3
- package/dist/check-45NKUVSQ.js +0 -0
- package/dist/chunk-2CVV6CNN.js +0 -0
- package/dist/{chunk-ORKNVORZ.js → chunk-47CINLWF.js} +2 -2
- package/dist/chunk-47VNWTL4.js +0 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/chunk-6GGWS7J3.js +0 -0
- package/dist/{chunk-O5W7XSUX.js → chunk-6PWNTTPI.js} +4 -4
- package/dist/{chunk-GEHX6CAE.js → chunk-6RJSGTMN.js} +3 -3
- package/dist/{chunk-GEHX6CAE.js.map → chunk-6RJSGTMN.js.map} +1 -1
- package/dist/{chunk-HRCNHWLP.js → chunk-7CQ6DEWZ.js} +2 -2
- package/dist/{chunk-YRRWQDKK.js → chunk-7QAYC757.js} +89 -14
- package/dist/chunk-7QAYC757.js.map +1 -0
- package/dist/{chunk-CUYEA62B.js → chunk-7VPQHEAJ.js} +2 -4
- package/dist/chunk-7VPQHEAJ.js.map +1 -0
- package/dist/{chunk-5JJCNEUS.js → chunk-A4ZEP3FE.js} +17 -8
- package/dist/chunk-A4ZEP3FE.js.map +1 -0
- package/dist/chunk-A7JBF5FO.js +0 -0
- package/dist/{chunk-MNIQ5F3Y.js → chunk-BMHS4WAN.js} +1 -1
- package/dist/{chunk-MNIQ5F3Y.js.map → chunk-BMHS4WAN.js.map} +1 -1
- package/dist/{chunk-DGQO372T.js → chunk-E576KYYT.js} +3 -3
- package/dist/chunk-E576KYYT.js.map +1 -0
- package/dist/{chunk-L5Z6IY5U.js → chunk-FNTLKHZ7.js} +1 -1
- package/dist/{chunk-L5Z6IY5U.js.map → chunk-FNTLKHZ7.js.map} +1 -1
- package/dist/chunk-GEQJAFSV.js +0 -0
- package/dist/{chunk-AOGYAXPY.js → chunk-GH32R55S.js} +2 -2
- package/dist/{chunk-Y2KHTD2W.js → chunk-GQNBZDYK.js} +1 -1
- package/dist/{chunk-Y2KHTD2W.js.map → chunk-GQNBZDYK.js.map} +1 -1
- package/dist/{chunk-VTJMND23.js → chunk-H6YBKIUN.js} +2 -2
- package/dist/chunk-HBU2GIMZ.js +0 -0
- package/dist/chunk-IEES3CHD.js +0 -0
- package/dist/{chunk-62EU2BVZ.js → chunk-J5MXCGKA.js} +1 -1
- package/dist/{chunk-62EU2BVZ.js.map → chunk-J5MXCGKA.js.map} +1 -1
- package/dist/{chunk-HNBWZKIQ.js → chunk-KTMQUYED.js} +1 -1
- package/dist/{chunk-HNBWZKIQ.js.map → chunk-KTMQUYED.js.map} +1 -1
- package/dist/chunk-LNVMHQ2Z.js +0 -0
- package/dist/{chunk-2ADKT37V.js → chunk-LXAESF4Y.js} +2 -2
- package/dist/{chunk-2ADKT37V.js.map → chunk-LXAESF4Y.js.map} +1 -1
- package/dist/{chunk-FXZZS7YW.js → chunk-T5C46IIU.js} +1 -1
- package/dist/chunk-T5C46IIU.js.map +1 -0
- package/dist/{chunk-ZALJIZH2.js → chunk-VQ62FIQW.js} +80 -16
- package/dist/chunk-VQ62FIQW.js.map +1 -0
- package/dist/chunk-VQIUTCK2.js +0 -0
- package/dist/{chunk-FO55VGMQ.js → chunk-XR74JD4M.js} +1 -1
- package/dist/{chunk-FO55VGMQ.js.map → chunk-XR74JD4M.js.map} +1 -1
- package/dist/{chunk-KSZGBUTI.js → chunk-YGAXV5HR.js} +1 -1
- package/dist/{chunk-KSZGBUTI.js.map → chunk-YGAXV5HR.js.map} +1 -1
- package/dist/{chunk-D3425YDV.js → chunk-ZB7D62OA.js} +2 -2
- package/dist/cli/index.js +14 -14
- package/dist/cli/index.js.map +1 -1
- package/dist/{cloudflare-JTBKOX3F.js → cloudflare-KYUWCEPL.js} +3 -3
- package/dist/configure-agent-registry-ZOBVU3MV.js +0 -0
- package/dist/controller-swc-transform-LUJ4VAZJ.js +0 -0
- package/dist/db-IVVCQE5Z.js +0 -0
- package/dist/{define-websocket-CPQcQK9h.d.ts → define-websocket-DzPn1sR6.d.ts} +0 -1
- package/dist/{deno-deploy-3ESNGYZU.js → deno-deploy-CCTB7HGS.js} +3 -3
- package/dist/{dev-6TWDBX6G.js → dev-5BKIKJYI.js} +12 -12
- package/dist/{dev-emit-7ZZRTYF3.js → dev-emit-GWD6IXV7.js} +3 -3
- package/dist/{dev-emit-E5Z26AMV.js → dev-emit-NBT6JD7R.js} +2 -2
- package/dist/dispatcher-KMZBGMM5.js +0 -0
- package/dist/docker-LZZB4D5E.js +0 -0
- package/dist/doctor-Y53QTUSP.js +0 -0
- package/dist/{generate-WQSNW4F6.js → generate-Y3GWM57K.js} +2 -2
- package/dist/generate-Y3GWM57K.js.map +1 -0
- package/dist/index.d.ts +36 -8
- package/dist/index.js +3 -3
- package/dist/{info-QB7Q3SHP.js → info-MSBQ72EM.js} +4 -4
- package/dist/{internal-api-XQA2L663.js → internal-api-ICU7ITJD.js} +6 -6
- package/dist/{internal-api-REGN3WMX.js → internal-api-WEOSTMSN.js} +2 -2
- package/dist/{load-config-JKJ73TOD.js → load-config-2LP2OEKD.js} +4 -4
- package/dist/{mcp-CO4AGAXV.js → mcp-4FNNCFPM.js} +3 -3
- package/dist/{netlify-OFJUYXTZ.js → netlify-AFMDNKT4.js} +3 -3
- package/dist/node-ZJQEJSP3.js +10 -0
- package/dist/{openapi-5CZCL7YP.js → openapi-FWNYHB6T.js} +10 -10
- package/dist/{openapi-5CZCL7YP.js.map → openapi-FWNYHB6T.js.map} +1 -1
- package/dist/registry-UJ6KNQPJ.js +23 -0
- package/dist/{router-6MAACS25.js → router-V64GKAS6.js} +1 -1
- package/dist/router-V64GKAS6.js.map +1 -0
- package/dist/{routes-GWAWUVVQ.js → routes-6AMNVFMI.js} +5 -5
- package/dist/scan-7MJC6PYU.js +0 -0
- package/dist/{schema-BpH6ivDY.d.ts → schema-BdakLtuC.d.ts} +1 -1
- package/dist/{schema-2JEG6TTU.js → schema-Z3Y7SFLE.js} +3 -7
- package/dist/server/agent/index.d.ts +1 -1
- package/dist/server/cost/index.d.ts +0 -3
- package/dist/server/cost/index.js +1 -1
- package/dist/server/cron/index.d.ts +0 -4
- package/dist/server/cron/index.js +1 -1
- package/dist/server/define/index.d.ts +2 -3
- package/dist/server/define/index.js +1 -1
- package/dist/server/index.d.ts +4 -6
- package/dist/server/index.js +6 -6
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +1 -4
- package/dist/server/jobs/index.js +1 -1
- package/dist/server/realtime/index.d.ts +1 -1
- package/dist/server/scan/index.js +1 -1
- package/dist/server/storage/index.d.ts +1 -1
- package/dist/server/webhook/index.d.ts +2 -2
- package/dist/server-error-to-envelope-4TGCDQ2S.js +0 -0
- package/dist/{server-routes-hmr-4W5L2MKN.js → server-routes-hmr-5D2ZPL74.js} +1 -1
- package/dist/server-routes-hmr-5D2ZPL74.js.map +1 -0
- package/dist/{server-routes-hmr-6CRNSEWF.js → server-routes-hmr-BWN3WIGR.js} +1 -1
- package/dist/{server-routes-hmr-6CRNSEWF.js.map → server-routes-hmr-BWN3WIGR.js.map} +1 -1
- package/dist/services-json-7ZXJ4CIS.js +0 -0
- package/dist/{services-typed-client-II7LKXKO.js → services-typed-client-PSAGRPFF.js} +2 -2
- package/dist/{sessions-gc-H4WC4IXB.js → sessions-gc-VAZR654G.js} +3 -3
- package/dist/sessions-gc-VAZR654G.js.map +1 -0
- package/dist/{start-IO3ZYGEX.js → start-QDPYXPW2.js} +14 -14
- package/dist/start-QDPYXPW2.js.map +1 -0
- package/dist/{static-HTT2WNIT.js → static-R25HSXEF.js} +3 -3
- package/dist/{storage-manager-ZSQFLEYT.js → storage-manager-3O5PDO6O.js} +1 -1
- package/dist/storage-manager-3O5PDO6O.js.map +1 -0
- package/dist/{storage-manager-C4jsO0Tp.d.ts → storage-manager-C6hmsaer.d.ts} +1 -1
- package/dist/{theo-cloud-ZTQLXGPS.js → theo-cloud-XAHWOUFK.js} +2 -2
- package/dist/{upgrade-readiness-77P6VNB4.js → upgrade-readiness-OILJRFOV.js} +1 -1
- package/dist/{upgrade-readiness-77P6VNB4.js.map → upgrade-readiness-OILJRFOV.js.map} +1 -1
- package/dist/{vercel-72HFEMJ3.js → vercel-SRS2DH6O.js} +3 -3
- package/dist/vite-plugin/index.d.ts +1 -1
- package/dist/vite-plugin/index.js +3 -3
- package/dist/{vite-plugin-NQ6F7FGV.js → vite-plugin-YOCPU4C6.js} +11 -11
- package/dist/{webhook-types-CNUZY7o1.d.ts → webhook-types-DGIjjcaI.d.ts} +0 -2
- package/package.json +11 -10
- package/dist/agents-typed-client-TNHCUU7Q.js.map +0 -1
- package/dist/agents-typed-client-X56CQ6FX.js.map +0 -1
- package/dist/build-SJQDRD2B.js.map +0 -1
- package/dist/chunk-5JJCNEUS.js.map +0 -1
- package/dist/chunk-CUYEA62B.js.map +0 -1
- package/dist/chunk-DGQO372T.js.map +0 -1
- package/dist/chunk-FXZZS7YW.js.map +0 -1
- package/dist/chunk-YRRWQDKK.js.map +0 -1
- package/dist/chunk-ZALJIZH2.js.map +0 -1
- package/dist/generate-WQSNW4F6.js.map +0 -1
- package/dist/node-ND4MJU3Z.js +0 -10
- package/dist/registry-QTSMLOVG.js +0 -23
- package/dist/router-6MAACS25.js.map +0 -1
- package/dist/server-routes-hmr-4W5L2MKN.js.map +0 -1
- package/dist/sessions-gc-H4WC4IXB.js.map +0 -1
- package/dist/start-IO3ZYGEX.js.map +0 -1
- package/dist/storage-manager-ZSQFLEYT.js.map +0 -1
- /package/dist/{actions-virtual-module-2VVLVXHY.js.map → actions-virtual-module-GB5FWUPS.js.map} +0 -0
- /package/dist/{actions-virtual-module-Z5LXAX7E.js.map → actions-virtual-module-YQXRSSMW.js.map} +0 -0
- /package/dist/{agent-Y3JKRROD.js.map → agent-OAFIXOR5.js.map} +0 -0
- /package/dist/{app-typed-client-N4I4RBLT.js.map → app-typed-client-5QDNHANF.js.map} +0 -0
- /package/dist/{app-typed-client-NI3CM2FA.js.map → app-typed-client-GHO3EWZK.js.map} +0 -0
- /package/dist/{aws-lambda-7SUTORXM.js.map → aws-lambda-CYNDGU5O.js.map} +0 -0
- /package/dist/{bun-TLYZCEGE.js.map → bun-33RD6K7O.js.map} +0 -0
- /package/dist/{chunk-ORKNVORZ.js.map → chunk-47CINLWF.js.map} +0 -0
- /package/dist/{chunk-O5W7XSUX.js.map → chunk-6PWNTTPI.js.map} +0 -0
- /package/dist/{chunk-HRCNHWLP.js.map → chunk-7CQ6DEWZ.js.map} +0 -0
- /package/dist/{chunk-AOGYAXPY.js.map → chunk-GH32R55S.js.map} +0 -0
- /package/dist/{chunk-VTJMND23.js.map → chunk-H6YBKIUN.js.map} +0 -0
- /package/dist/{chunk-D3425YDV.js.map → chunk-ZB7D62OA.js.map} +0 -0
- /package/dist/{cloudflare-JTBKOX3F.js.map → cloudflare-KYUWCEPL.js.map} +0 -0
- /package/dist/{deno-deploy-3ESNGYZU.js.map → deno-deploy-CCTB7HGS.js.map} +0 -0
- /package/dist/{dev-6TWDBX6G.js.map → dev-5BKIKJYI.js.map} +0 -0
- /package/dist/{dev-emit-7ZZRTYF3.js.map → dev-emit-GWD6IXV7.js.map} +0 -0
- /package/dist/{dev-emit-E5Z26AMV.js.map → dev-emit-NBT6JD7R.js.map} +0 -0
- /package/dist/{info-QB7Q3SHP.js.map → info-MSBQ72EM.js.map} +0 -0
- /package/dist/{internal-api-XQA2L663.js.map → internal-api-ICU7ITJD.js.map} +0 -0
- /package/dist/{internal-api-REGN3WMX.js.map → internal-api-WEOSTMSN.js.map} +0 -0
- /package/dist/{load-config-JKJ73TOD.js.map → load-config-2LP2OEKD.js.map} +0 -0
- /package/dist/{mcp-CO4AGAXV.js.map → mcp-4FNNCFPM.js.map} +0 -0
- /package/dist/{netlify-OFJUYXTZ.js.map → netlify-AFMDNKT4.js.map} +0 -0
- /package/dist/{node-ND4MJU3Z.js.map → node-ZJQEJSP3.js.map} +0 -0
- /package/dist/{registry-QTSMLOVG.js.map → registry-UJ6KNQPJ.js.map} +0 -0
- /package/dist/{routes-GWAWUVVQ.js.map → routes-6AMNVFMI.js.map} +0 -0
- /package/dist/{schema-2JEG6TTU.js.map → schema-Z3Y7SFLE.js.map} +0 -0
- /package/dist/{services-typed-client-II7LKXKO.js.map → services-typed-client-PSAGRPFF.js.map} +0 -0
- /package/dist/{static-HTT2WNIT.js.map → static-R25HSXEF.js.map} +0 -0
- /package/dist/{theo-cloud-ZTQLXGPS.js.map → theo-cloud-XAHWOUFK.js.map} +0 -0
- /package/dist/{vercel-72HFEMJ3.js.map → vercel-SRS2DH6O.js.map} +0 -0
- /package/dist/{vite-plugin-NQ6F7FGV.js.map → vite-plugin-YOCPU4C6.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/server/body-parser.ts","../src/server/observability/request-log.ts","../src/server/observability/logger.ts","../src/server/http/send-response.ts","../src/server/http/node-request.ts","../src/server/http/controller-dispatch.ts","../src/server/security/csrf-warn-dispatch.ts","../src/server/observability/audit-log.ts","../src/server/security/csrf.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport { basename } from 'node:path'\n\nimport Busboy from 'busboy'\n\n// --- Types ---\n\nexport interface UploadedFile {\n fieldname: string\n filename: string\n encoding: string\n mimeType: string\n buffer: Buffer\n size: number\n}\n\nexport interface ParsedBody {\n fields: Record<string, string>\n files: UploadedFile[]\n json?: unknown\n}\n\nexport interface BodyParserOptions {\n maxFileSize?: number // bytes, default 10MB\n maxFiles?: number // default 10\n maxFieldSize?: number // bytes, default 1MB\n}\n\nconst METHODS_WITH_BODY = new Set(['POST', 'PUT', 'PATCH'])\n\nconst DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024 // 10MB\nconst DEFAULT_MAX_FILES = 10\nconst DEFAULT_MAX_FIELD_SIZE = 1 * 1024 * 1024 // 1MB\n\nexport class FileTooLargeError extends Error {\n readonly code = 'FILE_TOO_LARGE'\n readonly status = 413\n constructor(\n message: string,\n readonly truncatedFilenames: string[],\n readonly maxFileSize: number,\n ) {\n super(message)\n this.name = 'FileTooLargeError'\n }\n}\n\n// --- JSON parsing ---\n\n/**\n * Stash the parsed body on the IncomingMessage for the devtools forwarder\n * to read at log time. Symbol-keyed so it never leaks into user-visible\n * surfaces. Best-effort — failures (e.g. frozen req) are swallowed.\n */\nfunction stashBodyPreview(req: IncomingMessage, body: unknown): void {\n try {\n ;(req as unknown as Record<symbol, unknown>)[DEVTOOLS_BODY_PREVIEW] = body\n } catch {\n // best-effort; never break the request\n }\n}\n\nfunction parseJsonBody(req: IncomingMessage): Promise<unknown> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n req.on('data', (chunk: Buffer) => chunks.push(chunk))\n req.on('end', () => {\n const raw = Buffer.concat(chunks).toString()\n if (!raw) {\n resolve(undefined)\n return\n }\n try {\n resolve(JSON.parse(raw))\n } catch {\n reject(new Error('Invalid JSON body'))\n }\n })\n req.on('error', reject)\n })\n}\n\n// --- Multipart parsing ---\n\nfunction parseMultipartBody(\n req: IncomingMessage,\n contentType: string,\n options: Required<Pick<BodyParserOptions, 'maxFileSize' | 'maxFiles' | 'maxFieldSize'>>,\n): Promise<{ fields: Record<string, string>; files: UploadedFile[] }> {\n // EC-3: Validate boundary exists.\n if (!contentType.includes('boundary=')) {\n return Promise.reject(new Error('Missing multipart boundary'))\n }\n\n return new Promise((resolve, reject) => {\n const fields: Record<string, string> = {}\n const files: UploadedFile[] = []\n // CR-010: track filenames that were truncated mid-stream. The previous\n // implementation skipped truncated files from `files` and relied on\n // `files.some(f => f.size > maxFileSize)` to detect — but since the\n // file was never added, the guard never fired. Silent data loss.\n const truncatedFilenames: string[] = []\n let fileCount = 0\n\n const bb = Busboy({\n headers: req.headers,\n limits: {\n fileSize: options.maxFileSize,\n files: options.maxFiles,\n fieldSize: options.maxFieldSize,\n },\n })\n\n bb.on('field', (name: string, value: string) => {\n fields[name] = value\n })\n\n bb.on(\n 'file',\n (\n fieldname: string,\n stream: NodeJS.ReadableStream,\n info: { filename: string; encoding: string; mimeType: string },\n ) => {\n fileCount++\n if (fileCount > options.maxFiles) {\n stream.resume() // drain\n return\n }\n\n // EC-6: Sanitize filename — basename only, no path traversal.\n const safeName = basename(info.filename || 'unnamed')\n const chunks: Buffer[] = []\n let size = 0\n let truncated = false\n\n stream.on('data', (chunk: Buffer) => {\n size += chunk.length\n if (size > options.maxFileSize) {\n truncated = true\n stream.resume() // drain the rest\n return\n }\n chunks.push(chunk)\n })\n\n stream.on('end', () => {\n if (truncated) {\n truncatedFilenames.push(safeName)\n return\n }\n\n const buffer = Buffer.concat(chunks)\n files.push({\n fieldname,\n filename: safeName,\n encoding: info.encoding,\n mimeType: info.mimeType,\n buffer,\n size: buffer.length,\n })\n })\n },\n )\n\n bb.on('filesLimit', () => {\n reject(new Error(`Too many files. Maximum: ${options.maxFiles}`))\n })\n\n bb.on('error', (err: Error) => {\n reject(err)\n })\n\n bb.on('close', () => {\n if (truncatedFilenames.length > 0) {\n reject(\n new FileTooLargeError(\n `File too large (max ${options.maxFileSize} bytes): ${truncatedFilenames.join(', ')}`,\n truncatedFilenames,\n options.maxFileSize,\n ),\n )\n return\n }\n resolve({ fields, files })\n })\n\n req.pipe(bb)\n })\n}\n\n// --- Main parser ---\n\n/**\n * Symbol-keyed slot on IncomingMessage that the devtools request log\n * forwarder reads to enrich the broadcast payload with a body preview.\n * Symbol-keyed so it can't collide with user-land props and is invisible\n * to JSON.stringify / iteration. Always optional — readers must guard.\n */\nexport const DEVTOOLS_BODY_PREVIEW = Symbol('theo:devtools:bodyPreview')\n\nexport async function parseRequestBody(\n req: IncomingMessage,\n options?: BodyParserOptions,\n): Promise<ParsedBody> {\n const method = req.method?.toUpperCase() ?? 'GET'\n if (!METHODS_WITH_BODY.has(method)) {\n return { fields: {}, files: [], json: undefined }\n }\n\n const contentType = req.headers['content-type'] ?? ''\n\n // JSON\n if (contentType.includes('application/json')) {\n const json = await parseJsonBody(req)\n stashBodyPreview(req, json)\n return { fields: {}, files: [], json }\n }\n\n // Multipart\n if (contentType.includes('multipart/form-data')) {\n const limits = {\n maxFileSize: options?.maxFileSize ?? DEFAULT_MAX_FILE_SIZE,\n maxFiles: options?.maxFiles ?? DEFAULT_MAX_FILES,\n maxFieldSize: options?.maxFieldSize ?? DEFAULT_MAX_FIELD_SIZE,\n }\n const { fields, files } = await parseMultipartBody(req, contentType, limits)\n stashBodyPreview(req, {\n fields,\n files: files.map((f) => ({\n fieldname: f.fieldname,\n filename: f.filename,\n mimeType: f.mimeType,\n size: f.size,\n })),\n })\n return { fields, files }\n }\n\n // No body or empty\n if (!contentType) {\n return { fields: {}, files: [] }\n }\n\n // Unsupported content type\n throw new Error(`Unsupported Content-Type: ${contentType}`)\n}\n","/**\n * Request log primitive (T6.2 of architecture-review-remediation-plan).\n *\n * Split out from `logger.ts` per PV-12 (SRP) — the file previously mixed\n * 3 concerns (factory, warnOnce, request log). This module owns ONLY the\n * `logRequest` flow + the devtools broadcast best-effort forwarder.\n *\n * Public API surface is unchanged — `logger.ts` re-exports `logRequest`\n * + `RequestLog` + `LoggerFn` for backward compat.\n */\nimport type { IncomingMessage } from 'node:http'\n\nimport { DEVTOOLS_BODY_PREVIEW } from '../body-parser.js'\n\nexport interface RequestLog {\n level: string\n method: string\n url: string\n status: number\n duration: number\n requestId: string\n timestamp: string\n}\n\nexport type LoggerFn = (log: RequestLog) => void\n\nconst defaultLoggerFn: LoggerFn = (log) => {\n // eslint-disable-next-line no-console -- request-log default output IS the contract\n console.log(JSON.stringify(log))\n}\n\nexport function logRequest(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n customLogger?: LoggerFn,\n req?: IncomingMessage,\n): void {\n const log: RequestLog = {\n level: 'info',\n ...info,\n timestamp: new Date().toISOString(),\n }\n const logger = customLogger ?? defaultLoggerFn\n logger(log)\n // T2.1 — also broadcast to devtools (no-op in prod / when no dev server).\n // Pass `req` so the devtools forwarder can extract headers + body preview\n // stashed by body-parser (Symbol-keyed). Without `req`, only metadata\n // (method/path/status/duration) reaches the devtools UI — the expanded\n // row view then shows nothing useful.\n broadcastRequestToDevtools(info, req)\n}\n\n/**\n * T2.1 — Forward a request record to the devtools UI. Lazy import keeps\n * this module dep-free for prod; broadcast.ts internally guards on\n * `globalThis.__theoViteHotServer`.\n */\nfunction randomRequestId(): string {\n // eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id for devtools UI\n return `req-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`\n}\n\n/**\n * T5a.2 Phase C slice 2/2 — pure devtools broadcast that takes a\n * pre-extracted headers object + optional stashed body, so both\n * IncomingMessage and Web Request paths share the same dispatch logic.\n *\n * Errors here must not propagate — devtools forwarding is best-effort\n * and silenced.\n */\nfunction broadcastToDevtoolsCore(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n headers: Record<string, string> | undefined,\n stashedBody: unknown,\n): void {\n void Promise.all([\n import('../../devtools/server-side/broadcast.js'),\n import('../../devtools/server-side/build-request-body-preview.js'),\n ])\n .then(([{ broadcastRequest }, { buildRequestBodyPreview }]) => {\n const bodyInfo = buildRequestBodyPreview(stashedBody)\n broadcastRequest({\n id: randomRequestId(),\n traceId: info.requestId,\n method: info.method,\n path: info.url,\n status: info.status,\n durationMs: info.duration,\n startedAt: Date.now() - info.duration,\n ...(headers ? { headers } : {}),\n ...(bodyInfo\n ? {\n bodyPreview: bodyInfo.preview,\n bodyLength: bodyInfo.length,\n bodyTruncated: bodyInfo.truncated,\n }\n : {}),\n })\n })\n .catch(() => {\n // No-op: devtools forwarding is best-effort.\n })\n}\n\nfunction broadcastRequestToDevtools(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n req?: IncomingMessage,\n): void {\n // T2.1 forwarder. Errors here must not propagate.\n const headers = req?.headers\n ? Object.fromEntries(\n Object.entries(req.headers).map(([k, v]) => [\n k,\n Array.isArray(v) ? v.join(', ') : (v ?? ''),\n ]),\n )\n : undefined\n const stashedBody = req\n ? (req as unknown as Record<symbol, unknown>)[DEVTOOLS_BODY_PREVIEW]\n : undefined\n broadcastToDevtoolsCore(info, headers, stashedBody)\n}\n\n/**\n * T5a.2 Phase C slice 2/2 — Web-Standards request log + devtools\n * forward. Mirror of `logRequest(info, logger?, req?: IncomingMessage)`\n * accepting `request?: Request`.\n *\n * Extracts headers via `request.headers.entries()` (Web `Headers`\n * iterator). Body preview stash is NOT yet supported on the Web path —\n * `body-parser-web.ts` doesn't yet stash like `body-parser.ts` does for\n * the IncomingMessage path. That hookup is Phase E scope (body parsing\n * migration). Until then, the devtools UI shows headers but no body\n * preview for Web-handled requests — same surface as the IncomingMessage\n * path before the stash convention shipped.\n */\nexport function logRequestFromRequest(\n info: Omit<RequestLog, 'level' | 'timestamp'>,\n customLogger?: LoggerFn,\n request?: Request,\n): void {\n const log: RequestLog = {\n level: 'info',\n ...info,\n timestamp: new Date().toISOString(),\n }\n const logger = customLogger ?? defaultLoggerFn\n logger(log)\n\n let headers: Record<string, string> | undefined\n if (request) {\n headers = {}\n for (const [k, v] of request.headers.entries()) {\n headers[k] = v\n }\n }\n // Phase E will add body-preview stash on Web Request; for now no stash.\n broadcastToDevtoolsCore(info, headers, undefined)\n}\n","// --- Types ---\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent'\n\nexport interface StructuredLog {\n level: string\n msg: string\n timestamp: string\n [key: string]: unknown\n}\n\nexport interface TheoLogger {\n debug(msg: string, context?: Record<string, unknown>): void\n info(msg: string, context?: Record<string, unknown>): void\n warn(msg: string, context?: Record<string, unknown>): void\n error(msg: string, context?: Record<string, unknown>): void\n child(context: Record<string, unknown>): TheoLogger\n}\n\n// --- Level filtering ---\n\nconst LEVEL_ORDER: Record<string, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3,\n silent: 4,\n}\n\nfunction shouldLog(msgLevel: string, configLevel: string): boolean {\n return LEVEL_ORDER[msgLevel] >= LEVEL_ORDER[configLevel]\n}\n\n// --- Default output ---\n\nfunction defaultOutput(log: StructuredLog): void {\n // eslint-disable-next-line no-console -- structured logger output IS the contract here\n console.log(JSON.stringify(log))\n}\n\n// --- Factory ---\n\nexport function createLogger(options?: {\n level?: LogLevel\n output?: (log: StructuredLog) => void\n context?: Record<string, unknown>\n}): TheoLogger {\n const level = options?.level ?? 'info'\n const output = options?.output ?? defaultOutput\n const baseContext = options?.context ?? {}\n\n function log(msgLevel: string, msg: string, context?: Record<string, unknown>): void {\n if (!shouldLog(msgLevel, level)) return\n\n const entry: StructuredLog = {\n level: msgLevel,\n msg,\n timestamp: new Date().toISOString(),\n ...baseContext,\n ...context,\n }\n\n // Serialize Error objects to stack string\n if (entry.error instanceof Error) {\n entry.error = entry.error.stack ?? entry.error.message\n }\n\n output(entry)\n }\n\n return {\n debug: (msg, ctx) => {\n log('debug', msg, ctx)\n },\n info: (msg, ctx) => {\n log('info', msg, ctx)\n },\n warn: (msg, ctx) => {\n log('warn', msg, ctx)\n },\n error: (msg, ctx) => {\n log('error', msg, ctx)\n },\n child(ctx) {\n return createLogger({\n level,\n output,\n context: { ...baseContext, ...ctx },\n })\n },\n }\n}\n\n// --- warnOnce (T2.1) ---\n\n/**\n * Per-key dedup state. Module-scoped — reset per-process at boot.\n *\n * CR-011 fix: previous implementation used an unbounded `Set` which grew\n * forever in long-running prod processes with high-cardinality keys\n * (e.g. dynamic path segments). The bound below uses LRU-by-insertion:\n * when the Set hits `MAX_SEEN`, the OLDEST key is evicted. Subsequent\n * occurrences of that key will warn ONCE more — acceptable in exchange\n * for bounded memory.\n */\nconst _warnOnceSeen = new Set<string>()\nconst MAX_SEEN = 1024\n\n/**\n * Reset internal state. Test-only export — do not call from production.\n */\nexport function _resetWarnOnceForTests(): void {\n _warnOnceSeen.clear()\n}\n\n/**\n * Emit a structured warning ONCE per key. Subsequent calls with the same\n * key are suppressed. Used for cutover-style warnings (e.g. CSRF warn)\n * that would otherwise flood logs under load.\n *\n * Convention: key is `<event>:<method>:<path>` — see callers in csrf.ts.\n *\n * EC-2: payload may contain circular references. Wrap `JSON.stringify`\n * in try/catch so a malformed payload doesn't crash the request handler.\n */\nexport function warnOnce(key: string, payload: Record<string, unknown>): void {\n if (_warnOnceSeen.has(key)) return\n // CR-011: bounded LRU. Evict oldest insertion when at capacity.\n if (_warnOnceSeen.size >= MAX_SEEN) {\n const oldest = _warnOnceSeen.values().next().value\n if (oldest !== undefined) _warnOnceSeen.delete(oldest)\n }\n _warnOnceSeen.add(key)\n\n let line: string\n try {\n line = JSON.stringify({ ...payload, warnOnce: true })\n } catch {\n // EC-2 fallback — preserve the key for grep-ability even when payload\n // can't serialize (e.g., circular references, BigInt, etc.).\n line = `[warnOnce] ${key} — (payload could not be serialized)`\n }\n console.warn(line)\n\n // T2.1 — also broadcast to devtools (no-op in prod / when no dev server).\n // Imported lazily to keep logger.ts dependency-free for non-dev contexts;\n // broadcastToDevtools internally guards on globalThis.__theoViteHotServer.\n broadcastWarnOnceToDevtools(key, payload)\n}\n\n/**\n * T2.1 — Forward a warnOnce payload to the devtools UI. Recognizes the\n * canonical CSRF warn shape and dispatches as 'csrf.warn'; otherwise\n * falls back to a generic 'error' broadcast.\n *\n * No-op when:\n * - globalThis.__theoViteHotServer is undefined (prod / no dev server)\n * - any error occurs (default-deny via broadcastToDevtools' try/catch)\n */\nfunction payloadField(payload: Record<string, unknown>, key: string, fallback = ''): string {\n const value = payload[key]\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n return fallback\n}\n\nfunction randomDevtoolsId(): string {\n // eslint-disable-next-line sonarjs/pseudo-random -- non-secret correlation id for devtools UI\n return `warn-${String(Date.now())}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction broadcastWarnOnceToDevtools(key: string, payload: Record<string, unknown>): void {\n // CR-021: removed dead `void key` statement.\n // T2.1 forwarder. Errors here must not propagate (default-deny).\n void import('../../devtools/server-side/broadcast.js')\n .then(({ broadcastCsrfWarn, broadcastError }) => {\n if (\n payload.event === 'csrf.warn' &&\n typeof payload.code === 'string' &&\n typeof payload.docsUrl === 'string'\n ) {\n broadcastCsrfWarn({\n event: 'csrf.warn',\n code: payload.code,\n docsUrl: payload.docsUrl,\n method: payloadField(payload, 'method'),\n path: payloadField(payload, 'path'),\n reason: payloadField(payload, 'reason'),\n })\n } else {\n broadcastError({\n id: randomDevtoolsId(),\n type: 'console',\n message: payloadField(payload, 'event', key),\n timestamp: Date.now(),\n })\n }\n })\n .catch(() => {\n // No-op: devtools forwarding is best-effort.\n })\n}\n\n// --- Backward compat re-exports ---\n//\n// T6.2 (PV-12 SRP): `logRequest` + `RequestLog` + `LoggerFn` moved to a\n// dedicated `request-log.ts`. Re-exported here so consumers importing\n// from `theokit/server` see no change.\n\nexport { logRequest } from './request-log.js'\nexport type { RequestLog, LoggerFn } from './request-log.js'\n","import type { ServerResponse } from 'node:http'\n\nimport type { TheoTransformer } from '../transformer.js'\n\n/**\n * Canonical HTTP response helpers (T5.1 extraction).\n *\n * Moved out of execute.ts so request-pipeline stages (execute-stages.ts,\n * handle-request-error.ts, etc.) can depend on these helpers without\n * creating a cycle through execute.ts.\n *\n * Public surface re-exported from execute.ts for backward compat — every\n * existing caller of `sendError` / `sendJson` continues to work via the\n * `theokit/server` barrel.\n */\n\nexport function sendJson(\n res: ServerResponse,\n data: unknown,\n status = 200,\n transformer?: TheoTransformer,\n): void {\n // T1.2 — transformer-aware serialization. Default (no transformer) uses\n // JSON.stringify direct for backward compat.\n const body = transformer ? transformer.serialize(data) : JSON.stringify(data)\n res.writeHead(status, {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(body),\n })\n res.end(body)\n}\n\nexport interface SendErrorOptions {\n custom404Html?: string\n custom500Html?: string\n}\n\n/**\n * Canonical error response.\n *\n * T6.3 (PV-17): the positional 7-param signature is preserved for backward\n * compat. New call sites should use the options-bag form:\n *\n * sendError(res, { code, message, status, issues?, requestId?, options? })\n *\n * Both shapes resolve to the same implementation.\n */\nexport interface SendErrorInput {\n code: string\n message: string\n status: number\n issues?: unknown[]\n requestId?: string\n options?: SendErrorOptions\n}\n\nexport function sendError(res: ServerResponse, input: SendErrorInput): void\n/* eslint-disable-next-line max-params -- T6.3: positional overload preserved\n for backward compat (callers across cli/server still use positional). The\n options-bag overload above is the recommended path. */\nexport function sendError(\n res: ServerResponse,\n code: string,\n message: string,\n status: number,\n issues?: unknown[],\n requestId?: string,\n options?: SendErrorOptions,\n): void\n/* eslint-disable-next-line max-params, complexity -- delegates to two\n surface overloads above; the branch density mirrors the back-compat\n contract, not internal complexity. */\nexport function sendError(\n res: ServerResponse,\n codeOrInput: string | SendErrorInput,\n message?: string,\n status?: number,\n issues?: unknown[],\n requestId?: string,\n options?: SendErrorOptions,\n): void {\n let code: string\n if (typeof codeOrInput === 'string') {\n code = codeOrInput\n message = message ?? ''\n status = status ?? 500\n } else {\n code = codeOrInput.code\n message = codeOrInput.message\n status = codeOrInput.status\n issues = codeOrInput.issues\n requestId = codeOrInput.requestId\n options = codeOrInput.options\n }\n const errorMessage =\n code === 'INTERNAL_ERROR' && process.env.NODE_ENV === 'production'\n ? 'Internal server error'\n : message\n\n if (code === 'INTERNAL_ERROR') {\n console.error(`[${requestId ?? 'no-id'}] ${message}`)\n }\n\n if (status === 404 && options?.custom404Html) {\n const body = options.custom404Html\n res.writeHead(404, {\n 'Content-Type': 'text/html; charset=utf-8',\n 'Content-Length': Buffer.byteLength(body),\n })\n res.end(body)\n return\n }\n if (status === 500 && options?.custom500Html) {\n const body = options.custom500Html\n res.writeHead(500, {\n 'Content-Type': 'text/html; charset=utf-8',\n 'Content-Length': Buffer.byteLength(body),\n })\n res.end(body)\n return\n }\n\n sendJson(\n res,\n {\n error: {\n code,\n message: errorMessage,\n ...(requestId ? { requestId } : {}),\n ...(issues ? { issues } : {}),\n },\n },\n status,\n )\n}\n\n/**\n * T5a.2 Phase G slice 4/N — Web-Standards response helpers.\n *\n * Mirror of `sendJson` + `sendError` for the Web `Request`/`Response`\n * shape. Returns a native `Response` directly instead of mutating a\n * `ServerResponse`.\n *\n * v1.0 § Phase G.\n *\n * **Difference vs IncomingMessage path:**\n * - No `Content-Length` set explicitly — the runtime computes it from\n * the body when needed. CF Workers / Bun / Deno all do this; setting\n * it manually risks conflict if the body is a stream rather than a\n * fixed string.\n * - Custom 404/500 HTML options preserved (same opts shape).\n * - `requestId` flows into the response body's error envelope AND\n * surfaces as `x-request-id` header (parity with handleWebRequestError\n * Phase G slice 3/N).\n */\nexport function buildJsonResponse(\n data: unknown,\n status = 200,\n transformer?: TheoTransformer,\n): Response {\n const body = transformer ? transformer.serialize(data) : JSON.stringify(data)\n return new Response(body, {\n status,\n headers: { 'content-type': 'application/json' },\n })\n}\n\nexport function buildErrorResponse(input: SendErrorInput): Response {\n const { code, message, status, issues, requestId, options } = input\n const errorMessage =\n code === 'INTERNAL_ERROR' && process.env.NODE_ENV === 'production'\n ? 'Internal server error'\n : message\n\n if (code === 'INTERNAL_ERROR') {\n console.error(`[${requestId ?? 'no-id'}] ${message}`)\n }\n\n const headers: Record<string, string> = {}\n if (requestId !== undefined) headers['x-request-id'] = requestId\n\n if (status === 404 && options?.custom404Html !== undefined) {\n headers['content-type'] = 'text/html; charset=utf-8'\n return new Response(options.custom404Html, { status: 404, headers })\n }\n if (status === 500 && options?.custom500Html !== undefined) {\n headers['content-type'] = 'text/html; charset=utf-8'\n return new Response(options.custom500Html, { status: 500, headers })\n }\n\n headers['content-type'] = 'application/json'\n const body = JSON.stringify({\n error: {\n code,\n message: errorMessage,\n ...(requestId ? { requestId } : {}),\n ...(issues ? { issues } : {}),\n },\n })\n return new Response(body, { status, headers })\n}\n","/**\n * Pure Node `IncomingMessage` → Web `Request` converters.\n *\n * Kept free of any dependency on `web-handler.js` / `execute*.js` so the\n * executor (`execute.ts`) can build the handler-facing Web `Request` without\n * pulling the Web dispatch pipeline into its import graph (ADR-0028 R3a — the\n * Node adapter is the ONLY place IncomingMessage ↔ Request conversion lives;\n * these are the primitive converters it and the executor share).\n */\nimport type { IncomingMessage } from 'node:http'\nimport { Readable } from 'node:stream'\n\n/** Pick the first usable string from Node's `string | string[] | undefined` headers. */\nfunction pickHeaderString(value: string | string[] | undefined): string | undefined {\n if (typeof value === 'string') return value\n if (Array.isArray(value)) {\n for (const v of value) if (typeof v === 'string' && v.length > 0) return v\n }\n return undefined\n}\n\n/** Web Request requires an absolute URL; synthesize one from the Host header. */\nfunction synthesizeAbsoluteUrl(req: IncomingMessage): string {\n const host = pickHeaderString(req.headers.host) ?? 'localhost'\n return `http://${host}${req.url ?? '/'}`\n}\n\n/**\n * Collapse Node's `string | string[]` headers into a Web `Headers`. Repeated\n * headers are comma-joined (not `.append`ed) because `Headers.append` creates\n * multi-value entries that behave differently on `.get()` (EC-1).\n */\nfunction nodeHeadersToWeb(req: IncomingMessage): Headers {\n const headers = new Headers()\n for (const [key, value] of Object.entries(req.headers)) {\n if (value === undefined) continue\n if (Array.isArray(value)) {\n headers.set(key, value.join(', '))\n } else {\n headers.set(key, value)\n }\n }\n return headers\n}\n\n/**\n * Build a Web `Request` from a Node `IncomingMessage`, body included. The Web\n * Request spec requires an absolute URL; we synthesize one from the Host header\n * (fallback `localhost` for test doubles).\n *\n * For methods with a body (POST/PUT/PATCH/DELETE), the Node Readable stream is\n * wrapped as a Web ReadableStream via `Readable.toWeb()` so downstream consumers\n * can call `request.json()` / `request.formData()` / `request.text()` natively.\n */\nexport function incomingMessageToWebRequest(req: IncomingMessage): Request {\n const url = synthesizeAbsoluteUrl(req)\n const headers = nodeHeadersToWeb(req)\n\n const method = (req.method ?? 'GET').toUpperCase()\n const hasBody = method !== 'GET' && method !== 'HEAD'\n\n if (!hasBody) {\n return new Request(url, { method, headers })\n }\n\n // Drain Node's Readable into a Web ReadableStream. `Readable.toWeb` is\n // available in Node 18+ (theokit's engines.node floor is 22+, so safe).\n const webStream = Readable.toWeb(req) as ReadableStream\n return new Request(url, {\n method,\n headers,\n body: webStream,\n // EC-2: Node 18+ requires `duplex: 'half'` when body is a stream.\n // The `RequestInit` type omits it (Web spec gap); cast accordingly.\n ...({ duplex: 'half' } as { duplex: 'half' }),\n })\n}\n\n/**\n * Build the Web `Request` handed to a route handler as `ctx.request` in the\n * Node server path (dev + `theokit start`). Method + absolute URL + headers\n * only — NO body.\n *\n * Why no body: the Node executor parses the request body BEFORE the handler\n * runs and exposes the parsed value as `ctx.body` (the typed, documented body\n * API). By the time the handler is called the Node stream is already drained,\n * so re-wrapping it would yield an empty/closed stream. Handlers read the body\n * via `ctx.body`; `ctx.request` is for the Web-standard header/cookie/URL/method\n * surface (e.g. `createSessionManagerWeb.getSession(ctx.request)`).\n *\n * Per ADR-0028 R3a, handlers see a Web `Request` in every runtime — this closes\n * the gap where the Node path leaked the raw `IncomingMessage` (whose `.headers`\n * is a plain object, so `.headers.get(...)` threw for Web-standard consumers).\n */\nexport function incomingMessageToHandlerRequest(req: IncomingMessage): Request {\n return new Request(synthesizeAbsoluteUrl(req), {\n method: (req.method ?? 'GET').toUpperCase(),\n headers: nodeHeadersToWeb(req),\n })\n}\n","/* eslint-disable security/detect-non-literal-fs-filename --\n * Controller files are walked from the developer's `serverDir/controllers`\n * (a build-time config path), never from HTTP input. No injection vector.\n */\nimport { readdirSync, type Dirent } from 'node:fs'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { join } from 'node:path'\n\nimport { createDecoratorHandler, isControllerClass, type ServeAgent } from '@theokit/http'\n\nimport { dispatchCsrfWarn } from '../security/csrf-warn-dispatch.js'\nimport { enforceCsrf, type DisallowedConfig } from '../security/csrf.js'\n\nimport { incomingMessageToWebRequest } from './node-request.js'\nimport { sendError } from './send-response.js'\n\n/** A decorator controller constructor (`@Controller` class). */\ntype ControllerClass = new (...args: never[]) => object\n\n/** Loads a controller module by absolute path. In dev this is Vite's `ssrLoadModule`\n * (the Task 1.1 swc transform has already compiled the parameter decorators); tests\n * inject `@theokit/http`'s `loadControllerWithSwc`. */\nexport type ControllerModuleLoader = (absPath: string) => Promise<Record<string, unknown>>\n\n/** A built controller route table exposed as a pure Web-Standard handler. */\ninterface ControllerDispatcher {\n /** `null` = no controller route matched — the host owns the miss (404 / fall-through). */\n dispatch(request: Request): Promise<Response | null>\n /** Non-executing route probe — true when a controller route owns `method` + `pathname`. */\n matches(method: string, pathname: string): boolean\n}\n\n// State-mutating methods get CSRF, mirroring the file-route pipeline (execute.ts).\nconst CSRF_PROTECTED_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])\n\n/** Recursively collect `*.controller.ts` files under `dir` (absolute paths). */\n/**\n * Every `*.controller.ts` under `dir`, recursively. Exported for theokit#123: the build emitter\n * must find exactly the same set the dev dispatcher does, and two independent walks would be two\n * definitions of \"a controller\" that drift.\n */\nexport function findControllerFiles(dir: string): string[] {\n const found: string[] = []\n const walk = (current: string): void => {\n let entries: Dirent[]\n try {\n entries = readdirSync(current, { withFileTypes: true })\n } catch {\n return // dir doesn't exist — no controllers\n }\n for (const entry of entries) {\n const full = join(current, entry.name)\n if (entry.isDirectory()) walk(full)\n // theokit#123 — `.mjs` alongside `.ts`. Dev walks the SOURCE tree; production walks the\n // COMPILED tree under `dist/controllers`, where the same files exist as `*.controller.mjs`.\n // One walk for both keeps a single definition of \"a controller file\"; two would drift, and a\n // file that counts in dev and not in production is exactly the dev/prod split this fixes.\n else if (entry.name.endsWith('.controller.ts') || entry.name.endsWith('.controller.mjs'))\n found.push(full)\n }\n }\n walk(dir)\n return found\n}\n\n/** A discovered controller: its source file + the loaded `@Controller` class. */\ninterface ControllerModule {\n filePath: string\n cls: ControllerClass\n /**\n * The module's full export namespace — theokit#124.\n *\n * Kept alongside the class because it is the ONLY place a `@Body(schema)` regains a name. The\n * schema on `WalkResult.bodySchema` is a runtime `z.ZodType` with no source identifier, but it is\n * the very object this module exported, so matching it back by reference identity recovers the\n * exported name the typed-client codegen needs to write `z.infer<typeof ...>`.\n *\n * Unused by the dispatch path, which needs only the class.\n */\n exports: Readonly<Record<string, unknown>>\n}\n\n/**\n * Load every `@Controller` class under `controllersDir` via the injected loader,\n * keeping each class paired with its source file (needed by the typed-client\n * codegen to emit `import type { X } from '<file>'`). Non-controller exports are\n * ignored (`isControllerClass` — reused from @theokit/http).\n */\nexport async function scanControllerModules(\n controllersDir: string,\n loadModule: ControllerModuleLoader,\n): Promise<ControllerModule[]> {\n const files = findControllerFiles(controllersDir)\n const modules: ControllerModule[] = []\n for (const filePath of files) {\n const mod = await loadModule(filePath)\n for (const exported of Object.values(mod)) {\n if (typeof exported === 'function' && isControllerClass(exported)) {\n modules.push({ filePath, cls: exported as ControllerClass, exports: mod })\n }\n }\n }\n return modules\n}\n\n/** Load every `@Controller` class under `controllersDir` (classes only). */\nasync function scanControllers(\n controllersDir: string,\n loadModule: ControllerModuleLoader,\n): Promise<ControllerClass[]> {\n const modules = await scanControllerModules(controllersDir, loadModule)\n return modules.map((m) => m.cls)\n}\n\n/**\n * Scan `controllersDir` and build a Web-Standard dispatcher over the decorator\n * controllers found. Returns `null` when the directory has no controllers, so\n * the host can skip the controller path entirely (zero cost for routes-only apps).\n *\n * Dispatch REUSES @theokit/http's `createDecoratorHandler` (match + `@Param`\n * binding + `@Body` validation + Response building) — never re-implemented (ADR-1).\n */\nexport async function createControllerDispatcher(opts: {\n controllersDir: string\n loadModule: ControllerModuleLoader\n /** M47 — serves `@Expose`-bound agent routes (theo supplies a `mountAgent`-backed impl). */\n serveAgent?: ServeAgent\n}): Promise<ControllerDispatcher | null> {\n const classes = await scanControllers(opts.controllersDir, opts.loadModule)\n if (classes.length === 0) return null\n const handle = createDecoratorHandler({ controllers: classes, serveAgent: opts.serveAgent })\n return {\n dispatch: (request) => handle(request),\n matches: (method, pathname) => handle.matches(method, pathname),\n }\n}\n\n/** Write a buffered Web `Response` (controllers never stream) to a Node response. */\nasync function writeControllerResponse(res: ServerResponse, response: Response): Promise<void> {\n const headersBag: Record<string, string> = {}\n for (const [k, v] of response.headers) {\n if (k.toLowerCase() !== 'set-cookie') headersBag[k] = v\n }\n const setCookies = response.headers.getSetCookie()\n if (setCookies.length > 0) res.setHeader('Set-Cookie', setCookies)\n res.writeHead(response.status, headersBag)\n const body = response.body ? await response.text() : ''\n res.end(body || undefined)\n}\n\n/**\n * The `api-middleware` fall-through in one call: scan `controllersDir`, build the\n * dispatcher, and serve the request. Builds a body-ful Web `Request` (`@Body`\n * needs the body; the raw stream is undrained at a route miss) and enforces CSRF\n * with the SAME gate file routes use (parity). Returns `true` when a controller\n * handled it (or CSRF blocked it), `false` when there are no controllers OR none\n * matched (the host continues to its own 404). Built per-miss so controller edits\n * reflect via HMR.\n */\nexport async function dispatchControllerRequest(args: {\n controllersDir: string\n loadModule: ControllerModuleLoader\n req: IncomingMessage\n res: ServerResponse\n csrfMode: 'off' | 'warn' | 'strict'\n disallowed?: DisallowedConfig\n requestId: string\n /** M47 — serves `@Expose`-bound agent routes (mountAgent-backed); omit for routes-only apps. */\n serveAgent?: ServeAgent\n}): Promise<boolean> {\n const { req, res, csrfMode, disallowed, requestId } = args\n const dispatcher = await createControllerDispatcher({\n controllersDir: args.controllersDir,\n loadModule: args.loadModule,\n serveAgent: args.serveAgent,\n })\n if (!dispatcher) return false\n\n const method = (req.method ?? 'GET').toUpperCase()\n const webRequest = incomingMessageToWebRequest(req)\n const pathname = new URL(webRequest.url).pathname\n\n // CSRF parity: enforce ONLY when a protected-method controller route actually\n // owns this path (probe with the non-executing matcher — never double-dispatch,\n // which would run the handler + its side effects). An unrouted path falls\n // through to the host's own 404, not a 403.\n if (CSRF_PROTECTED_METHODS.has(method) && dispatcher.matches(method, pathname)) {\n const decision = enforceCsrf(\n req,\n csrfMode,\n { warn: dispatchCsrfWarn, path: req.url },\n disallowed,\n )\n if (!decision.allow) {\n sendError(\n res,\n 'CSRF_INVALID',\n decision.reason ?? 'CSRF check failed',\n 403,\n undefined,\n requestId,\n )\n return true\n }\n }\n\n const response = await dispatcher.dispatch(webRequest)\n if (response === null) return false\n await writeControllerResponse(res, response)\n return true\n}\n","/**\n * Canonical CSRF warn dispatcher (T3.3 of architecture-review-remediation-plan).\n *\n * Consolidates the duplicated `warn: (payload) => { warnOnce(...) }` closure\n * that previously appeared in both `http/execute.ts` and\n * `http/action-execute.ts`. Resolves PV-10 (DRY).\n *\n * `warnOnce` dedupes by `event:method:path` so a request loop with 1000 POSTs\n * doesn't flood logs with identical warnings. Apps grep for `event\":\"csrf.warn\"`\n * (stable event shape — see [[enforcement-cutover.md]]).\n */\nimport { warnOnce } from '../observability/logger.js'\n\ninterface CsrfWarnPayload {\n event: string\n method: string\n path?: string\n reason: string\n code?: string\n docsUrl?: string\n warnOnce?: boolean\n}\n\n/**\n * Build the warn callback that `enforceCsrf` invokes for soft-mode warnings.\n * Returned function is suitable for the `warn` field of `enforceCsrf`'s options.\n */\nexport function dispatchCsrfWarn(payload: CsrfWarnPayload): void {\n const key = `${payload.event}:${payload.method}:${payload.path ?? ''}`\n warnOnce(key, payload as unknown as Record<string, unknown>)\n}\n","/**\n * T4.1 — Audit logging interface + default JSON stdout sink.\n *\n * Per ADR D4: define the interface; ship a zero-dep default; reserve\n * adapter shapes for Postgres, File, OpenTelemetry, Sentry as follow-up\n * packages. Persistence has heavy deps (`pg`, `better-sqlite3`); we\n * keep core dep-free and let users opt in.\n *\n * Compatibility:\n * - Node / Bun / Deno / Vercel — console.log is sync, captured.\n * - Edge runtimes (CF Workers, Vercel Edge) — console.log is captured\n * but may be rate-limited by the platform. For high-volume edge audit,\n * implement a custom sink writing to a queue / HTTP endpoint.\n */\n\nexport interface AuditEvent {\n /** Domain-qualified verb. Convention: `<domain>.<verb>` (e.g. csrf.warn, session.rotated). */\n action: string\n /** Who triggered the event. Anonymous = no auth at time of event. */\n actor?: { type: 'user' | 'system' | 'anonymous'; id?: string }\n /** What was operated on (optional). */\n resource?: { type: string; id?: string }\n /** Arbitrary event-specific metadata. JSON-serializable. */\n metadata?: Record<string, unknown>\n /** ISO 8601 timestamp. If absent, sink fills in `new Date().toISOString()`. */\n timestamp?: string\n /** Optional trace id (populated by middleware from `x-trace-id`). */\n traceId?: string\n}\n\nexport interface AuditLogger {\n log(event: AuditEvent): void | Promise<void>\n}\n\n/**\n * Default sink: one JSON line per event to stdout. Sync. Never throws.\n *\n * EC: circular refs / BigInt values fall back to a placeholder line so\n * the event is still observable (action + traceId) without crashing the\n * request lifecycle.\n */\nexport class JsonStdoutSink implements AuditLogger {\n log(event: AuditEvent): void {\n const enriched = {\n level: 'audit' as const,\n ...event,\n timestamp: event.timestamp ?? new Date().toISOString(),\n }\n try {\n // eslint-disable-next-line no-console -- JsonStdoutSink IS the audit output\n console.log(JSON.stringify(enriched, jsonReplacer))\n } catch {\n // eslint-disable-next-line no-console -- fallback when payload won't serialize\n console.log(\n `{\"level\":\"audit\",\"action\":${JSON.stringify(event.action)},\"timestamp\":${JSON.stringify(enriched.timestamp)},\"note\":\"payload could not be serialized\"}`,\n )\n }\n }\n}\n\n/**\n * Replacer that walks BigInt → string. Circular ref handling is via the\n * outer try/catch (JSON.stringify throws TypeError on cycles; we drop to\n * the fallback line). We don't implement custom cycle-breaking walker\n * because the audit payload is meant to be JSON — if user metadata has\n * a cycle, the right answer is to fix the caller, not silently lose\n * the structure.\n */\nfunction jsonReplacer(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') return value.toString()\n return value\n}\n\n/**\n * No-op logger. Returned when `config.audit` is unset. Zero overhead;\n * framework wiring sites null-check before calling.\n */\nexport function createNoOpLogger(): AuditLogger {\n return {\n log() {\n // intentionally empty\n },\n }\n}\n\n/**\n * T4.2 — Safe-emit wrapper. Used by framework wiring sites (csrf.ts,\n * rate-limit.ts, session.ts) so a logger throw NEVER propagates into\n * the request handler.\n */\nexport function safeAudit(logger: AuditLogger | undefined, event: AuditEvent): void {\n if (!logger) return\n try {\n const r = logger.log(event)\n // Discard the Promise — async sinks are fire-and-forget by design.\n if (r && typeof r.then === 'function') {\n r.catch(() => {\n // swallow async sink failures — audit must never crash the request\n })\n }\n } catch {\n // swallow sync sink failures — audit must never crash the request\n }\n}\n","import type { IncomingMessage } from 'node:http'\n\nimport type { AuditLogger } from '../observability/audit-log.js'\nimport { safeAudit } from '../observability/audit-log.js'\n\n/**\n * CSRF enforcement mode.\n *\n * - `off` — skip CSRF entirely. Use only when you have another defense\n * (e.g. you don't ship session cookies, all auth is bearer).\n * - `warn` — log a structured warning when the check would fail, but\n * still serve the request. Default for 0.2.0. Migration mode.\n * - `strict` — reject failing requests with 403 + code `CSRF_INVALID`.\n * Will become the default in 0.3.0.\n */\nexport type CsrfMode = 'off' | 'warn' | 'strict'\n\n/**\n * Per-request structured logger surface. Only `warn` is used by enforceCsrf;\n * we don't require a full Logger here so callers can pass a mock or the\n * console directly.\n */\nexport interface CsrfLogger {\n warn: (payload: CsrfWarnPayload) => void\n /** Optional path the request was destined for — used for log correlation. */\n path?: string\n}\n\n/**\n * T5.1 — Rails-inspired per-route escalation.\n *\n * `routes` accepts string (exact match) or RegExp entries. When a request\n * path matches AND the request would otherwise emit a warning, the\n * `behavior` field decides what happens:\n *\n * - `'warn'` → normal warn dispatch (no-op vs default)\n * - `'raise'` → escalate to 403 regardless of global `csrf` mode\n *\n * `'raise'` never downgrades: when global mode is `'off'`, validation is\n * skipped entirely and disallowed dispatch never runs.\n */\nexport interface DisallowedConfig {\n routes: (string | RegExp)[]\n behavior: 'warn' | 'raise'\n}\n\n/**\n * Test whether `path` matches any of the supplied patterns. String\n * patterns are EXACT (trailing slash matters — use RegExp for tolerance).\n *\n * EC-5: when a RegExp carries the `/g` flag, `.test()` mutates\n * `lastIndex` and the next invocation may miss. We reset `lastIndex`\n * before each test so the matcher is a pure function.\n */\nexport function matchDisallowed(path: string, patterns: readonly (string | RegExp)[]): boolean {\n for (const p of patterns) {\n if (typeof p === 'string') {\n if (path === p) return true\n } else if (p instanceof RegExp) {\n p.lastIndex = 0\n if (p.test(path)) return true\n }\n // Neither string nor RegExp: ignore silently (defensive — the public\n // type forbids it but runtime data may slip past).\n }\n return false\n}\n\n/**\n * T2.2 — Stable cutover identifier shipped with every csrf.warn payload.\n *\n * Convention borrowed from Vite's `deprecations.ts:74` — a `code` plus a\n * `docsUrl` lets users (a) grep their logs for a single stable identifier\n * to find every csrf.warn line, and (b) click through directly to the\n * migration guide. Strings are exported constants so the analyzer (T2.3)\n * and migration guide can reference the same source of truth.\n */\nexport const CSRF_WARN_CODE = 'CSRF_STRICT_CUTOVER' as const\nexport const CSRF_WARN_DOCS_URL = 'https://theokit.dev/upgrade/csrf-strict-cutover' as const\n\n/**\n * Pick a header value, choosing the first entry when an array (Node sets\n * arrays for headers that legitimately appear multiple times). Returns\n * `''` when the header is absent or empty — callers treat `''` as \"skip\".\n */\nfunction pickHeader(value: string | string[] | undefined): string {\n if (typeof value === 'string') return value\n if (Array.isArray(value) && value.length > 0) return value[0]\n return ''\n}\n\nexport interface CsrfWarnPayload {\n event: 'csrf.warn'\n method: string\n path: string | undefined\n reason: string\n /**\n * Stable identifier for the 0.2 → 0.3 CSRF strict cutover. Always\n * `'CSRF_STRICT_CUTOVER'`. Grep-able from prod logs.\n */\n code: string\n /**\n * Link to the section of the migration guide explaining how to clear\n * this specific warning class.\n */\n docsUrl: string\n}\n\n/**\n * T5a.2 Phase B (slice 1/6): pure header-only CSRF check extracted from\n * `validateCsrf(req: IncomingMessage)` so it can be re-used by the Web-\n * Standards `validateCsrfRequest(request: Request)` sibling. Per the T5a.2\n * plan v1.0 § Phase B, header-only leaves are first to migrate. This is\n * the dual-signature pattern (anti-pattern #2 avoidance): IncomingMessage\n * consumers unchanged; new Request consumers go through the same logic\n * via the shared helper.\n *\n * Pure logic — accepts pre-extracted header values as strings or null.\n */\nfunction isCsrfValidFromHeaders(opts: {\n csrfActionHeader: string | null\n origin: string | null\n host: string | null\n}): { valid: true } | { valid: false; reason: string } {\n // 1. Custom header must be present (primary defense — simple form posts\n // cannot set custom headers, browsers gate via CORS preflight)\n if (opts.csrfActionHeader !== '1') {\n return { valid: false, reason: 'Missing X-Theo-Action header' }\n }\n\n // 2. Origin matching (secondary defense)\n if (opts.origin === null || opts.origin === '') {\n // Browsers omit Origin for same-origin requests — treat as valid\n return { valid: true }\n }\n\n if (opts.host === null || opts.host === '') {\n return { valid: true }\n }\n\n try {\n const originHost = new URL(opts.origin).host\n if (originHost !== opts.host) {\n return { valid: false, reason: `Origin ${opts.origin} does not match host ${opts.host}` }\n }\n } catch {\n return { valid: false, reason: `Invalid origin: ${opts.origin}` }\n }\n\n return { valid: true }\n}\n\nexport function validateCsrf(\n req: IncomingMessage,\n): { valid: true } | { valid: false; reason: string } {\n // IncomingMessage adapter — normalize Node header shape to the pure\n // helper's input shape (string|null).\n const action = req.headers['x-theo-action']\n const origin = req.headers.origin\n const host = req.headers.host\n return isCsrfValidFromHeaders({\n csrfActionHeader: typeof action === 'string' ? action : null,\n origin: origin !== undefined ? pickHeader(origin) || null : null,\n host: host !== undefined ? pickHeader(host) || null : null,\n })\n}\n\n/**\n * T5a.2 Phase B (slice 1/6) — Web-Standards-shaped CSRF validator.\n *\n * Mirror of `validateCsrf(req: IncomingMessage)` for the Web `Request`\n * shape. Consumes `request.headers.get(name)` (native Web `Headers` API)\n * instead of `req.headers[name]` (Node `IncomingMessage` indexer). Same\n * CSRF policy + same return shape — the difference is only the input\n * extraction.\n *\n * Used by `executeWebRequest` (T5a.2 Phase A) to enforce CSRF on the\n * Web-Standards request handler entry-point.\n */\nexport function validateCsrfRequest(\n request: Request,\n): { valid: true } | { valid: false; reason: string } {\n return isCsrfValidFromHeaders({\n csrfActionHeader: request.headers.get('x-theo-action'),\n origin: request.headers.get('origin'),\n host: request.headers.get('host'),\n })\n}\n\n/**\n * Enforce CSRF policy with mode-aware behavior. Wrapper over `validateCsrf`\n * that turns the boolean valid/invalid into a request-level allow decision,\n * gated by mode + structured warning in warn mode.\n *\n * Phase 5 — CSRF warn-first (EC-1).\n */\n/**\n * Dispatch the csrf.warn payload to both the structured logger and the\n * audit sink (when configured). Extracted from `enforceCsrf` to keep that\n * function's complexity within ceiling.\n */\nfunction dispatchCsrfWarn(\n req: IncomingMessage,\n reason: string,\n logger: CsrfLogger | undefined,\n auditLogger: AuditLogger | undefined,\n pathFallback = '',\n): void {\n const payload: CsrfWarnPayload = {\n event: 'csrf.warn',\n method: req.method ?? 'UNKNOWN',\n path: logger?.path ?? pathFallback,\n reason,\n code: CSRF_WARN_CODE,\n docsUrl: CSRF_WARN_DOCS_URL,\n }\n logger?.warn(payload)\n // `metadata` is typed as Record<string, unknown>; CsrfWarnPayload is a\n // structurally-equivalent shape but lacks the index signature.\n safeAudit(auditLogger, {\n action: 'csrf.warn',\n actor: { type: 'anonymous' },\n metadata: { ...payload },\n })\n}\n\nexport function enforceCsrf(\n req: IncomingMessage,\n mode: CsrfMode,\n logger?: CsrfLogger,\n disallowed?: DisallowedConfig,\n auditLogger?: AuditLogger,\n): { allow: boolean; reason?: string } {\n if (mode === 'off') {\n // `off` short-circuits before disallowed dispatch — users who set\n // csrf: 'off' globally have explicitly turned validation off, and\n // disallowed must never re-introduce it. The escape hatch is to\n // set csrf: 'warn' and use disallowed for surgical strict pockets.\n return { allow: true }\n }\n\n const check = validateCsrf(req)\n if (check.valid) {\n return { allow: true }\n }\n\n // T5.1 — disallowed dispatch: when the failing request matches a\n // disallowed pattern AND behavior is 'raise', escalate to 403 even if\n // global mode is 'warn'. Strict mode would 403 anyway, so the branch\n // is a no-op there.\n if (disallowed?.behavior === 'raise') {\n const path = logger?.path ?? req.url ?? ''\n if (matchDisallowed(path, disallowed.routes)) {\n return { allow: false, reason: check.reason }\n }\n }\n\n if (mode === 'warn') {\n // T2.1: emit via warnOnce by default — callers can override via the\n // injected logger.warn (tests, custom log routers).\n // T2.2: include the stable cutover code + docsUrl so logs are\n // grep-able and click-through-able.\n dispatchCsrfWarn(req, check.reason, logger, auditLogger)\n return { allow: true, reason: check.reason }\n }\n\n // strict — 403 the request, AND emit a warn payload so the dev (and\n // devtools UI) sees WHY it was blocked + the docsUrl to fix it.\n // Without this, strict-mode users get a silent 403 with no context.\n dispatchCsrfWarn(req, check.reason, logger, auditLogger)\n return { allow: false, reason: check.reason }\n}\n"],"mappings":"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","names":["dispatchCsrfWarn"]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
resolveTransformer,
|
|
9
9
|
serveAgentAuxRoute,
|
|
10
10
|
writeWebResponseToServerResponse
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-6RJSGTMN.js";
|
|
12
12
|
import {
|
|
13
13
|
isMcpPath
|
|
14
14
|
} from "./chunk-2CVV6CNN.js";
|
|
@@ -18,10 +18,10 @@ import {
|
|
|
18
18
|
} from "./chunk-IEES3CHD.js";
|
|
19
19
|
import {
|
|
20
20
|
loadConfig
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-GH32R55S.js";
|
|
22
22
|
import {
|
|
23
23
|
buildServicesProxyConfig
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-7VPQHEAJ.js";
|
|
25
25
|
import {
|
|
26
26
|
broadcastToDevtools
|
|
27
27
|
} from "./chunk-5ODOE6EF.js";
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
handleBatchRequest,
|
|
36
36
|
handleCspReport,
|
|
37
37
|
handleCsrfReadiness
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-7CQ6DEWZ.js";
|
|
39
39
|
import {
|
|
40
40
|
applySecurityHeaders,
|
|
41
41
|
createPluginRunnerFromConfig,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
findSuggestion,
|
|
47
47
|
generateNonce,
|
|
48
48
|
mountAgent
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-H6YBKIUN.js";
|
|
50
50
|
import {
|
|
51
51
|
getApprovalRegistry,
|
|
52
52
|
resolveProvider
|
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
incomingMessageToWebRequest,
|
|
58
58
|
logRequest,
|
|
59
59
|
sendError
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-T5C46IIU.js";
|
|
61
61
|
import {
|
|
62
62
|
scanAgents
|
|
63
63
|
} from "./chunk-LNVMHQ2Z.js";
|
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
scanServerActions,
|
|
67
67
|
scanServerRoutes,
|
|
68
68
|
scanWebSocketRoutes
|
|
69
|
-
} from "./chunk-
|
|
69
|
+
} from "./chunk-BMHS4WAN.js";
|
|
70
70
|
|
|
71
71
|
// src/vite-plugin/index.ts
|
|
72
72
|
import { existsSync as existsSync7 } from "fs";
|
|
@@ -810,6 +810,66 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
|
|
|
810
810
|
// src/vite-plugin/ssr-dev-middleware.ts
|
|
811
811
|
import { readFileSync as readFileSync2 } from "fs";
|
|
812
812
|
import { resolve as resolve3 } from "path";
|
|
813
|
+
|
|
814
|
+
// src/vite-plugin/hoist-head-tags.ts
|
|
815
|
+
var VOID_METADATA = /<(?:meta|link)\b[^>]*>/gi;
|
|
816
|
+
var TITLE_TAG = /<title\b[^>]*>[^<]*<\/title>/gi;
|
|
817
|
+
function replaceHoistable(html, replacer) {
|
|
818
|
+
return html.replace(TITLE_TAG, replacer).replace(VOID_METADATA, replacer);
|
|
819
|
+
}
|
|
820
|
+
function metadataKey(tag) {
|
|
821
|
+
if (/^<title\b/i.test(tag)) return "title";
|
|
822
|
+
const name = /\bname=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
823
|
+
const property = /\bproperty=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
824
|
+
const rel = /\brel=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
825
|
+
if (/^<meta\b/i.test(tag)) {
|
|
826
|
+
if (property !== void 0) return `property:${property.toLowerCase()}`;
|
|
827
|
+
if (name !== void 0) return `name:${name.toLowerCase()}`;
|
|
828
|
+
return void 0;
|
|
829
|
+
}
|
|
830
|
+
if (/^<link\b/i.test(tag) && rel !== void 0) {
|
|
831
|
+
const slug = rel.toLowerCase();
|
|
832
|
+
return slug === "canonical" || slug === "manifest" ? `link:${slug}` : void 0;
|
|
833
|
+
}
|
|
834
|
+
return void 0;
|
|
835
|
+
}
|
|
836
|
+
function extractHeadTags(ssrHtml) {
|
|
837
|
+
const headTags = [];
|
|
838
|
+
const html = replaceHoistable(ssrHtml, (tag) => {
|
|
839
|
+
headTags.push(tag);
|
|
840
|
+
return "";
|
|
841
|
+
});
|
|
842
|
+
return { html, headTags };
|
|
843
|
+
}
|
|
844
|
+
function injectIntoHead(template, headTags) {
|
|
845
|
+
if (headTags.length === 0) return template;
|
|
846
|
+
const closingHead = template.toLowerCase().lastIndexOf("</head>");
|
|
847
|
+
if (closingHead === -1) return template;
|
|
848
|
+
const supersededKeys = new Set(
|
|
849
|
+
headTags.map((tag) => metadataKey(tag)).filter((key) => key !== void 0)
|
|
850
|
+
);
|
|
851
|
+
let head = template.slice(0, closingHead);
|
|
852
|
+
if (supersededKeys.size > 0) {
|
|
853
|
+
head = replaceHoistable(head, (tag) => {
|
|
854
|
+
const key = metadataKey(tag);
|
|
855
|
+
return key !== void 0 && supersededKeys.has(key) ? "" : tag;
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
return `${head} ${headTags.join("\n ")}
|
|
859
|
+
${template.slice(closingHead)}`;
|
|
860
|
+
}
|
|
861
|
+
function hoistHeadTags(template, ssrHtml) {
|
|
862
|
+
const { html, headTags } = extractHeadTags(ssrHtml);
|
|
863
|
+
return { template: injectIntoHead(template, headTags), html };
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// src/vite-plugin/ssr-dev-middleware.ts
|
|
867
|
+
function applyNonceToInlineScripts(html, nonce) {
|
|
868
|
+
return html.replace(
|
|
869
|
+
/<script(?![^>]*\ssrc=)(?![^>]*\snonce=)([^>]*)>/gi,
|
|
870
|
+
`<script nonce="${nonce}"$1>`
|
|
871
|
+
);
|
|
872
|
+
}
|
|
813
873
|
function isSsrRenderResult(value) {
|
|
814
874
|
if (typeof value !== "object" || value === null) return false;
|
|
815
875
|
if (!("html" in value)) return false;
|
|
@@ -826,8 +886,9 @@ function setupSsrDevMiddleware(server, opts) {
|
|
|
826
886
|
try {
|
|
827
887
|
const indexPath = resolve3(opts.projectRoot, "index.html");
|
|
828
888
|
let template = readFileSync2(indexPath, "utf-8");
|
|
829
|
-
template = await server.transformIndexHtml(url, template);
|
|
830
889
|
const nonce = generateNonce();
|
|
890
|
+
template = await server.transformIndexHtml(url, template);
|
|
891
|
+
template = applyNonceToInlineScripts(template, nonce);
|
|
831
892
|
applySecurityHeaders(
|
|
832
893
|
res,
|
|
833
894
|
opts.securityHeaders ?? {},
|
|
@@ -854,6 +915,9 @@ function setupSsrDevMiddleware(server, opts) {
|
|
|
854
915
|
} else {
|
|
855
916
|
ssrHtml = "";
|
|
856
917
|
}
|
|
918
|
+
const hoisted = hoistHeadTags(template, ssrHtml);
|
|
919
|
+
template = hoisted.template;
|
|
920
|
+
ssrHtml = hoisted.html;
|
|
857
921
|
const rootDivMatch = /<div id=["']root["'][^>]*>/.exec(template);
|
|
858
922
|
if (!rootDivMatch) {
|
|
859
923
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
@@ -986,7 +1050,7 @@ async function runConfigureServer(server, ctx) {
|
|
|
986
1050
|
server.watcher.on("unlink", handleRouteChange);
|
|
987
1051
|
if (ctx.resolvedOpenApi !== void 0) {
|
|
988
1052
|
const openApiCfg = ctx.resolvedOpenApi;
|
|
989
|
-
const { reEmitOpenApi } = await import("./dev-emit-
|
|
1053
|
+
const { reEmitOpenApi } = await import("./dev-emit-GWD6IXV7.js");
|
|
990
1054
|
const openApiServerDir = ctx.serverDir;
|
|
991
1055
|
const openApiDistDir = resolve4(ctx.projectRoot, ctx.resolvedDistDir);
|
|
992
1056
|
const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
|
|
@@ -1988,7 +2052,7 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
1988
2052
|
const studioPlugins = await integrateStudio();
|
|
1989
2053
|
const servicesPlugins = [];
|
|
1990
2054
|
if (options.services && Object.keys(options.services).length > 0) {
|
|
1991
|
-
const { servicesTypedClientPlugin } = await import("./services-typed-client-
|
|
2055
|
+
const { servicesTypedClientPlugin } = await import("./services-typed-client-PSAGRPFF.js");
|
|
1992
2056
|
servicesPlugins.push(
|
|
1993
2057
|
servicesTypedClientPlugin({
|
|
1994
2058
|
cwd: projectRoot,
|
|
@@ -1996,19 +2060,19 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
1996
2060
|
})
|
|
1997
2061
|
);
|
|
1998
2062
|
}
|
|
1999
|
-
const { appTypedClientPlugin } = await import("./app-typed-client-
|
|
2063
|
+
const { appTypedClientPlugin } = await import("./app-typed-client-5QDNHANF.js");
|
|
2000
2064
|
const appClientPlugin = appTypedClientPlugin({
|
|
2001
2065
|
cwd: projectRoot,
|
|
2002
2066
|
serverDir: serverDirAbs,
|
|
2003
2067
|
distDir: resolve7(projectRoot, ".theokit")
|
|
2004
2068
|
});
|
|
2005
|
-
const { actionsVirtualModule } = await import("./actions-virtual-module-
|
|
2069
|
+
const { actionsVirtualModule } = await import("./actions-virtual-module-GB5FWUPS.js");
|
|
2006
2070
|
const actionsPlugin = actionsVirtualModule({
|
|
2007
2071
|
serverDir: serverDirAbs,
|
|
2008
2072
|
distDir: resolve7(projectRoot, ".theokit")
|
|
2009
2073
|
});
|
|
2010
|
-
const { agentsTypedClientPlugin } = await import("./agents-typed-client-
|
|
2011
|
-
const { generateManifest } = await import("./internal-api-
|
|
2074
|
+
const { agentsTypedClientPlugin } = await import("./agents-typed-client-D4OA2TXB.js");
|
|
2075
|
+
const { generateManifest } = await import("./internal-api-ICU7ITJD.js");
|
|
2012
2076
|
const agentsClientPlugin = agentsTypedClientPlugin({
|
|
2013
2077
|
projectRoot,
|
|
2014
2078
|
agentsDir: agentsDirName,
|
|
@@ -2017,7 +2081,7 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
2017
2081
|
// parent (projectRoot for the canonical layout) + the agents dir name (#95 follow-up).
|
|
2018
2082
|
scanManifest: () => generateManifest(serverDirAbs, projectRoot, agentsDirName)
|
|
2019
2083
|
});
|
|
2020
|
-
const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-
|
|
2084
|
+
const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-5D2ZPL74.js");
|
|
2021
2085
|
const routesHmrPlugin = serverRoutesHmrPlugin({
|
|
2022
2086
|
serverDir: serverDirAbs
|
|
2023
2087
|
});
|
|
@@ -2190,4 +2254,4 @@ export {
|
|
|
2190
2254
|
theoPluginAsync,
|
|
2191
2255
|
theoPlugin
|
|
2192
2256
|
};
|
|
2193
|
-
//# sourceMappingURL=chunk-
|
|
2257
|
+
//# sourceMappingURL=chunk-VQ62FIQW.js.map
|