@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
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { Context, Hono, MiddlewareHandler } from 'hono';\nimport type { PrerenderVariant, PrerenderedPage } from '../server/prerendered.js';\n\n/**\n * A hosting platform rshono can build for. Selected with {@link RSHonoConfig.deploy}, the\n * `--deploy` flag or the `RSHONO_DEPLOY` env var, and resolved to a preset by `deploy/presets.ts`.\n *\n * `rshono dev` always runs the `node` server whatever this says the dev server owns the process,\n * watches both compilers and fronts them on one port, none of which a hosting platform provides.\n */\nexport type DeployTarget = 'node' | 'cloudflare' | 'bun' | 'deno' | 'vercel' | 'netlify' | 'aws-lambda';\n\n/**\n * Everything the app server needs from the platform it is running on.\n *\n * One preset implements this per target, and the `@rshono/deploy` alias resolves to exactly that\n * module at build time (see `builder/rspack-config.ts`), so only the selected platform's code is\n * ever in the bundle. `runtime/entry.rsc.tsx` is written against this interface and nothing else —\n * it is the whole of what \"which platform is this\" means at request time.\n *\n * The split is deliberately by *capability*, not by runtime: a platform with a filesystem reuses the\n * implementations in `server/` (`static.ts`, `compress.ts`, `ssg.ts`) and only differs in how the\n * finished app is handed over.\n */\nexport interface DeployRuntime {\n /**\n * Hands the assembled app to the platform, and returns whatever the entry module should\n * `export default` there.\n *\n * The two shapes hosting takes, in one call: where rshono owns the process (node, bun, deno) this\n * binds a port and returns nothing; where the host owns it, it returns the export the platform\n * looks for `{ fetch }` on Workers, a handler function on Vercel/Netlify/Lambda.\n */\n serveApp(app: Hono): unknown;\n /**\n * Mounts the hashed client bundle at `/_static`. A no-op where the platform's own CDN serves it\n * before a request ever reaches the app.\n *\n * Called *before* the app's routes, so the bundle is never shadowed by one.\n */\n mountStaticAssets(app: Hono): void;\n /**\n * Mounts the `public/` fallback at the web root files served verbatim, and only for paths no\n * route claimed. Called *after* every route for exactly that reason.\n */\n mountPublicFallback(app: Hono): void;\n /**\n * Reads the page prerendered for `c.req.path` by `rshono build`, or `null` when there is none (in\n * which case the route renders per request, which is always a valid answer).\n *\n * Takes the whole {@link Context} rather than just the path because on a platform with no\n * filesystem the store *is* a request-scoped binding `c.env.ASSETS` on Workers. The path is\n * untrusted either way, so an implementation has to treat traversal as a miss, not a lookup\n * (see `prerenderedRelPath`).\n */\n readPrerendered(c: Context, variant: PrerenderVariant): Promise<PrerenderedPage | null>;\n /**\n * Response compression, or `null` where the platform already compresses on the way out (and\n * doing it twice would only cost CPU) or cannot stream a compressor at all.\n *\n * Whether it is *used* is still the app's `compress` setting; this is only whether it is available.\n */\n compress: MiddlewareHandler | null;\n /** Loads `.env` files, where the platform has a filesystem to read them from. Env is bindings elsewhere. */\n loadEnv(): void;\n}\n"]}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"","sourcesContent":["import type { Context, Hono } from 'hono';\nimport type { PrerenderVariant, PrerenderedPage } from '../server/prerendered.js';\n\n/**\n * A hosting platform `rshono build` targets. Selected with `deploy` in `rshono.config.ts`, the\n * `--deploy` flag or the `RSHONO_DEPLOY` env var.\n *\n * - `'node'` (the default) rshono binds the port and you run the build with `rshono start`. Covers a\n * VPS, a container, a PaaS, and through `node:` compatibility Bun and Deno.\n * - `'cloudflare'` a Worker; the entry exports `{ fetch }`.\n * - `'vercel'` a Vercel function, plus the on-disk layout and config streaming needs there.\n * - `'aws-lambda'` a streaming Lambda handler.\n *\n * There is one target per *handoff* — who opens the socket, and what shape a request arrives in — since\n * that is the part an app cannot arrange for itself. `rshono dev` always runs the `node` server.\n *\n * @example\n * ```ts\n * export default defineConfig({ deploy: 'cloudflare' });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/deployment#the-targets | Docs the targets}\n */\nexport type DeployTarget = 'node' | 'cloudflare' | 'vercel' | 'aws-lambda';\n\n/**\n * Everything the app server needs from the platform it runs on — the whole of what \"which platform is\n * this\" means at request time, since `runtime/entry.rsc.tsx` is written against this and nothing else.\n *\n * One preset implements it per target, and the build-time `@rshono/deploy` alias resolves to exactly\n * that module, so only the selected platform's code is ever in the bundle.\n */\nexport interface DeployRuntime {\n /**\n * Hands the app to the platform and returns whatever the entry module should `export default` there:\n * nothing where rshono owns the process (this binds the port), otherwise the export the platform looks\n * for `{ fetch }` on Workers, a streaming handler on Lambda, and on Vercel a Node\n * `(IncomingMessage, ServerResponse)` listener, which is what its `Nodejs` launcher calls.\n *\n * Note that a web `Request` is not the common currency here: two of the three targets are handed one by\n * their platform, and Vercel is not, so converting is part of the handoff rather than something the\n * request-handling code below can assume has already happened.\n */\n serveApp(app: Hono): unknown;\n /**\n * Mounts the hashed client bundle at `/_static` after src/server.ts, so the app's own middleware covers an\n * asset response too, and ahead of the page routes, which cannot claim that prefix. A no-op where a CDN\n * serves it.\n */\n mountStaticAssets(app: Hono): void;\n /**\n * Mounts `public/` at the web root, after every route, so it only answers paths no route claimed.\n *\n * **That ordering is only within the app**, which is the whole surface on `node` and `aws-lambda` and not\n * on the two targets with a CDN in front. There `public/` is part of the static output and the platform\n * answers from it *before* the app is invoked — `{ handle: 'filesystem' }` ahead of the function on\n * Vercel, Workers Assets ahead of the worker on Cloudflare so it is a CDN-first store rather than a\n * fallback. `rshono build` warns about a `public/` file that lands on a route's path for exactly that\n * reason; see `publicRouteCollisions`.\n *\n * The two targets do not answer that the same way, and the difference is worth having written down here\n * rather than discovered in one of them. **Vercel implements this as a no-op**: nothing is uploaded with\n * the function for it to read, so a mount could never answer. **Cloudflare implements it for real**,\n * against the `ASSETS` binding normally dead, since the CDN is checked first, but the binding is the\n * same store either way and an assets configuration that routes to the worker first would otherwise lose\n * `public/` entirely. That is the reason its `mountStaticAssets` is live too. So on Cloudflare a `public/`\n * file is CDN-first *and*, inside the worker, a genuine fallback that a route still beats — which is the\n * ordering this method's first line describes, reached by the one target whose CDN can be told to stand\n * aside.\n */\n mountPublicFallback(app: Hono): void;\n /**\n * Reads the page prerendered for `c.req.path`, or `null` when there is none in which case the route\n * renders per request.\n *\n * Takes the whole {@link Context} because without a filesystem the store *is* a request-scoped\n * binding (`c.env.ASSETS` on Workers). The path is untrusted either way, so an implementation treats\n * traversal as a miss — see `ssgFilePath`, the one mapping from a path to the file that holds its page.\n */\n readPrerendered(c: Context, variant: PrerenderVariant): Promise<PrerenderedPage | null>;\n /** Loads `.env` files where the platform has a filesystem. Env is bindings elsewhere. */\n loadEnv(): void;\n}\n"]}
@@ -1,10 +1,7 @@
1
1
  import type { DeployRuntime } from './contract.js';
2
2
  /**
3
- * Everything a deploy target with a real filesystem does the same way.
4
- *
5
- * Node, Bun, Deno and the serverless runtimes that unpack a bundle onto disk (Vercel, Netlify, Lambda)
6
- * differ only in how the finished app is handed over — so each of those presets is this object plus
7
- * its own {@link DeployRuntime.serveApp}, and the implementations live once, in `server/`.
3
+ * Everything a deploy target with a real filesystem does the same way. Node and the serverless runtimes that
4
+ * unpack onto a read-only disk (Vercel, AWS Lambda) differ only in how the app is handed over, so each of
5
+ * those presets is this object plus its own {@link DeployRuntime.serveApp}.
8
6
  */
9
7
  export declare const fileSystemRuntime: Omit<DeployRuntime, 'serveApp'>;
