@timber-js/app 0.2.0-alpha.182 → 0.2.0-alpha.183

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 (57) hide show
  1. package/dist/_chunks/{build-output-helper-DF5uScqS.js → build-output-helper-BC-Zg0_w.js} +38 -8
  2. package/dist/_chunks/build-output-helper-BC-Zg0_w.js.map +1 -0
  3. package/dist/_chunks/{cloudflare-AHoWYTYr.js → cloudflare-nlD9KhDj.js} +21 -3
  4. package/dist/_chunks/{cloudflare-AHoWYTYr.js.map → cloudflare-nlD9KhDj.js.map} +1 -1
  5. package/dist/_chunks/plugin-context---kTF5v8.js.map +1 -1
  6. package/dist/adapters/build-output-helper.d.ts +6 -0
  7. package/dist/adapters/build-output-helper.d.ts.map +1 -1
  8. package/dist/adapters/cloudflare-dev.js +1 -1
  9. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  10. package/dist/adapters/cloudflare.d.ts +10 -0
  11. package/dist/adapters/cloudflare.d.ts.map +1 -1
  12. package/dist/adapters/cloudflare.js +2 -2
  13. package/dist/adapters/nitro.d.ts +14 -0
  14. package/dist/adapters/nitro.d.ts.map +1 -1
  15. package/dist/adapters/nitro.js +14 -1
  16. package/dist/adapters/nitro.js.map +1 -1
  17. package/dist/adapters/shared.d.ts +1 -1
  18. package/dist/adapters/shared.d.ts.map +1 -1
  19. package/dist/adapters/types.d.ts +14 -0
  20. package/dist/adapters/types.d.ts.map +1 -1
  21. package/dist/client/internal.js +73 -11
  22. package/dist/client/internal.js.map +1 -1
  23. package/dist/client/rsc-fetch.d.ts +2 -0
  24. package/dist/client/rsc-fetch.d.ts.map +1 -1
  25. package/dist/index.js +324 -196
  26. package/dist/index.js.map +1 -1
  27. package/dist/plugin-context.d.ts +6 -0
  28. package/dist/plugin-context.d.ts.map +1 -1
  29. package/dist/plugins/adapter-build.d.ts.map +1 -1
  30. package/dist/plugins/build-report.d.ts +4 -3
  31. package/dist/plugins/build-report.d.ts.map +1 -1
  32. package/dist/plugins/static-build.d.ts +26 -2
  33. package/dist/plugins/static-build.d.ts.map +1 -1
  34. package/dist/server/rsc-entry/index.d.ts +12 -0
  35. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  36. package/dist/server/sitemap-generator.d.ts +1 -1
  37. package/dist/server/sitemap-generator.d.ts.map +1 -1
  38. package/dist/server/static-generator.d.ts +106 -0
  39. package/dist/server/static-generator.d.ts.map +1 -0
  40. package/docs/learn/13-configuration.mdx +5 -1
  41. package/package.json +1 -1
  42. package/src/adapters/build-output-helper.ts +34 -0
  43. package/src/adapters/cloudflare.ts +43 -1
  44. package/src/adapters/nitro.ts +58 -2
  45. package/src/adapters/shared.ts +38 -10
  46. package/src/adapters/types.ts +14 -0
  47. package/src/client/browser-entry/index.ts +7 -1
  48. package/src/client/rsc-fetch.ts +132 -19
  49. package/src/index.ts +1 -1
  50. package/src/plugin-context.ts +6 -0
  51. package/src/plugins/adapter-build.ts +5 -0
  52. package/src/plugins/build-report.ts +6 -12
  53. package/src/plugins/static-build.ts +264 -2
  54. package/src/server/rsc-entry/index.ts +24 -0
  55. package/src/server/sitemap-generator.ts +1 -1
  56. package/src/server/static-generator.ts +692 -0
  57. package/dist/_chunks/build-output-helper-DF5uScqS.js.map +0 -1
@@ -22,8 +22,8 @@ var STATIC_CACHE = "public, max-age=3600, must-revalidate";
22
22
  * producing duplicate Cache-Control directives on hashed assets
