@rshono/core 1.0.0-rc.2 → 1.0.0-rc.21

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 (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +348 -158
  3. package/bin/rshono.mjs +3 -4
  4. package/dist/builder/env-shadow-loader.cjs +123 -8
  5. package/dist/builder/page-entry-loader.cjs +28 -1
  6. package/dist/builder/page-files.d.ts +0 -1
  7. package/dist/builder/page-files.js +7 -3
  8. package/dist/builder/page-files.js.map +1 -1
  9. package/dist/builder/public-env.d.ts +7 -1
  10. package/dist/builder/public-env.js +7 -0
  11. package/dist/builder/public-env.js.map +1 -1
  12. package/dist/builder/react-versions.d.ts +12 -0
  13. package/dist/builder/react-versions.js +74 -0
  14. package/dist/builder/react-versions.js.map +1 -0
  15. package/dist/builder/rspack-config.d.ts +13 -4
  16. package/dist/builder/rspack-config.js +136 -53
  17. package/dist/builder/rspack-config.js.map +1 -1
  18. package/dist/builder/server-only-imports.d.ts +32 -0
  19. package/dist/builder/server-only-imports.js +51 -0
  20. package/dist/builder/server-only-imports.js.map +1 -0
  21. package/dist/cli/build.d.ts +2 -3
  22. package/dist/cli/build.js +51 -13
  23. package/dist/cli/build.js.map +1 -1
  24. package/dist/cli/dev.d.ts +2 -3
  25. package/dist/cli/dev.js +149 -45
  26. package/dist/cli/dev.js.map +1 -1
  27. package/dist/cli/exit.d.ts +12 -0
  28. package/dist/cli/exit.js +16 -0
  29. package/dist/cli/exit.js.map +1 -0
  30. package/dist/cli/index.d.ts +0 -1
  31. package/dist/cli/index.js +91 -30
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/start.d.ts +0 -1
  34. package/dist/cli/start.js +15 -18
  35. package/dist/cli/start.js.map +1 -1
  36. package/dist/config.d.ts +72 -78
  37. package/dist/config.js +17 -1
  38. package/dist/config.js.map +1 -1
  39. package/dist/deploy/aws-lambda/runtime.d.ts +4 -7
  40. package/dist/deploy/aws-lambda/runtime.js +5 -8
  41. package/dist/deploy/aws-lambda/runtime.js.map +1 -1
  42. package/dist/deploy/build-marker.d.ts +9 -6
  43. package/dist/deploy/build-marker.js +8 -4
  44. package/dist/deploy/build-marker.js.map +1 -1
  45. package/dist/deploy/cloudflare/build.d.ts +0 -1
  46. package/dist/deploy/cloudflare/build.js +17 -13
  47. package/dist/deploy/cloudflare/build.js.map +1 -1
  48. package/dist/deploy/cloudflare/runtime.d.ts +2 -6
  49. package/dist/deploy/cloudflare/runtime.js +73 -68
  50. package/dist/deploy/cloudflare/runtime.js.map +1 -1
  51. package/dist/deploy/contract.d.ts +57 -41
  52. package/dist/deploy/contract.js.map +1 -1
  53. package/dist/deploy/filesystem.d.ts +3 -6
  54. package/dist/deploy/filesystem.js +23 -20
  55. package/dist/deploy/filesystem.js.map +1 -1
  56. package/dist/deploy/node/runtime.d.ts +5 -3
  57. package/dist/deploy/node/runtime.js +23 -9
  58. package/dist/deploy/node/runtime.js.map +1 -1
  59. package/dist/deploy/presets.d.ts +34 -30
  60. package/dist/deploy/presets.js +56 -44
  61. package/dist/deploy/presets.js.map +1 -1
  62. package/dist/deploy/public-paths.d.ts +42 -0
  63. package/dist/deploy/public-paths.js +66 -0
  64. package/dist/deploy/public-paths.js.map +1 -0
  65. package/dist/deploy/vercel/build.d.ts +0 -1
  66. package/dist/deploy/vercel/build.js +25 -16
  67. package/dist/deploy/vercel/build.js.map +1 -1
  68. package/dist/deploy/vercel/runtime.d.ts +10 -7
  69. package/dist/deploy/vercel/runtime.js +48 -11
  70. package/dist/deploy/vercel/runtime.js.map +1 -1
  71. package/dist/index.d.ts +19 -17
  72. package/dist/index.js +20 -10
  73. package/dist/index.js.map +1 -1
  74. package/dist/router.d.ts +212 -120
  75. package/dist/router.js +3 -8
  76. package/dist/router.js.map +1 -1
  77. package/dist/runtime/boundaries.d.ts +62 -48
  78. package/dist/runtime/boundaries.js +32 -34
  79. package/dist/runtime/boundaries.js.map +1 -1
  80. package/dist/runtime/client.d.ts +20 -11
  81. package/dist/runtime/client.js +20 -10
  82. package/dist/runtime/client.js.map +1 -1
  83. package/dist/runtime/context.d.ts +304 -123
  84. package/dist/runtime/context.js +377 -149
  85. package/dist/runtime/context.js.map +1 -1
  86. package/dist/runtime/control.d.ts +15 -1
  87. package/dist/runtime/control.js +24 -0
  88. package/dist/runtime/control.js.map +1 -1
  89. package/dist/runtime/dev-protocol.d.ts +4 -9
  90. package/dist/runtime/dev-protocol.js.map +1 -1
  91. package/dist/runtime/empty-server-app.d.ts +0 -1
  92. package/dist/runtime/entry.client.d.ts +4 -1
  93. package/dist/runtime/entry.client.js +455 -313
  94. package/dist/runtime/entry.client.js.map +1 -1
  95. package/dist/runtime/entry.rsc.d.ts +15 -7
  96. package/dist/runtime/entry.rsc.js +811 -252
  97. package/dist/runtime/entry.rsc.js.map +1 -1
  98. package/dist/runtime/entry.ssr.d.ts +27 -10
  99. package/dist/runtime/entry.ssr.js +30 -48
  100. package/dist/runtime/entry.ssr.js.map +1 -1
  101. package/dist/runtime/failure-document.d.ts +11 -0
  102. package/dist/runtime/failure-document.js +35 -0
  103. package/dist/runtime/failure-document.js.map +1 -0
  104. package/dist/runtime/flight-inject.d.ts +23 -0
  105. package/dist/runtime/flight-inject.js +403 -0
  106. package/dist/runtime/flight-inject.js.map +1 -0
  107. package/dist/runtime/hot-update.d.ts +44 -0
  108. package/dist/runtime/hot-update.js +44 -0
  109. package/dist/runtime/hot-update.js.map +1 -0
  110. package/dist/runtime/navigation.d.ts +46 -54
  111. package/dist/runtime/navigation.js +22 -63
  112. package/dist/runtime/navigation.js.map +1 -1
  113. package/dist/runtime/request.d.ts +42 -8
  114. package/dist/runtime/request.js +84 -7
  115. package/dist/runtime/request.js.map +1 -1
  116. package/dist/runtime/server.d.ts +20 -18
  117. package/dist/runtime/server.js +22 -17
  118. package/dist/runtime/server.js.map +1 -1
  119. package/dist/runtime/validate-entries.d.ts +77 -0
  120. package/dist/runtime/validate-entries.js +405 -0
  121. package/dist/runtime/validate-entries.js.map +1 -0
  122. package/dist/server/headers.d.ts +41 -15
  123. package/dist/server/headers.js +53 -24
  124. package/dist/server/headers.js.map +1 -1
  125. package/dist/server/load-config.d.ts +4 -5
  126. package/dist/server/load-config.js +22 -14
  127. package/dist/server/load-config.js.map +1 -1
  128. package/dist/server/load-env.d.ts +0 -1
  129. package/dist/server/prerendered.d.ts +88 -34
  130. package/dist/server/prerendered.js +184 -24
  131. package/dist/server/prerendered.js.map +1 -1
  132. package/dist/server/server-config.d.ts +35 -50
  133. package/dist/server/server-config.js +27 -74
  134. package/dist/server/server-config.js.map +1 -1
  135. package/dist/server/shutdown.d.ts +5 -4
  136. package/dist/server/shutdown.js +5 -3
  137. package/dist/server/shutdown.js.map +1 -1
  138. package/dist/server/ssg.d.ts +9 -7
  139. package/dist/server/ssg.js +209 -65
  140. package/dist/server/ssg.js.map +1 -1
  141. package/dist/server/static.d.ts +0 -1
  142. package/dist/server/static.js +7 -1
  143. package/dist/server/static.js.map +1 -1
  144. package/package.json +21 -23
  145. package/dist/builder/page-files.d.ts.map +0 -1
  146. package/dist/builder/public-env.d.ts.map +0 -1
  147. package/dist/builder/rspack-config.d.ts.map +0 -1
  148. package/dist/cli/build.d.ts.map +0 -1
  149. package/dist/cli/dev.d.ts.map +0 -1
  150. package/dist/cli/index.d.ts.map +0 -1
  151. package/dist/cli/start.d.ts.map +0 -1
  152. package/dist/config.d.ts.map +0 -1
  153. package/dist/deploy/aws-lambda/runtime.d.ts.map +0 -1
  154. package/dist/deploy/build-marker.d.ts.map +0 -1
  155. package/dist/deploy/bun/runtime.d.ts +0 -11
  156. package/dist/deploy/bun/runtime.d.ts.map +0 -1
  157. package/dist/deploy/bun/runtime.js +0 -22
  158. package/dist/deploy/bun/runtime.js.map +0 -1
  159. package/dist/deploy/cloudflare/build.d.ts.map +0 -1
  160. package/dist/deploy/cloudflare/runtime.d.ts.map +0 -1
  161. package/dist/deploy/contract.d.ts.map +0 -1
  162. package/dist/deploy/deno/runtime.d.ts +0 -11
  163. package/dist/deploy/deno/runtime.d.ts.map +0 -1
  164. package/dist/deploy/deno/runtime.js +0 -16
  165. package/dist/deploy/deno/runtime.js.map +0 -1
  166. package/dist/deploy/filesystem.d.ts.map +0 -1
  167. package/dist/deploy/listen.d.ts +0 -20
  168. package/dist/deploy/listen.d.ts.map +0 -1
  169. package/dist/deploy/listen.js +0 -24
  170. package/dist/deploy/listen.js.map +0 -1
  171. package/dist/deploy/netlify/build.d.ts +0 -8
  172. package/dist/deploy/netlify/build.d.ts.map +0 -1
  173. package/dist/deploy/netlify/build.js +0 -52
  174. package/dist/deploy/netlify/build.js.map +0 -1
  175. package/dist/deploy/netlify/runtime.d.ts +0 -13
  176. package/dist/deploy/netlify/runtime.d.ts.map +0 -1
  177. package/dist/deploy/netlify/runtime.js +0 -24
  178. package/dist/deploy/netlify/runtime.js.map +0 -1
  179. package/dist/deploy/node/runtime.d.ts.map +0 -1
  180. package/dist/deploy/presets.d.ts.map +0 -1
  181. package/dist/deploy/vercel/build.d.ts.map +0 -1
  182. package/dist/deploy/vercel/runtime.d.ts.map +0 -1
  183. package/dist/index.d.ts.map +0 -1
  184. package/dist/router.d.ts.map +0 -1
  185. package/dist/runtime/boundaries.d.ts.map +0 -1
  186. package/dist/runtime/client.d.ts.map +0 -1
  187. package/dist/runtime/context.d.ts.map +0 -1
  188. package/dist/runtime/control.d.ts.map +0 -1
  189. package/dist/runtime/dev-protocol.d.ts.map +0 -1
  190. package/dist/runtime/empty-server-app.d.ts.map +0 -1
  191. package/dist/runtime/entry.client.d.ts.map +0 -1
  192. package/dist/runtime/entry.rsc.d.ts.map +0 -1
  193. package/dist/runtime/entry.ssr.d.ts.map +0 -1
  194. package/dist/runtime/navigation.d.ts.map +0 -1
  195. package/dist/runtime/request.d.ts.map +0 -1
  196. package/dist/runtime/server.d.ts.map +0 -1
  197. package/dist/server/compress.d.ts +0 -15
  198. package/dist/server/compress.d.ts.map +0 -1
  199. package/dist/server/compress.js +0 -76
  200. package/dist/server/compress.js.map +0 -1
  201. package/dist/server/headers.d.ts.map +0 -1
  202. package/dist/server/load-config.d.ts.map +0 -1
  203. package/dist/server/load-env.d.ts.map +0 -1
  204. package/dist/server/prerendered.d.ts.map +0 -1
  205. package/dist/server/server-config.d.ts.map +0 -1
  206. package/dist/server/shutdown.d.ts.map +0 -1
  207. package/dist/server/ssg.d.ts.map +0 -1
  208. package/dist/server/static.d.ts.map +0 -1
@@ -5,11 +5,14 @@ export interface BuildMarker {
5
5
  }
6
6
  export declare function writeBuildMarker(distDir: string, deploy: DeployTarget): void;
7
7
  /**
8
- * The target the build in `distDir` was produced for, or `null` when there is nothing to go on.
8
+ * The target the build in `distDir` was produced for, or `null` when there is nothing to go on — a missing
9
+ * marker is not an error, since a build from an older rshono has none and refusing to start one that would
10
+ * have worked is worse than the failure this guards against.
9
11
  *
10
- * A missing or unreadable marker is deliberately not an error: a build from an older rshono has none,
11
- * and refusing to start one that would have worked is worse than the confusing failure this guards
12
- * against.
12
+ * `string`, not {@link DeployTarget}: the file is on disk and whoever wrote it may be a *newer* rshono with
13
+ * a target this one has never heard of. Narrowing an unrecognised name to `null` would be the wrong repair
14
+ * `rshono start`'s only use for this is refusing anything that is not `'node'`, so a `null` would let it
15
+ * start a build with no listener in it and exit silently, which is the failure the marker exists to prevent.
16
+ * Reporting the name it does not know is the deliberate behaviour; the return type now says so.
13
17
  */
14
- export declare function readBuildMarker(distDir: string): DeployTarget | null;
15
- //# sourceMappingURL=build-marker.d.ts.map
18
+ export declare function readBuildMarker(distDir: string): string | null;
@@ -5,11 +5,15 @@ export function writeBuildMarker(distDir, deploy) {
5
5
  writeFileSync(join(distDir, MARKER_FILE), `${JSON.stringify({ deploy }, null, 2)}\n`);
6
6
  }
7
7
  /**
8
- * The target the build in `distDir` was produced for, or `null` when there is nothing to go on.
8
+ * The target the build in `distDir` was produced for, or `null` when there is nothing to go on — a missing
9
+ * marker is not an error, since a build from an older rshono has none and refusing to start one that would
10
+ * have worked is worse than the failure this guards against.
9
11
  *
10
- * A missing or unreadable marker is deliberately not an error: a build from an older rshono has none,
11
- * and refusing to start one that would have worked is worse than the confusing failure this guards
12
- * against.
12
+ * `string`, not {@link DeployTarget}: the file is on disk and whoever wrote it may be a *newer* rshono with
13
+ * a target this one has never heard of. Narrowing an unrecognised name to `null` would be the wrong repair
14
+ * `rshono start`'s only use for this is refusing anything that is not `'node'`, so a `null` would let it
15
+ * start a build with no listener in it and exit silently, which is the failure the marker exists to prevent.
16
+ * Reporting the name it does not know is the deliberate behaviour; the return type now says so.
13
17
  */
14
18
  export function readBuildMarker(distDir) {
15
19
  const file = join(distDir, MARKER_FILE);
@@ -1 +1 @@
1
- {"version":3,"file":"build-marker.js","sourceRoot":"","sources":["../../src/deploy/build-marker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,MAAoB;IACpE,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAwB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC9G,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACxC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC9E,OAAO,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAE,MAAM,CAAC,MAAuB,CAAC,CAAC,CAAC,IAAI,CAAC;IACpF,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { DeployTarget } from './contract.js';\n\n/** Records which platform a build was for, so `rshono start` can refuse one it cannot run. */\nexport interface BuildMarker {\n deploy: DeployTarget;\n}\n\nconst MARKER_FILE = 'rshono-build.json';\n\nexport function writeBuildMarker(distDir: string, deploy: DeployTarget): void {\n writeFileSync(join(distDir, MARKER_FILE), `${JSON.stringify({ deploy } satisfies BuildMarker, null, 2)}\\n`);\n}\n\n/**\n * The target the build in `distDir` was produced for, or `null` when there is nothing to go on.\n *\n * A missing or unreadable marker is deliberately not an error: a build from an older rshono has none,\n * and refusing to start one that would have worked is worse than the confusing failure this guards\n * against.\n */\nexport function readBuildMarker(distDir: string): DeployTarget | null {\n const file = join(distDir, MARKER_FILE);\n if (!existsSync(file)) return null;\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as Partial<BuildMarker>;\n return typeof parsed.deploy === 'string' ? (parsed.deploy as DeployTarget) : null;\n } catch {\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"build-marker.js","sourceRoot":"","sources":["../../src/deploy/build-marker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQjC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAExC,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,MAAoB;IACpE,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAwB,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AAC9G,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,eAAe,CAAC,OAAe;IAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACxC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC9E,OAAO,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { DeployTarget } from './contract.js';\n\n/** Records which platform a build was for, so `rshono start` can refuse one it cannot run. */\nexport interface BuildMarker {\n deploy: DeployTarget;\n}\n\nconst MARKER_FILE = 'rshono-build.json';\n\nexport function writeBuildMarker(distDir: string, deploy: DeployTarget): void {\n writeFileSync(join(distDir, MARKER_FILE), `${JSON.stringify({ deploy } satisfies BuildMarker, null, 2)}\\n`);\n}\n\n/**\n * The target the build in `distDir` was produced for, or `null` when there is nothing to go on a missing\n * marker is not an error, since a build from an older rshono has none and refusing to start one that would\n * have worked is worse than the failure this guards against.\n *\n * `string`, not {@link DeployTarget}: the file is on disk and whoever wrote it may be a *newer* rshono with\n * a target this one has never heard of. Narrowing an unrecognised name to `null` would be the wrong repair —\n * `rshono start`'s only use for this is refusing anything that is not `'node'`, so a `null` would let it\n * start a build with no listener in it and exit silently, which is the failure the marker exists to prevent.\n * Reporting the name it does not know is the deliberate behaviour; the return type now says so.\n */\nexport function readBuildMarker(distDir: string): string | null {\n const file = join(distDir, MARKER_FILE);\n if (!existsSync(file)) return null;\n try {\n const parsed = JSON.parse(readFileSync(file, 'utf8')) as Partial<BuildMarker>;\n return typeof parsed.deploy === 'string' ? parsed.deploy : null;\n } catch {\n return null;\n }\n}\n"]}
@@ -4,4 +4,3 @@ import type { DeployBuildContext } from '../presets.js';
4
4
  * `wrangler.jsonc` to deploy with if the project has no Wrangler config of its own.
5
5
  */
6
6
  export declare function finalizeCloudflareBuild(ctx: DeployBuildContext): Promise<void>;
7
- //# sourceMappingURL=build.d.ts.map
@@ -1,16 +1,14 @@
1
1
  import { cpSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { rm } from 'node:fs/promises';
3
3
  import { basename, join } from 'node:path';
4
+ import { publicRouteCollisions, warnAboutPublicCollisions } from '../public-paths.js';
4
5
  /** Wrangler serves one directory, so the three kinds of asset the build produces are assembled into it. */
5
6
  const ASSETS_DIR = join('cloudflare', 'assets');
6
7
  const WRANGLER_FILES = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml'];
7
8
  /**
8
- * Caching and crawler rules for the assembled tree, in the file Workers Assets reads them from.
9
- *
10
- * The hashed bundle is immutable by construction, which is the one thing the CDN cannot infer. The
11
- * prerender tree is reachable under its own prefix (see `SSG_PREFIX` in `runtime.ts`) and holds a
12
- * second copy of pages that already have real URLs, so it is marked `noindex` rather than left for a
13
- * crawler to find twice.
9
+ * Caching and crawler rules for the assembled tree, in the file Workers Assets reads them from: the hashed
10
+ * bundle is immutable by construction, which the CDN cannot infer, and the prerender tree holds a second copy
11
+ * of pages that already have real URLs so it is marked `noindex`.
14
12
  */
15
13
  const HEADERS_FILE = `/_static/*
16
14
  Cache-Control: public, max-age=31536000, immutable
@@ -26,13 +24,16 @@ function workerName(rootDir) {
26
24
  .replace(/^-+|-+$/g, '');
27
25
  return name || 'rshono-app';
28
26
  }
29
- function wranglerConfig(rootDir, today) {
27
+ // Fixed, not the day the build ran: wrangler's bundled workerd refuses a date newer than its own, so a config
28
+ // dated today would deploy fine and never start under `wrangler dev`.
29
+ const COMPATIBILITY_DATE = '2026-07-01';
30
+ function wranglerConfig(rootDir) {
30
31
  return `${JSON.stringify({
31
32
  $schema: 'node_modules/wrangler/config-schema.json',
32
33
  name: workerName(rootDir),
33
34
  main: 'dist/server/main.mjs',
34
- compatibility_date: today,
35
- // AsyncLocalStorage how the framework binds the request context that `getContext()` reads.
35
+ compatibility_date: COMPATIBILITY_DATE,
36
+ // For AsyncLocalStorage, which is how the request context behind `getRequestContext()` is bound.
36
37
  compatibility_flags: ['nodejs_compat'],
37
38
  assets: {
38
39
  directory: `dist/${ASSETS_DIR.split(/[\\/]/).join('/')}`,
@@ -47,8 +48,7 @@ function wranglerConfig(rootDir, today) {
47
48
  */
48
49
  export async function finalizeCloudflareBuild(ctx) {
49
50
  const assetsDir = join(ctx.distDir, ASSETS_DIR);
50
- // Rebuilt from scratch, so a file deleted from `public/` or a route that stopped being static does
51
- // not survive in the deployed tree.
51
+ // From scratch, so a deleted `public/` file or a route that stopped being static does not survive here.
52
52
  await rm(assetsDir, { recursive: true, force: true });
53
53
  mkdirSync(assetsDir, { recursive: true });
54
54
  cpSync(ctx.staticDir, join(assetsDir, '_static'), { recursive: true });
@@ -56,6 +56,11 @@ export async function finalizeCloudflareBuild(ctx) {
56
56
  cpSync(ctx.publicDir, assetsDir, { recursive: true });
57
57
  if (existsSync(ctx.ssgDir))
58
58
  cpSync(ctx.ssgDir, join(assetsDir, '__ssg'), { recursive: true });
59
+ // Workers Assets is checked before the worker runs, so a `public/` file on a route's path answers instead
60
+ // of the route — the same collision Vercel has, and neither `node` nor `aws-lambda` does.
61
+ // `htmlExtensionless: true`: the binding's default HTML handling is `auto-trailing-slash`, so
62
+ // `about.html` answers `/about` here as well as `/about.html`.
63
+ warnAboutPublicCollisions(publicRouteCollisions(ctx.publicDir, ctx.routes, { htmlExtensionless: true }), 'cloudflare');
59
64
  const headersFile = join(assetsDir, '_headers');
60
65
  if (existsSync(headersFile)) {
61
66
  console.warn(` ⚠ public/_headers is in the way of the generated one — /_static/* will not be marked immutable.`);
@@ -65,8 +70,7 @@ export async function finalizeCloudflareBuild(ctx) {
65
70
  }
66
71
  const existing = WRANGLER_FILES.find((file) => existsSync(join(ctx.rootDir, file)));
67
72
  if (!existing) {
68
- const today = new Date().toISOString().slice(0, 10);
69
- writeFileSync(join(ctx.rootDir, 'wrangler.jsonc'), wranglerConfig(ctx.rootDir, today));
73
+ writeFileSync(join(ctx.rootDir, 'wrangler.jsonc'), wranglerConfig(ctx.rootDir));
70
74
  console.log(' • wrote wrangler.jsonc (yours now — edit freely, the build will not touch it again)');
71
75
  }
72
76
  console.log(` • assembled ${join('dist', ASSETS_DIR)} for Workers Assets`);
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/build.ts"],"names":[],"mappings":"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","sourcesContent":["import { cpSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { DeployBuildContext } from '../presets.js';\n\n/** Wrangler serves one directory, so the three kinds of asset the build produces are assembled into it. */\nconst ASSETS_DIR = join('cloudflare', 'assets');\n\nconst WRANGLER_FILES = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml'];\n\n/**\n * Caching and crawler rules for the assembled tree, in the file Workers Assets reads them from.\n *\n * The hashed bundle is immutable by construction, which is the one thing the CDN cannot infer. The\n * prerender tree is reachable under its own prefix (see `SSG_PREFIX` in `runtime.ts`) and holds a\n * second copy of pages that already have real URLs, so it is marked `noindex` rather than left for a\n * crawler to find twice.\n */\nconst HEADERS_FILE = `/_static/*\n Cache-Control: public, max-age=31536000, immutable\n\n/__ssg/*\n X-Robots-Tag: noindex\n`;\n\n/** A Wrangler worker name: lowercase, and only the characters it accepts. */\nfunction workerName(rootDir: string): string {\n const name = basename(rootDir)\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return name || 'rshono-app';\n}\n\nfunction wranglerConfig(rootDir: string, today: string): string {\n return `${JSON.stringify(\n {\n $schema: 'node_modules/wrangler/config-schema.json',\n name: workerName(rootDir),\n main: 'dist/server/main.mjs',\n compatibility_date: today,\n // AsyncLocalStorage how the framework binds the request context that `getContext()` reads.\n compatibility_flags: ['nodejs_compat'],\n assets: {\n directory: `dist/${ASSETS_DIR.split(/[\\\\/]/).join('/')}`,\n // Read by the worker for `public/` files and prerendered pages; the CDN still answers first.\n binding: 'ASSETS',\n },\n },\n null,\n 2,\n )}\\n`;\n}\n\n/**\n * Arranges a Workers deployment: one assets directory holding everything the CDN should serve, and a\n * `wrangler.jsonc` to deploy with if the project has no Wrangler config of its own.\n */\nexport async function finalizeCloudflareBuild(ctx: DeployBuildContext): Promise<void> {\n const assetsDir = join(ctx.distDir, ASSETS_DIR);\n // Rebuilt from scratch, so a file deleted from `public/` or a route that stopped being static does\n // not survive in the deployed tree.\n await rm(assetsDir, { recursive: true, force: true });\n mkdirSync(assetsDir, { recursive: true });\n\n cpSync(ctx.staticDir, join(assetsDir, '_static'), { recursive: true });\n if (ctx.publicDir) cpSync(ctx.publicDir, assetsDir, { recursive: true });\n if (existsSync(ctx.ssgDir)) cpSync(ctx.ssgDir, join(assetsDir, '__ssg'), { recursive: true });\n\n const headersFile = join(assetsDir, '_headers');\n if (existsSync(headersFile)) {\n console.warn(` ⚠ public/_headers is in the way of the generated one — /_static/* will not be marked immutable.`);\n } else {\n writeFileSync(headersFile, HEADERS_FILE);\n }\n\n const existing = WRANGLER_FILES.find((file) => existsSync(join(ctx.rootDir, file)));\n if (!existing) {\n const today = new Date().toISOString().slice(0, 10);\n writeFileSync(join(ctx.rootDir, 'wrangler.jsonc'), wranglerConfig(ctx.rootDir, today));\n console.log(' • wrote wrangler.jsonc (yours now — edit freely, the build will not touch it again)');\n }\n\n console.log(` • assembled ${join('dist', ASSETS_DIR)} for Workers Assets`);\n}\n"]}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/build.ts"],"names":[],"mappings":"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","sourcesContent":["import { cpSync, existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport type { DeployBuildContext } from '../presets.js';\nimport { publicRouteCollisions, warnAboutPublicCollisions } from '../public-paths.js';\n\n/** Wrangler serves one directory, so the three kinds of asset the build produces are assembled into it. */\nconst ASSETS_DIR = join('cloudflare', 'assets');\n\nconst WRANGLER_FILES = ['wrangler.jsonc', 'wrangler.json', 'wrangler.toml'];\n\n/**\n * Caching and crawler rules for the assembled tree, in the file Workers Assets reads them from: the hashed\n * bundle is immutable by construction, which the CDN cannot infer, and the prerender tree holds a second copy\n * of pages that already have real URLs so it is marked `noindex`.\n */\nconst HEADERS_FILE = `/_static/*\n Cache-Control: public, max-age=31536000, immutable\n\n/__ssg/*\n X-Robots-Tag: noindex\n`;\n\n/** A Wrangler worker name: lowercase, and only the characters it accepts. */\nfunction workerName(rootDir: string): string {\n const name = basename(rootDir)\n .toLowerCase()\n .replace(/[^a-z0-9-]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return name || 'rshono-app';\n}\n\n// Fixed, not the day the build ran: wrangler's bundled workerd refuses a date newer than its own, so a config\n// dated today would deploy fine and never start under `wrangler dev`.\nconst COMPATIBILITY_DATE = '2026-07-01';\n\nfunction wranglerConfig(rootDir: string): string {\n return `${JSON.stringify(\n {\n $schema: 'node_modules/wrangler/config-schema.json',\n name: workerName(rootDir),\n main: 'dist/server/main.mjs',\n compatibility_date: COMPATIBILITY_DATE,\n // For AsyncLocalStorage, which is how the request context behind `getRequestContext()` is bound.\n compatibility_flags: ['nodejs_compat'],\n assets: {\n directory: `dist/${ASSETS_DIR.split(/[\\\\/]/).join('/')}`,\n // Read by the worker for `public/` files and prerendered pages; the CDN still answers first.\n binding: 'ASSETS',\n },\n },\n null,\n 2,\n )}\\n`;\n}\n\n/**\n * Arranges a Workers deployment: one assets directory holding everything the CDN should serve, and a\n * `wrangler.jsonc` to deploy with if the project has no Wrangler config of its own.\n */\nexport async function finalizeCloudflareBuild(ctx: DeployBuildContext): Promise<void> {\n const assetsDir = join(ctx.distDir, ASSETS_DIR);\n // From scratch, so a deleted `public/` file or a route that stopped being static does not survive here.\n await rm(assetsDir, { recursive: true, force: true });\n mkdirSync(assetsDir, { recursive: true });\n\n cpSync(ctx.staticDir, join(assetsDir, '_static'), { recursive: true });\n if (ctx.publicDir) cpSync(ctx.publicDir, assetsDir, { recursive: true });\n if (existsSync(ctx.ssgDir)) cpSync(ctx.ssgDir, join(assetsDir, '__ssg'), { recursive: true });\n\n // Workers Assets is checked before the worker runs, so a `public/` file on a route's path answers instead\n // of the route — the same collision Vercel has, and neither `node` nor `aws-lambda` does.\n // `htmlExtensionless: true`: the binding's default HTML handling is `auto-trailing-slash`, so\n // `about.html` answers `/about` here as well as `/about.html`.\n warnAboutPublicCollisions(publicRouteCollisions(ctx.publicDir, ctx.routes, { htmlExtensionless: true }), 'cloudflare');\n\n const headersFile = join(assetsDir, '_headers');\n if (existsSync(headersFile)) {\n console.warn(` ⚠ public/_headers is in the way of the generated one — /_static/* will not be marked immutable.`);\n } else {\n writeFileSync(headersFile, HEADERS_FILE);\n }\n\n const existing = WRANGLER_FILES.find((file) => existsSync(join(ctx.rootDir, file)));\n if (!existing) {\n writeFileSync(join(ctx.rootDir, 'wrangler.jsonc'), wranglerConfig(ctx.rootDir));\n console.log(' • wrote wrangler.jsonc (yours now — edit freely, the build will not touch it again)');\n }\n\n console.log(` • assembled ${join('dist', ASSETS_DIR)} for Workers Assets`);\n}\n"]}
@@ -1,10 +1,6 @@
1
1
  import type { DeployRuntime } from '../contract.js';
2
2
  /**
3
- * Cloudflare Workers: the host owns the process, so there is nothing to listen on; the CDN owns the
4
- * assets, so there is nothing to serve from disk; and there is no filesystem, so `.env` files and a
5
- * streaming gzip are both out.
6
- *
7
- * What is left is the assets binding, which every read here goes through.
3
+ * Cloudflare Workers: the host owns the process, so there is nothing to listen on, and there is no filesystem,
4
+ * so `.env` files are out. What is left is the assets binding, which every read here goes through.
8
5
  */
9
6
  export declare const runtime: DeployRuntime;
10
- //# sourceMappingURL=runtime.d.ts.map
@@ -1,14 +1,12 @@
1
- import { prerenderedRelPath } from '../../server/prerendered.js';
1
+ import { ASSET_MISS_CACHE_CONTROL } from '../../server/headers.js';
2
+ import { createPageCache, ssgAssetPath, ssgFilePath, SSG_MANIFEST_FILE, toPrerenderedPage } from '../../server/prerendered.js';
2
3
  /**
3
4
  * Where `finalize` puts the prerendered pages inside the assets directory.
4
5
  *
5
- * A prefix of its own, rather than the pages' real URLs, is what keeps every page URL reaching the
6
- * worker: one URL answers with a document or a flight payload depending on `Accept`, and a CDN keyed
7
- * on the path alone cannot make that choice. Assets miss, the worker runs, the worker negotiates.
8
- *
9
- * The bytes are public either way — they are a public page — but they are also reachable under this
10
- * path, so `finalize` writes a `_headers` rule marking the tree `noindex` rather than leaving a
11
- * crawler to find the same page twice.
6
+ * A prefix of its own, rather than the pages' real URLs, is what keeps every page URL reaching the worker: one
7
+ * URL answers with a document or a flight payload on the `RSC` request header, and a path-keyed CDN cannot make that
8
+ * choice. The tree is reachable here too, so `finalize` marks it `noindex` rather than leaving a crawler to
9
+ * find the same page twice.
12
10
  */
13
11
  const SSG_PREFIX = '/__ssg';
14
12
  function assetsBinding(c) {
@@ -16,78 +14,91 @@ function assetsBinding(c) {
16
14
  return binding && typeof binding.fetch === 'function' ? binding : null;
17
15
  }
18
16
  /**
19
- * Asks the assets binding for one path, or `null` when there is no binding at all so a caller can
20
- * tell "this deployment serves its assets some other way" from "that file is not there".
17
+ * Asks the assets binding for one path, or `null` when there is no binding which is how a caller tells "this
18
+ * deployment serves its assets some other way" from "that file is not there".
21
19
  *
22
- * `headers` is passed on only where the *client's* conditional and range requests should be answered
23
- * by the store: forwarding an `If-None-Match` while fetching a page to serve would come back 304 with
24
- * no body, which is a useless answer to "give me this page".
20
+ * `headers` is forwarded only where the *client's* conditional and range requests should be answered by the
21
+ * store: an `If-None-Match` on a page being fetched to serve would come back 304 with no body.
25
22
  */
26
23
  async function assetResponse(c, path, headers) {
27
24
  const binding = assetsBinding(c);
28
25
  if (!binding)
29
26
  return null;
30
- // Resolved against the request so the asset request carries this deployment's own origin; only the
31
- // path is ever used to look one up.
27
+ // Resolved against the request so the lookup carries this deployment's own origin; only the path is used.
32
28
  const url = new URL(path, c.req.url);
33
29
  return binding.fetch(new Request(url, { method: 'GET', headers }));
34
30
  }
35
31
  /**
36
- * Hands an asset response back as the app's own.
37
- *
38
- * Rebuilt rather than returned as-is because a `Response` that came from `fetch` has immutable
39
- * headers, and the framework's outermost middleware sets its baseline security headers on the way out.
32
+ * Hands an asset response back as the app's own. Rebuilt rather than returned as-is, because a `Response` from
33
+ * `fetch` has immutable headers and the framework's outermost middleware writes to them on the way out.
40
34
  */
41
35
  function serveAsset(asset) {
42
36
  return new Response(asset.body, asset);
43
37
  }
38
+ /** Prerendered pages, keyed by the path that produced them. Per isolate rather than per process. */
39
+ const pageCache = createPageCache();
40
+ /** The store's own index of itself, fetched once per isolate — see {@link SSG_MANIFEST_FILE}. */
41
+ let storeIndex;
44
42
  /**
45
- * A weak `ETag` for a body the asset store gave us no validator for.
43
+ * What the store holds, or `null` where there is no manifest to read an older build, or a deployment with
44
+ * no assets binding at all. `null` gates nothing, which is how it behaved before there was an index.
46
45
  *
47
- * Web Crypto rather than `node:crypto`: this module is compiled for `workerd`, and the digest is the
48
- * one thing here that has a platform-neutral form.
46
+ * Worth one fetch per isolate: without it every request for a path the build did not prerender spends a
47
+ * subrequest asking the binding for a file that is not there, and misses are deliberately not cached.
49
48
  */
50
- async function weakEtag(body) {
51
- const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(body));
52
- const bytes = String.fromCharCode(...new Uint8Array(digest));
53
- const base64url = btoa(bytes).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
54
- return `W/"${base64url.slice(0, 22)}"`;
49
+ function knownFiles(c) {
50
+ storeIndex ??= assetResponse(c, `${SSG_PREFIX}/${SSG_MANIFEST_FILE}`)
51
+ .then(async (asset) => {
52
+ if (!asset || asset.status !== 200)
53
+ return null;
54
+ const manifest = (await asset.json());
55
+ return new Set(manifest.files ?? []);
56
+ })
57
+ .catch(() => null);
58
+ return storeIndex;
55
59
  }
56
60
  /**
57
- * Prerendered pages, keyed by the path that produced them.
58
- *
59
- * Per isolate rather than per process, and bounded for the same reason the Node cache is: a site with
60
- * thousands of prerendered pages should keep a working set, not the whole build. Only *hits* are
61
- * cached, so nobody can mint entries by requesting paths that do not exist.
62
- */
63
- const pageCache = new Map();
64
- const MAX_CACHED_PAGES = 128;
65
- /**
66
- * Cloudflare Workers: the host owns the process, so there is nothing to listen on; the CDN owns the
67
- * assets, so there is nothing to serve from disk; and there is no filesystem, so `.env` files and a
68
- * streaming gzip are both out.
69
- *
70
- * What is left is the assets binding, which every read here goes through.
61
+ * Cloudflare Workers: the host owns the process, so there is nothing to listen on, and there is no filesystem,
62
+ * so `.env` files are out. What is left is the assets binding, which every read here goes through.
71
63
  */
72
64
  export const runtime = {
73
65
  serveApp(app) {
74
- // What Workers looks for on the default export. `app.fetch` already takes `(request, env, ctx)`,
75
- // which is why bindings arrive as `c.env` — and so why `getContext().env` sees them.
66
+ // `app.fetch` already takes `(request, env, ctx)`, which is why bindings arrive as `c.env`.
76
67
  return { fetch: app.fetch };
77
68
  },
78
69
  mountStaticAssets(app) {
79
- // Normally dead: with the scaffolded config the CDN answers `/_static/*` before the worker is
80
- // invoked at all. It exists so the deployment is still correct if slower — under an assets
81
- // configuration that routes everything to the worker first.
82
- app.on(['GET', 'HEAD'], '/_static/*', async (c, next) => {
70
+ // Normally dead the CDN answers `/_static/*` before the worker is invoked — but keeps the deployment
71
+ // correct, if slower, under an assets configuration that routes everything to the worker first.
72
+ // `GET` covers `HEAD` Hono dispatches one as the other. See `HTTPMethod`.
73
+ //
74
+ // Terminal, like the filesystem mount's last handler, and this used to be `next()`: a miss walked the
75
+ // whole route table, then `mountPublicFallback`, and landed in `app.notFound` — which for anything asking
76
+ // for HTML is a full server render of the app's 404 page under a prefix no app can own. A browser asking
77
+ // for a real subresource sends `Accept: */*` and got the plain 404 either way, so what it cost was a
78
+ // render for a crawler, a probe, or a hand-typed URL. `RESERVED_PREFIX`'s doc in `validate-entries.ts`
79
+ // says the mount ends in a terminal 404 and the reserved-route check leans on it; on this target it did
80
+ // not. `null` — no assets binding at all — answers the same way: nothing else in the worker can serve
81
+ // this prefix, so falling through would only find a longer route to the same status.
82
+ app.get('/_static/*', async (c) => {
83
83
  const asset = await assetResponse(c, c.req.path, c.req.raw.headers);
84
- return asset && asset.status !== 404 ? serveAsset(asset) : next();
84
+ if (asset && asset.status !== 404)
85
+ return serveAsset(asset);
86
+ return c.text('Not Found', 404, { 'cache-control': ASSET_MISS_CACHE_CONTROL });
85
87
  });
86
88
  },
87
89
  mountPublicFallback(app) {
88
- app.on(['GET', 'HEAD'], '/*', async (c, next) => {
89
- // The prerender tree lives in the same store; it is reachable directly from the CDN by design,
90
- // but the app itself only ever serves it through `readPrerendered`, negotiated on `Accept`.
90
+ // Live here and a no-op on `vercel`, which is the whole difference between the two CDN targets and is
91
+ // written up on {@link DeployRuntime.mountPublicFallback}. Normally dead for the same reason the
92
+ // `/_static` mount above is the CDN answers first — but `public/` is in the *same* binding this can
93
+ // read, so an assets configuration that routes to the worker first keeps its files instead of losing
94
+ // them. Vercel has nothing equivalent: `public/` went into the static output and is not uploaded with
95
+ // the function, so there is nothing there for a mount to answer from.
96
+ //
97
+ // Registered after every route, so inside the worker a route still beats a `public/` file — the
98
+ // ordering the contract describes, reached on the one target whose CDN can be told to stand aside. At
99
+ // the CDN the file wins, which is what `publicRouteCollisions` warns about at build time.
100
+ app.get('/*', async (c, next) => {
101
+ // The prerender tree lives in the same store, but the app only serves it through `readPrerendered`.
91
102
  if (c.req.path.startsWith(`${SSG_PREFIX}/`))
92
103
  return next();
93
104
  const asset = await assetResponse(c, c.req.path, c.req.raw.headers);
@@ -95,35 +106,29 @@ export const runtime = {
95
106
  });
96
107
  },
97
108
  async readPrerendered(c, variant) {
98
- const relPath = prerenderedRelPath(c.req.path, variant);
109
+ const relPath = ssgFilePath(c.req.path, variant);
99
110
  if (relPath === null)
100
111
  return null;
101
112
  const key = `${variant}\0${relPath}`;
102
113
  const cached = pageCache.get(key);
103
114
  if (cached)
104
115
  return cached;
105
- const asset = await assetResponse(c, `${SSG_PREFIX}/${relPath}`);
116
+ const index = await knownFiles(c);
117
+ if (index && !index.has(relPath))
118
+ return null;
119
+ // Escaped on the way into the URL and decoded again by the store, so this lands on the file the build
120
+ // wrote under exactly `relPath` — a page whose slug holds a `#` or a `?` included.
121
+ const asset = await assetResponse(c, `${SSG_PREFIX}/${ssgAssetPath(relPath)}`);
106
122
  if (!asset || asset.status !== 200)
107
123
  return null;
108
- const body = await asset.text();
109
- const page = {
110
- body,
111
- contentLength: String(new TextEncoder().encode(body).byteLength),
112
- // The store's own validator where it has one — it already describes these exact bytes. Weakened
113
- // because compression downstream would make a strong one wrong for the compressed encoding.
114
- etag: asset.headers.get('etag')?.replace(/^(?!W\/)/, 'W/') ?? (await weakEtag(body)),
115
- };
124
+ // The store's own validator where it has one — it already describes these exact bytes.
125
+ const page = await toPrerenderedPage(new Uint8Array(await asset.arrayBuffer()), asset.headers.get('etag'));
116
126
  pageCache.set(key, page);
117
- if (pageCache.size > MAX_CACHED_PAGES)
118
- pageCache.delete(pageCache.keys().next().value);
119
127
  return page;
120
128
  },
121
- // Cloudflare compresses on the way out at the edge, and `workerd` has no `node:zlib` stream to do
122
- // it with anyway. Doing it here would only spend CPU on bytes the edge is about to re-encode.
123
- compress: null,
124
129
  loadEnv() {
125
- // Nothing to load: a Worker has no filesystem and no `.env`. Secrets and bindings arrive per
126
- // request as `c.env`, which `getContext().env` already merges — see `runtime/context.ts`.
130
+ // Nothing to load: secrets and bindings arrive per request as `c.env`, which `getRequestContext().env`
131
+ // already merges.
127
132
  },
128
133
  };
129
134
  //# sourceMappingURL=runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context, Hono } from 'hono';\nimport { prerenderedRelPath, type PrerenderedPage } from '../../server/prerendered.js';\nimport type { DeployRuntime } from '../contract.js';\n\n/**\n * Where `finalize` puts the prerendered pages inside the assets directory.\n *\n * A prefix of its own, rather than the pages' real URLs, is what keeps every page URL reaching the\n * worker: one URL answers with a document or a flight payload depending on `Accept`, and a CDN keyed\n * on the path alone cannot make that choice. Assets miss, the worker runs, the worker negotiates.\n *\n * The bytes are public either way — they are a public page — but they are also reachable under this\n * path, so `finalize` writes a `_headers` rule marking the tree `noindex` rather than leaving a\n * crawler to find the same page twice.\n */\nconst SSG_PREFIX = '/__ssg';\n\n/** The Workers Assets binding, as much of it as this file uses. */\ninterface AssetsBinding {\n fetch(request: Request): Promise<Response>;\n}\n\nfunction assetsBinding(c: Context): AssetsBinding | null {\n const binding = (c.env as { ASSETS?: AssetsBinding } | undefined)?.ASSETS;\n return binding && typeof binding.fetch === 'function' ? binding : null;\n}\n\n/**\n * Asks the assets binding for one path, or `null` when there is no binding at all — so a caller can\n * tell \"this deployment serves its assets some other way\" from \"that file is not there\".\n *\n * `headers` is passed on only where the *client's* conditional and range requests should be answered\n * by the store: forwarding an `If-None-Match` while fetching a page to serve would come back 304 with\n * no body, which is a useless answer to \"give me this page\".\n */\nasync function assetResponse(c: Context, path: string, headers?: Headers): Promise<Response | null> {\n const binding = assetsBinding(c);\n if (!binding) return null;\n // Resolved against the request so the asset request carries this deployment's own origin; only the\n // path is ever used to look one up.\n const url = new URL(path, c.req.url);\n return binding.fetch(new Request(url, { method: 'GET', headers }));\n}\n\n/**\n * Hands an asset response back as the app's own.\n *\n * Rebuilt rather than returned as-is because a `Response` that came from `fetch` has immutable\n * headers, and the framework's outermost middleware sets its baseline security headers on the way out.\n */\nfunction serveAsset(asset: Response): Response {\n return new Response(asset.body, asset);\n}\n\n/**\n * A weak `ETag` for a body the asset store gave us no validator for.\n *\n * Web Crypto rather than `node:crypto`: this module is compiled for `workerd`, and the digest is the\n * one thing here that has a platform-neutral form.\n */\nasync function weakEtag(body: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(body));\n const bytes = String.fromCharCode(...new Uint8Array(digest));\n const base64url = btoa(bytes).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n return `W/\"${base64url.slice(0, 22)}\"`;\n}\n\n/**\n * Prerendered pages, keyed by the path that produced them.\n *\n * Per isolate rather than per process, and bounded for the same reason the Node cache is: a site with\n * thousands of prerendered pages should keep a working set, not the whole build. Only *hits* are\n * cached, so nobody can mint entries by requesting paths that do not exist.\n */\nconst pageCache = new Map<string, PrerenderedPage>();\nconst MAX_CACHED_PAGES = 128;\n\n/**\n * Cloudflare Workers: the host owns the process, so there is nothing to listen on; the CDN owns the\n * assets, so there is nothing to serve from disk; and there is no filesystem, so `.env` files and a\n * streaming gzip are both out.\n *\n * What is left is the assets binding, which every read here goes through.\n */\nexport const runtime: DeployRuntime = {\n serveApp(app: Hono): unknown {\n // What Workers looks for on the default export. `app.fetch` already takes `(request, env, ctx)`,\n // which is why bindings arrive as `c.env` — and so why `getContext().env` sees them.\n return { fetch: app.fetch };\n },\n\n mountStaticAssets(app: Hono): void {\n // Normally dead: with the scaffolded config the CDN answers `/_static/*` before the worker is\n // invoked at all. It exists so the deployment is still correct — if slower — under an assets\n // configuration that routes everything to the worker first.\n app.on(['GET', 'HEAD'], '/_static/*', async (c, next) => {\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n return asset && asset.status !== 404 ? serveAsset(asset) : next();\n });\n },\n\n mountPublicFallback(app: Hono): void {\n app.on(['GET', 'HEAD'], '/*', async (c, next) => {\n // The prerender tree lives in the same store; it is reachable directly from the CDN by design,\n // but the app itself only ever serves it through `readPrerendered`, negotiated on `Accept`.\n if (c.req.path.startsWith(`${SSG_PREFIX}/`)) return next();\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n return asset && asset.status !== 404 ? serveAsset(asset) : next();\n });\n },\n\n async readPrerendered(c: Context, variant): Promise<PrerenderedPage | null> {\n const relPath = prerenderedRelPath(c.req.path, variant);\n if (relPath === null) return null;\n\n const key = `${variant}\\0${relPath}`;\n const cached = pageCache.get(key);\n if (cached) return cached;\n\n const asset = await assetResponse(c, `${SSG_PREFIX}/${relPath}`);\n if (!asset || asset.status !== 200) return null;\n\n const body = await asset.text();\n const page: PrerenderedPage = {\n body,\n contentLength: String(new TextEncoder().encode(body).byteLength),\n // The store's own validator where it has one — it already describes these exact bytes. Weakened\n // because compression downstream would make a strong one wrong for the compressed encoding.\n etag: asset.headers.get('etag')?.replace(/^(?!W\\/)/, 'W/') ?? (await weakEtag(body)),\n };\n\n pageCache.set(key, page);\n if (pageCache.size > MAX_CACHED_PAGES) pageCache.delete(pageCache.keys().next().value!);\n return page;\n },\n\n // Cloudflare compresses on the way out at the edge, and `workerd` has no `node:zlib` stream to do\n // it with anyway. Doing it here would only spend CPU on bytes the edge is about to re-encode.\n compress: null,\n\n loadEnv(): void {\n // Nothing to load: a Worker has no filesystem and no `.env`. Secrets and bindings arrive per\n // request as `c.env`, which `getContext().env` already merges — see `runtime/context.ts`.\n },\n};\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Context, Hono } from 'hono';\nimport { ASSET_MISS_CACHE_CONTROL } from '../../server/headers.js';\nimport { createPageCache, ssgAssetPath, ssgFilePath, SSG_MANIFEST_FILE, toPrerenderedPage, type PrerenderedPage } from '../../server/prerendered.js';\nimport type { DeployRuntime } from '../contract.js';\n\n/**\n * Where `finalize` puts the prerendered pages inside the assets directory.\n *\n * A prefix of its own, rather than the pages' real URLs, is what keeps every page URL reaching the worker: one\n * URL answers with a document or a flight payload on the `RSC` request header, and a path-keyed CDN cannot make that\n * choice. The tree is reachable here too, so `finalize` marks it `noindex` rather than leaving a crawler to\n * find the same page twice.\n */\nconst SSG_PREFIX = '/__ssg';\n\n/** The Workers Assets binding, as much of it as this file uses. */\ninterface AssetsBinding {\n fetch(request: Request): Promise<Response>;\n}\n\nfunction assetsBinding(c: Context): AssetsBinding | null {\n const binding = (c.env as { ASSETS?: AssetsBinding } | undefined)?.ASSETS;\n return binding && typeof binding.fetch === 'function' ? binding : null;\n}\n\n/**\n * Asks the assets binding for one path, or `null` when there is no binding — which is how a caller tells \"this\n * deployment serves its assets some other way\" from \"that file is not there\".\n *\n * `headers` is forwarded only where the *client's* conditional and range requests should be answered by the\n * store: an `If-None-Match` on a page being fetched to serve would come back 304 with no body.\n */\nasync function assetResponse(c: Context, path: string, headers?: Headers): Promise<Response | null> {\n const binding = assetsBinding(c);\n if (!binding) return null;\n // Resolved against the request so the lookup carries this deployment's own origin; only the path is used.\n const url = new URL(path, c.req.url);\n return binding.fetch(new Request(url, { method: 'GET', headers }));\n}\n\n/**\n * Hands an asset response back as the app's own. Rebuilt rather than returned as-is, because a `Response` from\n * `fetch` has immutable headers and the framework's outermost middleware writes to them on the way out.\n */\nfunction serveAsset(asset: Response): Response {\n return new Response(asset.body, asset);\n}\n\n/** Prerendered pages, keyed by the path that produced them. Per isolate rather than per process. */\nconst pageCache = createPageCache();\n\n/** The store's own index of itself, fetched once per isolate — see {@link SSG_MANIFEST_FILE}. */\nlet storeIndex: Promise<ReadonlySet<string> | null> | undefined;\n\n/**\n * What the store holds, or `null` where there is no manifest to read — an older build, or a deployment with\n * no assets binding at all. `null` gates nothing, which is how it behaved before there was an index.\n *\n * Worth one fetch per isolate: without it every request for a path the build did not prerender spends a\n * subrequest asking the binding for a file that is not there, and misses are deliberately not cached.\n */\nfunction knownFiles(c: Context): Promise<ReadonlySet<string> | null> {\n storeIndex ??= assetResponse(c, `${SSG_PREFIX}/${SSG_MANIFEST_FILE}`)\n .then(async (asset) => {\n if (!asset || asset.status !== 200) return null;\n const manifest = (await asset.json()) as { files?: string[] };\n return new Set(manifest.files ?? []);\n })\n .catch(() => null);\n return storeIndex;\n}\n\n/**\n * Cloudflare Workers: the host owns the process, so there is nothing to listen on, and there is no filesystem,\n * so `.env` files are out. What is left is the assets binding, which every read here goes through.\n */\nexport const runtime: DeployRuntime = {\n serveApp(app: Hono): unknown {\n // `app.fetch` already takes `(request, env, ctx)`, which is why bindings arrive as `c.env`.\n return { fetch: app.fetch };\n },\n\n mountStaticAssets(app: Hono): void {\n // Normally dead — the CDN answers `/_static/*` before the worker is invoked — but keeps the deployment\n // correct, if slower, under an assets configuration that routes everything to the worker first.\n // `GET` covers `HEAD` — Hono dispatches one as the other. See `HTTPMethod`.\n //\n // Terminal, like the filesystem mount's last handler, and this used to be `next()`: a miss walked the\n // whole route table, then `mountPublicFallback`, and landed in `app.notFound` — which for anything asking\n // for HTML is a full server render of the app's 404 page under a prefix no app can own. A browser asking\n // for a real subresource sends `Accept: */*` and got the plain 404 either way, so what it cost was a\n // render for a crawler, a probe, or a hand-typed URL. `RESERVED_PREFIX`'s doc in `validate-entries.ts`\n // says the mount ends in a terminal 404 and the reserved-route check leans on it; on this target it did\n // not. `null` — no assets binding at all — answers the same way: nothing else in the worker can serve\n // this prefix, so falling through would only find a longer route to the same status.\n app.get('/_static/*', async (c) => {\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n if (asset && asset.status !== 404) return serveAsset(asset);\n return c.text('Not Found', 404, { 'cache-control': ASSET_MISS_CACHE_CONTROL });\n });\n },\n\n mountPublicFallback(app: Hono): void {\n // Live here and a no-op on `vercel`, which is the whole difference between the two CDN targets and is\n // written up on {@link DeployRuntime.mountPublicFallback}. Normally dead for the same reason the\n // `/_static` mount above is — the CDN answers first — but `public/` is in the *same* binding this can\n // read, so an assets configuration that routes to the worker first keeps its files instead of losing\n // them. Vercel has nothing equivalent: `public/` went into the static output and is not uploaded with\n // the function, so there is nothing there for a mount to answer from.\n //\n // Registered after every route, so inside the worker a route still beats a `public/` file — the\n // ordering the contract describes, reached on the one target whose CDN can be told to stand aside. At\n // the CDN the file wins, which is what `publicRouteCollisions` warns about at build time.\n app.get('/*', async (c, next) => {\n // The prerender tree lives in the same store, but the app only serves it through `readPrerendered`.\n if (c.req.path.startsWith(`${SSG_PREFIX}/`)) return next();\n const asset = await assetResponse(c, c.req.path, c.req.raw.headers);\n return asset && asset.status !== 404 ? serveAsset(asset) : next();\n });\n },\n\n async readPrerendered(c: Context, variant): Promise<PrerenderedPage | null> {\n const relPath = ssgFilePath(c.req.path, variant);\n if (relPath === null) return null;\n\n const key = `${variant}\\0${relPath}`;\n const cached = pageCache.get(key);\n if (cached) return cached;\n\n const index = await knownFiles(c);\n if (index && !index.has(relPath)) return null;\n\n // Escaped on the way into the URL and decoded again by the store, so this lands on the file the build\n // wrote under exactly `relPath` — a page whose slug holds a `#` or a `?` included.\n const asset = await assetResponse(c, `${SSG_PREFIX}/${ssgAssetPath(relPath)}`);\n if (!asset || asset.status !== 200) return null;\n\n // The store's own validator where it has one — it already describes these exact bytes.\n const page = await toPrerenderedPage(new Uint8Array(await asset.arrayBuffer()), asset.headers.get('etag'));\n pageCache.set(key, page);\n return page;\n },\n\n loadEnv(): void {\n // Nothing to load: secrets and bindings arrive per request as `c.env`, which `getRequestContext().env`\n // already merges.\n },\n};\n"]}
@@ -1,65 +1,81 @@
1
- import type { Context, Hono, MiddlewareHandler } from 'hono';
1
+ import type { Context, Hono } from 'hono';
2
2
  import type { PrerenderVariant, PrerenderedPage } from '../server/prerendered.js';
3
3
  /**
4
- * A hosting platform rshono can build for. Selected with {@link RSHonoConfig.deploy}, the
5
- * `--deploy` flag or the `RSHONO_DEPLOY` env var, and resolved to a preset by `deploy/presets.ts`.
4
+ * A hosting platform `rshono build` targets. Selected with `deploy` in `rshono.config.ts`, the
5
+ * `--deploy` flag or the `RSHONO_DEPLOY` env var.
6
6
  *
7
- * `rshono dev` always runs the `node` server whatever this says the dev server owns the process,
8
- * watches both compilers and fronts them on one port, none of which a hosting platform provides.
7
+ * - `'node'` (the default) rshono binds the port and you run the build with `rshono start`. Covers a
8
+ * VPS, a container, a PaaS, and through `node:` compatibility Bun and Deno.
9
+ * - `'cloudflare'` — a Worker; the entry exports `{ fetch }`.
10
+ * - `'vercel'` — a Vercel function, plus the on-disk layout and config streaming needs there.
11
+ * - `'aws-lambda'` — a streaming Lambda handler.
12
+ *
13
+ * There is one target per *handoff* — who opens the socket, and what shape a request arrives in — since
14
+ * that is the part an app cannot arrange for itself. `rshono dev` always runs the `node` server.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * export default defineConfig({ deploy: 'cloudflare' });
19
+ * ```
20
+ *
21
+ * @see {@link https://www.rshono.com/docs/deployment#the-targets | Docs — the targets}
9
22
  */
10
- export type DeployTarget = 'node' | 'cloudflare' | 'bun' | 'deno' | 'vercel' | 'netlify' | 'aws-lambda';
23
+ export type DeployTarget = 'node' | 'cloudflare' | 'vercel' | 'aws-lambda';
11
24
  /**
12
- * Everything the app server needs from the platform it is running on.
25
+ * Everything the app server needs from the platform it runs on — the whole of what "which platform is
26
+ * this" means at request time, since `runtime/entry.rsc.tsx` is written against this and nothing else.
13
27
  *
14
- * One preset implements this per target, and the `@rshono/deploy` alias resolves to exactly that
15
- * module at build time (see `builder/rspack-config.ts`), so only the selected platform's code is
16
- * ever in the bundle. `runtime/entry.rsc.tsx` is written against this interface and nothing else —
17
- * it is the whole of what "which platform is this" means at request time.
18
- *
19
- * The split is deliberately by *capability*, not by runtime: a platform with a filesystem reuses the
20
- * implementations in `server/` (`static.ts`, `compress.ts`, `ssg.ts`) and only differs in how the
21
- * finished app is handed over.
28
+ * One preset implements it per target, and the build-time `@rshono/deploy` alias resolves to exactly
29
+ * that module, so only the selected platform's code is ever in the bundle.
22
30
  */
23
31
  export interface DeployRuntime {
24
32
  /**
25
- * Hands the assembled app to the platform, and returns whatever the entry module should
26
- * `export default` there.
33
+ * Hands the app to the platform and returns whatever the entry module should `export default` there:
34
+ * nothing where rshono owns the process (this binds the port), otherwise the export the platform looks
35
+ * for — `{ fetch }` on Workers, a streaming handler on Lambda, and on Vercel a Node
36
+ * `(IncomingMessage, ServerResponse)` listener, which is what its `Nodejs` launcher calls.
27
37
  *
28
- * The two shapes hosting takes, in one call: where rshono owns the process (node, bun, deno) this
29
- * binds a port and returns nothing; where the host owns it, it returns the export the platform
30
- * looks for `{ fetch }` on Workers, a handler function on Vercel/Netlify/Lambda.
38
+ * Note that a web `Request` is not the common currency here: two of the three targets are handed one by
39
+ * their platform, and Vercel is not, so converting is part of the handoff rather than something the
40
+ * request-handling code below can assume has already happened.
31
41
  */
32
42
  serveApp(app: Hono): unknown;
33
43
  /**
34
- * Mounts the hashed client bundle at `/_static`. A no-op where the platform's own CDN serves it
35
- * before a request ever reaches the app.
36
- *
37
- * Called *before* the app's routes, so the bundle is never shadowed by one.
44
+ * Mounts the hashed client bundle at `/_static` after src/server.ts, so the app's own middleware covers an
45
+ * asset response too, and ahead of the page routes, which cannot claim that prefix. A no-op where a CDN
46
+ * serves it.
38
47
  */
39
48
  mountStaticAssets(app: Hono): void;
40
49
  /**
41
- * Mounts the `public/` fallback at the web root files served verbatim, and only for paths no
42
- * route claimed. Called *after* every route for exactly that reason.
50
+ * Mounts `public/` at the web root, after every route, so it only answers paths no route claimed.
51
+ *
52
+ * **That ordering is only within the app**, which is the whole surface on `node` and `aws-lambda` and not
53
+ * on the two targets with a CDN in front. There `public/` is part of the static output and the platform
54
+ * answers from it *before* the app is invoked — `{ handle: 'filesystem' }` ahead of the function on
55
+ * Vercel, Workers Assets ahead of the worker on Cloudflare — so it is a CDN-first store rather than a
56
+ * fallback. `rshono build` warns about a `public/` file that lands on a route's path for exactly that
57
+ * reason; see `publicRouteCollisions`.
58
+ *
59
+ * The two targets do not answer that the same way, and the difference is worth having written down here
60
+ * rather than discovered in one of them. **Vercel implements this as a no-op**: nothing is uploaded with
61
+ * the function for it to read, so a mount could never answer. **Cloudflare implements it for real**,
62
+ * against the `ASSETS` binding — normally dead, since the CDN is checked first, but the binding is the
63
+ * same store either way and an assets configuration that routes to the worker first would otherwise lose
64
+ * `public/` entirely. That is the reason its `mountStaticAssets` is live too. So on Cloudflare a `public/`
65
+ * file is CDN-first *and*, inside the worker, a genuine fallback that a route still beats — which is the
66
+ * ordering this method's first line describes, reached by the one target whose CDN can be told to stand
67
+ * aside.
43
68
  */
44
69
  mountPublicFallback(app: Hono): void;
45
70
  /**
46
- * Reads the page prerendered for `c.req.path` by `rshono build`, or `null` when there is none (in
47
- * which case the route renders per request, which is always a valid answer).
71
+ * Reads the page prerendered for `c.req.path`, or `null` when there is none in which case the route
72
+ * renders per request.
48
73
  *
49
- * Takes the whole {@link Context} rather than just the path because on a platform with no
50
- * filesystem the store *is* a request-scoped binding `c.env.ASSETS` on Workers. The path is
51
- * untrusted either way, so an implementation has to treat traversal as a miss, not a lookup
52
- * (see `prerenderedRelPath`).
74
+ * Takes the whole {@link Context} because without a filesystem the store *is* a request-scoped
75
+ * binding (`c.env.ASSETS` on Workers). The path is untrusted either way, so an implementation treats
76
+ * traversal as a miss see `ssgFilePath`, the one mapping from a path to the file that holds its page.
53
77
  */
54
78
  readPrerendered(c: Context, variant: PrerenderVariant): Promise<PrerenderedPage | null>;
55
- /**
56
- * Response compression, or `null` where the platform already compresses on the way out (and
57
- * doing it twice would only cost CPU) or cannot stream a compressor at all.
58
- *
59
- * Whether it is *used* is still the app's `compress` setting; this is only whether it is available.
60
- */
61
- compress: MiddlewareHandler | null;
62
- /** Loads `.env` files, where the platform has a filesystem to read them from. Env is bindings elsewhere. */
79
+ /** Loads `.env` files where the platform has a filesystem. Env is bindings elsewhere. */
63
80
  loadEnv(): void;
64
81
  }
65
- //# sourceMappingURL=contract.d.ts.map