@rshono/core 1.0.0-rc.1 → 1.0.0-rc.10

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 (143) hide show
  1. package/README.md +202 -159
  2. package/dist/builder/page-files.d.ts.map +1 -1
  3. package/dist/builder/page-files.js +7 -3
  4. package/dist/builder/page-files.js.map +1 -1
  5. package/dist/builder/public-env.d.ts +6 -0
  6. package/dist/builder/public-env.d.ts.map +1 -1
  7. package/dist/builder/public-env.js +6 -0
  8. package/dist/builder/public-env.js.map +1 -1
  9. package/dist/builder/rspack-config.d.ts +3 -3
  10. package/dist/builder/rspack-config.d.ts.map +1 -1
  11. package/dist/builder/rspack-config.js +18 -21
  12. package/dist/builder/rspack-config.js.map +1 -1
  13. package/dist/cli/build.d.ts +2 -2
  14. package/dist/cli/build.js.map +1 -1
  15. package/dist/cli/dev.d.ts +2 -2
  16. package/dist/cli/dev.d.ts.map +1 -1
  17. package/dist/cli/dev.js +63 -35
  18. package/dist/cli/dev.js.map +1 -1
  19. package/dist/cli/index.js +10 -10
  20. package/dist/cli/index.js.map +1 -1
  21. package/dist/config.d.ts +58 -70
  22. package/dist/config.d.ts.map +1 -1
  23. package/dist/config.js +17 -1
  24. package/dist/config.js.map +1 -1
  25. package/dist/deploy/cloudflare/build.js +1 -1
  26. package/dist/deploy/cloudflare/build.js.map +1 -1
  27. package/dist/deploy/cloudflare/runtime.d.ts.map +1 -1
  28. package/dist/deploy/cloudflare/runtime.js +7 -37
  29. package/dist/deploy/cloudflare/runtime.js.map +1 -1
  30. package/dist/deploy/contract.d.ts +22 -16
  31. package/dist/deploy/contract.d.ts.map +1 -1
  32. package/dist/deploy/contract.js.map +1 -1
  33. package/dist/deploy/filesystem.d.ts +1 -1
  34. package/dist/deploy/filesystem.d.ts.map +1 -1
  35. package/dist/deploy/filesystem.js +7 -10
  36. package/dist/deploy/filesystem.js.map +1 -1
  37. package/dist/deploy/node/runtime.d.ts +4 -0
  38. package/dist/deploy/node/runtime.d.ts.map +1 -1
  39. package/dist/deploy/node/runtime.js +24 -3
  40. package/dist/deploy/node/runtime.js.map +1 -1
  41. package/dist/deploy/presets.d.ts +3 -3
  42. package/dist/deploy/presets.d.ts.map +1 -1
  43. package/dist/deploy/presets.js +13 -30
  44. package/dist/deploy/presets.js.map +1 -1
  45. package/dist/deploy/vercel/runtime.d.ts.map +1 -1
  46. package/dist/deploy/vercel/runtime.js +0 -3
  47. package/dist/deploy/vercel/runtime.js.map +1 -1
  48. package/dist/index.d.ts +6 -9
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +8 -3
  51. package/dist/index.js.map +1 -1
  52. package/dist/router.d.ts +85 -48
  53. package/dist/router.d.ts.map +1 -1
  54. package/dist/router.js +4 -6
  55. package/dist/router.js.map +1 -1
  56. package/dist/runtime/boundaries.d.ts +39 -25
  57. package/dist/runtime/boundaries.d.ts.map +1 -1
  58. package/dist/runtime/boundaries.js +22 -17
  59. package/dist/runtime/boundaries.js.map +1 -1
  60. package/dist/runtime/client.d.ts +9 -8
  61. package/dist/runtime/client.d.ts.map +1 -1
  62. package/dist/runtime/client.js +9 -8
  63. package/dist/runtime/client.js.map +1 -1
  64. package/dist/runtime/context.d.ts +216 -70
  65. package/dist/runtime/context.d.ts.map +1 -1
  66. package/dist/runtime/context.js +299 -93
  67. package/dist/runtime/context.js.map +1 -1
  68. package/dist/runtime/control.d.ts.map +1 -1
  69. package/dist/runtime/control.js +7 -0
  70. package/dist/runtime/control.js.map +1 -1
  71. package/dist/runtime/dev-protocol.d.ts.map +1 -1
  72. package/dist/runtime/dev-protocol.js.map +1 -1
  73. package/dist/runtime/entry.client.d.ts +4 -0
  74. package/dist/runtime/entry.client.d.ts.map +1 -1
  75. package/dist/runtime/entry.client.js +190 -154
  76. package/dist/runtime/entry.client.js.map +1 -1
  77. package/dist/runtime/entry.rsc.d.ts.map +1 -1
  78. package/dist/runtime/entry.rsc.js +145 -159
  79. package/dist/runtime/entry.rsc.js.map +1 -1
  80. package/dist/runtime/entry.ssr.d.ts +9 -1
  81. package/dist/runtime/entry.ssr.d.ts.map +1 -1
  82. package/dist/runtime/entry.ssr.js +36 -18
  83. package/dist/runtime/entry.ssr.js.map +1 -1
  84. package/dist/runtime/flight-inject.d.ts +31 -0
  85. package/dist/runtime/flight-inject.d.ts.map +1 -0
  86. package/dist/runtime/flight-inject.js +221 -0
  87. package/dist/runtime/flight-inject.js.map +1 -0
  88. package/dist/runtime/navigation.d.ts +20 -38
  89. package/dist/runtime/navigation.d.ts.map +1 -1
  90. package/dist/runtime/navigation.js +10 -53
  91. package/dist/runtime/navigation.js.map +1 -1
  92. package/dist/runtime/request.d.ts +6 -0
  93. package/dist/runtime/request.d.ts.map +1 -1
  94. package/dist/runtime/request.js +8 -0
  95. package/dist/runtime/request.js.map +1 -1
  96. package/dist/runtime/server.d.ts +7 -12
  97. package/dist/runtime/server.d.ts.map +1 -1
  98. package/dist/runtime/server.js +15 -12
  99. package/dist/runtime/server.js.map +1 -1
  100. package/dist/server/headers.d.ts +9 -9
  101. package/dist/server/headers.js +9 -9
  102. package/dist/server/headers.js.map +1 -1
  103. package/dist/server/load-config.d.ts +2 -2
  104. package/dist/server/load-config.d.ts.map +1 -1
  105. package/dist/server/load-config.js +22 -11
  106. package/dist/server/load-config.js.map +1 -1
  107. package/dist/server/prerendered.d.ts +43 -15
  108. package/dist/server/prerendered.d.ts.map +1 -1
  109. package/dist/server/prerendered.js +47 -0
  110. package/dist/server/prerendered.js.map +1 -1
  111. package/dist/server/server-config.d.ts +13 -39
  112. package/dist/server/server-config.d.ts.map +1 -1
  113. package/dist/server/server-config.js +5 -74
  114. package/dist/server/server-config.js.map +1 -1
  115. package/dist/server/ssg.d.ts +2 -2
  116. package/dist/server/ssg.d.ts.map +1 -1
  117. package/dist/server/ssg.js +21 -34
  118. package/dist/server/ssg.js.map +1 -1
  119. package/package.json +13 -16
  120. package/dist/deploy/bun/runtime.d.ts +0 -11
  121. package/dist/deploy/bun/runtime.d.ts.map +0 -1
  122. package/dist/deploy/bun/runtime.js +0 -22
  123. package/dist/deploy/bun/runtime.js.map +0 -1
  124. package/dist/deploy/deno/runtime.d.ts +0 -11
  125. package/dist/deploy/deno/runtime.d.ts.map +0 -1
  126. package/dist/deploy/deno/runtime.js +0 -16
  127. package/dist/deploy/deno/runtime.js.map +0 -1
  128. package/dist/deploy/listen.d.ts +0 -20
  129. package/dist/deploy/listen.d.ts.map +0 -1
  130. package/dist/deploy/listen.js +0 -24
  131. package/dist/deploy/listen.js.map +0 -1
  132. package/dist/deploy/netlify/build.d.ts +0 -8
  133. package/dist/deploy/netlify/build.d.ts.map +0 -1
  134. package/dist/deploy/netlify/build.js +0 -52
  135. package/dist/deploy/netlify/build.js.map +0 -1
  136. package/dist/deploy/netlify/runtime.d.ts +0 -13
  137. package/dist/deploy/netlify/runtime.d.ts.map +0 -1
  138. package/dist/deploy/netlify/runtime.js +0 -24
  139. package/dist/deploy/netlify/runtime.js.map +0 -1
  140. package/dist/server/compress.d.ts +0 -15
  141. package/dist/server/compress.d.ts.map +0 -1
  142. package/dist/server/compress.js +0 -76
  143. package/dist/server/compress.js.map +0 -1