23
23
  * (invalid per RFC 9111).
24
24
  */
25
- var MAX_UNHASHED_RULES = 98;
26
- function generateHeadersFile(unhashedPaths) {
25
+ var MAX_PER_FILE_RULES = 98;
26
+ function generateHeadersFile(unhashedPaths, contentTypes) {
27
27
  let content = `# Auto-generated by @timber-js/app — static asset cache headers.
28
28
  # See design/25-production-deployments.md §"CDN / Edge Cache"
29
29
 
@@ -33,11 +33,21 @@ function generateHeadersFile(unhashedPaths) {
33
33
  /_timber/fonts/*
34
34
  Cache-Control: ${IMMUTABLE_CACHE}
35
35
  `;
36
+ const allCtEntries = contentTypes ? Object.entries(contentTypes).sort(([a], [b]) => a.localeCompare(b)) : [];
37
+ const ctEntries = allCtEntries.slice(0, MAX_PER_FILE_RULES);
38
+ if (allCtEntries.length > MAX_PER_FILE_RULES) console.warn(`[timber] _headers: ${allCtEntries.length} content-type rules exceed Cloudflare's 100-rule limit. Only the first ${MAX_PER_FILE_RULES} extensionless API routes will get Content-Type headers.`);
39
+ const remainingBudget = MAX_PER_FILE_RULES - ctEntries.length;
36
40
  if (unhashedPaths && unhashedPaths.length > 0) {
37
- const capped = unhashedPaths.slice(0, MAX_UNHASHED_RULES);
38
- if (unhashedPaths.length > MAX_UNHASHED_RULES) console.warn(`[timber] _headers: ${unhashedPaths.length} unhashed static files found, but Cloudflare limits _headers to 100 rules. Only the first ${MAX_UNHASHED_RULES} unhashed files will get Cache-Control headers.`);
39
- content += `\n# Unhashed static assets — short-lived cache with revalidation\n`;
40
- for (const p of capped) content += `\n${p}\n Cache-Control: ${STATIC_CACHE}\n`;
41
+ const capped = unhashedPaths.slice(0, Math.max(0, remainingBudget));
42
+ if (unhashedPaths.length > remainingBudget) console.warn(`[timber] _headers: ${unhashedPaths.length} unhashed static files + ${ctEntries.length} content-type rules found, but Cloudflare limits _headers to 100 rules. Only the first ${capped.length} unhashed files will get Cache-Control headers.`);
43
+ if (capped.length > 0) {
44
+ content += `\n# Unhashed static assets — short-lived cache with revalidation\n`;
45
+ for (const p of capped) content += `\n${p}\n Cache-Control: ${STATIC_CACHE}\n`;
46
+ }
47
+ }
48
+ if (ctEntries.length > 0) {
49
+ content += `\n# Content-Type for extensionless API routes\n`;
50
+ for (const [urlPath, ct] of ctEntries) content += `\n${urlPath}\n Content-Type: ${ct}\n`;
41
51
  }
42
52
  return content;
43
53
  }
@@ -142,6 +152,26 @@ async function findReferencedWasm(clientDir) {
142
152
  }
143
153
  return referenced;
144
154
  }
