theokit 0.74.3 → 0.74.5

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 (30) hide show
  1. package/dist/{agent-QJINXU6U.js → agent-XPZUWTHR.js} +2 -2
  2. package/dist/{build-4NRHCMFB.js → build-F3O5LJHY.js} +3 -3
  3. package/dist/{chunk-SHFZKJCU.js → chunk-FPC6ZEEU.js} +6 -1
  4. package/dist/{chunk-SHFZKJCU.js.map → chunk-FPC6ZEEU.js.map} +1 -1
  5. package/dist/{chunk-25OXNMVO.js → chunk-PZLH4WGU.js} +2 -2
  6. package/dist/{chunk-7FIK5NKN.js → chunk-XUAWD5HK.js} +6 -1
  7. package/dist/{chunk-7FIK5NKN.js.map → chunk-XUAWD5HK.js.map} +1 -1
  8. package/dist/cli/index.js +5 -5
  9. package/dist/{deno-deploy-ALX36LID.js → deno-deploy-M5V3PYWU.js} +66 -10
  10. package/dist/deno-deploy-M5V3PYWU.js.map +1 -0
  11. package/dist/{dev-LMZGOV4M.js → dev-TW6QD7F3.js} +2 -2
  12. package/dist/index.js +1 -1
  13. package/dist/{mcp-4CXNOZA4.js → mcp-LMV3NQEP.js} +2 -2
  14. package/dist/{netlify-2D2TM2UW.js → netlify-NI4HYDKN.js} +26 -7
  15. package/dist/netlify-NI4HYDKN.js.map +1 -0
  16. package/dist/{preview-LHNJSLS6.js → preview-AMNBV2DP.js} +2 -2
  17. package/dist/{registry-QSJ7X5NO.js → registry-SUE4HJXL.js} +3 -3
  18. package/dist/vite-plugin/index.js +1 -1
  19. package/dist/{vite-plugin-NXWE2AVM.js → vite-plugin-4ZMBW3KV.js} +2 -2
  20. package/package.json +4 -4
  21. package/dist/deno-deploy-ALX36LID.js.map +0 -1
  22. package/dist/netlify-2D2TM2UW.js.map +0 -1
  23. /package/dist/{agent-QJINXU6U.js.map → agent-XPZUWTHR.js.map} +0 -0
  24. /package/dist/{build-4NRHCMFB.js.map → build-F3O5LJHY.js.map} +0 -0
  25. /package/dist/{chunk-25OXNMVO.js.map → chunk-PZLH4WGU.js.map} +0 -0
  26. /package/dist/{dev-LMZGOV4M.js.map → dev-TW6QD7F3.js.map} +0 -0
  27. /package/dist/{mcp-4CXNOZA4.js.map → mcp-LMV3NQEP.js.map} +0 -0
  28. /package/dist/{preview-LHNJSLS6.js.map → preview-AMNBV2DP.js.map} +0 -0
  29. /package/dist/{registry-QSJ7X5NO.js.map → registry-SUE4HJXL.js.map} +0 -0
  30. /package/dist/{vite-plugin-NXWE2AVM.js.map → vite-plugin-4ZMBW3KV.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/adapters/netlify.ts"],"sourcesContent":["/* eslint-disable security/detect-non-literal-fs-filename --\n * Netlify deploy adapter. All paths derived from `cwd` and a fixed\n * `.theokit/netlify/` output layout. Build-time tool — no HTTP input.\n */\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\n\nimport type { TheoConfig } from '../config/schema.js'\nimport type { SecurityHeadersConfig } from '../core/contracts/security-headers.js'\nimport { assertServicesUnsupported, readManifest } from '../services/index.js'\n\nimport {\n bundleDeployedFunction,\n type BundleDeployedFunctionOptions,\n} from './bundle-deployed-function.js'\nimport { deployedAgentsFragment, JSON_NOT_FOUND_RESPONSE } from './deployed-agents.js'\nimport { renderBakedRoutes, routeRuntimeLines, type BakedRoute } from './deployed-baked-routes.js'\nimport { type DeployedCorsOptions } from './deployed-cors.js'\nimport { type DeployedCsrfOptions } from './deployed-csrf.js'\nimport { deployedEntryPreamble } from './deployed-preamble.js'\nimport { deployedRateLimitFragment, rateLimitCheckFragment } from './deployed-rate-limit.js'\nimport type { DeployedRateLimitOptions } from './deployed-rate-limit.js'\nimport {\n deployedRuntimeConfigFragment,\n type DeployedRuntimeConfigOptions,\n serverDirLiteral,\n type DeployedServerDirOptions,\n} from './deployed-runtime-config.js'\nimport { deployedTraceFragment } from './deployed-trace.js'\nimport { nodeAdapter } from './node.js'\nimport { buildSecurityHeaders, describeDeployedSecurityHeaders } from './security-headers.js'\nimport type { AdapterBuildContext, DeployAdapter } from './types.js'\n\nexport class NetlifyConflictError extends Error {\n constructor(path: string, currentTo: string) {\n super(\n `netlify.toml has a conflicting redirect: \"${path}\" → \"${currentTo}\". ` +\n `Remove it manually or edit it to point to \"/.netlify/functions/theo\" ` +\n `before running the Netlify adapter again.`,\n )\n this.name = 'NetlifyConflictError'\n }\n}\n\nexport class NetlifyFunctionsConflictError extends Error {\n constructor(key: string, declared: string, required: string) {\n super(\n `netlify.toml declares \\`[functions] ${key} = \"${declared}\"\\`, and this adapter needs ` +\n `\"${required}\". Measured on the Netlify emulator: \\`directory\\` is what makes the generated ` +\n `function discoverable at all (without it the redirect answers \"Function not found\"), and ` +\n `\\`node_bundler = \"none\"\\` is what stops Netlify re-bundling a function theokit already ` +\n `bundled — which produces a SyntaxError. Change the value, or remove the key and let this ` +\n `adapter write it.`,\n )\n this.name = 'NetlifyFunctionsConflictError'\n }\n}\n\nexport interface NetlifyBuildDeps {\n runNodeBuild?: (config: TheoConfig, cwd: string, ctx?: AdapterBuildContext) => Promise<void>\n writeFile?: (path: string, content: string) => void\n ensureDir?: (path: string) => void\n readTomlIfExists?: () => string | null\n /**\n * Seam for the bundling step, alongside `runNodeBuild` above.\n *\n * The entry cannot be written as source: it imports `theokit/server/scan` and five sibling\n * sub-paths by bare specifier, and Netlify's own bundler breaks on the result (measured on the\n * emulator — see `the-netlify-function-ships-pre-bundled.test.ts`). Bundling here rather than\n * through `writeFile` is what lets the toml then say `node_bundler = \"none\"`.\n */\n bundleFunction?: (options: BundleDeployedFunctionOptions) => Promise<void>\n}\n\nexport function renderNetlifyFunction(\n opts: { securityHeaders?: SecurityHeadersConfig } & DeployedCsrfOptions &\n DeployedRuntimeConfigOptions &\n DeployedCorsOptions &\n DeployedRateLimitOptions &\n DeployedServerDirOptions & {\n routes?: readonly BakedRoute[]\n /**\n * The agents, baked at build time — for the same reason the routes are (B-339).\n *\n * Netlify uploads a BUNDLE now, so the runtime scan cannot work here at any value: there is no\n * source tree to read, and `createProductionLoader()` would `import()` a path that was never\n * shipped. Vercel's own comment on this says it best — a fallback \"would preserve a behaviour\n * that is broken by construction: an entry that scans, finds nothing, and answers 404 while\n * looking like it tried.\"\n */\n agents?: readonly { filePath: string; agentPath: string; name: string }[]\n contextModule?: string\n } = {},\n): string {\n // B-338 — the routes are baked, not scanned. The scan was a `readdirSync` over the server directory,\n // and on the Netlify emulator it FOUND `src/server/routes/health.ts` and answered 500\n // `SyntaxError: Unexpected token ')'` — a TypeScript source a plain JS runtime cannot compile. On a real\n // deploy it finds nothing at all, because Netlify uploads the bundled function. The fix B-319 made for\n // Vercel and #369 for Cloudflare, on the fourth target; the emitter is shared and its own docblock says\n // \"the target only supplies what its own error message says\".\n const { routeImports, routeModuleEntries, routeTableEntries } = renderBakedRoutes(\n opts.routes ?? [],\n )\n const runtimeConfig = deployedRuntimeConfigFragment(opts)\n // B-235. Netlify's handler is `(request, context)` and already RECEIVES a Web `Request`, which\n // is the shape this branch needs — the item filed against it counted `new Request` occurrences\n // and concluded the opposite, measuring how the entry OBTAINS a request rather than whether it\n // has one. It used to take `scannedFromLoaderCache` with Deno as the model — same `loaderCache`,\n // same filesystem `serverDir`. That stopped being right when this target began bundling: the\n // emitted `if (!loaderCache) loaderCache = createProductionLoader()` referenced two names the\n // host no longer declared, which is a ReferenceError on the first /api/agents/<name> request and\n // is what `a-generated-entry-declares-every-identifier.test.ts` was written to catch.\n const agentsFragment = deployedAgentsFragment(\n { kind: 'baked', agents: opts.agents ?? [], contextModule: opts.contextModule },\n {\n // Netlify answers its own 404 inline rather than through a helper, so the fragment is given\n // the expression instead of a call it would have to declare.\n notFound: `new Response('Not Found', { status: 404 })`,\n },\n )\n return [\n `// Generated by Theo — Netlify Functions adapter`,\n `import { resolve } from 'node:path'`,\n `import { matchRoute, compilePattern } from 'theokit/server/scan'\\nimport { executeRoute, extractTraceIdFromRequest, TRACE_HEADER, createCorsWebHandler } from 'theokit/server/http'`,\n ...routeImports,\n `import { createWebShim } from 'theokit/adapters/web-shim'`,\n `import { buildSecurityHeaders, withSecurityHeaders } from 'theokit/adapters/security-headers'`,\n ``,\n `const cwd = process.cwd()`,\n // B-338 — was `resolve(cwd, 'server')`, a literal. A project declaring `src/server` got a function\n // scanning a directory that does not exist, and every `/api/*` answered 404 — measured on the Netlify\n // emulator. The FOURTH target with this shape: B-185 (bun, deno), B-235 (agentsDir here), B-315\n // (vercel, aws-lambda, cloudflare), and this file's own call site already names two of them.\n `const serverDir = resolve(cwd, ${serverDirLiteral(opts)})`,\n ...routeRuntimeLines(routeModuleEntries, routeTableEntries, 'netlify'),\n ``,\n `// #410 — the security baseline \\`theokit start\\` puts on every response,`,\n `// carried here as a literal because the deployed function has no`,\n `// theo.config.ts to read. The netlify.toml redirect routes only /api/* here,`,\n `// so this covers the API and not the document Netlify's static host serves`,\n `// (usetheokit/theokit#412).`,\n ...deployedEntryPreamble(runtimeConfig, agentsFragment, opts, 'netlify'),\n ``,\n // B-027 — emitted only when a limit is declared.\n ...(opts.rateLimit === undefined\n ? []\n : [\n // B-338 — the sub-path, not the umbrella. The umbrella re-exports the whole server half and\n // @swc/core's native .node addon is down that graph; the guard against it could not see this\n // adapter, because the shared comment stripper read this file's \"/*\" redirect glob as a\n // comment opener and deleted 36% of it before the sweep looked.\n `import { createRateLimiterWeb } from 'theokit/server/rate-limit'`,\n `import { resolveClientIpFromRequest } from 'theokit/server/rate-limit'`,\n ``,\n ]),\n ...deployedRateLimitFragment(\n opts.rateLimit,\n 'netlify',\n // `context.ip` is the platform's own answer, read from the handler context Netlify passes.\n // The forwarded fallback covers a function shape that carries no context, and returns\n // `undefined` unless a `trustProxy` was declared — which is the named 503, not a shared key.\n `context?.ip ?? resolveClientIpFromRequest(request, TRUST_PROXY)`,\n // Both names are bound in the OUTER handler, which is where the check runs. Threading\n // `context` into `handleRequest` — which the plan called for — buys nothing: the limiter\n // never runs in there.\n 'request, context',\n ),\n ``,\n `export default async (request, context) => {`,\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, context'),\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 ` if (!url.pathname.startsWith('/api/')${agentsFragment.hostBypass}) {`,\n ` return new Response('Not Found', { status: 404 })`,\n ` }`,\n ``,\n\n ``,\n ...agentsFragment.branch,\n ``,\n ` const match = matchRoute(url.pathname, routes)`,\n ` if (!match) return ${JSON_NOT_FOUND_RESPONSE}`,\n ``,\n ` const { req, res, toResponse } = createWebShim(request)`,\n ...deployedTraceFragment('request', ' '),\n ` const method = request.method.toUpperCase()`,\n ` // #382 — the run is not awaited: toResponse() settles at the headers and`,\n ` // carries a live body, so Netlify streams while the handler writes.`,\n ` return toResponse(executeRoute({ route: match.route, method, params: match.params, req, res, loadModule, serverDir, requestId, ...CSRF_CONFIG, ${runtimeConfig.executeRouteSpread} }))`,\n `}`,\n ].join('\\n')\n}\n\nconst THEO_REDIRECT_FROM = '/api/*'\nconst THEO_REDIRECT_TO = '/.netlify/functions/theo'\n\n/**\n * TOML merge (EC-2) — non-destructive.\n *\n * We avoid adding a TOML parser dependency by working at the line level. The\n * supported subset is sufficient for the Netlify use case:\n * - detect existing `[[redirects]]` blocks\n * - find a block whose `from = \"...\"` matches our target\n * - error if such a block points elsewhere\n * - otherwise append our block\n *\n * Unknown sections (e.g. `[build]`, `[[headers]]`, `[context.production.environment]`)\n * are preserved as-is.\n */\ninterface NetlifyRedirectBlock {\n startLine: number\n endLine: number\n from?: string\n to?: string\n}\n\nfunction parseRedirectBlocks(lines: readonly string[]): NetlifyRedirectBlock[] {\n const blocks: NetlifyRedirectBlock[] = []\n let current: NetlifyRedirectBlock | null = null\n\n for (let i = 0; i < lines.length; i++) {\n const trimmed = lines[i].trim()\n if (trimmed === '[[redirects]]') {\n if (current) {\n current.endLine = i - 1\n blocks.push(current)\n }\n current = { startLine: i, endLine: i }\n continue\n }\n if (current && /^\\[(\\[|[^[])/.test(trimmed)) {\n // Entering another section — close current.\n current.endLine = i - 1\n blocks.push(current)\n current = null\n }\n if (current) {\n const fromMatch = /^from\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (fromMatch) current.from = fromMatch[1]\n const toMatch = /^to\\s*=\\s*\"([^\"]+)\"/.exec(trimmed)\n if (toMatch) current.to = toMatch[1]\n current.endLine = i\n }\n }\n if (current) blocks.push(current)\n return blocks\n}\n\n/**\n * The `[functions]` keys this adapter requires, and why each one is not optional.\n *\n * `directory` — measured on the Netlify emulator 2026-09-29: without it the CLI scans its default\n * `netlify/functions/`, finds nothing, and `/api/*` answers `Function not found...` with a 404. The\n * adapter writes to `.netlify/functions/` deliberately (generated output belongs in an ignored\n * directory, not in the source tree the project commits), so the location has to be declared.\n *\n * `node_bundler` — the entry is already bundled here. Netlify re-bundling it produced\n * `SyntaxError: Invalid left-hand side in assignment`.\n */\nconst THEO_FUNCTIONS_DIR = '.netlify/functions'\nconst THEO_FUNCTION_NAME = 'theo'\nconst THEO_FUNCTIONS_KEYS: readonly (readonly [string, string])[] = [\n ['directory', THEO_FUNCTIONS_DIR],\n ['node_bundler', 'none'],\n]\nconst THEO_FUNCTIONS_TARGET = [\n '[functions]',\n ...THEO_FUNCTIONS_KEYS.map(([key, value]) => ` ${key} = \"${value}\"`),\n].join('\\n')\n\nconst THEO_REDIRECT_TARGET = [\n '[[redirects]]',\n ' from = \"/api/*\"',\n ' to = \"/.netlify/functions/theo\"',\n ' status = 200',\n ' force = true',\n].join('\\n')\n\n/**\n * Marks the block this build owns, so it can be REGENERATED rather than merely not duplicated.\n *\n * Idempotence alone would be the wrong contract here: the block carries configuration, so a\n * `security.headers` change has to reach the file. Leaving an existing block in place — which is\n * what the redirect above correctly does, because its content is fixed — would silently pin the\n * baseline to whatever the first build emitted.\n */\nconst THEO_HEADERS_MARKER = '# Generated by Theo — security baseline (usetheokit/theokit#412)'\n\n/**\n * The `[[headers]]` block telling Netlify's static host the baseline.\n *\n * The emitted function applies these to every response IT returns, and it never returns the HTML\n * document: the `[[redirects]]` block routes only `/api/*`, so the page comes from Netlify's static\n * host and carried none of them. The values come from `buildSecurityHeaders` — the same function\n * the handler calls — rather than being written out here, because two lists of headers that must\n * agree are two lists that eventually do not.\n */\nfunction renderHeadersBlock(securityHeaders: SecurityHeadersConfig | undefined): string {\n const values = buildSecurityHeaders(securityHeaders ?? {}, { production: true })\n return [\n THEO_HEADERS_MARKER,\n '[[headers]]',\n ' for = \"/*\"',\n ' [headers.values]',\n ...Object.entries(values).map(([k, v]) => ` ${k} = ${JSON.stringify(v)}`),\n ].join('\\n')\n}\n\n/**\n * Drop a previously generated block, so the next one replaces it.\n *\n * Bounded by the marker and the next top-level section at column 0 (or EOF) — the shape TOML gives\n * a block. A user's own `[[headers]]` is untouched because it does not carry the marker: their\n * file, their rules, and Netlify applies both.\n */\nfunction withoutGeneratedHeaders(source: string): string {\n const lines = source.split(/\\r?\\n/)\n const start = lines.indexOf(THEO_HEADERS_MARKER)\n if (start < 0) return source\n\n const opensSection = (line: string | undefined): boolean => (line ?? '').startsWith('[')\n\n let end = start + 1\n while (end < lines.length && !opensSection(lines[end])) end += 1\n // The marker line itself opens the block, so `end` now sits on the NEXT section — but the block's\n // own `[[headers]]` is the first `[` after the marker, so step past it before scanning again.\n end += 1\n while (end < lines.length && !opensSection(lines[end])) end += 1\n\n return [...lines.slice(0, start), ...lines.slice(end)].join('\\n').replace(/\\n{3,}/gu, '\\n\\n')\n}\n\n/**\n * Declare the `[functions]` keys this adapter requires, without emitting a second table.\n *\n * A duplicate table is a TOML parse error, so this is not tidiness: a project that already declares\n * `[functions]` for `directory`, `included_files` or `external_node_modules` must keep it and gain\n * one key. Line-level like the rest of this merge, and for the same reason — no TOML parser\n * dependency. `[functions.\"name\"]` is a DIFFERENT table and is left alone, which is why the match\n * is exact rather than a prefix.\n *\n * An explicit conflicting value is refused rather than overwritten, on the same argument\n * `NetlifyConflictError` makes about a `/api/*` redirect pointing elsewhere: it is a deliberate\n * declaration, and silently replacing it breaks their build for a reason nothing states.\n */\n/**\n * Read a TOML scalar exactly as written, with no parser.\n *\n * Neither \"strip the quotes\" nor \"cut at the first #\" is correct alone: TOML allows an inline\n * comment after a value, and a `#` INSIDE a quoted value is legal. Stripping quotes alone read\n * `node_bundler = \"none\" # keep` as `none\" # keep` and refused a legal file; cutting at `#` alone\n * would mangle `directory = \"a#b\"`. Both cases are asserted, in both directions, in\n * `the-netlify-function-ships-pre-bundled.test.ts`.\n */\nfunction readTomlScalar(raw: string): string {\n for (const quote of ['\"', \"'\"]) {\n if (!raw.startsWith(quote)) continue\n const close = raw.indexOf(quote, 1)\n return close < 0 ? raw.slice(1) : raw.slice(1, close)\n }\n const hash = raw.indexOf('#')\n return (hash < 0 ? raw : raw.slice(0, hash)).trim()\n}\n\n/**\n * The value a key carries inside one table's line range, or `null` when the table does not set it.\n *\n * Split rather than matched: the rest of this merge is line-level by design, and a regex with `\\s*`\n * either side of a lazy group backtracks (sonarjs/slow-regex) for no gain here. A commented-out\n * `# node_bundler = …` reads as the key `# node_bundler`, which matches nothing — correct, and free.\n */\nfunction declaredIn(\n lines: readonly string[],\n from: number,\n to: number,\n key: string,\n): string | null {\n for (let i = from; i < to; i += 1) {\n const line = lines[i] ?? ''\n const eq = line.indexOf('=')\n if (eq < 0 || line.slice(0, eq).trim() !== key) continue\n return readTomlScalar(line.slice(eq + 1).trim())\n }\n return null\n}\n\nfunction withFunctionsDeclared(source: string): string {\n const lines = source.split(/\\r?\\n/)\n const at = lines.findIndex((line) => line.trimEnd() === '[functions]')\n\n if (at < 0) {\n const sep = source.endsWith('\\n') ? '' : '\\n'\n return `${source}${sep}\\n${THEO_FUNCTIONS_TARGET}\\n`\n }\n\n let end = at + 1\n while (end < lines.length && !(lines[end] ?? '').startsWith('[')) end += 1\n\n const missing: string[] = []\n for (const [key, required] of THEO_FUNCTIONS_KEYS) {\n const declared = declaredIn(lines, at + 1, end, key)\n if (declared === null) {\n missing.push(` ${key} = \"${required}\"`)\n continue\n }\n if (declared !== required) throw new NetlifyFunctionsConflictError(key, declared, required)\n }\n\n if (missing.length === 0) return source\n return [...lines.slice(0, at + 1), ...missing, ...lines.slice(at + 1)].join('\\n')\n}\n\nexport function mergeNetlifyToml(\n existing: string | null,\n securityHeaders?: SecurityHeadersConfig,\n): string {\n const headersBlock = renderHeadersBlock(securityHeaders)\n\n if (existing === null || existing.trim().length === 0) {\n return withFunctionsDeclared(`${THEO_REDIRECT_TARGET}\\n\\n${headersBlock}\\n`)\n }\n\n existing = withoutGeneratedHeaders(existing)\n\n const blocks = parseRedirectBlocks(existing.split(/\\r?\\n/))\n\n // Detect conflict: every `/api/*` block must point at our function.\n for (const b of blocks) {\n if (b.from !== THEO_REDIRECT_FROM) continue\n if (b.to === THEO_REDIRECT_TO) {\n // The redirect is already there. The headers block is regenerated regardless, because it\n // carries configuration and the redirect does not.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return withFunctionsDeclared(`${existing}${sep}\\n${headersBlock}\\n`)\n }\n throw new NetlifyConflictError(b.from, b.to ?? '(unknown)')\n }\n\n // No conflict — append target block.\n const sep = existing.endsWith('\\n') ? '' : '\\n'\n return withFunctionsDeclared(`${existing}${sep}\\n${THEO_REDIRECT_TARGET}\\n\\n${headersBlock}\\n`)\n}\n\n/**\n * Resolve the build's effective dependencies once.\n *\n * Extracted when `buildNetlify` crossed the complexity ceiling after B-339 added a fifth seam. The\n * defaults are a separate concern from the build's sequence (SRP), and the sequence is the part a\n * reader comes here to follow.\n */\nfunction resolveNetlifyDeps(\n cwd: string,\n deps: NetlifyBuildDeps,\n): {\n runNodeBuild: NonNullable<NetlifyBuildDeps['runNodeBuild']>\n writeFile: NonNullable<NetlifyBuildDeps['writeFile']>\n ensureDir: NonNullable<NetlifyBuildDeps['ensureDir']>\n readTomlIfExists: NonNullable<NetlifyBuildDeps['readTomlIfExists']>\n bundleFunction: NonNullable<NetlifyBuildDeps['bundleFunction']>\n} {\n return {\n runNodeBuild: deps.runNodeBuild ?? nodeAdapter.build.bind(nodeAdapter),\n writeFile:\n deps.writeFile ??\n ((path, content) => {\n writeFileSync(path, content)\n }),\n ensureDir: deps.ensureDir ?? ((path: string) => mkdirSync(path, { recursive: true })),\n readTomlIfExists:\n deps.readTomlIfExists ??\n (() => {\n const path = resolve(cwd, 'netlify.toml')\n return existsSync(path) ? readFileSync(path, 'utf-8') : null\n }),\n bundleFunction: deps.bundleFunction ?? bundleDeployedFunction,\n }\n}\n\nexport async function buildNetlify(\n config: TheoConfig,\n cwd: string,\n deps: NetlifyBuildDeps = {},\n ctx?: AdapterBuildContext,\n): Promise<void> {\n // Wave 2 (T2.2) — reject polyglot services on this adapter.\n assertServicesUnsupported('netlify', readManifest(cwd))\n\n const { runNodeBuild, writeFile, ensureDir, readTomlIfExists, bundleFunction } =\n resolveNetlifyDeps(cwd, deps)\n\n const merged = mergeNetlifyToml(readTomlIfExists(), config.security?.headers)\n\n await runNodeBuild(config, cwd, ctx)\n\n const scanned = ctx?.scanRoutes?.(config.serverDir)\n\n const fnDir = resolve(cwd, THEO_FUNCTIONS_DIR)\n ensureDir(fnDir)\n // B-339 — bundled, not written as source. The entry imports `theokit/server/scan` and five\n // sibling sub-paths by bare specifier; Netlify's own bundler produced a `ReferenceError` on\n // an identifier that was base64 of the source, and re-bundled an already-bundled file into a\n // `SyntaxError`. Measured on the emulator: bundled here plus `node_bundler = \"none\"` below,\n // `GET /api/health` answers 200. Cloudflare and Vercel already bundle; this was the target\n // shipping raw source and trusting the platform to resolve it.\n await bundleFunction({\n projectRoot: cwd,\n entrySource: renderNetlifyFunction({\n routes: scanned?.routes,\n // B-339 — from the same scan as the routes, for the same reason: this target bundles, so a\n // runtime agents scan has no source tree to read.\n agents: scanned?.agents,\n contextModule: scanned?.contextModule,\n securityHeaders: config.security?.headers,\n // B-235 wired `agentsDir` through to this renderer, and B-339 made it unreadable here: the\n // agents are baked from the BUILD's scan now, so the entry never resolves a directory. An\n // option a renderer accepts and ignores is the defect `rules/foreign-config-surfaces.md` is\n // about, so it is dropped rather than left looking wired. The scan the build performs still\n // honours `config.agentsDir` — that is where the option belongs.\n // B-338 — the same pillar as the comment above, one option over. The sweep in\n // `every-deployed-entry-is-told-its-server-dir.test.ts` could not see this adapter: its\n // population was the adapters that CALL `serverDirLiteral(opts)`, so the one that never\n // adopted it was outside the population the case iterates.\n serverDir: config.serverDir,\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 }),\n stagePath: '.theokit/netlify/entry.mjs',\n // A DIRECTORY, not a bare file. The bundler code-splits: this project emitted `theo.mjs` plus\n // 40 chunks under `assets/`, which `theo.mjs` imports. Netlify zips a directory-shaped function\n // whole, so the chunks travel; a bare `theo.mjs` with a sibling `assets/` loads in `netlify dev`\n // (it reads from disk) and would reach a deploy without them. Vercel already uses a dedicated\n // `functions/api.func` for the same reason.\n outDir: resolve(fnDir, THEO_FUNCTION_NAME),\n entryFileName: `${THEO_FUNCTION_NAME}.mjs`,\n })\n\n writeFile(resolve(cwd, 'netlify.toml'), merged)\n\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(`\\n ✓ Netlify output → ${THEO_FUNCTIONS_DIR}/${THEO_FUNCTION_NAME}/ + netlify.toml`)\n // eslint-disable-next-line no-console -- CLI build progress\n console.log(\n `${describeDeployedSecurityHeaders({\n target: 'netlify',\n securityHeaders: config.security?.headers,\n mintsNonce: false,\n documentHeaders: 'platform-configured',\n })}\\n`,\n )\n}\n\nexport const netlifyAdapter: DeployAdapter = {\n // B-339 / B-341 / B-343 — measured on the Netlify emulator. Its own bundler turned the raw\n // source into a `ReferenceError`, and re-bundled an already-bundled file into a `SyntaxError`;\n // the toml declares `node_bundler = \"none\"` so the bundle this adapter produces ships intact.\n readsSourceAtRunTime: false,\n specifiersResolvedBy: 'this-adapter',\n name: 'netlify',\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 function returns.\n // The document is served by Netlify's static host and does not pass through\n // it (usetheokit/theokit#412).\n appliesConfig: ['securityHeaders', 'csrf', 'disallowed', 'cors', 'serialization'],\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 buildNetlify(config, cwd, {}, ctx)\n },\n}\n"],"mappings":"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","names":["sep"]}