theokit 0.74.0 → 0.74.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 (40) hide show
  1. package/dist/{agent-GU6JIEFJ.js → agent-5J7CQAYR.js} +2 -2
  2. package/dist/{build-6FVZKORI.js → build-7YZSJGP2.js} +3 -3
  3. package/dist/{chunk-FJEKQ4FT.js → chunk-EVKNHQWG.js} +1 -1
  4. package/dist/{chunk-FJEKQ4FT.js.map → chunk-EVKNHQWG.js.map} +1 -1
  5. package/dist/{chunk-S5QN3O22.js → chunk-NQZIQJFH.js} +3 -2
  6. package/dist/{chunk-S5QN3O22.js.map → chunk-NQZIQJFH.js.map} +1 -1
  7. package/dist/{chunk-RFZ263XP.js → chunk-QX5Q3FBJ.js} +3 -2
  8. package/dist/chunk-QX5Q3FBJ.js.map +1 -0
  9. package/dist/{chunk-ODYLKWRT.js → chunk-SG62LZOJ.js} +2 -2
  10. package/dist/cli/index.js +5 -5
  11. package/dist/{cloudflare-AMH4TJNL.js → cloudflare-Q5SUXRXY.js} +83 -3
  12. package/dist/cloudflare-Q5SUXRXY.js.map +1 -0
  13. package/dist/{deno-deploy-6H354VRA.js → deno-deploy-XW3Q6OMD.js} +94 -6
  14. package/dist/deno-deploy-XW3Q6OMD.js.map +1 -0
  15. package/dist/{dev-HJUD6X3R.js → dev-FZ4OTCPT.js} +2 -2
  16. package/dist/index.js +1 -1
  17. package/dist/{mcp-MVKDWZEQ.js → mcp-VUHRRX36.js} +2 -2
  18. package/dist/{preview-5HUID27O.js → preview-II33QNAC.js} +2 -2
  19. package/dist/{registry-OMLCP4AS.js → registry-WVUO2WNR.js} +4 -4
  20. package/dist/server/index.js +1 -1
  21. package/dist/server/storage/index.js +1 -1
  22. package/dist/theo-cloud-GIA67L4Q.js +40 -0
  23. package/dist/theo-cloud-GIA67L4Q.js.map +1 -0
  24. package/dist/vite-plugin/index.d.ts +10 -1
  25. package/dist/vite-plugin/index.js +3 -1
  26. package/dist/{vite-plugin-JVGI6QUO.js → vite-plugin-3LTZJTUM.js} +4 -2
  27. package/package.json +4 -4
  28. package/dist/chunk-RFZ263XP.js.map +0 -1
  29. package/dist/cloudflare-AMH4TJNL.js.map +0 -1
  30. package/dist/deno-deploy-6H354VRA.js.map +0 -1
  31. package/dist/theo-cloud-HMFNZ3FS.js +0 -33
  32. package/dist/theo-cloud-HMFNZ3FS.js.map +0 -1
  33. /package/dist/{agent-GU6JIEFJ.js.map → agent-5J7CQAYR.js.map} +0 -0
  34. /package/dist/{build-6FVZKORI.js.map → build-7YZSJGP2.js.map} +0 -0
  35. /package/dist/{chunk-ODYLKWRT.js.map → chunk-SG62LZOJ.js.map} +0 -0
  36. /package/dist/{dev-HJUD6X3R.js.map → dev-FZ4OTCPT.js.map} +0 -0
  37. /package/dist/{mcp-MVKDWZEQ.js.map → mcp-VUHRRX36.js.map} +0 -0
  38. /package/dist/{preview-5HUID27O.js.map → preview-II33QNAC.js.map} +0 -0
  39. /package/dist/{registry-OMLCP4AS.js.map → registry-WVUO2WNR.js.map} +0 -0
  40. /package/dist/{vite-plugin-JVGI6QUO.js.map → vite-plugin-3LTZJTUM.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -15,12 +15,12 @@ function cliVersion() {
15
15
  // src/cli/index.ts
16
16
  var cli = cac("theokit");
17
17
  cli.command("dev", "Start development server").option("--port <port>", "Port number").action(async (options) => {
18
- const { devCommand } = await import("../dev-HJUD6X3R.js");
18
+ const { devCommand } = await import("../dev-FZ4OTCPT.js");
19
19
  await devCommand({ port: options.port ? Number(options.port) : void 0 });
20
20
  });
21
21
  cli.command("build", "Build for production").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
22
22
  try {
23
- const { buildCommand } = await import("../build-6FVZKORI.js");
23
+ const { buildCommand } = await import("../build-7YZSJGP2.js");
24
24
  await buildCommand({ target: options.target });
25
25
  } catch (err) {
26
26
  const msg = err instanceof Error ? err.message : String(err);
@@ -44,7 +44,7 @@ cli.command("start", "Start production server").option("--port <port>", "Port nu
44
44
  });
45
45
  cli.command("preview", "Build for production, then serve it \u2014 one step (B-030)").option("--port <port>", "Port number").option("--target <target>", "Deploy target (node, vercel, cloudflare)").action(async (options) => {
46
46
  try {
47
- const { previewCommand } = await import("../preview-5HUID27O.js");
47
+ const { previewCommand } = await import("../preview-II33QNAC.js");
48
48
  await previewCommand({
49
49
  port: options.port ? Number(options.port) : void 0,
50
50
  target: options.target
@@ -77,7 +77,7 @@ cli.command(
77
77
  "Run an agent in the terminal (stream + tool calls + approval)"
78
78
  ).action(async (name, message) => {
79
79
  try {
80
- const { agentCommand } = await import("../agent-GU6JIEFJ.js");
80
+ const { agentCommand } = await import("../agent-5J7CQAYR.js");
81
81
  const { sawError } = await agentCommand(name, message);
82
82
  if (sawError) process.exit(1);
83
83
  } catch (err) {
@@ -108,7 +108,7 @@ cli.command("agent sessions gc", "Collect old transcripts for this project (dry
108
108
  });
109
109
  cli.command("mcp <agent>", "Serve an agent as an MCP server over stdio (for desktop MCP clients)").action(async (agent) => {
110
110
  try {
111
- const { mcpCommand } = await import("../mcp-MVKDWZEQ.js");
111
+ const { mcpCommand } = await import("../mcp-VUHRRX36.js");
112
112
  await mcpCommand(agent);
113
113
  } catch (err) {
114
114
  const msg = err instanceof Error ? err.message : String(err);
@@ -37,6 +37,7 @@ import {
37
37
 
38
38
  // src/adapters/cloudflare.ts
39
39
  import { mkdirSync, readFileSync, writeFileSync } from "fs";
40
+ import { createRequire } from "module";
40
41
  import { resolve } from "path";
41
42
  function readAssetsMapForBake(path) {
42
43
  try {
@@ -282,6 +283,56 @@ function cloudflareHandleRequestFragment(nonApiBranch, runtimeSpread, agentBranc
282
283
  `}`
283
284
  ];
284
285
  }
286
+ var WORKERS_UNSUPPORTED_STUB_PATH = ".theokit/cloudflare/unsupported-on-workers.mjs";
287
+ function absentOptionalPeers(project) {
288
+ const names = [];
289
+ for (const dependency of project.directDependencies) {
290
+ for (const peer of project.optionalPeersOf(dependency)) {
291
+ if (peer.startsWith("@types/")) continue;
292
+ if (project.isInstalled(peer)) continue;
293
+ if (!names.includes(peer)) names.push(peer);
294
+ }
295
+ }
296
+ return names;
297
+ }
298
+ function absentOptionalPeersOnDisk(cwd) {
299
+ const require_ = createRequire(resolve(cwd, "package.json"));
300
+ const readManifestOf = (name) => {
301
+ try {
302
+ return require_(`${name}/package.json`);
303
+ } catch {
304
+ return void 0;
305
+ }
306
+ };
307
+ let own;
308
+ try {
309
+ own = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf-8"));
310
+ } catch {
311
+ return [];
312
+ }
313
+ return absentOptionalPeers({
314
+ directDependencies: Object.keys(own.dependencies ?? {}),
315
+ optionalPeersOf: (name) => {
316
+ const meta = readManifestOf(name)?.peerDependenciesMeta;
317
+ return Object.entries(meta ?? {}).filter(([, value]) => value.optional === true).map(([peer]) => peer);
318
+ },
319
+ isInstalled: (name) => readManifestOf(name) !== void 0
320
+ });
321
+ }
322
+ function renderWorkersUnsupportedStub() {
323
+ return [
324
+ "// Generated by Theo \u2014 Cloudflare adapter",
325
+ "//",
326
+ "// Every optional peer dependency this project did not install is aliased here, because",
327
+ "// wrangler resolves a bare dynamic import at BUILD time and a missing one fails the whole",
328
+ "// bundle. Reaching this module means the code asked for an optional backend and no loader",
329
+ "// override was supplied.",
330
+ "throw new Error(",
331
+ " 'An optional dependency was imported on Cloudflare Workers and this project did not install it. Supply a loader override for it, or install it and rebuild \u2014 the build regenerates the [alias] table in wrangler.toml from what is present.',",
332
+ ")",
333
+ ""
334
+ ].join("\n");
335
+ }
285
336
  function renderWranglerToml(opts) {
286
337
  return [
287
338
  `# Generated by Theo \u2014 Cloudflare Workers`,
@@ -332,7 +383,25 @@ function renderWranglerToml(opts) {
332
383
  `# with the same vice, in silence.`,
333
384
  ``,
334
385
  `# Environment variables are set via wrangler secret or dashboard`,
335
- `# Example: wrangler secret put DATABASE_URL`
386
+ `# Example: wrangler secret put DATABASE_URL`,
387
+ // Emitted only when something is absent: an empty `[alias]` reads as a decision nobody made.
388
+ // After `[assets]` on purpose — TOML tables are positional, so these keys must open their own.
389
+ ...(opts?.workersUnsupported ?? []).length === 0 ? [] : [
390
+ ``,
391
+ `# Optional peer dependencies this project did not install, derived from every direct`,
392
+ `# dependency's \`peerDependenciesMeta.<name>.optional\`. Wrangler resolves a bare dynamic`,
393
+ `# import at BUILD time, so one absent module fails the whole bundle over a code path that`,
394
+ `# only runs when a consumer asked for that backend and supplied no override. Measured`,
395
+ `# 2026-09-30 on a fresh scaffold: \`Could not resolve "better-sqlite3"\` from`,
396
+ `# @theokit/sdk/dist, before any request existed.`,
397
+ `#`,
398
+ `# Install one of these and rebuild: it leaves this table, because the build derives it from`,
399
+ `# what is present rather than from a list written here.`,
400
+ `[alias]`,
401
+ ...(opts?.workersUnsupported ?? []).map(
402
+ (name) => `"${name}" = "./${WORKERS_UNSUPPORTED_STUB_PATH}"`
403
+ )
404
+ ]
336
405
  ].join("\n");
337
406
  }
338
407
  var cloudflareAdapter = {
@@ -407,12 +476,19 @@ var cloudflareAdapter = {
407
476
  wsRoutes: scanned.wsRoutes
408
477
  })
409
478
  );
479
+ const workersUnsupported = absentOptionalPeersOnDisk(cwd);
480
+ writeFileSync(resolve(cwd, WORKERS_UNSUPPORTED_STUB_PATH), renderWorkersUnsupportedStub());
410
481
  writeFileSync(
411
482
  resolve(cwd, "wrangler.toml"),
412
483
  // B-263 — the toml decides whether the worker or the CDN answers the document, and the two
413
484
  // modes need different `not_found_handling`. Passing the flag is what keeps them apart.
414
- renderWranglerToml({ ssrStreaming: config.ssrStreaming })
485
+ renderWranglerToml({ ssrStreaming: config.ssrStreaming, workersUnsupported })
415
486
  );
487
+ if (workersUnsupported.length > 0) {
488
+ console.log(
489
+ ` \u2139 ${String(workersUnsupported.length)} absent optional peer dependenc${workersUnsupported.length === 1 ? "y" : "ies"} aliased so wrangler can bundle \u2014 see [alias] in wrangler.toml`
490
+ );
491
+ }
416
492
  console.log("\n \u2713 Cloudflare output \u2192 .theokit/cloudflare/ + wrangler.toml");
417
493
  console.log(
418
494
  `${describeDeployedSecurityHeaders({
@@ -430,10 +506,14 @@ var cloudflareAdapter = {
430
506
  }
431
507
  };
432
508
  export {
509
+ WORKERS_UNSUPPORTED_STUB_PATH,
510
+ absentOptionalPeers,
511
+ absentOptionalPeersOnDisk,
433
512
  cloudflareAdapter,
434
513
  readAssetsMapForBake,
435
514
  readDocumentShell,
436
515
  renderCloudflareWorkerEntry,
516
+ renderWorkersUnsupportedStub,
437
517
  renderWranglerToml
438
518
  };
439
- //# sourceMappingURL=cloudflare-AMH4TJNL.js.map
519
+ //# sourceMappingURL=cloudflare-Q5SUXRXY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/cloudflare.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport { parseAssetsMap } from '../core/contracts/module-preloads.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { deployedAgentsFragment, type DeployedAgent } from './deployed-agents.js'\nimport { renderBakedRoutes, routeRuntimeLines } from './deployed-baked-routes.js'\nimport { deployedCorsFragment, type DeployedCorsOptions } from './deployed-cors.js'\nimport { deployedCsrfFragment, type DeployedCsrfOptions } from './deployed-csrf.js'\nimport { readDocumentShell } from './deployed-document-shell.js'\nimport { planDeployedPlugins } from './deployed-plugins-module.js'\nimport {\n deployedRateLimitFragment,\n rateLimitCheckFragment,\n type DeployedRateLimitOptions,\n} from './deployed-rate-limit.js'\nimport {\n deployedRuntimeConfigFragment,\n serverDirLiteral,\n type DeployedRuntimeConfigOptions,\n type DeployedServerDirOptions,\n} from './deployed-runtime-config.js'\nimport { deployedTraceFragment } from './deployed-trace.js'\nimport { nodeAdapter } from './node.js'\nimport { describeDeployedSecurityHeaders, securityHeadersDeclarations } from './security-headers.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\n/**\n * T2.1 — Cloudflare adapter rewritten to consume `theokit/adapters/web-shim`\n * instead of emitting an inline plain-object Request/Response shim. Reduces\n * template from ~50 lines to ~25 and centralizes maintenance.\n */\n\n/**\n * The built `index.html` split into the part before `<div id=\"root\">` and the\n * part after it.\n *\n * Refuses by name when streaming is on and the template is missing, rather than\n * emitting a worker that serves a headless document. A build that cannot produce\n * a correct artifact should say so at build time; the alternative is a deploy\n * that looks successful and serves pages with no stylesheet.\n */\n// `readDocumentShell` moved to `./deployed-document-shell.js` (B-317): the Vercel target needs the\n// same shell, and an adapter importing another adapter is the coupling `deployed-baked-routes.ts`\n// was extracted to avoid.\n\n// Re-exported because this module's tests import it from here, and the move is not their subject.\nexport { readDocumentShell }\n\n/**\n * Read the map `theokit build` emitted, for baking into the worker (B-035).\n *\n * Returns `undefined` — never throws — for an absent or malformed file. A deploy that failed\n * because an optimisation was missing would turn a lost round trip into a broken build, and the\n * worker is correct without it.\n */\nexport function readAssetsMapForBake(path: string): Record<string, string[]> | undefined {\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- `path` is built by the one caller from a fixed `.theokit/client` layout; this returns `undefined` for anything it cannot read\n return parseAssetsMap(readFileSync(path, 'utf8'))\n } catch {\n return undefined\n }\n}\n\n/**\n * The baked route-to-chunks map, as worker source (B-035).\n *\n * A literal and not a read, for the same reason the document shell is: a Worker has no filesystem\n * at request time. Emitted only when the build produced a map; otherwise the worker carries\n * nothing about preloads and behaves exactly as before.\n *\n * **It emits the DATA and no longer the code.** An earlier revision emitted a hand-written copy of\n * `injectModulePreloads`, justified by a claim that the worker could not import it. That claim was\n * false — `theokit/server` re-exports from `core/contracts/` and the generated worker already\n * imports that subpath — and the copy drifted within a day: a fix for proxy-form request targets\n * landed in the original and not in the duplicate. The worker now imports the real function, which\n * is the shape `adapters/security-headers.ts` established one directory over for exactly this\n * build-half/runtime-half pair.\n */\n/**\n * Whether this worker can preload at all (B-035).\n *\n * ONE predicate, read by the bake, the import and the call. Two conditions for one decision is\n * how the import came to be emitted for a worker that had no map to give it — found at review, in\n * the same change where a copy and its original had already drifted apart.\n *\n * Streaming is required because the other branch serves the document from `env.ASSETS` as a\n * static file and can inject nothing into it.\n */\nfunction preloadsApplyTo(opts: {\n ssrStreaming?: boolean\n assetsMap?: Record<string, string[]>\n}): boolean {\n return opts.ssrStreaming === true && opts.assetsMap !== undefined\n}\n\nfunction renderPreloadSupport(assetsMap: Record<string, string[]> | undefined): string {\n if (assetsMap === undefined) return ''\n return `const __THEO_ASSETS_MAP = ${JSON.stringify(assetsMap)}`\n}\n\n/**\n * The branch that answers a non-API request: streamed SSR, or the static asset the Worker\n * platform serves. Extracted at review — it is the one decision in this emitter with two whole\n * shapes behind it, and keeping it inline pushed the caller past its line budget.\n */\nfunction renderNonApiBranch(\n opts: NonNullable<Parameters<typeof renderCloudflareWorkerEntry>[0]>,\n preloadsApply: boolean,\n): string {\n return opts.ssrStreaming\n ? [\n ` // B-263 — a worker that owns \\`/\\` also receives \\`/robots.txt\\` and \\`/logo.png\\`, because`,\n ` // \\`run_worker_first\\` in wrangler.toml is what stops the asset handler answering the`,\n ` // document before this code runs. Rendering SSR for those would answer a text file with HTML.`,\n ` //`,\n ` // B-331 — the extension is a PRE-FILTER, no longer the decision. It used to decide: a dot in`,\n ` // the last segment meant \"static file\", so \\`/users/john.doe\\`, \\`/v1.2/docs\\` and`,\n ` // \\`/reports/2026.q3\\` were handed to a handler that does not have them, and with`,\n ` // \\`not_found_handling = \"none\"\\` that is a 404 for a page the app renders.`,\n ` //`,\n ` // Now a miss falls through to SSR, so a wrong guess costs one local lookup instead of the`,\n ` // response. Keeping the pre-filter is what keeps that lookup OFF every ordinary page`,\n ` // request: \\`/dashboard\\` never touches the binding.`,\n ` //`,\n ` // Still uncovered, and stated rather than implied: a static file with NO extension, such as`,\n ` // a \\`public/CNAME\\`. The pre-filter never asks for it, so it renders as a document. That`,\n ` // is the pre-existing behaviour and the rarer direction of the two.`,\n ` const lastSegment = url.pathname.slice(url.pathname.lastIndexOf('/') + 1)`,\n ` if (lastSegment.includes('.')) {`,\n ` const staticAssets = env?.ASSETS`,\n ` if (staticAssets === undefined) return notFoundResponse()`,\n ` const hit = await staticAssets.fetch(request)`,\n ` if (hit.status !== 404) return withSecurityHeaders(hit, SECURITY_HEADERS)`,\n ` }`,\n ``,\n ` // T2.3 — streaming SSR for non-API routes`,\n ` // The same primitive \\`theokit start\\` uses, not a second one:`,\n ` // 16 bytes of Web Crypto entropy, base64.`,\n ` const nonce = generateNonce()`,\n ` return withSecurityHeaders(`,\n ` await renderStreamingWeb(request, {`,\n !preloadsApply\n ? ` htmlHead: ${JSON.stringify(opts.htmlHead ?? '')},`\n : ` htmlHead: injectModulePreloads(${JSON.stringify(opts.htmlHead ?? '')}, __THEO_ASSETS_MAP, new URL(request.url).pathname),`,\n ` htmlTail: ${JSON.stringify(opts.htmlTail ?? '')},`,\n ` nonce,`,\n ` }),`,\n ` buildSecurityHeaders(SECURITY_HEADERS_CONFIG, { production: true }, { nonce }),`,\n ` )`,\n ].join('\\n')\n : [\n ` // #412 — the document, served by the worker so it carries the same baseline every`,\n ` // API response carries. It used to return 404 here while wrangler.toml declared a`,\n ` // \\`[site]\\` bucket nothing read, so the page was missing rather than unprotected.`,\n ` const assets = env?.ASSETS`,\n ` // A wrangler.toml that predates this binding has no ASSETS. Reading .fetch off`,\n ` // undefined would turn every page request into a 500; 404 is what this target`,\n ` // answered before, which is the honest fallback rather than a new failure.`,\n ` if (assets === undefined) return notFoundResponse()`,\n ` return withSecurityHeaders(await assets.fetch(request), SECURITY_HEADERS)`,\n ].join('\\n')\n}\n\nexport function renderCloudflareWorkerEntry(\n opts: {\n ssrStreaming?: boolean\n htmlHead?: string\n htmlTail?: string\n /**\n * B-035 — the route-to-chunks map, baked as a literal for the same reason the shell above\n * is: a Worker has no filesystem at request time. Absent when the build produced no map,\n * and then nothing about preloads is emitted at all.\n */\n assetsMap?: Record<string, string[]>\n securityHeaders?: SecurityHeadersConfig\n /**\n * The server routes, scanned on the BUILD machine (#369).\n *\n * A Worker has no filesystem, so the worker used to call `scanServerRoutes` — a `readdirSync` —\n * against a directory that does not exist there, and load each module through `import()` of a\n * file path. Both are baked here instead, which is the road this adapter already takes for the\n * document shell one function away.\n *\n * `filePath` is relative to the project root and is used for two things: the key the executor\n * looks a module up by, and the specifier the static import uses. Paths are emitted relative to\n * `.theokit/cloudflare/`, where the worker is written.\n */\n routes?: readonly { filePath: string; routePath: string; methods?: readonly string[] }[]\n /**\n * The two concerns #410 could not bake (#425), composed from one place so the six targets\n * cannot drift into six spellings. `serialization` is a literal like the security values above;\n * `plugins` is an import, because a closure has no literal.\n */\n runtimeConfigModule?: DeployedRuntimeConfigOptions['runtimeConfigModule']\n serialization?: DeployedRuntimeConfigOptions['serialization']\n /**\n * The app's agents, scanned on the build machine (#367).\n *\n * A different scan from `routes` and served by a different function — which is why a worker\n * that only consulted the route table answered every `/api/agents/<name>` with a 404.\n */\n agents?: readonly DeployedAgent[]\n /** B-185 — the app's `server/context.ts`, project-relative, or absent when it declares none. */\n contextModule?: string\n /** WebSocket route files, scanned on the build machine. Only their presence is used (#369). */\n wsRoutes?: readonly string[]\n } & DeployedServerDirOptions &\n DeployedCsrfOptions &\n DeployedCorsOptions &\n DeployedRateLimitOptions = {},\n): string {\n // `/@theo/entry-server` is a VITE virtual id (`vite-plugin/index.ts`), and `adapters/node.ts`\n // resolves it by passing it as the vite build's `input`. This file is not bundled by vite: wrangler\n // hands it to esbuild, which has no such module and fails the build before a request is ever sent —\n // measured 2026-09-26 on the first real `wrangler deploy` this repository attempted (B-263).\n //\n // The same build writes the real thing next door. The worker lands at\n // `.theokit/cloudflare/worker.mjs` and the renderer at `.theokit/server/entry-server.js`, so one\n // directory up is the spelling esbuild can follow.\n const streamingImport = opts.ssrStreaming\n ? `import { renderStreamingWeb } from '../server/entry-server.js'`\n : `// (ssrStreaming off: renderStreamingWeb not imported)`\n // #343 — the document shell is inlined as a build-time literal because a Worker\n // has no filesystem to read `index.html` from at request time. Without it,\n // `renderStreamingWeb` falls back to its empty-string defaults and the response\n // is React output with no `<html>`, no `<head>`, no stylesheet and no client\n // entry — hydration data for a page that cannot hydrate. The streaming\n // assembly was fixed in the generated entry and this, its only caller, was\n // left passing nothing.\n //\n // `JSON.stringify` and not a template literal: the shell contains quotes,\n // angle brackets and a `</script>`, and embedding it naively produces a worker\n // that fails to parse at deploy time rather than here.\n //\n // #410 — this is also the ONE deploy path that renders HTML at request time,\n // so the one that can mint a per-request CSP nonce: `renderStreamingWeb`\n // threads `options.nonce` into `renderToReadableStream` and into the hydration\n // script (`router/entry-server.ts`). Every other response below carries the\n // nonce-less baseline, which is what `buildSecurityHeaders` already does for a\n // prerendered route (EC-4).\n // Only the streaming branch renders HTML at request time; the other serves the document from\n // `env.ASSETS` as a static file and can inject nothing. Baking the map for it would ship the whole\n // route table as dead weight — found at review, where the emitter was measured declaring it with\n // zero call sites.\n const preloadsApply = preloadsApplyTo(opts)\n const preloadSupport = preloadsApply ? renderPreloadSupport(opts.assetsMap) : ''\n\n const nonApiBranch = renderNonApiBranch(opts, preloadsApply)\n\n // CR-006: Workers lack `process.cwd()` and the `node:*` import surface\n // is brittle even under `nodejs_compat`. We use the Web Crypto\n // `crypto.randomUUID()` instead of `node:crypto.randomUUID`, and embed\n // the server directory as a build-time literal instead of resolving via\n // `node:path` at runtime.\n const { routeImports, routeModuleEntries, routeTableEntries } = renderBakedRoutes(\n opts.routes ?? [],\n )\n const runtimeConfig = deployedRuntimeConfigFragment(opts)\n const agentsFragment = deployedAgentsFragment(\n opts.agents === undefined\n ? undefined\n : // B-185 — a Worker has no filesystem, so its identity module is baked exactly like its\n // agents and its routes (ADR 0014). `contextModule` is `undefined` for an app that\n // declares none, and the generator then emits no import for it.\n { kind: 'baked', agents: opts.agents, contextModule: opts.contextModule },\n {\n wrapSecurityHeaders: true,\n // B-185 — bound only when the runtime-config fragment declared the const, which is\n // exactly when a plugins module was emitted. Referencing it otherwise would emit an\n // identifier the entry never declares.\n pluginRunnerExpr:\n opts.runtimeConfigModule === undefined ? undefined : 'await THEO_PLUGIN_RUNNER',\n },\n )\n\n return [\n `// Generated by Theo — Cloudflare Workers Adapter`,\n `//`,\n `// REQUIREMENTS (EC-3):`,\n `// - wrangler.toml MUST include compatibility_flags = [\"nodejs_compat\"]`,\n `// (still required for transitive theokit/server deps, e.g. busboy)`,\n `// - package.json MUST list \"theokit\" in dependencies (not devDependencies)`,\n `// so Wrangler bundles theokit and its transitive deps`,\n `// - Deploy: wrangler deploy`,\n ``,\n // NEVER the `theokit/server` umbrella. It is deprecated (the package prints so on import) and it\n // is what made the first real `wrangler deploy` fail: bisected 2026-09-26 with a worker whose\n // only line was `import { matchRoute } from 'theokit/server'`, and that alone pulled\n // `@swc/core`'s `.node` native addon into the bundle. workerd cannot load a `.node` at any\n // bundler setting, so it is not a flag away from working (B-263).\n //\n // The narrow subpaths were probed the same way and are clean — `wrangler deploy --dry-run`\n // exit 0, zero swc, zero errors for both. Which symbol lives where was read from the modules\n // rather than grepped, since these indexes use `export *`:\n //\n // theokit/server/scan matchRoute, compilePattern\n // theokit/server/http executeRoute, extractTraceIdFromRequest, TRACE_HEADER,\n // createCorsWebHandler, injectModulePreloads\n !preloadsApply\n ? `import { matchRoute, compilePattern } from 'theokit/server/scan'\\nimport { executeRoute, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler } from 'theokit/server/http'`\n : `import { matchRoute, compilePattern } from 'theokit/server/scan'\\nimport { executeRoute, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler, injectModulePreloads } from 'theokit/server/http'`,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n opts.ssrStreaming\n ? `import { buildSecurityHeaders, generateNonce, withSecurityHeaders } from 'theokit/adapters/security-headers'`\n : `import { buildSecurityHeaders, withSecurityHeaders } from 'theokit/adapters/security-headers'`,\n `// T3.4 — WS bridge for Cloudflare Workers`,\n `import { createCloudflareWsBridge } from 'theokit/adapters/ws-shim'`,\n // B-027 — only when a limit is declared. `createRateLimiterWeb` is used at module scope by the\n // fragment below and was imported by no target but bun, so an entry that declared a limit threw\n // when it LOADED. `resolveClientIpFromRequest` is the forwarded-header fallback, behind\n // `trustProxy`; the primary source is `cf-connecting-ip`, which the Workers runtime writes.\n ...(opts.rateLimit === undefined\n ? []\n : [\n `import { createRateLimiterWeb } from 'theokit/server/rate-limit'`,\n `import { resolveClientIpFromRequest } from 'theokit/server/rate-limit'`,\n ]),\n streamingImport,\n ``,\n ...runtimeConfig.imports,\n ...agentsFragment.imports,\n ...routeImports,\n ``,\n `// CR-006: server directory is a build-time literal — Workers cannot`,\n `// call process.cwd() and resolving paths at runtime returned '/server'.`,\n `const serverDir = ${serverDirLiteral(opts)}`,\n ``,\n ...routeRuntimeLines(routeModuleEntries, routeTableEntries, 'cloudflare'),\n `// #410 — the security baseline \\`theokit start\\` puts on every response, carried`,\n `// here as a literal because a Worker has no theo.config.ts to read. Same`,\n `// function, same input, so the deployed page and the local one cannot`,\n `// disagree about what the configuration means.`,\n preloadSupport,\n ...securityHeadersDeclarations(opts.securityHeaders),\n ``,\n ...deployedCsrfFragment(opts, 'a Worker'),\n ``,\n ...runtimeConfig.declarations,\n ...agentsFragment.declarations,\n ...deployedCorsFragment(opts.cors, 'cloudflare'),\n `// #369 — whether this project declares a WebSocket route, decided at build time. It used`,\n `// to answer it with \\`scanWebSocketRoutes\\`, which is the same readdirSync.`,\n `const HAS_WS_ROUTES = ${String((opts.wsRoutes ?? []).length > 0)}`,\n ``,\n `function notFoundResponse() {`,\n ` return withSecurityHeaders(`,\n ` new Response(`,\n ` JSON.stringify({ error: { code: 'NOT_FOUND', message: 'Route not found' } }),`,\n ` { status: 404, headers: { 'Content-Type': 'application/json' } },`,\n ` ),`,\n ` SECURITY_HEADERS,`,\n ` )`,\n `}`,\n ``,\n ...cloudflareHandleRequestFragment(\n nonApiBranch,\n runtimeConfig.executeRouteSpread,\n agentsFragment.branch,\n agentsFragment.hostBypass,\n opts.rateLimit,\n ),\n ].join('\\n')\n}\n\n/**\n * The Worker's request handler, as generated source.\n *\n * Extracted for the reason `vercel.ts` extracts its own fragments: the emitter is one array\n * literal, so every line the entry gains counts against `max-lines-per-function`, and #410 added\n * the CSRF literal to an emitter already sitting exactly at the ceiling.\n *\n * @param nonApiBranch - what a non-`/api/` request gets, which differs with `ssrStreaming`\n */\nfunction cloudflareHandleRequestFragment(\n nonApiBranch: string,\n runtimeSpread: string,\n agentBranch: readonly string[],\n /** SI-020 — the condition that keeps an agent card path out of the static-asset branch. */\n hostBypass: string,\n /** B-027 — the declared limit, or `undefined`. Passed rather than read: this fragment is a\n * separate function from `renderCloudflareWorkerEntry`, where `opts` is bound. */\n rateLimit: DeployedRateLimitOptions['rateLimit'],\n): string[] {\n return [\n `async function handleRequest(request, url, env) {`,\n ` if (!url.pathname.startsWith('/api/')${hostBypass}) {`,\n nonApiBranch,\n ` }`,\n ``,\n ...agentBranch,\n ``,\n ` const match = matchRoute(url.pathname, routes)`,\n ` if (!match) return notFoundResponse()`,\n ``,\n ` const { req, res, toResponse } = createWebShim(request, { trustedProxy: 'platform' })`,\n ...deployedTraceFragment('request', ' '),\n ` const method = request.method.toUpperCase()`,\n ` // #382 — the run is NOT awaited before the Response is taken. toResponse()`,\n ` // settles as soon as status + headers are known and carries a live body, so`,\n ` // the Worker starts flushing while the handler is still writing. Awaiting`,\n ` // executeRoute() first would re-buffer the whole response here even though`,\n ` // the shim streams; awaiting toResponse() does not — it settles at the head.`,\n ` return withSecurityHeaders(await toResponse(executeRoute({`,\n ` route: match.route, method, params: match.params,`,\n ` req, res, loadModule, serverDir, requestId, ...CSRF_CONFIG, ${runtimeSpread}`,\n ` })), SECURITY_HEADERS)`,\n `}`,\n ``,\n ...deployedRateLimitFragment(\n rateLimit,\n 'cloudflare',\n // `cf-connecting-ip` is the RUNTIME's answer and a caller cannot forge it through the edge.\n // A Worker reached directly has none, and then only a declared `trustProxy` produces an\n // address — otherwise `undefined`, which is the named 503 rather than everyone's bucket.\n `request.headers.get('cf-connecting-ip') ?? resolveClientIpFromRequest(request, TRUST_PROXY)`,\n // `request` alone: the handler binds `(request, env, ctx)` and the header is on the request.\n 'request',\n ),\n ``,\n `export default {`,\n ` async fetch(request, env, ctx) {`,\n ` const url = new URL(request.url)`,\n ``,\n ` // #409 — the preflight is answered BEFORE anything routes: an OPTIONS the router handles`,\n ` // is an OPTIONS the browser never gets a CORS answer to. The WebSocket upgrade above is`,\n ` // deliberately upstream of it — a 101 is not a CORS-governed response.`,\n ` const preflight = corsPreflight(request)`,\n ` if (preflight !== null) return withSecurityHeaders(preflight, SECURITY_HEADERS)`,\n ...rateLimitCheckFragment(rateLimit, ' ', 'request'),\n ``,\n ` // LCR0103 — the upgrade branch sits BELOW the limiter, and the order is the point.`,\n ` // It used to sit above, under the comment \"a 101 carries no document and no script,`,\n ` // so the security baseline does not apply to it\". That is true of the security`,\n ` // HEADERS — a document concern — and false of the limiter, which is a resource`,\n ` // concern: a long-lived socket is the most expensive thing this entry hands out, so`,\n ` // the upgrade is the path that most needs a budget, not the one that may skip it.`,\n ` // T3.4 — Detect WebSocket upgrade and delegate to the CF bridge.`,\n ` // A 101 carries no document and no script, so the security baseline does`,\n ` // not apply to it.`,\n ` if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {`,\n ` if (!HAS_WS_ROUTES) return notFoundResponse()`,\n ` const cfWs = createCloudflareWsBridge({`,\n ` onOpen: () => {},`,\n ` onMessage: (ws, data) => { ws.send(data) },`,\n ` onClose: () => {},`,\n ` })`,\n ` return cfWs.handle(request)`,\n ` }`,\n ``,\n ` return withCors(request, await handleRequest(request, url, env))`,\n ` },`,\n `}`,\n ]\n}\n\n/** Where the build writes the stub every alias points at, relative to the project root. */\nexport const WORKERS_UNSUPPORTED_STUB_PATH = '.theokit/cloudflare/unsupported-on-workers.mjs'\n\n/** What `absentOptionalPeers` needs to know, injectable so the decision is testable without a tree. */\nexport interface OptionalPeerProject {\n /** The project's own direct dependency names. */\n readonly directDependencies: readonly string[]\n /** The names each dependency declares as `peerDependenciesMeta.<name>.optional === true`. */\n readonly optionalPeersOf: (name: string) => readonly string[]\n /** Whether a name resolves from the project. */\n readonly isInstalled: (name: string) => boolean\n}\n\n/**\n * The optional peer dependencies this project does NOT have, which wrangler will try to resolve.\n *\n * A dependency that reaches an optional backend through `import('better-sqlite3')` leaves a\n * statically resolvable specifier in its dist. `nodejs_compat` does not help — these are npm\n * packages, not builtins — so esbuild fails the whole bundle at BUILD time over a module the code\n * only reaches when a consumer asked for that backend and supplied no override.\n *\n * Measured 2026-09-30 on a fresh `create-theokit` scaffold installed from npm: `wrangler deploy`\n * answered `Could not resolve \"better-sqlite3\"` from `@theokit/sdk/dist/chunk-XZHJYSPB.js:76`, and\n * the scaffold had 28 absent optional peers across four owners.\n *\n * **Derived, never written down.** The first cut of this listed the three names a grep of that dist\n * produced, and was already incomplete — the SDK declares seven optional peers and `@lancedb/lancedb`\n * was not among the three. A list of names here is a second copy of a fact the dependency publishes,\n * and it rots on the release where the dependency adds the eighth.\n *\n * Two filters, both load-bearing:\n *\n * - an INSTALLED optional peer is left alone. The consumer installed it deliberately, and the table\n * shrinks by itself the day they do.\n * - `@types/*` is skipped: a types package never appears in a runtime import graph.\n *\n * Aliasing a module the worker graph never imports is inert — it changes nothing. That is why this\n * does not try to decide which owners are \"server-side\"; that judgement is the hardcoding this\n * design removes.\n *\n * @param project the dependency facts, read from the project or injected by a test\n * @returns the names to alias, in first-seen order, each appearing once\n */\nexport function absentOptionalPeers(project: OptionalPeerProject): string[] {\n const names: string[] = []\n for (const dependency of project.directDependencies) {\n for (const peer of project.optionalPeersOf(dependency)) {\n if (peer.startsWith('@types/')) continue\n if (project.isInstalled(peer)) continue\n if (!names.includes(peer)) names.push(peer)\n }\n }\n return names\n}\n\n/**\n * The module every alias resolves to: it throws when the import is actually reached.\n *\n * Reaching it means the code asked for an optional backend and no override was supplied, which is\n * precisely the condition the calling seam exists for. The message names the module and the remedy,\n * because without it the runtime error is about a path inside `.theokit`.\n *\n * @returns the stub's source\n */\n/**\n * `absentOptionalPeers` against the project on disk.\n *\n * Separated from the decision so the decision stays testable without a `node_modules` tree, which is\n * the same split `unpublished-pins.ts` makes and for the same reason. A dependency whose manifest\n * cannot be read contributes nothing rather than failing the build: an unreadable manifest is a\n * different problem, and a build that refuses over it would refuse over a shape nobody predicted.\n *\n * @param cwd the project root\n * @returns the names to alias, or an empty list when the project's own manifest cannot be read\n */\nexport function absentOptionalPeersOnDisk(cwd: string): string[] {\n const require_ = createRequire(resolve(cwd, 'package.json'))\n const readManifestOf = (name: string): Record<string, unknown> | undefined => {\n try {\n return require_(`${name}/package.json`) as Record<string, unknown>\n } catch {\n return undefined\n }\n }\n\n let own: Record<string, unknown>\n try {\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- the project's own manifest\n own = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf-8')) as Record<string, unknown>\n } catch {\n return []\n }\n\n return absentOptionalPeers({\n directDependencies: Object.keys(own.dependencies ?? {}),\n optionalPeersOf: (name) => {\n const meta = readManifestOf(name)?.peerDependenciesMeta as\n | Record<string, { optional?: boolean }>\n | undefined\n return Object.entries(meta ?? {})\n .filter(([, value]) => value.optional === true)\n .map(([peer]) => peer)\n },\n isInstalled: (name) => readManifestOf(name) !== undefined,\n })\n}\n\nexport function renderWorkersUnsupportedStub(): string {\n return [\n '// Generated by Theo — Cloudflare adapter',\n '//',\n '// Every optional peer dependency this project did not install is aliased here, because',\n '// wrangler resolves a bare dynamic import at BUILD time and a missing one fails the whole',\n '// bundle. Reaching this module means the code asked for an optional backend and no loader',\n '// override was supplied.',\n 'throw new Error(',\n \" 'An optional dependency was imported on Cloudflare Workers and this project did not install \" +\n 'it. Supply a loader override for it, or install it and rebuild — the build regenerates the ' +\n \"[alias] table in wrangler.toml from what is present.',\",\n ')',\n '',\n ].join('\\n')\n}\n\nexport function renderWranglerToml(opts?: {\n ssrStreaming?: boolean\n workersUnsupported?: readonly string[]\n}): string {\n return [\n `# Generated by Theo — Cloudflare Workers`,\n `name = \"theo-app\"`,\n `main = \".theokit/cloudflare/worker.mjs\"`,\n `compatibility_date = \"2025-09-01\"`,\n `compatibility_flags = [\"nodejs_compat\"]`,\n ``,\n `# #412 — \\`[assets]\\` with a BINDING, not the legacy \\`[site]\\` bucket.`,\n `#`,\n `# \\`[site]\\` uploads to KV and is read through \\`kv-asset-handler\\` and a`,\n `# \\`__STATIC_CONTENT\\` binding — neither of which this worker has ever had. So the`,\n `# bucket was declared, uploaded, and consumed by nothing: with ssrStreaming off, a`,\n `# deploy answered 404 for its own page. This binding is what the worker calls.`,\n `[assets]`,\n `directory = \".theokit/client\"`,\n `binding = \"ASSETS\"`,\n `# B-263 — WITHOUT this key Cloudflare's asset handler answers before the worker for any path`,\n `# that matches a file, and \\`.theokit/client/index.html\\` matches \\`/\\`. Measured on the first real`,\n `# deploy: \\`GET /\\` returned 200 with an empty \\`<div id=\"root\">\\` and NONE of the six security`,\n `# headers, while \\`/api/health\\` — a path no file matches — was answered by the worker and carried`,\n `# them. The document was coming off the CDN and the code that renders it never ran.`,\n `#`,\n `# The negation keeps the 300-odd hashed build outputs on the CDN path: \\`true\\` would satisfy the`,\n `# precedence and pay a JS invocation per stylesheet and chunk.`,\n `run_worker_first = [\"/*\", \"!/assets/*\"]`,\n ...(opts?.ssrStreaming === true\n ? [\n `# The worker renders every extension-less path itself, so an asset MISS is a genuine 404.`,\n `# \\`single-page-application\\` here would answer \\`/nope.png\\` with 200 and an HTML body.`,\n `not_found_handling = \"none\"`,\n ]\n : [\n `# A client-routed app asks for /dashboard, which is no file, and with streaming off the`,\n `# worker forwards it to ASSETS rather than rendering it. Without this the asset handler`,\n `# 404s a deep link and the SPA never boots.`,\n `not_found_handling = \"single-page-application\"`,\n ]),\n ``,\n `# B-332 — the [define] \"import.meta.url\" compensation lived here until 2026-09-28.`,\n `#`,\n `# Cloudflare executes the top-level module during validation, so a dependency that resolves a`,\n `# path at load time refused the whole upload (code 10021). The cause was @theokit/sdk, in`,\n `# internal/providers/catalog-loader.ts, fixed upstream and published in 5.9.2 — measured on the`,\n `# published tarball: 0 of 254 executable files resolve a path at module scope.`,\n `#`,\n `# The floor this framework declares is now ^5.9.2, so the versions that needed the compensation`,\n `# cannot be installed. A declared dependency is the honest form of that requirement;`,\n `# substituting a literal for import.meta.url was the form that also covered the NEXT dependency`,\n `# with the same vice, in silence.`,\n ``,\n `# Environment variables are set via wrangler secret or dashboard`,\n `# Example: wrangler secret put DATABASE_URL`,\n // Emitted only when something is absent: an empty `[alias]` reads as a decision nobody made.\n // After `[assets]` on purpose — TOML tables are positional, so these keys must open their own.\n ...((opts?.workersUnsupported ?? []).length === 0\n ? []\n : [\n ``,\n `# Optional peer dependencies this project did not install, derived from every direct`,\n `# dependency's \\`peerDependenciesMeta.<name>.optional\\`. Wrangler resolves a bare dynamic`,\n `# import at BUILD time, so one absent module fails the whole bundle over a code path that`,\n `# only runs when a consumer asked for that backend and supplied no override. Measured`,\n `# 2026-09-30 on a fresh scaffold: \\`Could not resolve \"better-sqlite3\"\\` from`,\n `# @theokit/sdk/dist, before any request existed.`,\n `#`,\n `# Install one of these and rebuild: it leaves this table, because the build derives it from`,\n `# what is present rather than from a list written here.`,\n `[alias]`,\n ...(opts?.workersUnsupported ?? []).map(\n (name) => `\"${name}\" = \"./${WORKERS_UNSUPPORTED_STUB_PATH}\"`,\n ),\n ]),\n ].join('\\n')\n}\n\nexport const cloudflareAdapter: DeployAdapter = {\n // #367 / #369 and `docs/adr/0020`. Wrangler runs esbuild at deploy time, so bundling here\n // twice would be work with no observable effect — validated live 2026-09-26\n // (`records/acceptance/evidence/b263-cloudflare-deploy.txt`). There is still no source tree in\n // a Worker, which is why it bakes.\n readsSourceAtRunTime: false,\n specifiersResolvedBy: 'the-platform',\n name: 'cloudflare',\n streamsResponses: true,\n // #409 / #410 — the generated entry calls `executeRoute` with routes, loader\n // and serverDir only. CSRF, route policy, file middleware and Zod validation\n // still run because they live inside `executeRoute`; none of the remaining\n // configurable concerns reach it. Declared explicitly rather than omitted so\n // the gap is a statement in the source and not an absence.\n //\n // `securityHeaders` IS applied: the worker carries `security.headers` as a\n // literal and puts the built baseline on every response it returns, including\n // the streamed SSR document — with a per-request nonce, the only deploy path\n // that can mint one (`adapters/security-headers.ts`).\n servesAgents: true,\n appliesConfig: ['securityHeaders', 'csrf', 'disallowed', 'cors', 'serialization', 'plugins'],\n // B-257 — per-invocation: an in-process counter does not survive, so a declared limit needs a durable store.\n enforcesRateLimit: 'with-a-store',\n\n async build(config: TheoConfig, cwd: string, ctx?: AdapterBuildContext): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('cloudflare', readManifest(cwd))\n\n // 1. Run the standard Node build first (ctx forwarded so nodeAdapter has makeVitePlugins)\n await nodeAdapter.build(config, cwd, ctx)\n\n const outputDir = resolve(cwd, '.theokit/cloudflare')\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time output directory, derived from the project root\n mkdirSync(outputDir, { recursive: true })\n\n // 2. Emit Worker entry (now uses the shared web-shim)\n //\n // The document shell is read HERE, after the Node build produced\n // `.theokit/client/index.html`, and inlined into the worker: a Worker has no\n // filesystem at request time. Split on the root div with the same helper the\n // Node server uses (`ssr-setup.ts`), so the two paths cannot disagree about\n // where the shell ends (#343).\n const shell = readDocumentShell(cwd, config.ssrStreaming)\n // B-035 — the route-to-chunks map, read HERE on the build machine for the same reason the\n // shell above is: a Worker has no filesystem at request time. `nodeAdapter.build` ran\n // first, so the map the Node build emitted is on disk by now. Absent map -> nothing about\n // preloads is emitted, and the worker behaves exactly as it did before.\n const assetsMap = readAssetsMapForBake(resolve(cwd, '.theokit', 'client', 'assets-map.json'))\n\n // #369 — the routes are resolved HERE, on the build machine, for the same reason the document\n // shell above is read here: a Worker has no filesystem at request time, and the worker used to\n // run the scan itself against a directory that does not exist there.\n //\n // The scanner is INJECTED rather than imported: importing it would add an `adapters → server`\n // edge, which is the layering inversion ADR-0001 v3 removed for `vite-plugin` and which\n // `adapters-may-only-depend-on-core-router-services` refuses. An absent scanner emits a worker\n // with no routes rather than falling back to a runtime scan — the fallback IS the defect.\n const scanned = ctx?.scanRoutes?.(config.serverDir) ?? {\n routes: [],\n wsRoutes: [],\n agents: [],\n // B-185 — no provider means no scan, and an app whose context nobody looked for is\n // indistinguishable here from one that has none. Both resolve an anonymous caller, which is\n // the honest answer rather than an invented subject.\n contextModule: undefined,\n }\n\n const pluginsPlan = planDeployedPlugins(config.plugins, 'cloudflare')\n if (pluginsPlan !== undefined) {\n // Beside the worker, so the emitted import is a sibling. Wrangler bundles from here, which is\n // what lets the static import reach the app's own module at all (#425).\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time write under the project's own `.theokit/cloudflare`\n writeFileSync(resolve(outputDir, 'theo.plugins.mjs'), pluginsPlan.source)\n }\n\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time write under the project's own `.theokit/cloudflare`\n writeFileSync(\n resolve(outputDir, 'worker.mjs'),\n renderCloudflareWorkerEntry({\n ssrStreaming: config.ssrStreaming,\n // B-315 — LATENT here, not broken: the routes are baked (#369) so the worker never scans,\n // and a live deploy on 2026-09-26 served every route correctly with the wrong literal. It\n // is still handed to `executeRoute`, so it is one refactor away from mattering.\n serverDir: config.serverDir,\n ...shell,\n securityHeaders: config.security?.headers,\n csrf: config.security?.csrf,\n disallowed: config.security?.disallowed,\n cors: config.security?.cors,\n assetsMap,\n // #425 — a selector, not a transformer, so it rides as a literal like the values above.\n serialization: config.serialization,\n // #425 — the ONE concern that is not a literal. A closure cannot be baked, so a plugin\n // declared by module specifier is imported by the emitted module instead; a constructed one\n // is refused by name at build time rather than dropped in silence.\n runtimeConfigModule: pluginsPlan?.moduleSpecifier,\n routes: scanned.routes,\n // #367 — a Worker has no filesystem, so its agents are decided here like its routes.\n agents: scanned.agents,\n // B-185 — rides beside the agents because it is decided by the same provider, for the same\n // reason: `build.ts` holds `serverDir` and a Worker cannot look for the module itself.\n contextModule: scanned.contextModule,\n wsRoutes: scanned.wsRoutes,\n }),\n )\n\n // 3. Emit the stub every alias resolves to, then wrangler.toml.\n //\n // The stub is written whether or not the table needs it: an alias pointing at a path nothing\n // wrote fails the same way the missing module failed, one layer down, and the file costs nothing.\n const workersUnsupported = absentOptionalPeersOnDisk(cwd)\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time write under the project's own `.theokit/cloudflare`\n writeFileSync(resolve(cwd, WORKERS_UNSUPPORTED_STUB_PATH), renderWorkersUnsupportedStub())\n\n // eslint-disable-next-line security/detect-non-literal-fs-filename -- build-time write under the project's own `.theokit/cloudflare`\n writeFileSync(\n resolve(cwd, 'wrangler.toml'),\n // B-263 — the toml decides whether the worker or the CDN answers the document, and the two\n // modes need different `not_found_handling`. Passing the flag is what keeps them apart.\n renderWranglerToml({ ssrStreaming: config.ssrStreaming, workersUnsupported }),\n )\n\n if (workersUnsupported.length > 0) {\n // The COUNT on every build, and the names in the file. Printing all 25 of them — measured on a\n // fresh scaffold — buries the rest of the build output in a list nobody reads twice, and the\n // list is already written where a reader can look it up.\n //\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n ` ℹ ${String(workersUnsupported.length)} absent optional peer dependenc${\n workersUnsupported.length === 1 ? 'y' : 'ies'\n } aliased so wrangler can bundle — see [alias] in wrangler.toml`,\n )\n }\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log('\\n ✓ Cloudflare output → .theokit/cloudflare/ + wrangler.toml')\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `${describeDeployedSecurityHeaders({\n target: 'cloudflare',\n securityHeaders: config.security?.headers,\n // Only the streaming worker renders HTML per request, so only it can put\n // the same nonce on the header and on the script tag it emits.\n mintsNonce: config.ssrStreaming,\n // #412 — either way the worker returns the document now: streamed by\n // `renderStreamingWeb`, or from the ASSETS binding it finally calls.\n documentHeaders: 'handler',\n })}\\n`,\n )\n },\n}\n"],"mappings":"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","names":[]}
@@ -27,8 +27,29 @@ import {
27
27
  } from "./chunk-ZNZISJDZ.js";
28
28
 
29
29
  // src/adapters/deno-deploy.ts
30
- import { mkdirSync, writeFileSync } from "fs";
30
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "fs";
31
31
  import { resolve } from "path";
32
+ var DEPLOY_INSTRUCTIONS = [
33
+ `// Deploy from the project root, so the entry finds its own src/server tree:`,
34
+ `// deno deploy --org <org> --app <name> --prod \\`,
35
+ `// --ignore node_modules --ignore package.json`,
36
+ `// Both exclusions are load-bearing, and both were isolated on the platform by`,
37
+ `// changing one thing at a time. node_modules: pnpm's is a symlink farm the upload`,
38
+ `// cannot carry, and this entry reaches the framework through npm: specifiers, so`,
39
+ `// it needs none of it. package.json: it is what Deno resolves those specifiers`,
40
+ `// against, so its version ranges bring the minimum-dependency-age policy with`,
41
+ `// them \u2014 with the manifest in the upload the revision failed, without it the same`,
42
+ `// directory deployed and served.`,
43
+ `//`,
44
+ `// If a deploy that keeps the manifest fails with "Could not find npm package`,
45
+ `// <name> matching <range>",`,
46
+ `// read the rest of that message before changing anything: Deno applies a`,
47
+ `// minimum-dependency-age policy (24h by default) and refuses a version published`,
48
+ `// inside that window, naming the PACKAGE rather than the policy. The range comes`,
49
+ `// from this project's package.json, which the upload carries. It clears by itself;`,
50
+ `// to deploy now, pass --min-dep-age=0 or set minimumDependencyAge in deno.json \u2014`,
51
+ `// both lower a supply-chain protection, which is why nothing here sets it for you.`
52
+ ];
32
53
  var FRAMEWORK_IMPORTS = [
33
54
  `// Use npm: specifier so Deno resolves theokit from the user's package.json`,
34
55
  `// equivalent (works in both local 'deno run' and Deno Deploy).`,
@@ -57,7 +78,7 @@ function renderDenoEntry(port, opts = {}) {
57
78
  });
58
79
  return [
59
80
  `// Generated by Theo \u2014 Deno Deploy Adapter`,
60
- `// Deploy: deployctl deploy --project=<name> .theokit/deno/server.ts`,
81
+ ...DEPLOY_INSTRUCTIONS,
61
82
  `// Requires Deno >= 1.40 (Deno.serve + npm: specifier support)`,
62
83
  ``,
63
84
  `// Runtime guard`,
@@ -170,7 +191,7 @@ async function buildDeno(config, cwd, deps = {}, ctx) {
170
191
  assertServicesUnsupported("deno-deploy", readManifest(cwd));
171
192
  const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter);
172
193
  await runNodeBuild(config, cwd, ctx);
173
- const outputDir = resolve(cwd, ".theokit/deno");
194
+ const outputDir = resolve(cwd, DENO_DEPLOY_ENTRY_PATH, "..");
174
195
  const ensureDir = deps.ensureDir ?? ((p) => mkdirSync(p, { recursive: true }));
175
196
  ensureDir(outputDir);
176
197
  const pluginsPlan = planDeployedPlugins(config.plugins, denoDeployAdapter.name);
@@ -197,8 +218,19 @@ async function buildDeno(config, cwd, deps = {}, ctx) {
197
218
  if (pluginsPlan !== void 0) {
198
219
  write(resolve(outputDir, "theo.plugins.mjs"), pluginsPlan.source);
199
220
  }
200
- write(resolve(outputDir, "server.ts"), entry);
201
- console.log("\n \u2713 Deno Deploy output \u2192 .theokit/deno/server.ts");
221
+ write(resolve(cwd, DENO_DEPLOY_ENTRY_PATH), entry);
222
+ const readSources = deps.readProjectSources ?? readProjectSourcesFromDisk;
223
+ const readConfig = deps.readDenoConfig ?? readDenoConfigFromDisk;
224
+ const denoConfig = renderDenoConfig({
225
+ specifiers: bareSpecifiersOf(readSources(cwd, config)),
226
+ existing: readConfig(cwd)
227
+ });
228
+ write(resolve(cwd, DENO_DEPLOY_CONFIG_PATH), `${JSON.stringify(denoConfig, void 0, 2)}
229
+ `);
230
+ console.log(
231
+ `
232
+ \u2713 Deno Deploy output \u2192 ${DENO_DEPLOY_ENTRY_PATH} + ${DENO_DEPLOY_CONFIG_PATH} (${Object.keys(denoConfig.imports ?? {}).length} specifiers mapped)`
233
+ );
202
234
  console.log(
203
235
  `${describeDeployedSecurityHeaders({
204
236
  target: TARGET,
@@ -209,6 +241,58 @@ async function buildDeno(config, cwd, deps = {}, ctx) {
209
241
  `
210
242
  );
211
243
  }
244
+ var DENO_DEPLOY_ENTRY_PATH = "theokit-deploy/server.ts";
245
+ var DENO_DEPLOY_CONFIG_PATH = "deno.json";
246
+ function bareSpecifiersOf(sources) {
247
+ const found = /* @__PURE__ */ new Set();
248
+ for (const source of sources) {
249
+ for (const match of source.matchAll(/(?:from|import)\s+['"]([^'"]+)['"]/gu)) {
250
+ const specifier = match[1];
251
+ if (specifier === void 0) continue;
252
+ if (/^(?:[./]|npm:|jsr:|node:|data:|https?:)/u.test(specifier)) continue;
253
+ found.add(specifier);
254
+ }
255
+ }
256
+ return [...found].sort((a, b) => a.localeCompare(b, "en"));
257
+ }
258
+ function renderDenoConfig(opts) {
259
+ const config = { ...opts.existing ?? {} };
260
+ const imports = { ...config.imports ?? {} };
261
+ for (const specifier of opts.specifiers) imports[specifier] = `npm:${specifier}`;
262
+ config.imports = imports;
263
+ const unstable = [...config.unstable ?? []];
264
+ if (!unstable.includes("sloppy-imports")) unstable.push("sloppy-imports");
265
+ config.unstable = unstable;
266
+ return config;
267
+ }
268
+ function readProjectSourcesFromDisk(cwd, config) {
269
+ const roots = [config.serverDir, config.agentsDir];
270
+ const texts = [];
271
+ for (const root of roots) {
272
+ const dir = resolve(cwd, root);
273
+ if (!existsSync(dir)) continue;
274
+ for (const entry of readdirSync(dir, { recursive: true, withFileTypes: true })) {
275
+ if (!entry.isFile()) continue;
276
+ if (!/\.(?:ts|tsx|mts)$/u.test(entry.name)) continue;
277
+ if (/\.(?:test|spec)\./u.test(entry.name)) continue;
278
+ texts.push(readFileSync(resolve(entry.parentPath, entry.name), "utf8"));
279
+ }
280
+ }
281
+ const themeConfig = resolve(cwd, "theo.config.ts");
282
+ if (existsSync(themeConfig)) texts.push(readFileSync(themeConfig, "utf8"));
283
+ return texts;
284
+ }
285
+ function readDenoConfigFromDisk(cwd) {
286
+ const file = resolve(cwd, DENO_DEPLOY_CONFIG_PATH);
287
+ if (!existsSync(file)) return void 0;
288
+ try {
289
+ return JSON.parse(readFileSync(file, "utf8"));
290
+ } catch {
291
+ throw new Error(
292
+ `${DENO_DEPLOY_CONFIG_PATH} exists and is not valid JSON. Fix or remove it before building for deno-deploy.`
293
+ );
294
+ }
295
+ }
212
296
  var denoDeployAdapter = {
213
297
  // Same as `bun`, plus the runtime compiles the TypeScript it finds — which is why a scan that
214
298
  // answered `SyntaxError` on netlify is legitimate here. The last runtime scanner in the fleet.
@@ -234,8 +318,12 @@ var denoDeployAdapter = {
234
318
  }
235
319
  };
236
320
  export {
321
+ DENO_DEPLOY_CONFIG_PATH,
322
+ DENO_DEPLOY_ENTRY_PATH,
323
+ bareSpecifiersOf,
237
324
  buildDeno,
238
325
  denoDeployAdapter,
326
+ renderDenoConfig,
239
327
  renderDenoEntry
240
328
  };
241
- //# sourceMappingURL=deno-deploy-6H354VRA.js.map
329
+ //# sourceMappingURL=deno-deploy-XW3Q6OMD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/adapters/deno-deploy.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Deno Deploy adapter. Writes to `cwd/.theokit/deno-deploy/`. Build-time.\n */\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport { deployedAgentsFragment, scannedFromLoaderCache } from './deployed-agents.js'\nimport { planDeployedPlugins } from './deployed-plugins-module.js'\nimport { deployedEntryPreamble } from './deployed-preamble.js'\nimport { deployedRateLimitFragment, rateLimitCheckFragment } from './deployed-rate-limit.js'\nimport {\n agentsDirLiteral,\n deployedRuntimeConfigFragment,\n serverDirLiteral,\n} from './deployed-runtime-config.js'\nimport { deployedTraceFragment } from './deployed-trace.js'\nimport { nodeAdapter } from './node.js'\nimport { describeDeployedSecurityHeaders } from './security-headers.js'\nimport type { AdapterBuildContext, DeployAdapter, DeployedEntryOptions } from './types.js'\n\nexport interface DenoBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeEntry?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n /** The project's own source texts, whose bare imports the emitted map has to resolve. */\n readProjectSources?: (cwd: string, config: TheoConfig) => string[]\n /** The Deno config already on disk, merged rather than replaced. */\n readDenoConfig?: (cwd: string) => DenoConfig | undefined\n}\n\n/**\n * The framework imports this entry opens with, as `npm:` specifiers.\n *\n * Extracted from `renderDenoEntry` when adding the configured-directory literal pushed it past the\n * per-function line budget. A cohesive block — every line is one import of the same package family\n * — so lifting it keeps the budget a signal about the renderer rather than about its preamble.\n */\n/**\n * How this entry is deployed, and the one failure whose message names the wrong thing.\n *\n * Extracted for the same reason `FRAMEWORK_IMPORTS` above was: a cohesive block of emitted\n * comment lines that pushed the renderer past its line budget, where the budget is meant to\n * be a signal about the renderer rather than about its preamble.\n */\nconst DEPLOY_INSTRUCTIONS: readonly string[] = [\n `// Deploy from the project root, so the entry finds its own src/server tree:`,\n `// deno deploy --org <org> --app <name> --prod \\\\`,\n `// --ignore node_modules --ignore package.json`,\n `// Both exclusions are load-bearing, and both were isolated on the platform by`,\n `// changing one thing at a time. node_modules: pnpm's is a symlink farm the upload`,\n `// cannot carry, and this entry reaches the framework through npm: specifiers, so`,\n `// it needs none of it. package.json: it is what Deno resolves those specifiers`,\n `// against, so its version ranges bring the minimum-dependency-age policy with`,\n `// them — with the manifest in the upload the revision failed, without it the same`,\n `// directory deployed and served.`,\n `//`,\n `// If a deploy that keeps the manifest fails with \"Could not find npm package`,\n `// <name> matching <range>\",`,\n `// read the rest of that message before changing anything: Deno applies a`,\n `// minimum-dependency-age policy (24h by default) and refuses a version published`,\n `// inside that window, naming the PACKAGE rather than the policy. The range comes`,\n `// from this project's package.json, which the upload carries. It clears by itself;`,\n `// to deploy now, pass --min-dep-age=0 or set minimumDependencyAge in deno.json —`,\n `// both lower a supply-chain protection, which is why nothing here sets it for you.`,\n]\n\nconst FRAMEWORK_IMPORTS: readonly string[] = [\n `// Use npm: specifier so Deno resolves theokit from the user's package.json`,\n `// equivalent (works in both local 'deno run' and Deno Deploy).`,\n // Sub-paths, not the umbrella. `theokit/server` is DEPRECATED by the framework itself with a\n // REMOVAL scheduled, so this entry would stop loading on that release, before a request exists —\n // measured on Deno 2.9.5, where the warning printed on every start. This was the only adapter still\n // on the umbrella; the split below is the mapping four siblings already use and Bun proves by\n // running.\n `import { scanServerRoutes, matchRoute, createProductionLoader, scanWebSocketRoutes } from 'npm:theokit/server/scan'`,\n `import { executeRoute, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler } from 'npm:theokit/server/http'`,\n `import { createWebShim } from 'npm:theokit/adapters/web-shim'`,\n `import { buildSecurityHeaders, withSecurityHeaders } from 'npm:theokit/adapters/security-headers'`,\n `// T3.3 — WS bridge for Deno runtime`,\n `import { createDenoWsBridge } from 'npm:theokit/adapters/ws-shim'`,\n]\n\n/** The target's name, in the five places that spell it. Adding a sixth tripped the duplicate-literal gate. */\n/* The name in the places a gate does NOT read. `assertServicesUnsupported` keeps the literal:\n * `services-other-adapters-reject.test.ts:30` requires each adapter to name ITSELF there, so a\n * copy-paste carrying another adapter's name cannot hide behind a constant. */\nconst TARGET = 'deno-deploy'\n\nexport function renderDenoEntry(port: number, opts: DeployedEntryOptions = {}): string {\n const runtimeConfig = deployedRuntimeConfigFragment(opts)\n const agentsFragment = deployedAgentsFragment(scannedFromLoaderCache(agentsDirLiteral(opts)), {\n notFound: 'notFound()',\n importPrefix: 'npm:',\n // B-185 — bound only when the runtime-config fragment declared the const, which is\n // exactly when a plugins module was emitted. Referencing it otherwise would emit an\n // identifier the entry never declares.\n pluginRunnerExpr:\n opts.runtimeConfigModule === undefined ? undefined : 'await THEO_PLUGIN_RUNNER',\n })\n return [\n `// Generated by Theo — Deno Deploy Adapter`,\n ...DEPLOY_INSTRUCTIONS,\n `// Requires Deno >= 1.40 (Deno.serve + npm: specifier support)`,\n ``,\n `// Runtime guard`,\n `if (typeof Deno === 'undefined') {`,\n ` throw new Error('TheoDenoDeployAdapter must run inside Deno.')`,\n `}`,\n ``,\n ...FRAMEWORK_IMPORTS,\n ``,\n `const port = Number(Deno.env.get('PORT') ?? '${port}')`,\n `const cwd = Deno.cwd()`,\n `const serverDir = cwd + '/' + ${serverDirLiteral(opts)}`,\n `let routesCache = null`,\n `let wsRoutesCache = null`,\n `let loaderCache = null`,\n ``,\n `// #410 — the security baseline \\`theokit start\\` puts on every response,`,\n `// carried here as a literal because the deployed isolate has no`,\n `// theo.config.ts to read. Non-API paths 404 here and are served by Deno`,\n `// Deploy's static asset handler, so this covers the API and not the`,\n `// document (usetheokit/theokit#412).`,\n ...deployedEntryPreamble(runtimeConfig, agentsFragment, opts, TARGET),\n ``,\n `function notFound() {`,\n ` return new Response(JSON.stringify({ error: { code: 'NOT_FOUND' } }), {`,\n ` status: 404,`,\n ` headers: { 'Content-Type': 'application/json' },`,\n ` })`,\n `}`,\n ``,\n // B-027 — `npm:`, like every other specifier this target emits (`:37-43`). A bare one does\n // not resolve on Deno Deploy, and the harness asserts the prefix is here before rewriting it.\n ...(opts.rateLimit === undefined\n ? []\n : [\n `import { createRateLimiterWeb } from 'npm:theokit/server/rate-limit'`,\n `import { resolveClientIpFromRequest } from 'npm:theokit/server/rate-limit'`,\n ``,\n ]),\n ...deployedRateLimitFragment(\n opts.rateLimit,\n TARGET,\n // `info.remoteAddr` is the runtime's own answer. A `UnixAddr` has no `hostname`, so the\n // optional chain is what keeps the read from throwing there rather than a guess.\n `info?.remoteAddr?.hostname ?? resolveClientIpFromRequest(request, TRUST_PROXY)`,\n 'request, info',\n // B-257 — `npm:`, like every other specifier this target emits. A bare one does not resolve\n // on Deno Deploy, and the durable limiter's import is emitted by the fragment rather than\n // here, so the prefix has to travel with it.\n 'npm:',\n ),\n ``,\n // The serve handler's SECOND parameter is bound here for the first time: `Deno.serve` has\n // always passed it and the emitted entry discarded it.\n `Deno.serve({ port }, async (request, info) => {`,\n ` // #409 — the preflight is answered BEFORE anything routes: an OPTIONS the router`,\n ` // handles is an OPTIONS the browser never gets a CORS answer to.`,\n ` const preflight = corsPreflight(request)`,\n ` if (preflight !== null) return withSecurityHeaders(preflight, SECURITY_HEADERS)`,\n ...rateLimitCheckFragment(opts.rateLimit, ' ', 'request, info'),\n ``,\n ` // LCR0103 — the upgrade branch sits BELOW the limiter, and the order is the point.`,\n ` // It used to sit above, under the comment \"a 101 carries no document and no script,`,\n ` // so the security baseline does not apply to it\". That is true of the security`,\n ` // HEADERS — a document concern — and false of the limiter, which is a resource`,\n ` // concern: a long-lived socket is the most expensive thing this entry hands out, so`,\n ` // the upgrade is the path that most needs a budget, not the one that may skip it.`,\n ` // T3.3 — Detect WebSocket upgrade and delegate to the Deno bridge.`,\n ` // A 101 carries no document and no script, so the security baseline does`,\n ` // not apply to it.`,\n ` if (request.headers.get('upgrade')?.toLowerCase() === 'websocket') {`,\n ` if (!wsRoutesCache) wsRoutesCache = scanWebSocketRoutes(serverDir)`,\n ` if (wsRoutesCache.length === 0) return notFound()`,\n ` const denoWs = createDenoWsBridge({`,\n ` onOpen: () => {},`,\n ` onMessage: (ws, data) => { ws.send(data) },`,\n ` onClose: () => {},`,\n ` }, Deno)`,\n ` return denoWs.handle(request)`,\n ` }`,\n ` return withCors(request, withSecurityHeaders(await handleRequest(request), SECURITY_HEADERS))`,\n `})`,\n ``,\n `async function handleRequest(request) {`,\n ` const url = new URL(request.url)`,\n ``,\n ` if (!url.pathname.startsWith('/api/')${agentsFragment.hostBypass}) {`,\n ` // Static + SPA fallback are served by Deno Deploy's static asset handler.`,\n ` return notFound()`,\n ` }`,\n ``,\n ` if (!routesCache) routesCache = scanServerRoutes(serverDir)`,\n ` if (!loaderCache) loaderCache = createProductionLoader()`,\n ``,\n ...agentsFragment.branch,\n ``,\n ` const match = matchRoute(url.pathname, routesCache)`,\n ` if (!match) return notFound()`,\n ``,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ...deployedTraceFragment('request', ' '),\n ` const method = request.method.toUpperCase()`,\n ` // #382 — not awaited: toResponse() settles at the headers and carries a`,\n ` // live body, so Deno.serve streams while the handler writes.`,\n ` return toResponse(executeRoute({ route: match.route, method, params: match.params, req, res, loadModule: loaderCache, serverDir, requestId, ...CSRF_CONFIG, ${runtimeConfig.executeRouteSpread} }))`,\n `}`,\n ``,\n `console.log('Theo (Deno Deploy) listening on :' + port)`,\n ].join('\\n')\n}\n\nexport async function buildDeno(\n config: TheoConfig,\n cwd: string,\n deps: DenoBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('deno-deploy', readManifest(cwd))\n\n const runNodeBuild = deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter)\n await runNodeBuild(config, cwd, ctx)\n\n const outputDir = resolve(cwd, DENO_DEPLOY_ENTRY_PATH, '..')\n const ensureDir = deps.ensureDir ?? ((p: string) => mkdirSync(p, { recursive: true }))\n ensureDir(outputDir)\n\n const pluginsPlan = planDeployedPlugins(config.plugins, denoDeployAdapter.name)\n const entry = renderDenoEntry(config.port, {\n securityHeaders: config.security?.headers,\n csrf: config.security?.csrf,\n disallowed: config.security?.disallowed,\n cors: config.security?.cors,\n // #425 — a selector, not a transformer, so it rides as a literal like the values above.\n serialization: config.serialization,\n // #425 — the ONE concern that is not a literal. A closure cannot be baked, so a plugin\n // declared by module specifier is imported by the emitted module instead; a constructed one\n // is refused by name at build time rather than dropped in silence.\n runtimeConfigModule: pluginsPlan?.moduleSpecifier,\n // #95 — the configured directories, which `build.ts:210` already threads into the Vite\n // plugins. Without these two lines the literals above fall to their defaults and a project\n // with a custom dir deploys an entry that resolves a directory it does not have.\n serverDir: config.serverDir,\n agentsDir: config.agentsDir,\n })\n const write =\n deps.writeEntry ??\n ((p, c) => {\n writeFileSync(p, c)\n })\n if (pluginsPlan !== undefined) {\n // Beside the entry, so the emitted import is a sibling. Written through the same seam as\n // the entry so a test that captures one captures both (#425).\n write(resolve(outputDir, 'theo.plugins.mjs'), pluginsPlan.source)\n }\n write(resolve(cwd, DENO_DEPLOY_ENTRY_PATH), entry)\n\n // The import map and `sloppy-imports`, at the upload root. Measured on the platform: a\n // config in a subdirectory failed the revision, so this path is not a preference.\n const readSources = deps.readProjectSources ?? readProjectSourcesFromDisk\n const readConfig = deps.readDenoConfig ?? readDenoConfigFromDisk\n const denoConfig = renderDenoConfig({\n specifiers: bareSpecifiersOf(readSources(cwd, config)),\n existing: readConfig(cwd),\n })\n write(resolve(cwd, DENO_DEPLOY_CONFIG_PATH), `${JSON.stringify(denoConfig, undefined, 2)}\\n`)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `\\n ✓ Deno Deploy output → ${DENO_DEPLOY_ENTRY_PATH} + ${DENO_DEPLOY_CONFIG_PATH} (${\n Object.keys(denoConfig.imports ?? {}).length\n } specifiers mapped)`,\n )\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `${describeDeployedSecurityHeaders({\n target: TARGET,\n securityHeaders: config.security?.headers,\n mintsNonce: false,\n documentHeaders: 'platform-unmanaged',\n })}\\n`,\n )\n}\n\n/**\n * Where the entry is written, relative to the project root.\n *\n * NOT under `.theokit/`, and that is the whole point: measured against the real platform on\n * 2026-09-30, a dot directory is never uploaded. `./.theokit/deno/probe.ts` failed the\n * revision while the byte-identical file at `./visible/probe.ts` served HTTP 200. An entry\n * the upload cannot see is an entry that cannot run, and the build reported success either\n * way — so nothing short of a deploy could find this.\n *\n * One directory deep, because the upload root has to stay the PROJECT root: the entry scans\n * `<cwd>/src/server` for the project's own route modules, so a deploy rooted anywhere else\n * finds no routes and answers 404 on every path.\n */\nexport const DENO_DEPLOY_ENTRY_PATH = 'theokit-deploy/server.ts'\n\n/** Where the Deno config is written. The upload root, and nowhere else — see below. */\nexport const DENO_DEPLOY_CONFIG_PATH = 'deno.json'\n\n/**\n * The bare specifiers a set of source texts import — the ones needing a registry, in the\n * order they will be emitted.\n *\n * Derived from the project's own sources rather than from `package.json`, because what has\n * to resolve is what is actually imported: a declared dependency nobody imports needs no\n * entry, and a subpath like `theokit/server/define` appears in no manifest at all.\n */\nexport function bareSpecifiersOf(sources: Iterable<string>): string[] {\n const found = new Set<string>()\n for (const source of sources) {\n for (const match of source.matchAll(/(?:from|import)\\s+['\"]([^'\"]+)['\"]/gu)) {\n // `match[1]` and not a guarded read: the capture group is not optional in this pattern,\n // so it participates in every match the loop yields.\n const specifier = match[1]\n // A relative or absolute path resolves on its own; a specifier that already names its\n // registry is not ours to map.\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- a regex group's type is wider than this pattern\n if (specifier === undefined) continue\n if (/^(?:[./]|npm:|jsr:|node:|data:|https?:)/u.test(specifier)) continue\n found.add(specifier)\n }\n }\n // An explicit comparator: the emitted map is read in diffs, so its order has to be the\n // same on every machine rather than whatever the default collation is.\n return [...found].sort((a, b) => a.localeCompare(b, 'en'))\n}\n\n/** The shape this adapter reads and writes. Anything else in the file is carried through. */\nexport interface DenoConfig {\n imports?: Record<string, string>\n unstable?: string[]\n [key: string]: unknown\n}\n\n/**\n * The Deno config the platform needs, merged onto whatever the project already wrote.\n *\n * Two measured facts decide its whole content:\n *\n * **Each specifier is mapped exactly, never as a prefix.** The emitted entry imports\n * `npm:theokit/...`; the project's own modules import the bare `theokit/server/define`. A\n * trailing-slash prefix cannot bridge them — Deno refuses `\"theokit/\": \"npm:theokit/\"` with\n * *\"its after-prefix portion could not be URL-parsed relative to the URL prefix\"*, because\n * the part after the prefix is not joinable onto an `npm:` URL. Exact entries resolve; the\n * prefix form fails at the first import, at runtime, with a 500.\n *\n * **`sloppy-imports` is declared.** A generated project's own agent modules import siblings\n * with a `.js` extension against files on disk ending `.ts` — six of them in the default\n * template. Deno refuses that pairing and says so: *\"Maybe change the extension to '.ts' or\n * run with --sloppy-imports\"*.\n */\nexport function renderDenoConfig(opts: {\n specifiers: readonly string[]\n existing?: DenoConfig\n}): DenoConfig {\n const config: DenoConfig = { ...(opts.existing ?? {}) }\n const imports: Record<string, string> = { ...(config.imports ?? {}) }\n for (const specifier of opts.specifiers) imports[specifier] = `npm:${specifier}`\n config.imports = imports\n const unstable = [...(config.unstable ?? [])]\n if (!unstable.includes('sloppy-imports')) unstable.push('sloppy-imports')\n config.unstable = unstable\n return config\n}\n\n/**\n * The project's own TypeScript sources, from the directories the entry scans at runtime.\n *\n * Scoped to those directories plus the config file rather than the whole tree: what the map\n * must resolve is what the DEPLOYED code imports, and a specifier reachable only from a test\n * or a build script never runs on the platform.\n */\nfunction readProjectSourcesFromDisk(cwd: string, config: TheoConfig): string[] {\n // No fallback: `config/schema.ts` gives both a zod `.default()`, so a parsed config always\n // carries them — and the defaults are `server`/`agents`, so the `src/`-prefixed guesses this\n // line used to hold would have read the wrong directories on any project that took them.\n const roots = [config.serverDir, config.agentsDir]\n const texts: string[] = []\n for (const root of roots) {\n const dir = resolve(cwd, root)\n if (!existsSync(dir)) continue\n for (const entry of readdirSync(dir, { recursive: true, withFileTypes: true })) {\n if (!entry.isFile()) continue\n if (!/\\.(?:ts|tsx|mts)$/u.test(entry.name)) continue\n if (/\\.(?:test|spec)\\./u.test(entry.name)) continue\n texts.push(readFileSync(resolve(entry.parentPath, entry.name), 'utf8'))\n }\n }\n const themeConfig = resolve(cwd, 'theo.config.ts')\n if (existsSync(themeConfig)) texts.push(readFileSync(themeConfig, 'utf8'))\n return texts\n}\n\nfunction readDenoConfigFromDisk(cwd: string): DenoConfig | undefined {\n const file = resolve(cwd, DENO_DEPLOY_CONFIG_PATH)\n if (!existsSync(file)) return undefined\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as DenoConfig\n } catch {\n // A config we cannot parse is one we must not overwrite blind: returning undefined would\n // replace whatever the project wrote. Refusing here surfaces it at the write site instead.\n throw new Error(\n `${DENO_DEPLOY_CONFIG_PATH} exists and is not valid JSON. Fix or remove it before building for deno-deploy.`,\n )\n }\n}\n\nexport const denoDeployAdapter: DeployAdapter = {\n // Same as `bun`, plus the runtime compiles the TypeScript it finds — which is why a scan that\n // answered `SyntaxError` on netlify is legitimate here. The last runtime scanner in the fleet.\n readsSourceAtRunTime: true,\n specifiersResolvedBy: 'the-project-at-run-time',\n name: 'deno-deploy',\n streamsResponses: true,\n // #409 / #410 — the generated entry calls `executeRoute` with routes, loader\n // and serverDir only. CSRF, route policy, file middleware and Zod validation\n // still run because they live inside `executeRoute`; none of the remaining\n // configurable concerns reach it. Declared explicitly rather than omitted so\n // the gap is a statement in the source and not an absence.\n //\n // `securityHeaders` IS applied -- to every response this isolate returns. The\n // document comes from Deno Deploy's static asset handler and does not pass\n // through it (usetheokit/theokit#412).\n servesAgents: true,\n appliesConfig: ['securityHeaders', 'csrf', 'disallowed', 'cors', 'serialization', 'plugins'],\n // B-257 — per-invocation: an in-process counter does not survive, so a declared limit needs a durable store.\n enforcesRateLimit: 'with-a-store',\n build(config, cwd, ctx) {\n return buildDeno(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"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","names":[]}
@@ -11,7 +11,7 @@ import {
11
11
  } from "./chunk-KTMQUYED.js";
12
12
  import {
13
13
  theoPluginAsync
14
- } from "./chunk-RFZ263XP.js";
14
+ } from "./chunk-QX5Q3FBJ.js";
15
15
  import "./chunk-QAS5SKZP.js";
16
16
  import "./chunk-BDH5GKFZ.js";
17
17
  import "./chunk-3PWQQWT6.js";
@@ -128,4 +128,4 @@ export {
128
128
  devCommand,
129
129
  startDevServer
130
130
  };
131
- //# sourceMappingURL=dev-HJUD6X3R.js.map
131
+ //# sourceMappingURL=dev-FZ4OTCPT.js.map
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  scanRoutes,
9
9
  theoConfigSchema,
10
10
  theoPlugin
11
- } from "./chunk-S5QN3O22.js";
11
+ } from "./chunk-NQZIQJFH.js";
12
12
  import "./chunk-7NRFZBAL.js";
13
13
  import "./chunk-SSYDEKWF.js";
14
14
  import "./chunk-D7STTNQN.js";
@@ -2,7 +2,7 @@
2
2
  import "tsx/esm";
3
3
  import {
4
4
  createAgentSsrLoader
5
- } from "./chunk-ODYLKWRT.js";
5
+ } from "./chunk-SG62LZOJ.js";
6
6
  import {
7
7
  extractAppResources,
8
8
  handleMcpJsonRpc
@@ -75,4 +75,4 @@ async function mcpCommand(name, deps = {}) {
75
75
  export {
76
76
  mcpCommand
77
77
  };
78
- //# sourceMappingURL=mcp-MVKDWZEQ.js.map
78
+ //# sourceMappingURL=mcp-VUHRRX36.js.map
@@ -4,7 +4,7 @@ import "tsx/esm";
4
4
  // src/cli/commands/preview.ts
5
5
  async function defaultSteps() {
6
6
  const [{ buildCommand }, { startCommand }] = await Promise.all([
7
- import("./build-6FVZKORI.js"),
7
+ import("./build-7YZSJGP2.js"),
8
8
  import("./start-EREUCSN6.js")
9
9
  ]);
10
10
  return {
@@ -20,4 +20,4 @@ async function previewCommand(options = {}, steps) {
20
20
  export {
21
21
  previewCommand
22
22
  };
23
- //# sourceMappingURL=preview-5HUID27O.js.map
23
+ //# sourceMappingURL=preview-II33QNAC.js.map
@@ -8,13 +8,13 @@ import {
8
8
  var adapterRegistry = {
9
9
  node: async () => (await import("./node-4HQO6KKZ.js")).nodeAdapter,
10
10
  vercel: async () => (await import("./vercel-VLR52RXT.js")).vercelAdapter,
11
- cloudflare: async () => (await import("./cloudflare-AMH4TJNL.js")).cloudflareAdapter,
11
+ cloudflare: async () => (await import("./cloudflare-Q5SUXRXY.js")).cloudflareAdapter,
12
12
  static: async () => (await import("./static-7LP7LPGN.js")).staticAdapter,
13
13
  bun: async () => (await import("./bun-I7YM3RDB.js")).bunAdapter,
14
- "deno-deploy": async () => (await import("./deno-deploy-6H354VRA.js")).denoDeployAdapter,
14
+ "deno-deploy": async () => (await import("./deno-deploy-XW3Q6OMD.js")).denoDeployAdapter,
15
15
  netlify: async () => (await import("./netlify-ALPCTQFO.js")).netlifyAdapter,
16
16
  "aws-lambda": async () => (await import("./aws-lambda-ZYSPTD3O.js")).awsLambdaAdapter,
17
- "theo-cloud": async () => (await import("./theo-cloud-HMFNZ3FS.js")).theoCloudAdapter
17
+ "theo-cloud": async () => (await import("./theo-cloud-GIA67L4Q.js")).theoCloudAdapter
18
18
  };
19
19
  async function resolveAdapter(target) {
20
20
  const factory = adapterRegistry[target];
@@ -31,4 +31,4 @@ export {
31
31
  resolveAdapter,
32
32
  streamingTargets
33
33
  };
34
- //# sourceMappingURL=registry-OMLCP4AS.js.map
34
+ //# sourceMappingURL=registry-WVUO2WNR.js.map
@@ -8,7 +8,7 @@ import {
8
8
  getStorageManager,
9
9
  useDatabase,
10
10
  useUnstorage
11
- } from "../chunk-FJEKQ4FT.js";
11
+ } from "../chunk-EVKNHQWG.js";
12
12
  import {
13
13
  BodyTooLargeError,
14
14
  DEFAULT_MAX_BODY_BYTES,
@@ -3,7 +3,7 @@ import {
3
3
  getStorageManager,
4
4
  useDatabase,
5
5
  useUnstorage
6
- } from "../../chunk-FJEKQ4FT.js";
6
+ } from "../../chunk-EVKNHQWG.js";
7
7
  import "../../chunk-XTHN6JBX.js";
8
8
  export {
9
9
  StorageManager,