10
- //# sourceMappingURL=filesystem.d.ts.map
@@ -1,31 +1,34 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { compress } from '../server/compress.js';
4
3
  import { loadEnvFiles } from '../server/load-env.js';
5
4
  import { readPrerendered } from '../server/ssg.js';
6
5
  import { createPublicFallback, createStaticAssetsApp } from '../server/static.js';
7
- const isDev = __RSHONO_CONFIG__.isDev;
6
+ const { isDev, outDir } = __RSHONO_CONFIG__;
7
+ // The server bundle is minified and ships a source map, so this is what makes a production stack trace point
8
+ // at the original TypeScript. Done here rather than left to `--enable-source-maps` because a serverless host
9
+ // starts the process itself and passes no flags of ours: Vercel and Lambda would otherwise report minified
10
+ // frames to whatever `onServerError` forwards them to. Guarded because the same `DeployRuntime` shape is
11
+ // implemented for runtimes with no `process`.
12
+ if (typeof process !== 'undefined' && typeof process.setSourceMapsEnabled === 'function') {
13
+ process.setSourceMapsEnabled(true);
14
+ }
8
15
  /**
9
- * The project root, derived from where the bundle itself ended up: `rshono build` writes the server
10
- * bundle to `<root>/dist/server/main.mjs`, and this module is bundled into it, which makes
11
- * `import.meta.dirname` `<root>/dist/server` at runtime.
16
+ * The project root, derived from where the bundle ended up: this module is compiled into
17
+ * `<root>/<outDir>/server/main.mjs`, which is why every output directory sits one level under the root.
12
18
  *
13
- * Derived rather than baked in at build time on purpose: an absolute build-time path would tie the
14
- * output to the machine that produced it, and building in CI to run somewhere else is the normal case.
15
- * A preset that relocates the bundle keeps `dist/server/main.mjs` intact inside its own layout for
16
- * exactly this reason.
19
+ * Derived rather than baked in, because an absolute build-time path would tie the output to the machine that
20
+ * produced it building in CI to run elsewhere is the normal case. A preset that relocates the bundle keeps
21
+ * `dist/server/main.mjs` intact inside its own layout for this reason.
17
22
  */
18
23
  const rootDir = join(import.meta.dirname, '..', '..');
19
- const staticDir = join(rootDir, 'dist', 'static');
20
- const ssgDir = join(rootDir, 'dist', 'ssg');
21
- /** `public/` is copied into `dist/` by the build, so a deployed build is self-contained; dev reads the source. */
22
- const publicDir = isDev ? join(rootDir, 'public') : join(rootDir, 'dist', 'public');
24
+ const staticDir = join(rootDir, outDir, 'static');
25
+ const ssgDir = join(rootDir, outDir, 'ssg');
26
+ /** `public/` is copied into the build output, so a deployed build is self-contained; dev reads the source. */
27
+ const publicDir = isDev ? join(rootDir, 'public') : join(rootDir, outDir, 'public');
23
28
  /**
24
- * Everything a deploy target with a real filesystem does the same way.
25
- *
26
- * Node, Bun, Deno and the serverless runtimes that unpack a bundle onto disk (Vercel, Netlify, Lambda)
27
- * differ only in how the finished app is handed over — so each of those presets is this object plus
28
- * its own {@link DeployRuntime.serveApp}, and the implementations live once, in `server/`.
29
+ * Everything a deploy target with a real filesystem does the same way. Node and the serverless runtimes that
30
+ * unpack onto a read-only disk (Vercel, AWS Lambda) differ only in how the app is handed over, so each of
31
+ * those presets is this object plus its own {@link DeployRuntime.serveApp}.
29
32
  */