155
+ /**
156
+ * Build a static-only output directory (no server entry). Copies client
157
+ * assets and pre-rendered HTML/RSC/API files into `outDir/publicDirName`.
158
+ * Used by both Cloudflare and Nitro adapters in `output: 'static'` mode.
159
+ */
160
+ async function buildStaticDirectoryOutput(config, buildDir, outDir, publicDirName) {
161
+ await mkdir(outDir, { recursive: true });
162
+ const publicDir = join(outDir, publicDirName);
163
+ await mkdir(publicDir, { recursive: true });
164
+ await cp(join(buildDir, "client"), publicDir, {
165
+ recursive: true,
166
+ filter: (src) => {
167
+ if (config.clientJavascriptDisabled && src.endsWith(".js")) return false;
168
+ return true;
169
+ }
170
+ }).catch((e) => {
171
+ if (e.code !== "ENOENT") throw e;
172
+ });
173
+ if (config.staticPagesDir) await cp(config.staticPagesDir, publicDir, { recursive: true });
174
+ }
145
175
  async function collectFiles(dir) {
146
176
  const result = [];
147
177
  const entries = await readdir(dir, { withFileTypes: true });
@@ -153,6 +183,6 @@ async function collectFiles(dir) {
153
183
  return result;
154
184
  }
155
185
  //#endregion
156
- export { IMMUTABLE_CACHE as n, runSharedBuildSteps as t };
186
+ export { generateHeadersFile as a, IMMUTABLE_CACHE as i, findUnhashedStaticFiles as n, runSharedBuildSteps as r, buildStaticDirectoryOutput as t };
157
187
 
158
- //# sourceMappingURL=build-output-helper-DF5uScqS.js.map
188
+ //# sourceMappingURL=build-output-helper-BC-Zg0_w.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-output-helper-BC-Zg0_w.js","names":[],"sources":["../../src/adapters/shared.ts","../../src/adapters/build-output-helper.ts"],"sourcesContent":["// Shared adapter utilities — leaf module with zero deps on server/, client/,\n// or any Vite-dependent code.\n//\n// Adapters are loaded by Node at Vite startup time, before Vite's module\n// resolver is available. This module uses only Node built-ins and can be\n// imported by both adapters via a relative path.\n//\n// IMPORTANT: Do NOT add imports from ../server/, ../client/, ../plugins/,\n// or any module that transitively depends on Vite. A test in\n// tests/adapters/shared-no-forbidden-imports.test.ts enforces this.\n\n// ─── Static asset cache headers ──────────────────────────────────────────\n\n/** Cache-Control value for hashed (immutable) assets. */\nexport const IMMUTABLE_CACHE = 'public, max-age=31536000, immutable';\n\n/** Cache-Control value for unhashed static assets. */\nexport const STATIC_CACHE = 'public, max-age=3600, must-revalidate';\n\n/**\n * Generate a `_headers` file for static asset cache control.\n *\n * The `_headers` file is a platform convention supported by Cloudflare Workers\n * Static Assets, Cloudflare Pages, and Netlify. It maps URL patterns to\n * HTTP response headers.\n *\n * Only hashed (immutable) asset paths are listed here by default. When\n * `unhashedPaths` is provided, per-file rules are appended for unhashed\n * static assets (favicon.ico, robots.txt, etc.) so they get Cache-Control\n * on platforms like Cloudflare Workers Static Assets where the server\n * handler never sees static file requests.\n *\n * A `/*` catch-all is NOT used — it would be additive on Cloudflare,\n * producing duplicate Cache-Control directives on hashed assets\n * (invalid per RFC 9111).\n */\n// Cloudflare Workers Static Assets caps _headers at 100 rules.\n// Reserve 2 for the wildcard hashed-asset rules.\nconst MAX_PER_FILE_RULES = 98;\n\nexport function generateHeadersFile(\n unhashedPaths?: string[],\n contentTypes?: Record<string, string>\n): string {\n let content = `# Auto-generated by @timber-js/app — static asset cache headers.\n# See design/25-production-deployments.md §\"CDN / Edge Cache\"\n\n/assets/*\n Cache-Control: ${IMMUTABLE_CACHE}\n\n/_timber/fonts/*\n Cache-Control: ${IMMUTABLE_CACHE}\n`;\n\n // Content-Type rules are higher priority than cache headers for\n // extensionless files — without them the file is unreadable.\n const allCtEntries = contentTypes\n ? Object.entries(contentTypes).sort(([a], [b]) => a.localeCompare(b))\n : [];\n const ctEntries = allCtEntries.slice(0, MAX_PER_FILE_RULES);\n if (allCtEntries.length > MAX_PER_FILE_RULES) {\n console.warn(\n `[timber] _headers: ${allCtEntries.length} content-type rules exceed ` +\n `Cloudflare's 100-rule limit. Only the first ${MAX_PER_FILE_RULES} ` +\n `extensionless API routes will get Content-Type headers.`\n );\n }\n const remainingBudget = MAX_PER_FILE_RULES - ctEntries.length;\n\n if (unhashedPaths && unhashedPaths.length > 0) {\n const capped = unhashedPaths.slice(0, Math.max(0, remainingBudget));\n if (unhashedPaths.length > remainingBudget) {\n console.warn(\n `[timber] _headers: ${unhashedPaths.length} unhashed static files + ` +\n `${ctEntries.length} content-type rules found, but Cloudflare limits ` +\n `_headers to 100 rules. Only the first ${capped.length} unhashed ` +\n `files will get Cache-Control headers.`\n );\n }\n if (capped.length > 0) {\n content += `\\n# Unhashed static assets — short-lived cache with revalidation\\n`;\n for (const p of capped) {\n content += `\\n${p}\\n Cache-Control: ${STATIC_CACHE}\\n`;\n }\n }\n }\n\n if (ctEntries.length > 0) {\n content += `\\n# Content-Type for extensionless API routes\\n`;\n for (const [urlPath, ct] of ctEntries) {\n content += `\\n${urlPath}\\n Content-Type: ${ct}\\n`;\n }\n }\n\n return content;\n}\n","// Shared build-output helper for adapter buildOutput() methods.\n//\n// Steps 1–5 of every adapter's buildOutput are platform-agnostic:\n// 1. Copy client assets to the public/static directory\n// 2. Write _headers file for static asset cache control\n// 3. Copy RSC + SSR server bundles\n// 4. Write manifest-init module (if present)\n//\n// Only the final steps (entry codegen, platform config) vary per adapter.\n//\n// IMPORTANT: This module must remain a leaf — no imports from ../server/,\n// ../client/, or Vite-dependent code. See adapters/shared.ts header.\n\nimport { writeFile, mkdir, cp, readdir, readFile } from 'node:fs/promises';\nimport { join, basename, relative } from 'node:path';\nimport { generateHeadersFile } from './shared.js';\nimport type { TimberConfig } from './types';\n\n/** Options for the shared build output steps. */\nexport interface BuildOutputBaseOptions {\n /** Resolved timber config. */\n config: TimberConfig;\n /** Root build directory (e.g. `.timber/build`). */\n buildDir: string;\n /** Adapter-specific output directory (e.g. `.timber/build/nitro`). */\n outDir: string;\n /**\n * Name of the public/static directory within outDir.\n * Nitro uses 'public', Cloudflare uses 'static'.\n */\n publicDirName: string;\n}\n\n/**\n * Run the platform-agnostic build output steps shared by all adapters:\n *\n * 1. Create the output directory\n * 2. Copy client assets to the public/static directory\n * 3. Write the `_headers` file for static asset cache control\n * 4. Copy RSC and SSR server bundles into the output directory\n * 5. Write the manifest-init module if present\n *\n * Returns the resolved public directory path for further adapter-specific use.\n */\nexport async function runSharedBuildSteps(opts: BuildOutputBaseOptions): Promise<string> {\n const { config, buildDir, outDir, publicDirName } = opts;\n\n // 1. Create the output directory.\n await mkdir(outDir, { recursive: true });\n\n // 2. Copy client assets to public/static directory.\n // When client JavaScript is disabled, skip .js files — only CSS,\n // fonts, images, and other static assets are needed.\n const clientDir = join(buildDir, 'client');\n const publicDir = join(outDir, publicDirName);\n await mkdir(publicDir, { recursive: true });\n // Find WASM files that are referenced by client JS (legitimate client WASM)\n // vs orphaned server-only WASM (e.g. @takumi-rs for OG image generation).\n // Only strip orphaned WASM to avoid breaking client-side WASM usage.\n const referencedWasm = await findReferencedWasm(clientDir);\n\n await cp(clientDir, publicDir, {\n recursive: true,\n filter: (src: string) => {\n if (src.endsWith('.wasm') && !referencedWasm.has(basename(src))) {\n return false;\n }\n if (config.clientJavascriptDisabled && src.endsWith('.js')) return false;\n return true;\n },\n }).catch((e: NodeJS.ErrnoException) => {\n // Client dir may not exist when client JavaScript is disabled\n if (e.code !== 'ENOENT') throw e;\n });\n\n // 3. Write _headers file for static asset cache control.\n // Scan for unhashed files so they get per-file Cache-Control rules in the\n // _headers file. On platforms like Cloudflare Workers Static Assets, the\n // worker never sees static file requests — _headers is the only mechanism.\n const unhashedPaths = await findUnhashedStaticFiles(publicDir);\n await writeFile(join(publicDir, '_headers'), generateHeadersFile(unhashedPaths));\n\n // 4. Copy RSC + SSR server bundles into the output directory.\n await cp(join(buildDir, 'rsc'), join(outDir, 'rsc'), { recursive: true });\n await cp(join(buildDir, 'ssr'), join(outDir, 'ssr'), { recursive: true }).catch(\n (e: NodeJS.ErrnoException) => {\n // SSR dir may not exist for static-only builds\n if (e.code !== 'ENOENT') throw e;\n }\n );\n\n // 4b. Copy prebuilt/ flight payloads if present (cache.component artifacts).\n // These are produced by the post-build capture pass (design/44) and read\n // at request time via the adapter's PrebuiltPayloadSource.\n await cp(join(buildDir, 'prebuilt'), join(outDir, 'prebuilt'), { recursive: true }).catch(\n (e: NodeJS.ErrnoException) => {\n // No prebuilt dir when the app has no cache.component calls\n if (e.code !== 'ENOENT') throw e;\n }\n );\n\n // 5. Write manifest-init module if present.\n if (config.manifestInit) {\n await writeFile(join(outDir, '_timber-manifest-init.js'), config.manifestInit);\n }\n\n return publicDir;\n}\n\n/**\n * Find unhashed static files in the public directory that need per-file\n * Cache-Control rules in the `_headers` file.\n *\n * Excludes:\n * - Files under `assets/` and `_timber/` (covered by wildcard rules)\n * - Underscore-prefixed files (`_headers`, `_worker.js`, etc.)\n *\n * @internal Exported for testing.\n */\n// Well-known metadata files that should be prioritized when capping\n// _headers rules, so they're never dropped in favor of less important files.\nconst PRIORITY_FILES = new Set([\n '/favicon.ico',\n '/robots.txt',\n '/sitemap.xml',\n '/manifest.json',\n '/manifest.webmanifest',\n '/apple-touch-icon.png',\n]);\n\nexport async function findUnhashedStaticFiles(publicDir: string): Promise<string[]> {\n let files: string[];\n try {\n files = await collectFiles(publicDir);\n } catch {\n return [];\n }\n\n const priority: string[] = [];\n const rest: string[] = [];\n for (const file of files) {\n const rel = relative(publicDir, file).replace(/\\\\/g, '/');\n if (rel.startsWith('assets/') || rel.startsWith('_timber/')) continue;\n if (basename(rel).startsWith('_')) continue;\n const urlPath = '/' + rel;\n if (PRIORITY_FILES.has(urlPath)) {\n priority.push(urlPath);\n } else {\n rest.push(urlPath);\n }\n }\n\n return [...priority.sort(), ...rest.sort()];\n}\n\n/**\n * Scan client JS files for references to .wasm filenames.\n * Returns the set of WASM basenames that are actually imported/referenced\n * by client code and must be preserved in the static output.\n */\nasync function findReferencedWasm(clientDir: string): Promise<Set<string>> {\n const referenced = new Set<string>();\n\n let files: string[];\n try {\n files = await collectFiles(clientDir);\n } catch {\n return referenced;\n }\n\n const jsFiles = files.filter((f) => f.endsWith('.js'));\n const wasmFiles = files.filter((f) => f.endsWith('.wasm')).map((f) => basename(f));\n if (wasmFiles.length === 0) return referenced;\n\n for (const jsFile of jsFiles) {\n const content = await readFile(jsFile, 'utf-8');\n for (const wasmName of wasmFiles) {\n if (content.includes(wasmName)) {\n referenced.add(wasmName);\n }\n }\n }\n\n return referenced;\n}\n\n/**\n * Build a static-only output directory (no server entry). Copies client\n * assets and pre-rendered HTML/RSC/API files into `outDir/publicDirName`.\n * Used by both Cloudflare and Nitro adapters in `output: 'static'` mode.\n */\nexport async function buildStaticDirectoryOutput(\n config: TimberConfig,\n buildDir: string,\n outDir: string,\n publicDirName: string\n): Promise<void> {\n await mkdir(outDir, { recursive: true });\n\n const publicDir = join(outDir, publicDirName);\n await mkdir(publicDir, { recursive: true });\n\n // Copy client assets (JS, CSS, fonts, images).\n const clientDir = join(buildDir, 'client');\n await cp(clientDir, publicDir, {\n recursive: true,\n filter: (src: string) => {\n if (config.clientJavascriptDisabled && src.endsWith('.js')) return false;\n return true;\n },\n }).catch((e: NodeJS.ErrnoException) => {\n if (e.code !== 'ENOENT') throw e;\n });\n\n // Copy pre-rendered static pages (HTML + RSC flight + API routes).\n if (config.staticPagesDir) {\n await cp(config.staticPagesDir, publicDir, { recursive: true });\n }\n}\n\nasync function collectFiles(dir: string): Promise<string[]> {\n const result: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n result.push(...(await collectFiles(fullPath)));\n } else {\n result.push(fullPath);\n }\n }\n return result;\n}\n"],"mappings":";;;;AAcA,IAAa,kBAAkB;;AAG/B,IAAa,eAAe;;;;;;;;;;;;;;;;;;AAqB5B,IAAM,qBAAqB;AAE3B,SAAgB,oBACd,eACA,cACQ;CACR,IAAI,UAAU;;;;mBAIG,gBAAgB;;;mBAGhB,gBAAgB;;CAKjC,MAAM,eAAe,eACjB,OAAO,QAAQ,YAAY,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,IAClE,CAAC;CACL,MAAM,YAAY,aAAa,MAAM,GAAG,kBAAkB;CAC1D,IAAI,aAAa,SAAS,oBACxB,QAAQ,KACN,sBAAsB,aAAa,OAAO,yEACO,mBAAmB,yDAEtE;CAEF,MAAM,kBAAkB,qBAAqB,UAAU;CAEvD,IAAI,iBAAiB,cAAc,SAAS,GAAG;EAC7C,MAAM,SAAS,cAAc,MAAM,GAAG,KAAK,IAAI,GAAG,eAAe,CAAC;EAClE,IAAI,cAAc,SAAS,iBACzB,QAAQ,KACN,sBAAsB,cAAc,OAAO,2BACtC,UAAU,OAAO,yFACqB,OAAO,OAAO,gDAE3D;EAEF,IAAI,OAAO,SAAS,GAAG;GACrB,WAAW;GACX,KAAK,MAAM,KAAK,QACd,WAAW,KAAK,EAAE,qBAAqB,aAAa;EAExD;CACF;CAEA,IAAI,UAAU,SAAS,GAAG;EACxB,WAAW;EACX,KAAK,MAAM,CAAC,SAAS,OAAO,WAC1B,WAAW,KAAK,QAAQ,oBAAoB,GAAG;CAEnD;CAEA,OAAO;AACT;;;;;;;;;;;;;;ACnDA,eAAsB,oBAAoB,MAA+C;CACvF,MAAM,EAAE,QAAQ,UAAU,QAAQ,kBAAkB;CAGpD,MAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;CAKvC,MAAM,YAAY,KAAK,UAAU,QAAQ;CACzC,MAAM,YAAY,KAAK,QAAQ,aAAa;CAC5C,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;CAI1C,MAAM,iBAAiB,MAAM,mBAAmB,SAAS;CAEzD,MAAM,GAAG,WAAW,WAAW;EAC7B,WAAW;EACX,SAAS,QAAgB;GACvB,IAAI,IAAI,SAAS,OAAO,KAAK,CAAC,eAAe,IAAI,SAAS,GAAG,CAAC,GAC5D,OAAO;GAET,IAAI,OAAO,4BAA4B,IAAI,SAAS,KAAK,GAAG,OAAO;GACnE,OAAO;EACT;CACF,CAAC,CAAC,CAAC,OAAO,MAA6B;EAErC,IAAI,EAAE,SAAS,UAAU,MAAM;CACjC,CAAC;CAMD,MAAM,gBAAgB,MAAM,wBAAwB,SAAS;CAC7D,MAAM,UAAU,KAAK,WAAW,UAAU,GAAG,oBAAoB,aAAa,CAAC;CAG/E,MAAM,GAAG,KAAK,UAAU,KAAK,GAAG,KAAK,QAAQ,KAAK,GAAG,EAAE,WAAW,KAAK,CAAC;CACxE,MAAM,GAAG,KAAK,UAAU,KAAK,GAAG,KAAK,QAAQ,KAAK,GAAG,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,OACvE,MAA6B;EAE5B,IAAI,EAAE,SAAS,UAAU,MAAM;CACjC,CACF;CAKA,MAAM,GAAG,KAAK,UAAU,UAAU,GAAG,KAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC,CAAC,CAAC,OACjF,MAA6B;EAE5B,IAAI,EAAE,SAAS,UAAU,MAAM;CACjC,CACF;CAGA,IAAI,OAAO,cACT,MAAM,UAAU,KAAK,QAAQ,0BAA0B,GAAG,OAAO,YAAY;CAG/E,OAAO;AACT;;;;;;;;;;;AAcA,IAAM,iCAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,eAAsB,wBAAwB,WAAsC;CAClF,IAAI;CACJ,IAAI;EACF,QAAQ,MAAM,aAAa,SAAS;CACtC,QAAQ;EACN,OAAO,CAAC;CACV;CAEA,MAAM,WAAqB,CAAC;CAC5B,MAAM,OAAiB,CAAC;CACxB,KAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,MAAM,SAAS,WAAW,IAAI,CAAC,CAAC,QAAQ,OAAO,GAAG;EACxD,IAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,GAAG;EAC7D,IAAI,SAAS,GAAG,CAAC,CAAC,WAAW,GAAG,GAAG;EACnC,MAAM,UAAU,MAAM;EACtB,IAAI,eAAe,IAAI,OAAO,GAC5B,SAAS,KAAK,OAAO;OAErB,KAAK,KAAK,OAAO;CAErB;CAEA,OAAO,CAAC,GAAG,SAAS,KAAK,GAAG,GAAG,KAAK,KAAK,CAAC;AAC5C;;;;;;AAOA,eAAe,mBAAmB,WAAyC;CACzE,MAAM,6BAAa,IAAI,IAAY;CAEnC,IAAI;CACJ,IAAI;EACF,QAAQ,MAAM,aAAa,SAAS;CACtC,QAAQ;EACN,OAAO;CACT;CAEA,MAAM,UAAU,MAAM,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC;CACrD,MAAM,YAAY,MAAM,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC,CAAC,CAAC,KAAK,MAAM,SAAS,CAAC,CAAC;CACjF,IAAI,UAAU,WAAW,GAAG,OAAO;CAEnC,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,MAAM,SAAS,QAAQ,OAAO;EAC9C,KAAK,MAAM,YAAY,WACrB,IAAI,QAAQ,SAAS,QAAQ,GAC3B,WAAW,IAAI,QAAQ;CAG7B;CAEA,OAAO;AACT;;;;;;AAOA,eAAsB,2BACpB,QACA,UACA,QACA,eACe;CACf,MAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;CAEvC,MAAM,YAAY,KAAK,QAAQ,aAAa;CAC5C,MAAM,MAAM,WAAW,EAAE,WAAW,KAAK,CAAC;CAI1C,MAAM,GADY,KAAK,UAAU,QACxB,GAAW,WAAW;EAC7B,WAAW;EACX,SAAS,QAAgB;GACvB,IAAI,OAAO,4BAA4B,IAAI,SAAS,KAAK,GAAG,OAAO;GACnE,OAAO;EACT;CACF,CAAC,CAAC,CAAC,OAAO,MAA6B;EACrC,IAAI,EAAE,SAAS,UAAU,MAAM;CACjC,CAAC;CAGD,IAAI,OAAO,gBACT,MAAM,GAAG,OAAO,gBAAgB,WAAW,EAAE,WAAW,KAAK,CAAC;AAElE;AAEA,eAAe,aAAa,KAAgC;CAC1D,MAAM,SAAmB,CAAC;CAC1B,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;CAC1D,KAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,MAAM,IAAI;EACrC,IAAI,MAAM,YAAY,GACpB,OAAO,KAAK,GAAI,MAAM,aAAa,QAAQ,CAAE;OAE7C,OAAO,KAAK,QAAQ;CAExB;CACA,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { t as runSharedBuildSteps } from "./build-output-helper-DF5uScqS.js";
1
+ import { a as generateHeadersFile, n as findUnhashedStaticFiles, r as runSharedBuildSteps, t as buildStaticDirectoryOutput } from "./build-output-helper-BC-Zg0_w.js";
2
2
  import { join, relative } from "node:path";
3
3
  import { access, readFile, readdir, rm, writeFile } from "node:fs/promises";
4
4
  import { AsyncLocalStorage } from "node:async_hooks";
@@ -764,6 +764,10 @@ function cloudflare(options = {}) {
764
764
  name: "cloudflare",
765
765
  async buildOutput(config, buildDir) {
766
766
  const outDir = join(buildDir, "cloudflare");
767
+ if (config.output === "static" && config.staticPagesDir) {
768
+ await buildStaticOutput(config, buildDir, outDir, options);
769
+ return;
770
+ }
767
771
  await rm(join(outDir, "prebuilt"), {
768
772
  recursive: true,
769
773
  force: true
@@ -1154,6 +1158,20 @@ function generateWranglerConfig(config, options, userConfig) {
1154
1158
  merged.find_additional_modules = true;
1155
1159
  return merged;
1156
1160
  }
1161
+ /**
1162
+ * Build a Cloudflare Pages-compatible static output directory.
1163
+ *
1164
+ * No Workers entry — just pre-rendered HTML, RSC flight files, and client
1165
+ * assets in a flat directory. Uses the shared buildStaticDirectoryOutput
1166
+ * then adds a `_headers` file for cache control.
1167
+ *
1168
+ * @internal Exported for testing.
1169
+ */
1170
+ async function buildStaticOutput(config, buildDir, outDir, _options) {
1171
+ await buildStaticDirectoryOutput(config, buildDir, outDir, ".");
1172
+ const unhashedPaths = await findUnhashedStaticFiles(outDir);
1173
+ await writeFile(join(outDir, "_headers"), generateHeadersFile(unhashedPaths, config.staticContentTypes));
1174
+ }
1157
1175
  /** @internal Exported for testing. */
1158
1176
  function generatePreviewCommand(buildDir) {
1159
1177
  const cfDir = join(buildDir, "cloudflare");
@@ -1183,6 +1201,6 @@ function spawnPreviewProcess(command, args, cwd) {
1183
1201
  });
1184
1202
  }
1185
1203
  //#endregion
1186
- export { generateRuntimeInitModule as a, getCloudflareBindings as c, runWithBindings as d, generatePreviewCommand as i, readPrebuiltData as l, generateBindingsConfig as n, generateWorkerEntry as o, generatePrebuiltSourceModule as r, generateWranglerConfig as s, cloudflare as t, readUserWranglerConfig as u };
1204
+ export { generatePreviewCommand as a, generateWranglerConfig as c, readUserWranglerConfig as d, runWithBindings as f, generatePrebuiltSourceModule as i, getCloudflareBindings as l, cloudflare as n, generateRuntimeInitModule as o, generateBindingsConfig as r, generateWorkerEntry as s, buildStaticOutput as t, readPrebuiltData as u };
1187
1205
 
1188
- //# sourceMappingURL=cloudflare-AHoWYTYr.js.map
1206
+ //# sourceMappingURL=cloudflare-nlD9KhDj.js.map