package/dist/config.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { RspackOptions } from '@rspack/core';
2
2
  import type { DeployTarget } from './deploy/contract.js';
3
- /** Which of the two Rspack compilers the {@link RSHonoConfig.rspack} hook is being called for. */
3
+ /** Which of the two Rspack compilers the {@link RshonoConfig.rspack} hook is being called for. */
4
4
  export interface RspackHookContext {
5
5
  /** `true` for the server (`target: node`) bundle, `false` for the client (`target: web`) bundle. */
6
6
  isServer: boolean;
@@ -11,18 +11,23 @@ export interface RspackHookContext {
11
11
  * Project configuration for rshono, default-exported from `rshono.config.ts` at the project root
12
12
  * (`.js` / `.mjs` also work). Every field is optional; omit the file entirely to accept all defaults.
13
13
  *
14
+ * Deliberately small: it holds what only the *build* can decide. Everything that is a per-request
15
+ * concern — CSRF, CSP, the body cap — is Hono middleware in `src/server.ts` instead, because Hono
16
+ * already ships all of it and a config field would only be a worse way to spell the same call.
17
+ *
14
18
  * @example
15
19
  * ```ts
16
20
  * import { defineConfig } from '@rshono/core';
17
21
  *
18
22
  * export default defineConfig({
19
- * csp: true,
20
- * bodySizeLimit: '4mb',
21
- * allowedOrigins: ['https://admin.example.com'],
23
+ * deploy: 'cloudflare',
24
+ * siteUrl: 'https://example.com',
22
25
  * });
23
26
  * ```
27
+ *
28
+ * @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}
24
29
  */
25
- export interface RSHonoConfig {
30
+ export interface RshonoConfig {
26
31
  /**
27
32
  * The hosting platform `rshono build` targets. Default `'node'` — a long-lived server process, for
28
33
  * a VPS, a container or anywhere else you run `rshono start`.
@@ -30,92 +35,75 @@ export interface RSHonoConfig {
30
35
  * Overridden by the `--deploy` flag or the `RSHONO_DEPLOY` env var, so one config can still be
31
36
  * built for more than one place. `rshono dev` ignores it entirely and always runs the Node dev
32
37
  * server.
38
+ *
39
+ * @see {@link https://www.rshono.com/docs/deployment | Docs — deployment}
33
40
  */
34
41
  deploy?: DeployTarget;
35
42
  /**
36
43
  * The public origin the site is served from, e.g. `'https://example.com'`.
37
44
  *
38
- * Only used when prerendering `render: 'static'` routes. A prerendered page is one fixed file
39
- * handed to everyone, so any absolute URL inside it has to be decided at build time — there is no
40
- * request to read a `Host` from. That is what a page's `url` prop is, so without this a static
41
- * page bakes in `http://localhost` wherever it builds a canonical tag, an absolute link or an
42
- * `og:url`. Dynamic routes are unaffected: they resolve the URL per request.
45
+ * Only used when prerendering `render: 'static'` routes: they are rendered once at build time, with
46
+ * no request to read a `Host` from, so a page's `url` prop falls back to `http://localhost` without
47
+ * this — and that is what its canonical tag, absolute links and `og:url` get baked with. Dynamic
48
+ * routes resolve the URL per request and are unaffected.
43
49
  *
44
- * The origin is what's used; a path is rejected rather than silently dropped.
50
+ * Must be a bare origin; a path is rejected rather than silently dropped.
51
+ *
52
+ * @see {@link https://www.rshono.com/docs/configuration#siteurl | Docs — siteUrl}
45
53
  */
46
54
  siteUrl?: string;
47
- /** Default port for `dev` / `start`. Overridden by the `--port` flag or the `PORT` env var. Default `3000`. */
48
- port?: number;
49
- /** Bind address for `start`. Overridden by the `HOST` env var. Default `'0.0.0.0'`. */
50
- host?: string;
51
55
  /**
52
56
  * Honour `X-Forwarded-Host` / `X-Forwarded-Proto` when resolving the browser-facing request
53
- * URL (`getContext().url`, a page's `url` prop, and the origin the CSRF check compares against).
57
+ * URL — `getRequestContext().url` and a page's `url` prop.
58
+ *
59
+ * Middleware in `src/server.ts` is handed Hono's `c` and so reads `c.req.url`, the *internal*
60
+ * address, whatever this says. Hono's `csrf()` is the one to watch: behind a proxy that rewrites
61
+ * `Host`, give it the public origin explicitly (`csrf({ origin: 'https://example.com' })`) rather
62
+ * than leaving it to compare against an address the browser never used.
54
63
  *
55
- * **Off by default, and leave it off unless a proxy you control sets those headers**, because
56
- * any client can send them: with it on and nothing stripping them at the edge, one request can
57
- * point every absolute URL your app builds at an attacker's host (and poison a shared cache).
58
- * Turn it on when you terminate TLS or rewrite `Host` at a reverse proxy / load balancer.
59
- * Always `true` under `rshono dev`, where the framework's own proxy sets them and binds to
60
- * localhost. Default `false`.
64
+ * **Off by default, and leave it off unless a proxy you control sets those headers**: any client
65
+ * can send them, so with nothing stripping them at the edge one request can point every absolute
66
+ * URL the app builds at an attacker's host (and poison a shared cache). Turn it on when you
67
+ * terminate TLS or rewrite `Host` at a reverse proxy / load balancer. Always `true` under
68
+ * `rshono dev`, where the framework's own proxy sets them and binds to localhost. Default `false`.
69
+ *
70
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host | MDN — X-Forwarded-Host}
71
+ * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}
61
72
  */
62
73
  trustProxy?: boolean;
63
74
  /**
64
- * CSRF origin check on server-action POSTs — rejects a cross-origin request with 403.
65
- * Turn off only behind a gateway that already enforces it. Default `true`.
66
- */
67
- checkOrigin?: boolean;
68
- /**
69
- * Extra origins allowed to post server actions, in addition to the app's own origin.
70
- * Accepts full origins or bare hosts, e.g. `['https://admin.example.com', 'localhost:4000']`.
71
- */
72
- allowedOrigins?: string[];
73
- /**
74
- * Send a strict per-request-nonce `Content-Security-Policy` with every HTML document.
75
- * While enabled, `render: 'static'` routes render per request (a prerendered file can't carry a
76
- * per-request nonce). Default `false`.
77
- */
78
- csp?: boolean;
79
- /**
80
- * Directives merged over the built-in {@link csp} policy, which is deliberately strict
81
- * (`default-src 'self'`, no framing, no plugins) and so blocks third-party images, fonts and
82
- * API hosts until you widen it here. Set a directive to `''` to drop it entirely.
83
- *
84
- * The per-request nonce is always appended to `script-src`, whatever you put there.
85
- * Ignored unless `csp` is `true`.
75
+ * Escape hatch: mutate the generated Rspack config just before it's compiled. Called once per
76
+ * compiler — inspect {@link RspackHookContext.isServer} to tell them apart. Mutate `config` in
77
+ * place and return nothing, or return a replacement.
86
78
  *
87
79
  * @example
88
80
  * ```ts
89
- * cspDirectives: {
90
- * 'img-src': "'self' data: https://images.example.com",
91
- * 'font-src': "'self' https://fonts.gstatic.com",
92
- * 'frame-ancestors': "'self'",
81
+ * rspack(config, { isServer }) {
82
+ * config.module?.rules?.push({ test: /\.svg$/, type: 'asset/source' });
93
83
  * }
94
84
  * ```
95
- */
96
- cspDirectives?: Record<string, string>;
97
- /**
98
- * Max server-action request body before it's rejected with 413 — a memory-exhaustion guard.
99
- * A number is bytes; a string carries a unit (`'512kb'`, `'4mb'`); `false` (or `0`) disables the cap.
100
- * Default `'1mb'`.
101
- */
102
- bodySizeLimit?: string | number | false;
103
- /** Deadline in milliseconds for a single page render (flight + SSR). Default `10000`. */
104
- renderTimeout?: number;
105
- /**
106
- * Gzip compressible responses (HTML, flight payloads, JSON, CSS, JS). Streaming-safe — each
107
- * chunk the renderer flushes is flushed on the wire too. Default `true`.
108
85
  *
109
- * Set `false` behind a proxy or CDN that already compresses, to avoid doing the work twice.
110
- */
111
- compress?: boolean;
112
- /**
113
- * Escape hatch: mutate the generated Rspack config just before it's compiled. Called once per
114
- * compiler — inspect {@link RspackHookContext.isServer} to tell them apart. Mutate `config` in
115
- * place and return nothing, or return a replacement.
86
+ * @see {@link https://rspack.rs/config/ | Rspack — configuration reference}
87
+ * @see {@link https://www.rshono.com/docs/configuration#the-rspack-hook | Docs — the rspack hook}
116
88
  */
117
89
  rspack?: (config: RspackOptions, ctx: RspackHookContext) => RspackOptions | void;
118
90
  }
119
- /** Identity helper that types a config object — gives editor autocomplete without an explicit annotation. */
120
- export declare function defineConfig(config: RSHonoConfig): RSHonoConfig;
91
+ /**
92
+ * Identity helper that types a config object — gives editor autocomplete without an explicit
93
+ * annotation. Default-export the result from `rshono.config.ts`.
94
+ *
95
+ * @param config - The project's {@link RshonoConfig}; every field is optional.
96
+ * @returns The config, unchanged and fully typed.
97
+ *
98
+ * @example
99
+ * ```ts
100
+ * // rshono.config.ts
101
+ * import { defineConfig } from '@rshono/core';
102
+ *
103
+ * export default defineConfig({ deploy: 'cloudflare', siteUrl: 'https://example.com' });
104
+ * ```
105
+ *
106
+ * @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}
107
+ */
108
+ export declare function defineConfig(config: RshonoConfig): RshonoConfig;
121
109
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+GAA+G;IAC/G,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B;;;;OAIG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;IACd;;;;;;;;;;;;;;;;OAgBG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,CAAC;IACxC,yFAAyF;IACzF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,iBAAiB,KAAK,aAAa,GAAG,IAAI,CAAC;CAClF;AAED,6GAA6G;AAC7G,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE/D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzD,kGAAkG;AAClG,MAAM,WAAW,iBAAiB;IAChC,oGAAoG;IACpG,QAAQ,EAAE,OAAO,CAAC;IAClB,+DAA+D;IAC/D,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;;OASG;IACH,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;;;;;;;;;;OAiBG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,iBAAiB,KAAK,aAAa,GAAG,IAAI,CAAC;CAClF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE/D"}
package/dist/config.js CHANGED
@@ -1,4 +1,20 @@
1
- /** Identity helper that types a config object — gives editor autocomplete without an explicit annotation. */
1
+ /**
2
+ * Identity helper that types a config object — gives editor autocomplete without an explicit
3
+ * annotation. Default-export the result from `rshono.config.ts`.
4
+ *
5
+ * @param config - The project's {@link RshonoConfig}; every field is optional.
6
+ * @returns The config, unchanged and fully typed.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // rshono.config.ts
11
+ * import { defineConfig } from '@rshono/core';
12
+ *
13
+ * export default defineConfig({ deploy: 'cloudflare', siteUrl: 'https://example.com' });
14
+ * ```
15
+ *
16
+ * @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}
17
+ */
2
18
  export function defineConfig(config) {
3
19
  return config;
4
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAyHA,6GAA6G;AAC7G,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { RspackOptions } from '@rspack/core';\nimport type { DeployTarget } from './deploy/contract.js';\n\n/** Which of the two Rspack compilers the {@link RSHonoConfig.rspack} hook is being called for. */\nexport interface RspackHookContext {\n /** `true` for the server (`target: node`) bundle, `false` for the client (`target: web`) bundle. */\n isServer: boolean;\n /** `true` under `rshono dev`, `false` under `rshono build`. */\n isDev: boolean;\n}\n\n/**\n * Project configuration for rshono, default-exported from `rshono.config.ts` at the project root\n * (`.js` / `.mjs` also work). Every field is optional; omit the file entirely to accept all defaults.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@rshono/core';\n *\n * export default defineConfig({\n * csp: true,\n * bodySizeLimit: '4mb',\n * allowedOrigins: ['https://admin.example.com'],\n * });\n * ```\n */\nexport interface RSHonoConfig {\n /**\n * The hosting platform `rshono build` targets. Default `'node'` — a long-lived server process, for\n * a VPS, a container or anywhere else you run `rshono start`.\n *\n * Overridden by the `--deploy` flag or the `RSHONO_DEPLOY` env var, so one config can still be\n * built for more than one place. `rshono dev` ignores it entirely and always runs the Node dev\n * server.\n */\n deploy?: DeployTarget;\n /**\n * The public origin the site is served from, e.g. `'https://example.com'`.\n *\n * Only used when prerendering `render: 'static'` routes. A prerendered page is one fixed file\n * handed to everyone, so any absolute URL inside it has to be decided at build time — there is no\n * request to read a `Host` from. That is what a page's `url` prop is, so without this a static\n * page bakes in `http://localhost` wherever it builds a canonical tag, an absolute link or an\n * `og:url`. Dynamic routes are unaffected: they resolve the URL per request.\n *\n * The origin is what's used; a path is rejected rather than silently dropped.\n */\n siteUrl?: string;\n /** Default port for `dev` / `start`. Overridden by the `--port` flag or the `PORT` env var. Default `3000`. */\n port?: number;\n /** Bind address for `start`. Overridden by the `HOST` env var. Default `'0.0.0.0'`. */\n host?: string;\n /**\n * Honour `X-Forwarded-Host` / `X-Forwarded-Proto` when resolving the browser-facing request\n * URL (`getContext().url`, a page's `url` prop, and the origin the CSRF check compares against).\n *\n * **Off by default, and leave it off unless a proxy you control sets those headers**, because\n * any client can send them: with it on and nothing stripping them at the edge, one request can\n * point every absolute URL your app builds at an attacker's host (and poison a shared cache).\n * Turn it on when you terminate TLS or rewrite `Host` at a reverse proxy / load balancer.\n * Always `true` under `rshono dev`, where the framework's own proxy sets them and binds to\n * localhost. Default `false`.\n */\n trustProxy?: boolean;\n /**\n * CSRF origin check on server-action POSTs — rejects a cross-origin request with 403.\n * Turn off only behind a gateway that already enforces it. Default `true`.\n */\n checkOrigin?: boolean;\n /**\n * Extra origins allowed to post server actions, in addition to the app's own origin.\n * Accepts full origins or bare hosts, e.g. `['https://admin.example.com', 'localhost:4000']`.\n */\n allowedOrigins?: string[];\n /**\n * Send a strict per-request-nonce `Content-Security-Policy` with every HTML document.\n * While enabled, `render: 'static'` routes render per request (a prerendered file can't carry a\n * per-request nonce). Default `false`.\n */\n csp?: boolean;\n /**\n * Directives merged over the built-in {@link csp} policy, which is deliberately strict\n * (`default-src 'self'`, no framing, no plugins) and so blocks third-party images, fonts and\n * API hosts until you widen it here. Set a directive to `''` to drop it entirely.\n *\n * The per-request nonce is always appended to `script-src`, whatever you put there.\n * Ignored unless `csp` is `true`.\n *\n * @example\n * ```ts\n * cspDirectives: {\n * 'img-src': \"'self' data: https://images.example.com\",\n * 'font-src': \"'self' https://fonts.gstatic.com\",\n * 'frame-ancestors': \"'self'\",\n * }\n * ```\n */\n cspDirectives?: Record<string, string>;\n /**\n * Max server-action request body before it's rejected with 413 — a memory-exhaustion guard.\n * A number is bytes; a string carries a unit (`'512kb'`, `'4mb'`); `false` (or `0`) disables the cap.\n * Default `'1mb'`.\n */\n bodySizeLimit?: string | number | false;\n /** Deadline in milliseconds for a single page render (flight + SSR). Default `10000`. */\n renderTimeout?: number;\n /**\n * Gzip compressible responses (HTML, flight payloads, JSON, CSS, JS). Streaming-safe — each\n * chunk the renderer flushes is flushed on the wire too. Default `true`.\n *\n * Set `false` behind a proxy or CDN that already compresses, to avoid doing the work twice.\n */\n compress?: boolean;\n /**\n * Escape hatch: mutate the generated Rspack config just before it's compiled. Called once per\n * compiler — inspect {@link RspackHookContext.isServer} to tell them apart. Mutate `config` in\n * place and return nothing, or return a replacement.\n */\n rspack?: (config: RspackOptions, ctx: RspackHookContext) => RspackOptions | void;\n}\n\n/** Identity helper that types a config object — gives editor autocomplete without an explicit annotation. */\nexport function defineConfig(config: RSHonoConfig): RSHonoConfig {\n return config;\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AA6FA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,MAAoB;IAC/C,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { RspackOptions } from '@rspack/core';\nimport type { DeployTarget } from './deploy/contract.js';\n\n/** Which of the two Rspack compilers the {@link RshonoConfig.rspack} hook is being called for. */\nexport interface RspackHookContext {\n /** `true` for the server (`target: node`) bundle, `false` for the client (`target: web`) bundle. */\n isServer: boolean;\n /** `true` under `rshono dev`, `false` under `rshono build`. */\n isDev: boolean;\n}\n\n/**\n * Project configuration for rshono, default-exported from `rshono.config.ts` at the project root\n * (`.js` / `.mjs` also work). Every field is optional; omit the file entirely to accept all defaults.\n *\n * Deliberately small: it holds what only the *build* can decide. Everything that is a per-request\n * concern — CSRF, CSP, the body cap — is Hono middleware in `src/server.ts` instead, because Hono\n * already ships all of it and a config field would only be a worse way to spell the same call.\n *\n * @example\n * ```ts\n * import { defineConfig } from '@rshono/core';\n *\n * export default defineConfig({\n * deploy: 'cloudflare',\n * siteUrl: 'https://example.com',\n * });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}\n */\nexport interface RshonoConfig {\n /**\n * The hosting platform `rshono build` targets. Default `'node'` — a long-lived server process, for\n * a VPS, a container or anywhere else you run `rshono start`.\n *\n * Overridden by the `--deploy` flag or the `RSHONO_DEPLOY` env var, so one config can still be\n * built for more than one place. `rshono dev` ignores it entirely and always runs the Node dev\n * server.\n *\n * @see {@link https://www.rshono.com/docs/deployment | Docs — deployment}\n */\n deploy?: DeployTarget;\n /**\n * The public origin the site is served from, e.g. `'https://example.com'`.\n *\n * Only used when prerendering `render: 'static'` routes: they are rendered once at build time, with\n * no request to read a `Host` from, so a page's `url` prop falls back to `http://localhost` without\n * this — and that is what its canonical tag, absolute links and `og:url` get baked with. Dynamic\n * routes resolve the URL per request and are unaffected.\n *\n * Must be a bare origin; a path is rejected rather than silently dropped.\n *\n * @see {@link https://www.rshono.com/docs/configuration#siteurl | Docs — siteUrl}\n */\n siteUrl?: string;\n /**\n * Honour `X-Forwarded-Host` / `X-Forwarded-Proto` when resolving the browser-facing request\n * URL — `getRequestContext().url` and a page's `url` prop.\n *\n * Middleware in `src/server.ts` is handed Hono's `c` and so reads `c.req.url`, the *internal*\n * address, whatever this says. Hono's `csrf()` is the one to watch: behind a proxy that rewrites\n * `Host`, give it the public origin explicitly (`csrf({ origin: 'https://example.com' })`) rather\n * than leaving it to compare against an address the browser never used.\n *\n * **Off by default, and leave it off unless a proxy you control sets those headers**: any client\n * can send them, so with nothing stripping them at the edge one request can point every absolute\n * URL the app builds at an attacker's host (and poison a shared cache). Turn it on when you\n * terminate TLS or rewrite `Host` at a reverse proxy / load balancer. Always `true` under\n * `rshono dev`, where the framework's own proxy sets them and binds to localhost. Default `false`.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/X-Forwarded-Host | MDN — X-Forwarded-Host}\n * @see {@link https://www.rshono.com/docs/configuration#proxy-headers | Docs — proxy headers}\n */\n trustProxy?: boolean;\n /**\n * Escape hatch: mutate the generated Rspack config just before it's compiled. Called once per\n * compiler — inspect {@link RspackHookContext.isServer} to tell them apart. Mutate `config` in\n * place and return nothing, or return a replacement.\n *\n * @example\n * ```ts\n * rspack(config, { isServer }) {\n * config.module?.rules?.push({ test: /\\.svg$/, type: 'asset/source' });\n * }\n * ```\n *\n * @see {@link https://rspack.rs/config/ | Rspack — configuration reference}\n * @see {@link https://www.rshono.com/docs/configuration#the-rspack-hook | Docs — the rspack hook}\n */\n rspack?: (config: RspackOptions, ctx: RspackHookContext) => RspackOptions | void;\n}\n\n/**\n * Identity helper that types a config object — gives editor autocomplete without an explicit\n * annotation. Default-export the result from `rshono.config.ts`.\n *\n * @param config - The project's {@link RshonoConfig}; every field is optional.\n * @returns The config, unchanged and fully typed.\n *\n * @example\n * ```ts\n * // rshono.config.ts\n * import { defineConfig } from '@rshono/core';\n *\n * export default defineConfig({ deploy: 'cloudflare', siteUrl: 'https://example.com' });\n * ```\n *\n * @see {@link https://www.rshono.com/docs/configuration | Docs — configuration}\n */\nexport function defineConfig(config: RshonoConfig): RshonoConfig {\n return config;\n}\n"]}
@@ -32,7 +32,7 @@ function wranglerConfig(rootDir, today) {
32
32
  name: workerName(rootDir),
33
33
  main: 'dist/server/main.mjs',
34
34
  compatibility_date: today,
35
- // AsyncLocalStorage — how the framework binds the request context that `getContext()` reads.
35
+ // AsyncLocalStorage — how the framework binds the request context that `getRequestContext()` reads.
36
36
  compatibility_flags: ['nodejs_compat'],
37
37
  assets: {
38
38
  directory: `dist/${ASSETS_DIR.split(/[\\/]/).join('/')}`,
@@ -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';\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 `getRequestContext()` 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 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AA2EpD;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,aA4DrB,CAAC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAuDpD;;;;;;GAMG;AACH,eAAO,MAAM,OAAO,EAAE,aAgDrB,CAAC"}
@@ -1,4 +1,4 @@
1
- import { prerenderedRelPath } from '../../server/prerendered.js';
1
+ import { createPageCache, prerenderedRelPath, toPrerenderedPage } from '../../server/prerendered.js';
2
2
  /**
3
3
  * Where `finalize` puts the prerendered pages inside the assets directory.
4
4
  *
@@ -41,27 +41,8 @@ async function assetResponse(c, path, headers) {
41
41
  function serveAsset(asset) {
42
42
  return new Response(asset.body, asset);
43
43
  }
44
- /**
45
- * A weak `ETag` for a body the asset store gave us no validator for.
46
- *
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.
49
- */
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)}"`;
55
- }
56
- /**
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;
44
+ /** Prerendered pages, keyed by the path that produced them. Per isolate rather than per process. */
45
+ const pageCache = createPageCache();
65
46
  /**
66
47
  * Cloudflare Workers: the host owns the process, so there is nothing to listen on; the CDN owns the
67
48
  * assets, so there is nothing to serve from disk; and there is no filesystem, so `.env` files and a
@@ -72,7 +53,7 @@ const MAX_CACHED_PAGES = 128;
72
53
  export const runtime = {
73
54
  serveApp(app) {
74
55
  // 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.
56
+ // which is why bindings arrive as `c.env` — and so why `getRequestContext().env` sees them.
76
57
  return { fetch: app.fetch };
77
58
  },
78
59
  mountStaticAssets(app) {
@@ -105,25 +86,14 @@ export const runtime = {
105
86
  const asset = await assetResponse(c, `${SSG_PREFIX}/${relPath}`);
106
87
  if (!asset || asset.status !== 200)
107
88
  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
- };
89
+ // The store's own validator where it has one — it already describes these exact bytes.
90
+ const page = await toPrerenderedPage(new Uint8Array(await asset.arrayBuffer()), asset.headers.get('etag'));
116
91
  pageCache.set(key, page);
117
- if (pageCache.size > MAX_CACHED_PAGES)
118
- pageCache.delete(pageCache.keys().next().value);
119
92
  return page;
120
93
  },
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
94
  loadEnv() {
125
95
  // 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`.
96
+ // request as `c.env`, which `getRequestContext().env` already merges — see `runtime/context.ts`.
127
97
  },
128
98
  };
129
99
  //# sourceMappingURL=runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../../src/deploy/cloudflare/runtime.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAwB,MAAM,6BAA6B,CAAC;AAGvF;;;;;;;;;;GAUG;AACH,MAAM,UAAU,GAAG,QAAQ,CAAC;AAO5B,SAAS,aAAa,CAAC,CAAU;IAC/B,MAAM,OAAO,GAAI,CAAC,CAAC,GAA8C,EAAE,MAAM,CAAC;IAC1E,OAAO,OAAO,IAAI,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;AACzE,CAAC;AAED;;;;;;;GAOG;AACH,KAAK,UAAU,aAAa,CAAC,CAAU,EAAE,IAAY,EAAE,OAAiB;IACtE,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IACjC,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,mGAAmG;IACnG,oCAAoC;IACpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACrC,OAAO,OAAO,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;AACrE,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,KAAe;IACjC,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,QAAQ,CAAC,IAAY;IAClC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACrF,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzF,OAAO,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC;AACzC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;AACrD,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,OAAO,GAAkB;IACpC,QAAQ,CAAC,GAAS;QAChB,iGAAiG;QACjG,qFAAqF;QACrF,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,CAAC;IAC9B,CAAC;IAED,iBAAiB,CAAC,GAAS;QACzB,8FAA8F;QAC9F,6FAA6F;QAC7F,4DAA4D;QAC5D,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YACtD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACpE,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,mBAAmB,CAAC,GAAS;QAC3B,GAAG,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YAC9C,+FAA+F;YAC/F,4FAA4F;YAC5F,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,GAAG,CAAC;gBAAE,OAAO,IAAI,EAAE,CAAC;YAC3D,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACpE,OAAO,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,CAAU,EAAE,OAAO;QACvC,MAAM,OAAO,GAAG,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACxD,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAElC,MAAM,GAAG,GAAG,GAAG,OAAO,KAAK,OAAO,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,GAAG,UAAU,IAAI,OAAO,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;QAEhD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,IAAI,GAAoB;YAC5B,IAAI;YACJ,aAAa,EAAE,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC;YAChE,gGAAgG;YAChG,4FAA4F;YAC5F,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;SACrF,CAAC;QAEF,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACzB,IAAI,SAAS,CAAC,IAAI,GAAG,gBAAgB;YAAE,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAM,CAAC,CAAC;QACxF,OAAO,IAAI,CAAC;IACd,CAAC;IAED,kGAAkG;IAClG,8FAA8F;IAC9F,QAAQ,EAAE,IAAI;IAEd,OAAO;QACL,6FAA6F;QAC7F,0FAA0F;IAC5F,CAAC;CACF,CAAC","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 { createPageCache, prerenderedRelPath, 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\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/** Prerendered pages, keyed by the path that produced them. Per isolate rather than per process. */\nconst pageCache = createPageCache();\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 `getRequestContext().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 // 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: a Worker has no filesystem and no `.env`. Secrets and bindings arrive per\n // request as `c.env`, which `getRequestContext().env` already merges — see `runtime/context.ts`.\n },\n};\n"]}
@@ -1,13 +1,26 @@
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
4
+ * A hosting platform `rshono build` targets. Selected with `deploy` in `rshono.config.ts`, the
5
5
  * `--deploy` flag or the `RSHONO_DEPLOY` env var, and resolved to a preset by `deploy/presets.ts`.
6
6
  *
7
+ * - `'node'` (the default) — rshono binds the port itself and you run the build with `rshono start`.
8
+ * Covers a 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 file streaming needs there.
11
+ * - `'aws-lambda'` — a streaming Lambda handler.
12
+ *
13
+ * One entry per *handoff* — who opens the socket, and what shape a request arrives in — because that
14
+ * is the part an app cannot arrange for itself. There is deliberately no target whose only content
15
+ * would be "run the Node build", which is why Bun and Deno have none: importing the `node` bundle
16
+ * under those runtimes is the whole of what one would say.
17
+ *
7
18
  * `rshono dev` always runs the `node` server whatever this says — the dev server owns the process,
8
19
  * watches both compilers and fronts them on one port, none of which a hosting platform provides.
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
25
  * Everything the app server needs from the platform it is running on.
13
26
  *
@@ -16,18 +29,18 @@ export type DeployTarget = 'node' | 'cloudflare' | 'bun' | 'deno' | 'vercel' | '
16
29
  * ever in the bundle. `runtime/entry.rsc.tsx` is written against this interface and nothing else —
17
30
  * it is the whole of what "which platform is this" means at request time.
18
31
  *
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.
32
+ * The members are the capabilities that genuinely differ between a host with a disk and one without:
33
+ * who opens the socket, who serves the assets, where a prerendered page is read from, and whether
34
+ * there is a `.env` to load at all.
22
35
  */
23
36
  export interface DeployRuntime {
24
37
  /**
25
38
  * Hands the assembled app to the platform, and returns whatever the entry module should
26
39
  * `export default` there.
27
40
  *
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.
41
+ * The two shapes hosting takes, in one call: where rshono owns the process this binds a port and
42
+ * returns nothing; where the host owns it, it returns the export the platform looks for —
43
+ * `{ fetch }` on Workers, a handler function on Vercel and Lambda.
31
44
  */
32
45
  serveApp(app: Hono): unknown;
33
46
  /**
@@ -52,13 +65,6 @@ export interface DeployRuntime {
52
65
  * (see `prerenderedRelPath`).
53
66
  */
54
67
  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
68
  /** Loads `.env` files, where the platform has a filesystem to read them from. Env is bindings elsewhere. */
63
69
  loadEnv(): void;
64
70
  }
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAElF;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,YAAY,CAAC;AAExG;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;IACnC;;;OAGG;IACH,mBAAmB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;IACrC;;;;;;;;OAQG;IACH,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACxF;;;;;OAKG;IACH,QAAQ,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACnC,4GAA4G;IAC5G,OAAO,IAAI,IAAI,CAAC;CACjB"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../src/deploy/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAElF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,YAAY,CAAC;AAE3E;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,aAAa;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,iBAAiB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;IACnC;;;OAGG;IACH,mBAAmB,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAAC;IACrC;;;;;;;;OAQG;IACH,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACxF,4GAA4G;IAC5G,OAAO,IAAI,IAAI,CAAC;CACjB"}
@@ -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, and resolved to a preset by `deploy/presets.ts`.\n *\n * - `'node'` (the default) — rshono binds the port itself and you run the build with `rshono start`.\n * Covers a 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 file streaming needs there.\n * - `'aws-lambda'` — a streaming Lambda handler.\n *\n * One entry per *handoff* — who opens the socket, and what shape a request arrives in — because that\n * is the part an app cannot arrange for itself. There is deliberately no target whose only content\n * would be \"run the Node build\", which is why Bun and Deno have none: importing the `node` bundle\n * under those runtimes is the whole of what one would say.\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 *\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 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 members are the capabilities that genuinely differ between a host with a disk and one without:\n * who opens the socket, who serves the assets, where a prerendered page is read from, and whether\n * there is a `.env` to load at all.\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 this binds a port and\n * returns nothing; where the host owns it, it returns the export the platform looks for —\n * `{ fetch }` on Workers, a handler function on Vercel and 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 /** Loads `.env` files, where the platform has a filesystem to read them from. Env is bindings elsewhere. */\n loadEnv(): void;\n}\n"]}
@@ -2,7 +2,7 @@ import type { DeployRuntime } from './contract.js';
2
2
  /**
3
3
  * Everything a deploy target with a real filesystem does the same way.
4
4
  *
5
- * Node, Bun, Deno and the serverless runtimes that unpack a bundle onto disk (Vercel, Netlify, Lambda)
5
+ * Node and the serverless runtimes that unpack a bundle onto a read-only disk (Vercel, AWS Lambda)
6
6
  * differ only in how the finished app is handed over — so each of those presets is this object plus
7
7
  * its own {@link DeployRuntime.serveApp}, and the implementations live once, in `server/`.
8
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/deploy/filesystem.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAqBnD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,CAmB7D,CAAC"}
1
+ {"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/deploy/filesystem.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAoBnD;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,CAiB7D,CAAC"}
@@ -1,19 +1,17 @@
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
6
  const isDev = __RSHONO_CONFIG__.isDev;
8
7
  /**
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.
8
+ * The project root, derived from where the bundle ended up: this module is compiled into
9
+ * `<root>/dist/server/main.mjs`, so `import.meta.dirname` is `<root>/dist/server` at runtime.
12
10
  *
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.
11
+ * Derived rather than baked in at build time, because an absolute build-time path would tie the
12
+ * output to the machine that produced it — and building in CI to run somewhere else is the normal
13
+ * case. A preset that relocates the bundle keeps `dist/server/main.mjs` intact inside its own layout
14
+ * for exactly this reason.
17
15
  */
18
16
  const rootDir = join(import.meta.dirname, '..', '..');
19
17
  const staticDir = join(rootDir, 'dist', 'static');
@@ -23,7 +21,7 @@ const publicDir = isDev ? join(rootDir, 'public') : join(rootDir, 'dist', 'publi
23
21
  /**
24
22
  * Everything a deploy target with a real filesystem does the same way.
25
23
  *
26
- * Node, Bun, Deno and the serverless runtimes that unpack a bundle onto disk (Vercel, Netlify, Lambda)
24
+ * Node and the serverless runtimes that unpack a bundle onto a read-only disk (Vercel, AWS Lambda)
27
25
  * differ only in how the finished app is handed over — so each of those presets is this object plus
28
26
  * its own {@link DeployRuntime.serveApp}, and the implementations live once, in `server/`.
29
27
  */
@@ -39,7 +37,6 @@ export const fileSystemRuntime = {
39
37
  readPrerendered(c, variant) {
40
38
  return readPrerendered(ssgDir, c.req.path, variant);
41
39
  },
42
- compress: compress(),
43
40
  loadEnv() {
44
41
  loadEnvFiles(rootDir);
45
42
  },
@@ -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,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC;AAEtC;;;;;;;;GAQG;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,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 = __RSHONO_CONFIG__.isDev;\n\n/**\n * The project root, derived from where the bundle ended up: this module is compiled into\n * `<root>/dist/server/main.mjs`, so `import.meta.dirname` is `<root>/dist/server` at runtime.\n *\n * Derived rather than baked in at build time, because 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\n * case. A preset that relocates the bundle keeps `dist/server/main.mjs` intact inside its own layout\n * for 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 and the serverless runtimes that unpack a bundle onto a read-only disk (Vercel, AWS 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 loadEnv(): void {\n loadEnvFiles(rootDir);\n },\n};\n"]}