30
33
  export const fileSystemRuntime = {
31
34
  mountStaticAssets(app) {
@@ -34,12 +37,12 @@ export const fileSystemRuntime = {
34
37
  mountPublicFallback(app) {
35
38
  if (!existsSync(publicDir))
36
39
  return;
37
- app.on(['GET', 'HEAD'], '/*', createPublicFallback({ root: publicDir, isDev }));
40
+ // `GET` covers `HEAD` Hono dispatches one as the other. See `HTTPMethod`.
41
+ app.get('/*', createPublicFallback({ root: publicDir, isDev }));
38
42
  },
39
43
  readPrerendered(c, variant) {
40
44
  return readPrerendered(ssgDir, c.req.path, variant);
41
45
  },
42
- compress: compress(),
43
46
  loadEnv() {
44
47
  loadEnvFiles(rootDir);
45
48
  },
@@ -1 +1 @@
1
- {"version":3,"file":"filesystem.js","sourceRoot":"","sources":["../../src/deploy/filesystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGlF,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC;AAEtC;;;;;;;;;GASG;AACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEtD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAClD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAC5C,kHAAkH;AAClH,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAoC;IAChE,iBAAiB,CAAC,GAAS;QACzB,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,mBAAmB,CAAC,GAAS;QAC3B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO;QACnC,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,oBAAoB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAClF,CAAC;IAED,eAAe,CAAC,CAAC,EAAE,OAAO;QACxB,OAAO,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,QAAQ,EAAE,QAAQ,EAAE;IAEpB,OAAO;QACL,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;CACF,CAAC","sourcesContent":["import type { Hono } from 'hono';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { compress } from '../server/compress.js';\nimport { loadEnvFiles } from '../server/load-env.js';\nimport { readPrerendered } from '../server/ssg.js';\nimport { createPublicFallback, createStaticAssetsApp } from '../server/static.js';\nimport type { DeployRuntime } from './contract.js';\n\nconst isDev = __RSHONO_CONFIG__.isDev;\n\n/**\n * The project root, derived from where the bundle itself ended up: `rshono build` writes the server\n * bundle to `<root>/dist/server/main.mjs`, and this module is bundled into it, which makes\n * `import.meta.dirname` `<root>/dist/server` at runtime.\n *\n * Derived rather than baked in at build time on purpose: an absolute build-time path would tie the\n * output to the machine that produced it, and building in CI to run somewhere else is the normal case.\n * A preset that relocates the bundle keeps `dist/server/main.mjs` intact inside its own layout for\n * exactly this reason.\n */\nconst rootDir = join(import.meta.dirname, '..', '..');\n\nconst staticDir = join(rootDir, 'dist', 'static');\nconst ssgDir = join(rootDir, 'dist', 'ssg');\n/** `public/` is copied into `dist/` by the build, so a deployed build is self-contained; dev reads the source. */\nconst publicDir = isDev ? join(rootDir, 'public') : join(rootDir, 'dist', 'public');\n\n/**\n * Everything a deploy target with a real filesystem does the same way.\n *\n * Node, Bun, Deno and the serverless runtimes that unpack a bundle onto disk (Vercel, Netlify, Lambda)\n * differ only in how the finished app is handed over so each of those presets is this object plus\n * its own {@link DeployRuntime.serveApp}, and the implementations live once, in `server/`.\n */\nexport const fileSystemRuntime: Omit<DeployRuntime, 'serveApp'> = {\n mountStaticAssets(app: Hono): void {\n app.route('/_static', createStaticAssetsApp({ root: staticDir, isDev }));\n },\n\n mountPublicFallback(app: Hono): void {\n if (!existsSync(publicDir)) return;\n app.on(['GET', 'HEAD'], '/*', createPublicFallback({ root: publicDir, isDev }));\n },\n\n readPrerendered(c, variant) {\n return readPrerendered(ssgDir, c.req.path, variant);\n },\n\n compress: compress(),\n\n loadEnv(): void {\n loadEnvFiles(rootDir);\n },\n};\n"]}
1
+ {"version":3,"file":"filesystem.js","sourceRoot":"","sources":["../../src/deploy/filesystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAGlF,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAAC;AAE5C,6GAA6G;AAC7G,6GAA6G;AAC7G,2GAA2G;AAC3G,yGAAyG;AACzG,8CAA8C;AAC9C,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,OAAO,CAAC,oBAAoB,KAAK,UAAU,EAAE,CAAC;IACzF,OAAO,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AAEtD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAClD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAC5C,8GAA8G;AAC9G,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAEpF;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAoC;IAChE,iBAAiB,CAAC,GAAS;QACzB,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAC3E,CAAC;IAED,mBAAmB,CAAC,GAAS;QAC3B,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO;QACnC,4EAA4E;QAC5E,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,oBAAoB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,eAAe,CAAC,CAAC,EAAE,OAAO;QACxB,OAAO,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtD,CAAC;IAED,OAAO;QACL,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;CACF,CAAC","sourcesContent":["import type { Hono } from 'hono';\nimport { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { loadEnvFiles } from '../server/load-env.js';\nimport { readPrerendered } from '../server/ssg.js';\nimport { createPublicFallback, createStaticAssetsApp } from '../server/static.js';\nimport type { DeployRuntime } from './contract.js';\n\nconst { isDev, outDir } = __RSHONO_CONFIG__;\n\n// The server bundle is minified and ships a source map, so this is what makes a production stack trace point\n// at the original TypeScript. Done here rather than left to `--enable-source-maps` because a serverless host\n// starts the process itself and passes no flags of ours: Vercel and Lambda would otherwise report minified\n// frames to whatever `onServerError` forwards them to. Guarded because the same `DeployRuntime` shape is\n// implemented for runtimes with no `process`.\nif (typeof process !== 'undefined' && typeof process.setSourceMapsEnabled === 'function') {\n process.setSourceMapsEnabled(true);\n}\n\n/**\n * The project root, derived from where the bundle ended up: this module is compiled into\n * `<root>/<outDir>/server/main.mjs`, which is why every output directory sits one level under the root.\n *\n * Derived rather than baked in, because an absolute build-time path would tie the output to the machine that\n * produced it building in CI to run elsewhere is the normal case. A preset that relocates the bundle keeps\n * `dist/server/main.mjs` intact inside its own layout for this reason.\n */\nconst rootDir = join(import.meta.dirname, '..', '..');\n\nconst staticDir = join(rootDir, outDir, 'static');\nconst ssgDir = join(rootDir, outDir, 'ssg');\n/** `public/` is copied into the build output, so a deployed build is self-contained; dev reads the source. */\nconst publicDir = isDev ? join(rootDir, 'public') : join(rootDir, outDir, 'public');\n\n/**\n * Everything a deploy target with a real filesystem does the same way. Node and the serverless runtimes that\n * unpack onto a read-only disk (Vercel, AWS Lambda) differ only in how the app is handed over, so each of\n * those presets is this object plus its own {@link DeployRuntime.serveApp}.\n */\nexport const fileSystemRuntime: Omit<DeployRuntime, 'serveApp'> = {\n mountStaticAssets(app: Hono): void {\n app.route('/_static', createStaticAssetsApp({ root: staticDir, isDev }));\n },\n\n mountPublicFallback(app: Hono): void {\n if (!existsSync(publicDir)) return;\n // `GET` covers `HEAD` — Hono dispatches one as the other. See `HTTPMethod`.\n app.get('/*', createPublicFallback({ root: publicDir, isDev }));\n },\n\n readPrerendered(c, variant) {\n return readPrerendered(ssgDir, c.req.path, variant);\n },\n\n loadEnv(): void {\n loadEnvFiles(rootDir);\n },\n};\n"]}
@@ -1,7 +1,9 @@
1
1
  import type { DeployRuntime } from '../contract.js';
2
2
  /**
3
- * Node: a long-lived process that owns its own port, with a filesystem behind every asset. The shape
4
- * the framework was built against, and the only target `rshono dev` ever produces.
3
+ * Node: a long-lived process that owns its own port, with a filesystem behind every asset the shape the
4
+ * framework was built against, and the only target `rshono dev` produces.
5
+ *
6
+ * Anything that runs a Node process runs this build, Bun and Deno included: the listener is
7
+ * `@hono/node-server`, and both implement the `node:` APIs it needs.
5
8
  */
6
9
  export declare const runtime: DeployRuntime;
7
- //# sourceMappingURL=runtime.d.ts.map
@@ -1,22 +1,35 @@
1
1
  import { serve } from '@hono/node-server';
2
2
  import { parentPort, workerData } from 'node:worker_threads';
3
+ import { parsePort, SERVER_DEFAULTS } from '../../server/server-config.js';
3
4
  import { onShutdown } from '../../server/shutdown.js';
4
5
  import { fileSystemRuntime } from '../filesystem.js';
5
- import { listenAddress, readyMessage } from '../listen.js';
6
+ /** Bound to every interface so the address printed on start is `localhost`, not this. */
7
+ const WILDCARD_HOST = '0.0.0.0';
6
8
  /**
7
- * Node: a long-lived process that owns its own port, with a filesystem behind every asset. The shape
8
- * the framework was built against, and the only target `rshono dev` ever produces.
9
+ * The address to listen on: an explicit override (the dev server, which picks the port for its worker) beats
10
+ * `PORT` / `HOST`, which beat the built-in default. `??` rather than `||`, so an explicit `PORT=0` "any free
11
+ * port" — is honoured, and so that {@link parsePort} is never consulted for a port the override already won.
12
+ */
13
+ function listenAddress(overrides) {
14
+ return {
15
+ port: overrides?.port ?? parsePort(process.env.PORT, 'PORT') ?? SERVER_DEFAULTS.port,
16
+ hostname: overrides?.hostname ?? process.env.HOST ?? SERVER_DEFAULTS.host,
17
+ };
18
+ }
19
+ /**
20
+ * Node: a long-lived process that owns its own port, with a filesystem behind every asset — the shape the
21
+ * framework was built against, and the only target `rshono dev` produces.
22
+ *
23
+ * Anything that runs a Node process runs this build, Bun and Deno included: the listener is
24
+ * `@hono/node-server`, and both implement the `node:` APIs it needs.
9
25
  */
10
26
  export const runtime = {
11
27
  ...fileSystemRuntime,
12
28
  serveApp(app) {
13
- // `rshono build` imports this bundle to prerender `render: 'static'` routes. That pass renders
14
- // through `app.fetch` directly and must not bind a port — nothing is listening for it, and the
15
- // build would never exit.
29
+ // The prerender pass renders through `app.fetch` directly, and a bound port would keep the build alive.
16
30
  if (process.env.RSHONO_PRERENDER)
17
31
  return;
18
- // The dev server runs this bundle in a worker thread and picks the port itself, so its choice wins
19
- // over both the environment and the config file.
32
+ // The dev server runs this in a worker thread and picks the port itself, so its choice wins.
20
33
  const devWorker = workerData;
21
34
  const address = listenAddress(devWorker ?? undefined);
22
35
  const server = serve({ fetch: app.fetch, ...address }, (info) => {
@@ -24,7 +37,8 @@ export const runtime = {
24
37
  parentPort.postMessage({ type: 'ready', port: info.port });
25
38
  }
26
39
  else {
27
- console.log(readyMessage({ ...address, port: info.port }));
40
+ const host = address.hostname === WILDCARD_HOST ? 'localhost' : address.hostname;
41
+ console.log(` ➜ rshono serving on http://${host}:${info.port}`);
28
42
  }
29
43
  });
30
44
  onShutdown(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/node/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE3D;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAkB;IACpC,GAAG,iBAAiB;IAEpB,QAAQ,CAAC,GAAS;QAChB,+FAA+F;QAC/F,+FAA+F;QAC/F,0BAA0B;QAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB;YAAE,OAAO;QAEzC,mGAAmG;QACnG,iDAAiD;QACjD,MAAM,SAAS,GAAG,UAAyD,CAAC;QAC5E,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC;QAEtD,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE;YAC9D,IAAI,UAAU,EAAE,CAAC;gBACf,UAAU,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,GAAG,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,UAAU,CAAC,GAAG,EAAE;YACd,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC","sourcesContent":["import { serve } from '@hono/node-server';\nimport type { Hono } from 'hono';\nimport { parentPort, workerData } from 'node:worker_threads';\nimport { onShutdown } from '../../server/shutdown.js';\nimport type { DeployRuntime } from '../contract.js';\nimport { fileSystemRuntime } from '../filesystem.js';\nimport { listenAddress, readyMessage } from '../listen.js';\n\n/**\n * Node: a long-lived process that owns its own port, with a filesystem behind every asset. The shape\n * the framework was built against, and the only target `rshono dev` ever produces.\n */\nexport const runtime: DeployRuntime = {\n ...fileSystemRuntime,\n\n serveApp(app: Hono): undefined {\n // `rshono build` imports this bundle to prerender `render: 'static'` routes. That pass renders\n // through `app.fetch` directly and must not bind a port nothing is listening for it, and the\n // build would never exit.\n if (process.env.RSHONO_PRERENDER) return;\n\n // The dev server runs this bundle in a worker thread and picks the port itself, so its choice wins\n // over both the environment and the config file.\n const devWorker = workerData as { port?: number; hostname?: string } | null;\n const address = listenAddress(devWorker ?? undefined);\n\n const server = serve({ fetch: app.fetch, ...address }, (info) => {\n if (parentPort) {\n parentPort.postMessage({ type: 'ready', port: info.port });\n } else {\n console.log(readyMessage({ ...address, port: info.port }));\n }\n });\n\n onShutdown(() => {\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(0), 3000).unref();\n });\n },\n};\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/node/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,2FAA2F;AAC3F,MAAM,aAAa,GAAG,SAAS,CAAC;AAEhC;;;;GAIG;AACH,SAAS,aAAa,CAAC,SAAgD;IACrE,OAAO;QACL,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,eAAe,CAAC,IAAI;QACpF,QAAQ,EAAE,SAAS,EAAE,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,eAAe,CAAC,IAAI;KAC1E,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAkB;IACpC,GAAG,iBAAiB;IAEpB,QAAQ,CAAC,GAAS;QAChB,wGAAwG;QACxG,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB;YAAE,OAAO;QAEzC,6FAA6F;QAC7F,MAAM,SAAS,GAAG,UAAyD,CAAC;QAC5E,MAAM,OAAO,GAAG,aAAa,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC;QAEtD,MAAM,MAAM,GAAG,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,GAAG,OAAO,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE;YAC9D,IAAI,UAAU,EAAE,CAAC;gBACf,UAAU,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YAC7D,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;gBACjF,OAAO,CAAC,GAAG,CAAC,gCAAgC,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YACnE,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,UAAU,CAAC,GAAG,EAAE;YACd,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACpC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;CACF,CAAC","sourcesContent":["import { serve } from '@hono/node-server';\nimport type { Hono } from 'hono';\nimport { parentPort, workerData } from 'node:worker_threads';\nimport { parsePort, SERVER_DEFAULTS } from '../../server/server-config.js';\nimport { onShutdown } from '../../server/shutdown.js';\nimport type { DeployRuntime } from '../contract.js';\nimport { fileSystemRuntime } from '../filesystem.js';\n\n/** Bound to every interface — so the address printed on start is `localhost`, not this. */\nconst WILDCARD_HOST = '0.0.0.0';\n\n/**\n * The address to listen on: an explicit override (the dev server, which picks the port for its worker) beats\n * `PORT` / `HOST`, which beat the built-in default. `??` rather than `||`, so an explicit `PORT=0` — \"any free\n * port\" — is honoured, and so that {@link parsePort} is never consulted for a port the override already won.\n */\nfunction listenAddress(overrides?: { port?: number; hostname?: string }): { port: number; hostname: string } {\n return {\n port: overrides?.port ?? parsePort(process.env.PORT, 'PORT') ?? SERVER_DEFAULTS.port,\n hostname: overrides?.hostname ?? process.env.HOST ?? SERVER_DEFAULTS.host,\n };\n}\n\n/**\n * Node: a long-lived process that owns its own port, with a filesystem behind every asset the shape the\n * framework was built against, and the only target `rshono dev` produces.\n *\n * Anything that runs a Node process runs this build, Bun and Deno included: the listener is\n * `@hono/node-server`, and both implement the `node:` APIs it needs.\n */\nexport const runtime: DeployRuntime = {\n ...fileSystemRuntime,\n\n serveApp(app: Hono): undefined {\n // The prerender pass renders through `app.fetch` directly, and a bound port would keep the build alive.\n if (process.env.RSHONO_PRERENDER) return;\n\n // The dev server runs this in a worker thread and picks the port itself, so its choice wins.\n const devWorker = workerData as { port?: number; hostname?: string } | null;\n const address = listenAddress(devWorker ?? undefined);\n\n const server = serve({ fetch: app.fetch, ...address }, (info) => {\n if (parentPort) {\n parentPort.postMessage({ type: 'ready', port: info.port });\n } else {\n const host = address.hostname === WILDCARD_HOST ? 'localhost' : address.hostname;\n console.log(` ➜ rshono serving on http://${host}:${info.port}`);\n }\n });\n\n onShutdown(() => {\n server.close(() => process.exit(0));\n setTimeout(() => process.exit(0), 3000).unref();\n });\n },\n};\n"]}
@@ -1,4 +1,5 @@
1
1
  import type { RspackOptions } from '@rspack/core';
2
+ import type { Route } from '../router.js';
2
3
  import type { DeployTarget } from './contract.js';
3
4
  /** What a preset's {@link DeployPreset.finalize} is told about the build it is arranging. */
4
5
  export interface DeployBuildContext {
@@ -12,14 +13,19 @@ export interface DeployBuildContext {
12
13
  publicDir: string | null;
13
14
  /** Prerendered pages, `<root>/dist/ssg` — empty when the app has no `render: 'static'` routes. */
14
15
  ssgDir: string;
16
+ /**
17
+ * The app's route table, as the bundle validated it. Read by the two CDN presets: their platform serves
18
+ * the static output before the app is invoked, so a `public/` file that lands on a route's path answers
19
+ * instead of it — see `publicRouteCollisions`.
20
+ */
21
+ routes: readonly Route[];
15
22
  }
16
23
  /**
17
- * The build-time half of a deploy target: which runtime module the bundle gets, how the server
18
- * compiler has to change to produce something the platform can run, and how the output is arranged
19
- * once it exists.
24
+ * The build-time half of a deploy target: which runtime module the bundle gets, how the server compiler has
25
+ * to change to produce something the platform can run, and how the output is arranged once it exists.
20
26
  *
21
- * The runtime half is {@link DeployRuntime}, in its own file because it is compiled *into* the app
22
- * bundle — this side only ever runs in the CLI.
27
+ * The runtime half is `DeployRuntime`, in its own file because it is compiled *into* the app bundle — this
28
+ * side only ever runs in the CLI.
23
29
  */
24
30
  export interface DeployPreset {
25
31
  readonly name: DeployTarget;
@@ -31,41 +37,42 @@ export interface DeployPreset {
31
37
  /** How to run what was just built, completing the "build complete —" line. */
32
38
  readonly deployHint: string;
33
39
  /**
34
- * Extra resolve conditions for the server bundle, most specific first.
35
- *
36
- * This is what picks the right build of React and the RSC runtime: both ship one per runtime behind
37
- * `workerd` / `deno` / `edge-light` / `node` conditions. Omit it to accept whatever the Rspack
38
- * target implies, which is correct for Node.
40
+ * `true` where the platform hands per-request bindings to `app.fetch(request, env)` Workers, and
41
+ * nowhere else. It gates the `getRequestContext().env` merge, because every other target passes its own
42
+ * private handles in that argument (`{ incoming, outgoing }` on Node, the whole invocation on Lambda) and
43
+ * merging those would put a live socket, or a request's headers and cookies, behind a name typed
44
+ * `string | undefined`.
45
+ */
46
+ readonly envBindings?: boolean;
47
+ /**
48
+ * Extra resolve conditions for the server bundle, most specific first — what picks the right build of React
49
+ * and the RSC runtime, both of which ship one per runtime. Omit to accept whatever the Rspack target
50
+ * implies, which is correct for Node.
39
51
  */
40
52
  readonly resolveConditions?: readonly string[];
41
53
  /** browserslist-style targets for the server bundle's swc pass. Defaults to Node. */
42
54
  readonly syntaxTargets?: readonly string[];
43
55
  /**
44
- * Adjusts the generated server Rspack config for this platform — target, externals policy, output
45
- * shape. Mutates in place, and runs *before* the user's `rspack` hook so that hook keeps the last
46
- * word.
56
+ * Adjusts the generated server Rspack config for this platform — target, externals policy, output shape.
57
+ * Mutates in place, and runs before the user's `rspack` hook so that hook keeps the last word.
47
58
  */
48
59
  configureServer?(config: RspackOptions): void;
49
60
  /**
50
- * Arranges the finished build for the platform: assembles whatever directory layout it expects,
51
- * emits its config file, and prints how to deploy.
52
- *
53
- * Runs last — after both bundles, the `public/` copy and the prerender pass — so everything it
54
- * needs to move is already on disk.
61
+ * Arranges the finished build for the platform: assembles the directory layout it expects, emits its config
62
+ * file, and prints how to deploy. Runs last, so everything it needs to move is already on disk.
55
63
  */
56
64
  finalize?(ctx: DeployBuildContext): Promise<void> | void;
57
65
  }
58
66
  /**
59
- * Node: a long-lived server process. The generated config is already this shape, so the preset has
60
- * nothing to contribute — the platform-specific settings still living in `builder/rspack-config.ts`
61
- * (`target: 'node'`, the externals policy, ESM chunk output) are the Node ones by default.
67
+ * Node: a long-lived server process. The generated config is already this shape `target: 'node'`, the
68
+ * externals policy and ESM chunk output are all defaults so the preset has nothing to contribute.
62
69
  */
63
70
  export declare const NODE_PRESET: DeployPreset;
64
71
  /** Every target `deploy` accepts, for error messages and docs. */
65
72
  export declare const DEPLOY_TARGETS: DeployTarget[];
66
73
  /**
67
- * How to deploy what a given target built, or `null` for a name this rshono does not know — which a
68
- * `dist/` produced by a newer version can legitimately carry.
74
+ * How to deploy what a given target built, or `null` for a name this rshono does not know — which a `dist/`
75
+ * from a newer version can legitimately carry.
69
76
  */
70
77
  export declare function deployHintFor(target: string): string | null;
71
78
  /** Where a deploy target can be named, in precedence order. */
@@ -74,15 +81,12 @@ export interface DeploySources {
74
81
  flag?: string;
75
82
  /** The `RSHONO_DEPLOY` env var — for a CI job that deploys the same app to more than one place. */
76
83
  env?: string;
77
- /** {@link RSHonoConfig.deploy} from `rshono.config.ts`. */
84
+ /** The `deploy` field in `rshono.config.ts`. */
78
85
  config?: string;
79
86
  }
80
87
  /**
81
- * Resolves the preset to build with: the flag wins over the environment, which wins over the config
82
- * file, which wins over the `node` default.
83
- *
84
- * Blank values are ignored at every level, so an unset-but-present `RSHONO_DEPLOY=` in a CI
85
- * environment falls through to the config file instead of failing the build.
88
+ * Resolves the preset to build with: the flag wins over the environment, which wins over the config file,
89
+ * which wins over the `node` default. Blank values are ignored at every level, so an unset-but-present
90
+ * `RSHONO_DEPLOY=` in CI falls through rather than failing the build.
86
91
  */
87
92
  export declare function resolveDeployPreset(sources?: DeploySources): DeployPreset;
88
- //# sourceMappingURL=presets.d.ts.map
@@ -1,10 +1,8 @@
1
1
  import { finalizeCloudflareBuild } from './cloudflare/build.js';
2
- import { finalizeNetlifyBuild } from './netlify/build.js';
3
2
  import { finalizeVercelBuild } from './vercel/build.js';
4
3
  /**
5
- * Node: a long-lived server process. The generated config is already this shape, so the preset has
6
- * nothing to contribute — the platform-specific settings still living in `builder/rspack-config.ts`
7
- * (`target: 'node'`, the externals policy, ESM chunk output) are the Node ones by default.
4
+ * Node: a long-lived server process. The generated config is already this shape `target: 'node'`, the
5
+ * externals policy and ESM chunk output are all defaults so the preset has nothing to contribute.
8
6
  */
9
7
  export const NODE_PRESET = {
10
8
  name: 'node',
@@ -14,17 +12,16 @@ export const NODE_PRESET = {
14
12
  /**
15
13
  * Cloudflare Workers: the host owns the process, the CDN owns the assets, and there is no filesystem.
16
14
  *
17
- * Every compiler setting here follows from `workerd` not being Node. Dependencies are bundled because
18
- * nothing resolves `node_modules` at runtime; `node:` and `cloudflare:` imports stay external because
19
- * the runtime provides them (`nodejs_compat`, which the scaffolded config enables for
20
- * `AsyncLocalStorage`); and async chunks are inlined because Wrangler's bundler cannot follow the
21
- * computed specifier Rspack's ESM chunk loader emits — a split bundle would deploy and then fail on
22
- * the first page render.
15
+ * Every setting here follows from `workerd` not being Node. Dependencies are bundled because nothing resolves
16
+ * `node_modules` at runtime; `node:` and `cloudflare:` imports stay external because the runtime provides
17
+ * them under `nodejs_compat`; and async chunks are inlined because Wrangler's bundler cannot follow the
18
+ * computed specifier Rspack's ESM chunk loader emits a split bundle deploys and then fails on first render.
23
19
  */
24
20
  const CLOUDFLARE_PRESET = {
25
21
  name: 'cloudflare',
26
22
  runtimeModule: 'deploy/cloudflare/runtime.js',
27
23
  deployHint: 'deploy with `wrangler deploy`',
24
+ envBindings: true,
28
25
  resolveConditions: ['workerd'],
29
26
  syntaxTargets: ['chrome 120'],
30
27
  configureServer(config) {
@@ -36,72 +33,87 @@ const CLOUDFLARE_PRESET = {
36
33
  finalize: finalizeCloudflareBuild,
37
34
  };
38
35
  /**
39
- * Bun and Deno: like Node, but the runtime opens the socket from the module's default export. Their
40
- * `node:` compatibility covers everything `server/` uses, so the bundle is Node's — only the handoff
41
- * differs, which is precisely the thing an app cannot do for itself.
36
+ * Bundles the app's dependencies into the server output instead of importing them from `node_modules`.
37
+ *
38
+ * The generated config externalizes them, which is right where the bundle runs *inside* the project but a
39
+ * serverless function is uploaded as a directory rather than installed into one, so nothing resolves
40
+ * `node_modules` at request time and an externalized `import 'some-package'` is a cold start that dies on
41
+ * `ERR_MODULE_NOT_FOUND`. Node's own builtins stay external either way, through the `node` externals preset
42
+ * that `target: 'node'` already enables.
43
+ *
44
+ * The cost is a dependency that cannot be bundled — a native addon, or one that reads its own files off disk
45
+ * relative to `__dirname`. Those now fail the build rather than the deploy, which is the same constraint
46
+ * `cloudflare` has always had; the `rspack` hook in `rshono.config.ts` is the way out.
42
47
  */
43
- const BUN_PRESET = {
44
- name: 'bun',
45
- runtimeModule: 'deploy/bun/runtime.js',
46
- deployHint: 'run `bun dist/server/main.mjs`',
47
- };
48
- const DENO_PRESET = {
49
- name: 'deno',
50
- runtimeModule: 'deploy/deno/runtime.js',
51
- deployHint: 'run `deno serve -A dist/server/main.mjs`',
52
- };
48
+ function bundleDependencies(config) {
49
+ config.externals = [];
50
+ }
53
51
  /**
54
- * Vercel and Netlify: one Node function behind the platform's CDN, which serves the assets and reaches
55
- * the function only for a page. Both `finalize` hooks assemble the layout the platform uploads.
52
+ * Vercel: one Node function behind the platform's CDN, which serves the assets and reaches the function only
53
+ * for a page. `finalize` assembles the Build Output API layout the platform uploads — including the
54
+ * `supportsResponseStreaming` flag, without which Vercel buffers the whole response and silently undoes
55
+ * streamed SSR.
56
+ *
57
+ * Only `dist/server` is uploaded with the function, which is why dependencies are bundled: see
58
+ * {@link bundleDependencies}.
56
59
  */
57
60
  const VERCEL_PRESET = {
58
61
  name: 'vercel',
59
62
  runtimeModule: 'deploy/vercel/runtime.js',
60
63
  deployHint: 'deploy with `vercel deploy --prebuilt`',
64
+ configureServer: bundleDependencies,
61
65
  finalize: finalizeVercelBuild,
62
66
  };
63
- const NETLIFY_PRESET = {
64
- name: 'netlify',
65
- runtimeModule: 'deploy/netlify/runtime.js',
66
- deployHint: 'deploy with `netlify deploy --build=false --dir=.netlify/publish`',
67
- finalize: finalizeNetlifyBuild,
68
- };
69
- /** AWS Lambda behind a Function URL in `RESPONSE_STREAM` mode — the AWS shape that keeps streaming. */
67
+ /**
68
+ * AWS Lambda behind a Function URL in `RESPONSE_STREAM` mode — the AWS shape that keeps streaming. The
69
+ * runtime wraps the app in `awslambda.streamifyResponse`; the buffered alternative would deploy fine and then
70
+ * hold every page until its last byte rendered.
71
+ *
72
+ * The deployment package is `dist/` and nothing else, which is why dependencies are bundled: see
73
+ * {@link bundleDependencies}.
74
+ */
70
75
  const AWS_LAMBDA_PRESET = {
71
76
  name: 'aws-lambda',
72
77
  runtimeModule: 'deploy/aws-lambda/runtime.js',
73
78
  deployHint: 'zip dist/ with the handler at dist/server/main.mjs',
79
+ configureServer: bundleDependencies,
74
80
  };
75
81
  const PRESETS = {
76
82
  node: NODE_PRESET,
77
83
  cloudflare: CLOUDFLARE_PRESET,
78
- bun: BUN_PRESET,
79
- deno: DENO_PRESET,
80
84
  vercel: VERCEL_PRESET,
81
- netlify: NETLIFY_PRESET,
82
85
  'aws-lambda': AWS_LAMBDA_PRESET,
83
86
  };
84
87
  /** Every target `deploy` accepts, for error messages and docs. */
85
88
  export const DEPLOY_TARGETS = Object.keys(PRESETS);
86
89
  /**
87
- * How to deploy what a given target built, or `null` for a name this rshono does not know — which a
88
- * `dist/` produced by a newer version can legitimately carry.
90
+ * `PRESETS[target]` for a target that really is one.
91
+ *
92
+ * `Object.hasOwn` rather than a bare bracket access, which resolves every `Object.prototype` key —
93
+ * `constructor`, `__proto__`, `toString` — to an inherited value that then passes a truthiness guard. A typo
94
+ * that happens to be one of those used to reach the builder and die on `preset.runtimeModule.split('/')`
95
+ * instead of getting the message written for an unknown target.
96
+ */
97
+ function presetFor(target) {
98
+ return Object.hasOwn(PRESETS, target) ? PRESETS[target] : undefined;
99
+ }
100
+ /**
101
+ * How to deploy what a given target built, or `null` for a name this rshono does not know — which a `dist/`
102
+ * from a newer version can legitimately carry.
89
103
  */
90
104
  export function deployHintFor(target) {
91
- return PRESETS[target]?.deployHint ?? null;
105
+ return presetFor(target)?.deployHint ?? null;
92
106
  }
93
107
  /**
94
- * Resolves the preset to build with: the flag wins over the environment, which wins over the config
95
- * file, which wins over the `node` default.
96
- *
97
- * Blank values are ignored at every level, so an unset-but-present `RSHONO_DEPLOY=` in a CI
98
- * environment falls through to the config file instead of failing the build.
108
+ * Resolves the preset to build with: the flag wins over the environment, which wins over the config file,
109
+ * which wins over the `node` default. Blank values are ignored at every level, so an unset-but-present
110
+ * `RSHONO_DEPLOY=` in CI falls through rather than failing the build.
99
111
  */
100
112
  export function resolveDeployPreset(sources = {}) {
101
113
  const target = sources.flag?.trim() || sources.env?.trim() || sources.config?.trim();
102
114
  if (!target)
103
115
  return NODE_PRESET;
104
- const preset = PRESETS[target];
116
+ const preset = presetFor(target);
105
117
  if (!preset) {
106
118
  throw new Error(`[rshono] unknown deploy target ${JSON.stringify(target)} — expected one of: ${DEPLOY_TARGETS.join(', ')}.`);
107
119
  }
@@ -1 +1 @@
1
- {"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/deploy/presets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAEhE,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AA2DxD;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,IAAI,EAAE,MAAM;IACZ,aAAa,EAAE,wBAAwB;IACvC,UAAU,EAAE,oBAAoB;CACjC,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,iBAAiB,GAAiB;IACtC,IAAI,EAAE,YAAY;IAClB,aAAa,EAAE,8BAA8B;IAC7C,UAAU,EAAE,+BAA+B;IAC3C,iBAAiB,EAAE,CAAC,SAAS,CAAC;IAC9B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,eAAe,CAAC,MAAM;QACpB,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC;QAC5B,MAAM,CAAC,aAAa,GAAG,eAAe,CAAC;QACvC,MAAM,CAAC,SAAS,GAAG,CAAC,uBAAuB,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC3D,CAAC;IACD,QAAQ,EAAE,uBAAuB;CAClC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,GAAiB;IAC/B,IAAI,EAAE,KAAK;IACX,aAAa,EAAE,uBAAuB;IACtC,UAAU,EAAE,gCAAgC;CAC7C,CAAC;AAEF,MAAM,WAAW,GAAiB;IAChC,IAAI,EAAE,MAAM;IACZ,aAAa,EAAE,wBAAwB;IACvC,UAAU,EAAE,0CAA0C;CACvD,CAAC;AAEF;;;GAGG;AACH,MAAM,aAAa,GAAiB;IAClC,IAAI,EAAE,QAAQ;IACd,aAAa,EAAE,0BAA0B;IACzC,UAAU,EAAE,wCAAwC;IACpD,QAAQ,EAAE,mBAAmB;CAC9B,CAAC;AAEF,MAAM,cAAc,GAAiB;IACnC,IAAI,EAAE,SAAS;IACf,aAAa,EAAE,2BAA2B;IAC1C,UAAU,EAAE,mEAAmE;IAC/E,QAAQ,EAAE,oBAAoB;CAC/B,CAAC;AAEF,uGAAuG;AACvG,MAAM,iBAAiB,GAAiB;IACtC,IAAI,EAAE,YAAY;IAClB,aAAa,EAAE,8BAA8B;IAC7C,UAAU,EAAE,oDAAoD;CACjE,CAAC;AAEF,MAAM,OAAO,GAAuC;IAClD,IAAI,EAAE,WAAW;IACjB,UAAU,EAAE,iBAAiB;IAC7B,GAAG,EAAE,UAAU;IACf,IAAI,EAAE,WAAW;IACjB,MAAM,EAAE,aAAa;IACrB,OAAO,EAAE,cAAc;IACvB,YAAY,EAAE,iBAAiB;CAChC,CAAC;AAEF,kEAAkE;AAClE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAmB,CAAC;AAErE;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAQ,OAAoD,CAAC,MAAM,CAAC,EAAE,UAAU,IAAI,IAAI,CAAC;AAC3F,CAAC;AAYD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAO,GAAkB,EAAE;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;IACrF,IAAI,CAAC,MAAM;QAAE,OAAO,WAAW,CAAC;IAEhC,MAAM,MAAM,GAAI,OAAoD,CAAC,MAAM,CAAC,CAAC;IAC7E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,uBAAuB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/H,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { RspackOptions } from '@rspack/core';\nimport { finalizeCloudflareBuild } from './cloudflare/build.js';\nimport type { DeployTarget } from './contract.js';\nimport { finalizeNetlifyBuild } from './netlify/build.js';\nimport { finalizeVercelBuild } from './vercel/build.js';\n\n/** What a preset's {@link DeployPreset.finalize} is told about the build it is arranging. */\nexport interface DeployBuildContext {\n /** The project root — where `rshono build` ran, and what a platform config file is written next to. */\n rootDir: string;\n /** `<root>/dist`. */\n distDir: string;\n /** The hashed client bundle, `<root>/dist/static` — served at `/_static`. */\n staticDir: string;\n /** The copy of the app's `public/`, or `null` when it has none. Served at the web root. */\n publicDir: string | null;\n /** Prerendered pages, `<root>/dist/ssg` — empty when the app has no `render: 'static'` routes. */\n ssgDir: string;\n}\n\n/**\n * The build-time half of a deploy target: which runtime module the bundle gets, how the server\n * compiler has to change to produce something the platform can run, and how the output is arranged\n * once it exists.\n *\n * The runtime half is {@link DeployRuntime}, in its own file because it is compiled *into* the app\n * bundle — this side only ever runs in the CLI.\n */\nexport interface DeployPreset {\n readonly name: DeployTarget;\n /**\n * The module `@rshono/deploy` resolves to, as a path relative to the framework's own `dist/`.\n * Slash-separated and split on use, so it stays a valid path on Windows too.\n */\n readonly runtimeModule: string;\n /** How to run what was just built, completing the \"build complete —\" line. */\n readonly deployHint: string;\n /**\n * Extra resolve conditions for the server bundle, most specific first.\n *\n * This is what picks the right build of React and the RSC runtime: both ship one per runtime behind\n * `workerd` / `deno` / `edge-light` / `node` conditions. Omit it to accept whatever the Rspack\n * target implies, which is correct for Node.\n */\n readonly resolveConditions?: readonly string[];\n /** browserslist-style targets for the server bundle's swc pass. Defaults to Node. */\n readonly syntaxTargets?: readonly string[];\n /**\n * Adjusts the generated server Rspack config for this platform — target, externals policy, output\n * shape. Mutates in place, and runs *before* the user's `rspack` hook so that hook keeps the last\n * word.\n */\n configureServer?(config: RspackOptions): void;\n /**\n * Arranges the finished build for the platform: assembles whatever directory layout it expects,\n * emits its config file, and prints how to deploy.\n *\n * Runs last — after both bundles, the `public/` copy and the prerender pass — so everything it\n * needs to move is already on disk.\n */\n finalize?(ctx: DeployBuildContext): Promise<void> | void;\n}\n\n/**\n * Node: a long-lived server process. The generated config is already this shape, so the preset has\n * nothing to contribute — the platform-specific settings still living in `builder/rspack-config.ts`\n * (`target: 'node'`, the externals policy, ESM chunk output) are the Node ones by default.\n */\nexport const NODE_PRESET: DeployPreset = {\n name: 'node',\n runtimeModule: 'deploy/node/runtime.js',\n deployHint: 'run `rshono start`',\n};\n\n/**\n * Cloudflare Workers: the host owns the process, the CDN owns the assets, and there is no filesystem.\n *\n * Every compiler setting here follows from `workerd` not being Node. Dependencies are bundled because\n * nothing resolves `node_modules` at runtime; `node:` and `cloudflare:` imports stay external because\n * the runtime provides them (`nodejs_compat`, which the scaffolded config enables for\n * `AsyncLocalStorage`); and async chunks are inlined because Wrangler's bundler cannot follow the\n * computed specifier Rspack's ESM chunk loader emits — a split bundle would deploy and then fail on\n * the first page render.\n */\nconst CLOUDFLARE_PRESET: DeployPreset = {\n name: 'cloudflare',\n runtimeModule: 'deploy/cloudflare/runtime.js',\n deployHint: 'deploy with `wrangler deploy`',\n resolveConditions: ['workerd'],\n syntaxTargets: ['chrome 120'],\n configureServer(config) {\n config.target = 'webworker';\n config.externalsType = 'module-import';\n config.externals = [/^(?:node|cloudflare):/];\n config.output = { ...config.output, asyncChunks: false };\n },\n finalize: finalizeCloudflareBuild,\n};\n\n/**\n * Bun and Deno: like Node, but the runtime opens the socket from the module's default export. Their\n * `node:` compatibility covers everything `server/` uses, so the bundle is Node's — only the handoff\n * differs, which is precisely the thing an app cannot do for itself.\n */\nconst BUN_PRESET: DeployPreset = {\n name: 'bun',\n runtimeModule: 'deploy/bun/runtime.js',\n deployHint: 'run `bun dist/server/main.mjs`',\n};\n\nconst DENO_PRESET: DeployPreset = {\n name: 'deno',\n runtimeModule: 'deploy/deno/runtime.js',\n deployHint: 'run `deno serve -A dist/server/main.mjs`',\n};\n\n/**\n * Vercel and Netlify: one Node function behind the platform's CDN, which serves the assets and reaches\n * the function only for a page. Both `finalize` hooks assemble the layout the platform uploads.\n */\nconst VERCEL_PRESET: DeployPreset = {\n name: 'vercel',\n runtimeModule: 'deploy/vercel/runtime.js',\n deployHint: 'deploy with `vercel deploy --prebuilt`',\n finalize: finalizeVercelBuild,\n};\n\nconst NETLIFY_PRESET: DeployPreset = {\n name: 'netlify',\n runtimeModule: 'deploy/netlify/runtime.js',\n deployHint: 'deploy with `netlify deploy --build=false --dir=.netlify/publish`',\n finalize: finalizeNetlifyBuild,\n};\n\n/** AWS Lambda behind a Function URL in `RESPONSE_STREAM` mode — the AWS shape that keeps streaming. */\nconst AWS_LAMBDA_PRESET: DeployPreset = {\n name: 'aws-lambda',\n runtimeModule: 'deploy/aws-lambda/runtime.js',\n deployHint: 'zip dist/ with the handler at dist/server/main.mjs',\n};\n\nconst PRESETS: Record<DeployTarget, DeployPreset> = {\n node: NODE_PRESET,\n cloudflare: CLOUDFLARE_PRESET,\n bun: BUN_PRESET,\n deno: DENO_PRESET,\n vercel: VERCEL_PRESET,\n netlify: NETLIFY_PRESET,\n 'aws-lambda': AWS_LAMBDA_PRESET,\n};\n\n/** Every target `deploy` accepts, for error messages and docs. */\nexport const DEPLOY_TARGETS = Object.keys(PRESETS) as DeployTarget[];\n\n/**\n * How to deploy what a given target built, or `null` for a name this rshono does not know — which a\n * `dist/` produced by a newer version can legitimately carry.\n */\nexport function deployHintFor(target: string): string | null {\n return (PRESETS as Record<string, DeployPreset | undefined>)[target]?.deployHint ?? null;\n}\n\n/** Where a deploy target can be named, in precedence order. */\nexport interface DeploySources {\n /** The `--deploy` flag. */\n flag?: string;\n /** The `RSHONO_DEPLOY` env var — for a CI job that deploys the same app to more than one place. */\n env?: string;\n /** {@link RSHonoConfig.deploy} from `rshono.config.ts`. */\n config?: string;\n}\n\n/**\n * Resolves the preset to build with: the flag wins over the environment, which wins over the config\n * file, which wins over the `node` default.\n *\n * Blank values are ignored at every level, so an unset-but-present `RSHONO_DEPLOY=` in a CI\n * environment falls through to the config file instead of failing the build.\n */\nexport function resolveDeployPreset(sources: DeploySources = {}): DeployPreset {\n const target = sources.flag?.trim() || sources.env?.trim() || sources.config?.trim();\n if (!target) return NODE_PRESET;\n\n const preset = (PRESETS as Record<string, DeployPreset | undefined>)[target];\n if (!preset) {\n throw new Error(`[rshono] unknown deploy target ${JSON.stringify(target)} — expected one of: ${DEPLOY_TARGETS.join(', ')}.`);\n }\n return preset;\n}\n"]}
1
+ {"version":3,"file":"presets.js","sourceRoot":"","sources":["../../src/deploy/presets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAkExD;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAiB;IACvC,IAAI,EAAE,MAAM;IACZ,aAAa,EAAE,wBAAwB;IACvC,UAAU,EAAE,oBAAoB;CACjC,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAiB;IACtC,IAAI,EAAE,YAAY;IAClB,aAAa,EAAE,8BAA8B;IAC7C,UAAU,EAAE,+BAA+B;IAC3C,WAAW,EAAE,IAAI;IACjB,iBAAiB,EAAE,CAAC,SAAS,CAAC;IAC9B,aAAa,EAAE,CAAC,YAAY,CAAC;IAC7B,eAAe,CAAC,MAAM;QACpB,MAAM,CAAC,MAAM,GAAG,WAAW,CAAC;QAC5B,MAAM,CAAC,aAAa,GAAG,eAAe,CAAC;QACvC,MAAM,CAAC,SAAS,GAAG,CAAC,uBAAuB,CAAC,CAAC;QAC7C,MAAM,CAAC,MAAM,GAAG,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC;IAC3D,CAAC;IACD,QAAQ,EAAE,uBAAuB;CAClC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,SAAS,kBAAkB,CAAC,MAAqB;IAC/C,MAAM,CAAC,SAAS,GAAG,EAAE,CAAC;AACxB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,aAAa,GAAiB;IAClC,IAAI,EAAE,QAAQ;IACd,aAAa,EAAE,0BAA0B;IACzC,UAAU,EAAE,wCAAwC;IACpD,eAAe,EAAE,kBAAkB;IACnC,QAAQ,EAAE,mBAAmB;CAC9B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,iBAAiB,GAAiB;IACtC,IAAI,EAAE,YAAY;IAClB,aAAa,EAAE,8BAA8B;IAC7C,UAAU,EAAE,oDAAoD;IAChE,eAAe,EAAE,kBAAkB;CACpC,CAAC;AAEF,MAAM,OAAO,GAAuC;IAClD,IAAI,EAAE,WAAW;IACjB,UAAU,EAAE,iBAAiB;IAC7B,MAAM,EAAE,aAAa;IACrB,YAAY,EAAE,iBAAiB;CAChC,CAAC;AAEF,kEAAkE;AAClE,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAmB,CAAC;AAErE;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,MAAc;IAC/B,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAsB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtF,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,OAAO,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU,IAAI,IAAI,CAAC;AAC/C,CAAC;AAYD;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAO,GAAkB,EAAE;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC;IACrF,IAAI,CAAC,MAAM;QAAE,OAAO,WAAW,CAAC;IAEhC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,kCAAkC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,uBAAuB,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/H,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { RspackOptions } from '@rspack/core';\nimport { finalizeCloudflareBuild } from './cloudflare/build.js';\nimport type { Route } from '../router.js';\nimport type { DeployTarget } from './contract.js';\nimport { finalizeVercelBuild } from './vercel/build.js';\n\n/** What a preset's {@link DeployPreset.finalize} is told about the build it is arranging. */\nexport interface DeployBuildContext {\n /** The project root — where `rshono build` ran, and what a platform config file is written next to. */\n rootDir: string;\n /** `<root>/dist`. */\n distDir: string;\n /** The hashed client bundle, `<root>/dist/static` — served at `/_static`. */\n staticDir: string;\n /** The copy of the app's `public/`, or `null` when it has none. Served at the web root. */\n publicDir: string | null;\n /** Prerendered pages, `<root>/dist/ssg` — empty when the app has no `render: 'static'` routes. */\n ssgDir: string;\n /**\n * The app's route table, as the bundle validated it. Read by the two CDN presets: their platform serves\n * the static output before the app is invoked, so a `public/` file that lands on a route's path answers\n * instead of it — see `publicRouteCollisions`.\n */\n routes: readonly Route[];\n}\n\n/**\n * The build-time half of a deploy target: which runtime module the bundle gets, how the server compiler has\n * to change to produce something the platform can run, and how the output is arranged once it exists.\n *\n * The runtime half is `DeployRuntime`, in its own file because it is compiled *into* the app bundle — this\n * side only ever runs in the CLI.\n */\nexport interface DeployPreset {\n readonly name: DeployTarget;\n /**\n * The module `@rshono/deploy` resolves to, as a path relative to the framework's own `dist/`.\n * Slash-separated and split on use, so it stays a valid path on Windows too.\n */\n readonly runtimeModule: string;\n /** How to run what was just built, completing the \"build complete —\" line. */\n readonly deployHint: string;\n /**\n * `true` where the platform hands per-request bindings to `app.fetch(request, env)` — Workers, and\n * nowhere else. It gates the `getRequestContext().env` merge, because every other target passes its own\n * private handles in that argument (`{ incoming, outgoing }` on Node, the whole invocation on Lambda) and\n * merging those would put a live socket, or a request's headers and cookies, behind a name typed\n * `string | undefined`.\n */\n readonly envBindings?: boolean;\n /**\n * Extra resolve conditions for the server bundle, most specific first — what picks the right build of React\n * and the RSC runtime, both of which ship one per runtime. Omit to accept whatever the Rspack target\n * implies, which is correct for Node.\n */\n readonly resolveConditions?: readonly string[];\n /** browserslist-style targets for the server bundle's swc pass. Defaults to Node. */\n readonly syntaxTargets?: readonly string[];\n /**\n * Adjusts the generated server Rspack config for this platform — target, externals policy, output shape.\n * Mutates in place, and runs before the user's `rspack` hook so that hook keeps the last word.\n */\n configureServer?(config: RspackOptions): void;\n /**\n * Arranges the finished build for the platform: assembles the directory layout it expects, emits its config\n * file, and prints how to deploy. Runs last, so everything it needs to move is already on disk.\n */\n finalize?(ctx: DeployBuildContext): Promise<void> | void;\n}\n\n/**\n * Node: a long-lived server process. The generated config is already this shape — `target: 'node'`, the\n * externals policy and ESM chunk output are all defaults — so the preset has nothing to contribute.\n */\nexport const NODE_PRESET: DeployPreset = {\n name: 'node',\n runtimeModule: 'deploy/node/runtime.js',\n deployHint: 'run `rshono start`',\n};\n\n/**\n * Cloudflare Workers: the host owns the process, the CDN owns the assets, and there is no filesystem.\n *\n * Every setting here follows from `workerd` not being Node. Dependencies are bundled because nothing resolves\n * `node_modules` at runtime; `node:` and `cloudflare:` imports stay external because the runtime provides\n * them under `nodejs_compat`; and async chunks are inlined because Wrangler's bundler cannot follow the\n * computed specifier Rspack's ESM chunk loader emits — a split bundle deploys and then fails on first render.\n */\nconst CLOUDFLARE_PRESET: DeployPreset = {\n name: 'cloudflare',\n runtimeModule: 'deploy/cloudflare/runtime.js',\n deployHint: 'deploy with `wrangler deploy`',\n envBindings: true,\n resolveConditions: ['workerd'],\n syntaxTargets: ['chrome 120'],\n configureServer(config) {\n config.target = 'webworker';\n config.externalsType = 'module-import';\n config.externals = [/^(?:node|cloudflare):/];\n config.output = { ...config.output, asyncChunks: false };\n },\n finalize: finalizeCloudflareBuild,\n};\n\n/**\n * Bundles the app's dependencies into the server output instead of importing them from `node_modules`.\n *\n * The generated config externalizes them, which is right where the bundle runs *inside* the project — but a\n * serverless function is uploaded as a directory rather than installed into one, so nothing resolves\n * `node_modules` at request time and an externalized `import 'some-package'` is a cold start that dies on\n * `ERR_MODULE_NOT_FOUND`. Node's own builtins stay external either way, through the `node` externals preset\n * that `target: 'node'` already enables.\n *\n * The cost is a dependency that cannot be bundled — a native addon, or one that reads its own files off disk\n * relative to `__dirname`. Those now fail the build rather than the deploy, which is the same constraint\n * `cloudflare` has always had; the `rspack` hook in `rshono.config.ts` is the way out.\n */\nfunction bundleDependencies(config: RspackOptions): void {\n config.externals = [];\n}\n\n/**\n * Vercel: one Node function behind the platform's CDN, which serves the assets and reaches the function only\n * for a page. `finalize` assembles the Build Output API layout the platform uploads — including the\n * `supportsResponseStreaming` flag, without which Vercel buffers the whole response and silently undoes\n * streamed SSR.\n *\n * Only `dist/server` is uploaded with the function, which is why dependencies are bundled: see\n * {@link bundleDependencies}.\n */\nconst VERCEL_PRESET: DeployPreset = {\n name: 'vercel',\n runtimeModule: 'deploy/vercel/runtime.js',\n deployHint: 'deploy with `vercel deploy --prebuilt`',\n configureServer: bundleDependencies,\n finalize: finalizeVercelBuild,\n};\n\n/**\n * AWS Lambda behind a Function URL in `RESPONSE_STREAM` mode — the AWS shape that keeps streaming. The\n * runtime wraps the app in `awslambda.streamifyResponse`; the buffered alternative would deploy fine and then\n * hold every page until its last byte rendered.\n *\n * The deployment package is `dist/` and nothing else, which is why dependencies are bundled: see\n * {@link bundleDependencies}.\n */\nconst AWS_LAMBDA_PRESET: DeployPreset = {\n name: 'aws-lambda',\n runtimeModule: 'deploy/aws-lambda/runtime.js',\n deployHint: 'zip dist/ with the handler at dist/server/main.mjs',\n configureServer: bundleDependencies,\n};\n\nconst PRESETS: Record<DeployTarget, DeployPreset> = {\n node: NODE_PRESET,\n cloudflare: CLOUDFLARE_PRESET,\n vercel: VERCEL_PRESET,\n 'aws-lambda': AWS_LAMBDA_PRESET,\n};\n\n/** Every target `deploy` accepts, for error messages and docs. */\nexport const DEPLOY_TARGETS = Object.keys(PRESETS) as DeployTarget[];\n\n/**\n * `PRESETS[target]` for a target that really is one.\n *\n * `Object.hasOwn` rather than a bare bracket access, which resolves every `Object.prototype` key —\n * `constructor`, `__proto__`, `toString` — to an inherited value that then passes a truthiness guard. A typo\n * that happens to be one of those used to reach the builder and die on `preset.runtimeModule.split('/')`\n * instead of getting the message written for an unknown target.\n */\nfunction presetFor(target: string): DeployPreset | undefined {\n return Object.hasOwn(PRESETS, target) ? PRESETS[target as DeployTarget] : undefined;\n}\n\n/**\n * How to deploy what a given target built, or `null` for a name this rshono does not know — which a `dist/`\n * from a newer version can legitimately carry.\n */\nexport function deployHintFor(target: string): string | null {\n return presetFor(target)?.deployHint ?? null;\n}\n\n/** Where a deploy target can be named, in precedence order. */\nexport interface DeploySources {\n /** The `--deploy` flag. */\n flag?: string;\n /** The `RSHONO_DEPLOY` env var — for a CI job that deploys the same app to more than one place. */\n env?: string;\n /** The `deploy` field in `rshono.config.ts`. */\n config?: string;\n}\n\n/**\n * Resolves the preset to build with: the flag wins over the environment, which wins over the config file,\n * which wins over the `node` default. Blank values are ignored at every level, so an unset-but-present\n * `RSHONO_DEPLOY=` in CI falls through rather than failing the build.\n */\nexport function resolveDeployPreset(sources: DeploySources = {}): DeployPreset {\n const target = sources.flag?.trim() || sources.env?.trim() || sources.config?.trim();\n if (!target) return NODE_PRESET;\n\n const preset = presetFor(target);\n if (!preset) {\n throw new Error(`[rshono] unknown deploy target ${JSON.stringify(target)} — expected one of: ${DEPLOY_TARGETS.join(', ')}.`);\n }\n return preset;\n}\n"]}