@nifrajs/web 5.2.0 → 5.4.0

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 (102) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/dist/build.d.ts +11 -2
  3. package/dist/build.d.ts.map +1 -1
  4. package/dist/build.js +265 -15
  5. package/dist/build.js.map +1 -1
  6. package/dist/csp.d.ts +2 -0
  7. package/dist/csp.d.ts.map +1 -1
  8. package/dist/csp.js +2 -0
  9. package/dist/csp.js.map +1 -1
  10. package/dist/deferred.d.ts.map +1 -1
  11. package/dist/deferred.js +23 -3
  12. package/dist/deferred.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal/deferred-free.d.ts +3 -0
  18. package/dist/internal/deferred-free.d.ts.map +1 -0
  19. package/dist/internal/deferred-free.js +15 -0
  20. package/dist/internal/deferred-free.js.map +1 -0
  21. package/dist/internal/module-preparation.d.ts +7 -0
  22. package/dist/internal/module-preparation.d.ts.map +1 -0
  23. package/dist/internal/module-preparation.js +24 -0
  24. package/dist/internal/module-preparation.js.map +1 -0
  25. package/dist/internal/node-adapter.d.ts +19 -0
  26. package/dist/internal/node-adapter.d.ts.map +1 -0
  27. package/dist/internal/node-adapter.js +48 -0
  28. package/dist/internal/node-adapter.js.map +1 -0
  29. package/dist/internal/output-guard.d.ts +2 -0
  30. package/dist/internal/output-guard.d.ts.map +1 -1
  31. package/dist/internal/output-guard.js +51 -9
  32. package/dist/internal/output-guard.js.map +1 -1
  33. package/dist/internal/page-execution.d.ts +1 -0
  34. package/dist/internal/page-execution.d.ts.map +1 -1
  35. package/dist/internal/page-execution.js +53 -43
  36. package/dist/internal/page-execution.js.map +1 -1
  37. package/dist/internal/parity.d.ts.map +1 -1
  38. package/dist/internal/parity.js +3 -0
  39. package/dist/internal/parity.js.map +1 -1
  40. package/dist/internal/prerendered-pages.d.ts +42 -0
  41. package/dist/internal/prerendered-pages.d.ts.map +1 -0
  42. package/dist/internal/prerendered-pages.js +92 -0
  43. package/dist/internal/prerendered-pages.js.map +1 -0
  44. package/dist/internal/render-document.d.ts.map +1 -1
  45. package/dist/internal/render-document.js +93 -35
  46. package/dist/internal/render-document.js.map +1 -1
  47. package/dist/internal/request-url.d.ts +4 -0
  48. package/dist/internal/request-url.d.ts.map +1 -1
  49. package/dist/internal/request-url.js +24 -0
  50. package/dist/internal/request-url.js.map +1 -1
  51. package/dist/internal/secret-scan.d.ts +4 -0
  52. package/dist/internal/secret-scan.d.ts.map +1 -1
  53. package/dist/internal/secret-scan.js +8 -1
  54. package/dist/internal/secret-scan.js.map +1 -1
  55. package/dist/internal/web-app.d.ts +11 -0
  56. package/dist/internal/web-app.d.ts.map +1 -1
  57. package/dist/internal/web-app.js +10 -1
  58. package/dist/internal/web-app.js.map +1 -1
  59. package/dist/isr.d.ts +1 -0
  60. package/dist/isr.d.ts.map +1 -1
  61. package/dist/isr.js +7 -4
  62. package/dist/isr.js.map +1 -1
  63. package/dist/prerender.d.ts +4 -0
  64. package/dist/prerender.d.ts.map +1 -1
  65. package/dist/prerender.js +9 -1
  66. package/dist/prerender.js.map +1 -1
  67. package/dist/public-dir.d.ts +19 -7
  68. package/dist/public-dir.d.ts.map +1 -1
  69. package/dist/public-dir.js +130 -99
  70. package/dist/public-dir.js.map +1 -1
  71. package/dist/render-seam.d.ts +5 -2
  72. package/dist/render-seam.d.ts.map +1 -1
  73. package/dist/render-seam.js.map +1 -1
  74. package/dist/search.d.ts.map +1 -1
  75. package/dist/search.js +2 -0
  76. package/dist/search.js.map +1 -1
  77. package/dist/vite.d.ts +6 -0
  78. package/dist/vite.d.ts.map +1 -1
  79. package/dist/vite.js +127 -54
  80. package/dist/vite.js.map +1 -1
  81. package/package.json +3 -3
  82. package/src/build.ts +330 -49
  83. package/src/csp.ts +4 -0
  84. package/src/deferred.ts +20 -3
  85. package/src/index.ts +3 -0
  86. package/src/internal/deferred-free.ts +15 -0
  87. package/src/internal/module-preparation.ts +30 -0
  88. package/src/internal/node-adapter.ts +57 -0
  89. package/src/internal/output-guard.ts +51 -9
  90. package/src/internal/page-execution.ts +55 -47
  91. package/src/internal/parity.ts +2 -0
  92. package/src/internal/prerendered-pages.ts +124 -0
  93. package/src/internal/render-document.ts +116 -52
  94. package/src/internal/request-url.ts +24 -0
  95. package/src/internal/secret-scan.ts +9 -1
  96. package/src/internal/web-app.ts +21 -1
  97. package/src/isr.ts +7 -4
  98. package/src/prerender.ts +13 -1
  99. package/src/public-dir.ts +158 -97
  100. package/src/render-seam.ts +5 -2
  101. package/src/search.ts +1 -0
  102. package/src/vite.ts +130 -54
package/CHANGELOG.md CHANGED
@@ -1,5 +1,117 @@
1
1
  # @nifrajs/web
2
2
 
3
+ ## 5.4.0
4
+
5
+ ### Patch Changes
6
+
7
+ - e7d43ca: On Bun, `servePublicDir` now sends `content-length` for files up to 1 MiB, whole or ranged. This covers the Bun server entry and `nifra dev`. `Bun.serve` drops the declared length of a stream body and sends it chunked, so these files used to arrive with no length: no download progress, and caches and proxies could not size them. They are now read whole through the already-open file, as files up to 256 KiB already were, which serves a 1 MiB file about a third faster. Files above 1 MiB still stream, and Bun still sends them chunked. Node and Deno keep a stream's length and are unchanged.
8
+ - 5183c00: The server entry `buildTarget` generates for `deno` now answers static files with the validators the Bun and Node entries send:
9
+
10
+ - an `etag` and `last-modified`, and a `304` with no body for a current copy, HEAD included.
11
+ - `Range` honored only when `If-Range` is absent or names the file's current validator; anything else gets the whole file.
12
+ - a link is typed by the file it resolves to, as on Bun and Node.
13
+
14
+ `@nifrajs/core/static-file` holds the comparisons `servePublicDir`, the Deno entry and `@nifrajs/node`'s `static` reader now share.
15
+ - e589893: The server entry `buildTarget` generates for `node` now passes the app itself to `@nifrajs/node`'s `serve` and serves files with the adapter's own reader. The entry needs the `@nifrajs/node` of the same release, so a `node` build with an older one installed stops before bundling, names both versions and leaves the last deploy dir in place. A 5.3 adapter used to build cleanly and then throw when the server started. It used to wrap `app.fetch` in a plain `{ fetch }` handler. That wrapper hid the node-direct lane, so every page built an undici `Request` and drained a `Response`, and every file went through one too. The entry mounts `/assets` and, at the root, only the `public/` files the build copied, with the same type, cache, range, HEAD and hidden-file answers the Bun entry gives.
16
+
17
+ `serve`'s `static` option on `@nifrajs/node`:
18
+
19
+ - takes an array of mounts; a path goes to the first mount that takes it, by its prefix and, when set, its `files`.
20
+ - `files` lists the only URL paths a mount serves; any other path passes to the next mount, then the app, without that mount touching the disk.
21
+ - `cacheControl` sets `cache-control` for files not served immutable.
22
+ - types files from the same table as `servePublicDir`, so `.pdf`, `.mp4`, `.xml`, `.csv` and other common types are no longer sent as `application/octet-stream`; a link is typed by the file it resolves to.
23
+ - serves `/.well-known` by default, as `servePublicDir` does, and refuses a visible name that links to a dotfile.
24
+ - answers a link that leaves the directory with `404`, the same as a missing file, instead of `403`.
25
+ - sends files up to 256 KiB, and HEAD, `304` and `416` answers, in one write with no `Response` or stream.
26
+
27
+ `@nifrajs/core/static-file` exports `CONTENT_TYPES`, `contentTypeOf` and `isHiddenPath`, which both readers now use.
28
+ - 02ac072: Static files on Bun and Deno evaluate `If-None-Match` before `If-Modified-Since`, including the wildcard condition for an existing file. A stale entity tag can no longer produce a `304` from its accompanying date.
29
+ - 529f8d2: Static files are tagged by their content on every self-hosting entry: `servePublicDir` (the Bun entry and `nifra dev`), `@nifrajs/node`'s `static` reader and the generated Deno entry. Each sends a strong `ETag` of 128 bits of the file's SHA-256, the same for the same bytes on every instance and runtime. A replacement that keeps a file's size and modification time (`cp -p`, `rsync -a`, tar, a container image layer, SOURCE_DATE_EPOCH, the Nix store's 1 s) is no longer answered with a `304` for the old copy, and a resumed download is no longer spliced from two versions. The Node reader's `"<mtime>-<size>"` tag is gone; the Bun and Deno entries send a tag for the first time.
30
+
31
+ - A tag is remembered per process for the file version it was read from (device, inode, size, mtime and ctime), so a known file costs no extra read. The first request for a file reads it once, and a file within the buffered size is then served from those bytes. Concurrent first requests share one read. A file changed in the last 2 s is read again until its ctime settles. A file over 256 MiB (`CONTENT_TAG_MAX`) is tagged by that identity instead: still strong, but different on each instance.
32
+ - `Last-Modified` is sent only while the file's mtime is the time of its own last change (within 1 s of its ctime). A file whose mtime a copy or build set, such as every file in a container image, is sent without it, so `If-Modified-Since` cannot validate it.
33
+ - An `If-Range` date no longer keeps a range for a file with a strong tag; a client resumes by the tag. On Node a configured strong `ETag` likewise sends the whole file for a date, while a configured weak one still lets a configured `Last-Modified` resume by its exact date.
34
+ - `@nifrajs/core/static-file` exports `contentTags`, `CONTENT_TAG_MAX`, `lastModifiedOf` and `isNotModified`, the `If-None-Match` and `If-Modified-Since` check all three readers share. `ifRangeAllowsPartial` now takes the file's tag.
35
+ - Updated dependencies [5183c00]
36
+ - Updated dependencies [01b97ec]
37
+ - Updated dependencies [e589893]
38
+ - Updated dependencies [ff66db6]
39
+ - Updated dependencies [529f8d2]
40
+ - @nifrajs/core@5.4.0
41
+ - @nifrajs/island-trigger@5.4.0
42
+
43
+ ## 5.3.0
44
+
45
+ ### Minor Changes
46
+
47
+ - 7ab9692: Add build-time schema compilation that emits typed static validators without runtime code generation. Generated modules preserve strict validation and structured issues while avoiding the schema builder and dynamic compiler in deployed bundles.
48
+
49
+ A schema whose property key, `format` name or custom kind holds a quote, backslash or line break, or whose literal holds a backslash or line break, now validates on the eval-free checker and is refused by build-time compilation. TypeBox splices that text into generated source unescaped, so a quote could end the string and run the rest as code.
50
+
51
+ Prepare immutable route modules and layout chains in production while executing authorization gates and loaders for every request. Mutable manifests can disable module preparation with `cacheRouteModules: false`.
52
+
53
+ Reduce output projection bookkeeping for uniform subtrees, reuse request-bound cache views, and record completed cache spans without allocating an active observation. Span parentage, sampling, redaction, exporter isolation, and capability enforcement remain intact.
54
+
55
+ Keep custom start-only `ObservationLifecycle` implementations compatible. The factory returns the extended `RecordingObservationLifecycle` interface with the new `record` method.
56
+
57
+ Mark the migration parser's TypeScript keyword-kind comparison as syntax, so the security check does not mistake it for a secret comparison.
58
+ - 2a429e6: `nifra build --target bun` and `--target node` prerender the routes that opt in (`export const prerender = true`, or `getStaticPaths`) into `_prerendered/` beside `server.js`. The server then answers those pages from their file instead of rendering them on every request.
59
+
60
+ The file is served from the page route, so `use` middleware and every request hook still run first. A gate in front of a page still applies, and response hooks still add their headers. The page carries the `content-language` and Content-Security-Policy its build-time render produced. Loader headers and cookies are not sent, as for any prerendered page.
61
+
62
+ These keep rendering on demand:
63
+ - a request with a query string
64
+ - a navigation data or server-component request
65
+ - a draft-mode visitor
66
+ - a page whose build-time render set a cookie, was `private` or `no-store`, or varied on a request header
67
+ - every page of an app with a `nonce`, whose documents need a nonce minted per request.
68
+
69
+ On Node, the SSR benchmark's prerendered catalog page is served about 2.1x (Svelte) to 2.6x (Vue) faster than rendering it on each request.
70
+
71
+ New in `@nifrajs/web`:
72
+ - `createWebApp({ prerenderedPages })`
73
+ - `prerenderedPageReader`, which opens only the build's own files inside the directory and keeps each page in memory once read, up to 32 MiB by default
74
+ - `PrerenderEntry.serveHeaders` on `prerenderRoutes` results.
75
+
76
+ Prerendering now runs during these builds, so it loads every route module and runs the opted-in loaders at build time. Two effects:
77
+ - If a route module cannot load there, the build warns and every page renders on demand, as before.
78
+ - A secret in a prerendered page fails the build, as it does for `--target static`.
79
+
80
+ The Vite pipeline, `deno`, and the edge targets are unchanged.
81
+
82
+ ### Patch Changes
83
+
84
+ - 0e1d03f: A build no longer copies hidden files from `public/` - any path segment starting with `.` other than `.well-known`, such as `.env`, `.git/` or `.DS_Store`. The Bun development pipeline never served them (the Vite one did; see below), but the build copied them and every target then served them: the Bun, Node and Deno entries from their allowlist, the static, Pages and Vercel hosts from the directory. The development parity check lists public files by the same rule, and `servePublicDir` also refuses a visible name whose real target is hidden.
85
+ - 605c246: Under a hash-based `createCspPolicy`, a buffered render that writes a script for that request alone (Solid serializing a server error an `ErrorBoundary` caught, for hydration) no longer ships it unauthorized. The render is handed a nonce; when the HTML it returns carries it, the document keeps the nonce in its `Content-Security-Policy` and becomes `private, no-store`, and every other document stays nonce-free and cacheable as before.
86
+ - 271917a: A soft navigation's deferred data stream is cancelled and released when a line fails to parse. A malformed first line threw out of `parseNdjsonData` with the body still locked and streaming and the abort listener attached, and a malformed later line stopped reading without cancelling the body; every exit now detaches from the signal, cancels an unfinished body and releases the reader.
87
+ - 69dfb60: `rangeResponse()` and `servePublicDir` ignore a `Range` header on a HEAD request, as RFC 9110 section 14.2 defines range handling for GET only: HEAD answers 200 with the full `content-length` and no `content-range`, the same as the Node static bridge. Before, HEAD with `Range` answered 206 or 416 with the metadata of the requested slice.
88
+ - 605c246: A React page that suspends without `defer()` (`use(promise)` or `lazy` inside `<Suspense>`) now renders its resolved content on the server. The buffered fast path rendered it with React's string renderer, which cannot wait, so the document kept the fallback for good (with `hydrate: false` or JavaScript off, the content never appeared), and a component suspending with no boundary above it failed the render. `RenderAdapter.renderToString` may now return `undefined` for a render that suspended; the React adapter does so, and the page is streamed instead, with a CSP nonce when the policy needs one.
89
+ - 13224fe: The server entry `buildTarget` generates for `bun`, `node` and `deno` serves static files the way `nifra dev` does. Bun and Node now hand a manifest-approved path to `servePublicDir`, and Deno runs the same checks on its own file API: the real path must sit inside the deploy directory, so a symbolic link planted in the deployed tree no longer reaches a file outside it; a HEAD request is answered from the file's size instead of reading it whole (the Node entry read the entire file); and a single byte range gets a `206` (an unsatisfiable one a `416`), with `accept-ranges` advertised.
90
+ - 00c266e: A streamed page now renders only as fast as the client reads it. The document stream read the whole app stream up front, so a client that stopped reading left the full rendered body queued in memory; it now pulls one renderer chunk per read. The Vite dev bridge (`pipeWebBodyToNode`) likewise waits for `drain` when the socket buffer is full instead of writing every chunk at once, and removes its listeners when the body ends.
91
+ - 042db5d: A Preact page that suspends without `defer()` (a `preact/compat` `<Suspense>` around a thrown promise, or `lazy`) is streamed and resolves on the server; it was a 500. A page whose string render suspended skips the string pass for its next 32 requests and goes straight to the stream, so a React page that suspends on `use` renders once per request rather than twice.
92
+ - 49e37bd: `nifra dev` on the Vite pipeline no longer serves hidden files from `public/`. Vite's public middleware served every file there, dotfiles included, so `/.env`, `/.git/config` and `/%2eenv` answered with their contents. A request for a hidden file, or a visible name whose real target is hidden, now goes to the app, as it does in production and on the Bun pipeline. The dev server binds 127.0.0.1 and Vite refuses a foreign `Host`, so the exposure was limited to local processes and the developer's own browser.
93
+ - 6826956: The Vite dev bridge cancels a non-HTML response body when the client left before piping began, or while a write was refused. It waited for a `drain` that never came, so an SSE source or a streamed body from the app stayed open for every aborted request.
94
+ - 3318232: `servePublicDir` reads a body of up to 256 KiB whole through its pinned descriptor and closes it before the response leaves. A larger body still streams. On Bun, a 200 KB public file is served in 0.17 ms where a `node:fs` stream took 0.28 ms; a 10 KB file takes 0.12-0.14 ms instead of 0.16 ms. The self-hosted Bun, Node and Deno entries serve static files through this handler.
95
+ - 7e5cd65: The self-hosted Deno entry's static handler applies `servePublicDir`'s hidden-file rule to the real path, so `/assets/<name>` linking to a `.env` inside the deploy directory is 404 rather than served. It also checks that the file it opened is still the one at the checked real path. `Deno.open` follows links, so a directory swapped for a link after the containment check used to redirect the open outside the deploy directory.
96
+ - d874102: No behavior change: the error a secret in a build artifact throws sets its name in a constructor, and the prerendered page reader's failure paths are tested.
97
+ - 80bf986: Page documents render with less work per request. Loader data that a closed output schema has settled skips the scan for deferred values. The request origin is parsed once per host. An empty query string skips search parsing. The render options are assembled without a copy per optional field. On the SSR bench apps this measured 6 to 10% more requests per second on Bun and Node.
98
+ - d890695: `MemoryCacheStore` no longer stores a render that began before a purge whose record it has since evicted. Purge records are bounded by `max`; once a page's or tag's record was evicted, a slow render that began before that purge was accepted and served the purged page again (one purge followed by 500 others was enough at the default `max`). Such a render is now served but not cached; a render that began after the purge is cached as before.
99
+ - Updated dependencies [7ab9692]
100
+ - Updated dependencies [808bac8]
101
+ - Updated dependencies [d4bae56]
102
+ - Updated dependencies [007060d]
103
+ - Updated dependencies [190ab39]
104
+ - Updated dependencies [7cf8df3]
105
+ - @nifrajs/core@5.3.0
106
+ - @nifrajs/island-trigger@5.3.0
107
+
108
+ ## 5.2.1
109
+
110
+ ### Patch Changes
111
+
112
+ - @nifrajs/core@5.2.1
113
+ - @nifrajs/island-trigger@5.2.1
114
+
3
115
  ## 5.2.0
4
116
 
5
117
  ### Minor Changes
package/dist/build.d.ts CHANGED
@@ -3,8 +3,10 @@ import { type BunPlugin } from "bun";
3
3
  import { type BuildManifest, type BuildTarget, type Bundler, type ServerBuild, type SizeReport } from "./build-plan.js";
4
4
  import { type CssLoadingMode } from "./css-contract.js";
5
5
  import { generateServerManifest } from "./index.js";
6
+ import { type ServerPrerenderedPage } from "./internal/prerendered-pages.js";
6
7
  import { type SecretExemption } from "./internal/secret-scan.js";
7
8
  export * from "./build-plan.js";
9
+ export type { ServerPrerenderedPage } from "./internal/prerendered-pages.js";
8
10
  export type { SecretExemption, SecretRule } from "./internal/secret-scan.js";
9
11
  export { type CloudflarePagesRoutes, type CloudflarePagesRoutesOptions, cloudflarePagesRoutes, dataFileFor, htmlFileFor, type PrerenderApp, type PrerenderEntry, type PrerenderOptions, type PrerenderResult, prerenderRoutes, } from "./prerender.js";
10
12
  export interface BuildClientOptions {
@@ -93,7 +95,9 @@ export declare function cloudflareRouteRules(publicFiles: readonly string[],
93
95
  routePatterns: readonly string[]): CloudflareRouteRules;
94
96
  /** The built asset map - the server reads `entry` for the client script + serves `assets`. */
95
97
  /**
96
- * Copy `from` into `to`, returning the URL paths copied (sorted).
98
+ * Copy `from` into `to`, returning the URL paths copied (sorted). Hidden entries other than
99
+ * `.well-known` are skipped, as development never serves them: every target ships this list, and a
100
+ * CDN-hosted one serves whatever was copied.
97
101
  *
98
102
  * Lives here, not beside `servePublicDir`, because it is BUILD-time: it reaches for `Bun.Glob` and
99
103
  * `node:fs`, and `public-dir.ts` is reachable from the client bundle graph through the package
@@ -276,6 +280,9 @@ export declare function generateServerEntry(options: {
276
280
  readonly clientIp?: ServerEntryClientIp;
277
281
  /** The server manifest exports `copyChunks` (the client build split copy tables by locale). */
278
282
  readonly copyChunks?: boolean;
283
+ /** `bun` / `node` only: the pages the build prerendered into `_prerendered/`, by request path, which
284
+ * the app answers from their file (see `createWebApp`'s `prerenderedPages`). */
285
+ readonly prerenderedPages?: Readonly<Record<string, ServerPrerenderedPage>>;
279
286
  }): string;
280
287
  export interface BuildTargetOptions {
281
288
  /** The `routes/` directory to discover (absolute path). */
@@ -304,7 +311,9 @@ export interface BuildTargetOptions {
304
311
  * emitted hydration `<script src>` uses the REAL content-hashed entry (`client.entry`) plus the same
305
312
  * styles/route-preload the server targets use. A pre-built instance can't work here: the hash isn't known
306
313
  * until `buildClient` runs inside `buildTarget`, so a hardcoded entry 404s → pages render but never
307
- * hydrate. Required for `target: "static"` (SSG drives `app.fetch`); ignored otherwise. */
314
+ * hydrate. Required for `target: "static"` (SSG drives `app.fetch`). For `bun` and `node` it is
315
+ * optional: given, the opted-in routes are prerendered into `_prerendered/` and the server answers
316
+ * them from their file; the other targets ignore it. */
308
317
  readonly prerenderApp?: (client: BuildManifest) => PrerenderAppLike | Promise<PrerenderAppLike>;
309
318
  /** Client-build plugins (e.g. the MDX/Vue/Solid Bun plugins). */
310
319
  readonly clientPlugins?: readonly BunPlugin[];
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AA0B3D,OAAO,EAAE,KAAK,SAAS,EAAQ,MAAM,KAAK,CAAA;AAC1C,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,WAAW,EAEhB,KAAK,OAAO,EAaZ,KAAK,WAAW,EAChB,KAAK,UAAU,EAChB,MAAM,iBAAiB,CAAA;AAExB,OAAO,EAAE,KAAK,cAAc,EAA8C,MAAM,mBAAmB,CAAA;AAEnG,OAAO,EAAuB,sBAAsB,EAAE,MAAM,YAAY,CAAA;AAWxE,OAAO,EACL,KAAK,qBAAqB,EAE3B,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EAQL,KAAK,eAAe,EAIrB,MAAM,2BAA2B,CAAA;AAyBlC,cAAc,iBAAiB,CAAA;AA+B/B,YAAY,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAA;AAE5E,YAAY,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAA;AAG5E,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,4BAA4B,EACjC,qBAAqB,EACrB,WAAW,EACX,WAAW,EACX,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,eAAe,GAChB,MAAM,gBAAgB,CAAA;AAEvB,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,gGAAgG;IAChG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3C,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IACvC,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,qFAAqF;IACrF,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,oGAAoG;IACpG,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACnC;;;;;;;;uCAQmC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IACtD;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAC7B;AAiBD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,mGAAmG;IACnG,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC;AA2DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,SAAS,MAAM,EAAE;AAC9B;;;;GAIG;AACH,aAAa,EAAE,SAAS,MAAM,EAAE,GAC/B,oBAAoB,CAgCtB;AAED,8FAA8F;AAC9F;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsB/E;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,UAAU,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAC,CAAC,EACtD,YAAY,EAAE,MAAM,GACnB,MAAM,CAYR;AAED;;;;;;GAMG;AACH;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAmYrF;AAED,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;kHAC8G;IAC9G,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;0FACsF;IACtF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B;;oEAEgE;IAChE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IAC/C,0GAA0G;IAC1G,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IAC9E,sGAAsG;IACtG,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAA;IAClE,oGAAoG;IACpG,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAA;IACpC;gFAC4E;IAC5E,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAC3C;wFACoF;IACpF,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B;gEAC4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IACvC;kCAC8B;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC;+FAC2F;IAC3F,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;;6BAIyB;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,MAAM,GAAG,KAAK,CAAA;IAC5C;;;;2GAIuG;IACvG,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;IACvB,iGAAiG;IACjG,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB;gGAC4F;IAC5F,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C;8CAC0C;IAC1C,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAClC;AA+BD,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,SAc/C,CAAA;AAcF,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,SAchD,CAAA;AAGF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,SAgDhD,CAAA;AAuFF,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AAEzD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,QAAO,SAWpC,CAAA;AAqBF;;;;;;;;GAQG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,WAAW,CAAC,CAiHnF;AAWD,sGAAsG;AACtG,eAAO,MAAM,oBAAoB,sEAOvB,CAAA;AACV,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAA;AACrE,oFAAoF;AACpF,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;AAE3F;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAA;AAgB5C;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE;IAC3C,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,mGAAmG;IACnG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;mDAC+C;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;0BACsB;IACtB,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAA;IACjD,mDAAmD;IACnD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC;kGAC8F;IAC9F,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,yGAAyG;IACzG,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IACvC,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;IAC7B;oFACgF;IAChF,QAAQ,CAAC,gBAAgB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC,CAAA;CAC5E,GAAG,MAAM,CAkPT;AAkLD,MAAM,WAAW,kBAAkB;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;wEAEoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB;2FACuF;IACvF,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,uGAAuG;IACvG,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,sFAAsF;IACtF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,sGAAsG;IACtG,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;;oCAEgC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B;8GAC0G;IAC1G,QAAQ,CAAC,aAAa,CAAC,EAAE,wBAAwB,CAAA;IACjD;;;;;;4DAMwD;IACxD,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC/F,iEAAiE;IACjE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IAC7C,oDAAoD;IACpD,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,SAAS,EAAE,CAAA;IAC7C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClD,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACnC,4FAA4F;IAC5F,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC,oGAAoG;IACpG,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,eAAe,EAAE,CAAA;IACtD;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,cAAc,CAAA;IACpC,yDAAyD;IACzD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;+FAC2F;IAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;CACxC;AAED,wGAAwG;AACxG,MAAM,WAAW,gBAAgB;IAC/B,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAClD;AAED,qGAAqG;AACrG,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAA;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB;kCAC8B;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAC1B;AAED;;;;;;;;;;;;GAYG;AACH,iFAAiF;AACjF,eAAO,MAAM,UAAU,EAAE,OAsCxB,CAAA;AAgED,uGAAuG;AACvG,wBAAsB,WAAW,CAC/B,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC,CAE5B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,WAAW,EACnB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,iBAAiB,CAAC,CAmO5B"}
package/dist/build.js CHANGED
@@ -11,6 +11,7 @@ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync
11
11
  import { cp, lstat, mkdir, realpath } from "node:fs/promises";
12
12
  import { dirname, isAbsolute, join, basename as pathBasename, posix, relative, resolve as resolvePath, sep, } from "node:path";
13
13
  import { singleCopyPlugin as declaredSingleCopyPlugin, readSingleCopyDeclaration, } from "@nifrajs/core/single-copy";
14
+ import { isHiddenPath } from "@nifrajs/core/static-file";
14
15
  import { Glob } from "bun";
15
16
  import { aggregateSizeReport, detectNodeBuiltinsInClient, detectServerOnlyInClient, formatNodeBuiltinLeak, formatServerOnlyLeak, parseBuildTarget, parseManifestClientEntry, parseManifestCopyChunks, parseManifestCssLoading, parseManifestRouteStyles, parseManifestStyles, planBuildTarget, } from "./build-plan.js";
16
17
  import { sanitizeOutputNames } from "./chunk-names.js";
@@ -21,10 +22,12 @@ import { loadCopySplit } from "./internal/copy-split.js";
21
22
  import { assertDisposableDir } from "./internal/disposable-dir.js";
22
23
  import { entryChunks } from "./internal/entry-chunks.js";
23
24
  import { dedupePolicyFor } from "./internal/identity-policy.js";
25
+ import { nodeAdapterRefusal } from "./internal/node-adapter.js";
24
26
  import { assertDevelopmentProductionParity, assertIdentityParity, collectDevelopmentParityInput, } from "./internal/parity.js";
27
+ import { servablePrerenderedPages, } from "./internal/prerendered-pages.js";
25
28
  import { privateEnvCheck } from "./internal/private-env.js";
26
29
  import { publicUrlPath } from "./internal/public-url.js";
27
- import { assertNoSecrets, buildEnvironment, emittedScanFiles, formatSecretFindings, graphScanInput, originName, publicScanFiles, scanForSecrets, textOf, } from "./internal/secret-scan.js";
30
+ import { assertNoSecrets, buildEnvironment, emittedScanFiles, formatSecretFindings, graphScanInput, originName, publicScanFiles, SecretScanError, scanForSecrets, textOf, } from "./internal/secret-scan.js";
28
31
  import { generateServerFnStub, SERVER_FN_MODULE, serverFnNamespace, } from "./internal/server-boundary.js";
29
32
  import { formatUnsupportedBuiltins, unsupportedBuiltins } from "./internal/target-compat.js";
30
33
  import { accountEmittedFiles, bunModuleSource, formatClientGraphVerdict, formatServerGraphVerdict, formatUnaccountedOutput, verifyClientGraph, verifyServerGraph, } from "./internal/zone-graph.js";
@@ -33,7 +36,7 @@ import { assertNoImportGlob, importGlobPlugin } from "./plugins/import-glob.js";
33
36
  import { zoneGuardPlugin } from "./plugins/zone-guard.js";
34
37
  // `buildTarget(static)` drives the SSG prerender engine directly (it's also re-exported below).
35
38
  import { prerenderRoutes } from "./prerender.js";
36
- import { CONTENT_TYPES, IMMUTABLE, ONE_DAY } from "./public-dir.js";
39
+ import { IMMUTABLE, ONE_DAY } from "./public-dir.js";
37
40
  import { createZoneClassifier } from "./zones.js";
38
41
  export * from "./build-plan.js";
39
42
  const basename = (path) => pathBasename(path);
@@ -166,7 +169,9 @@ routePatterns) {
166
169
  }
167
170
  /** The built asset map - the server reads `entry` for the client script + serves `assets`. */
168
171
  /**
169
- * Copy `from` into `to`, returning the URL paths copied (sorted).
172
+ * Copy `from` into `to`, returning the URL paths copied (sorted). Hidden entries other than
173
+ * `.well-known` are skipped, as development never serves them: every target ships this list, and a
174
+ * CDN-hosted one serves whatever was copied.
170
175
  *
171
176
  * Lives here, not beside `servePublicDir`, because it is BUILD-time: it reaches for `Bun.Glob` and
172
177
  * `node:fs`, and `public-dir.ts` is reachable from the client bundle graph through the package
@@ -177,6 +182,8 @@ export async function copyPublicDir(from, to) {
177
182
  const sourceReal = await realpath(source);
178
183
  const copied = [];
179
184
  for await (const rel of new Glob("**/*").scan({ cwd: source, dot: true, onlyFiles: false })) {
185
+ if (isHiddenPath(rel))
186
+ continue;
180
187
  const candidate = join(source, rel);
181
188
  const candidateStat = await lstat(candidate);
182
189
  if (candidateStat.isSymbolicLink()) {
@@ -982,6 +989,8 @@ const PLATFORM_CLIENT_IP_HEADERS = {
982
989
  cloudflare: "cf-connecting-ip",
983
990
  vercel: "x-real-ip",
984
991
  };
992
+ /** Where a self-hosting build writes its prerendered pages, beside `server.js`. */
993
+ const PRERENDERED_DIR = "_prerendered";
985
994
  /**
986
995
  * Codegen the per-target **server entry** module (source text) for `buildServer` to bundle. It imports
987
996
  * the app's `adapter` (from `framework.ts`), the optional `backend` (from `backend.ts`), and the
@@ -1016,7 +1025,20 @@ export function generateServerEntry(options) {
1016
1025
  if (target === "static") {
1017
1026
  throw new Error("[nifra/web] generateServerEntry: `static` has no server entry (SSG only)");
1018
1027
  }
1019
- const lines = ['import { createWebApp } from "@nifrajs/web"'];
1028
+ // Bun serves the client bundle through `servePublicDir` and Node through @nifrajs/node's own reader,
1029
+ // both on core's static-file policy. Deno bundles for the browser condition, where `node:fs` is a
1030
+ // stub, so its entry runs the same checks on Deno's own file API.
1031
+ const nodeFs = target === "bun" || target === "node";
1032
+ const prerendered = Object.entries(options.prerenderedPages ?? {});
1033
+ if (prerendered.length > 0 && !nodeFs) {
1034
+ throw new Error(`[nifra/web] generateServerEntry: prerendered pages are served by the bun and node targets, not ${target}`);
1035
+ }
1036
+ const webImports = ["createWebApp"];
1037
+ if (prerendered.length > 0)
1038
+ webImports.push("prerenderedPageReader");
1039
+ if (target === "bun")
1040
+ webImports.push("servePublicDir");
1041
+ const lines = [`import { ${webImports.join(", ")} } from "@nifrajs/web"`];
1020
1042
  if (backendImport !== undefined)
1021
1043
  lines.push('import { inProcessClient } from "@nifrajs/client"');
1022
1044
  lines.push(`import { adapter } from ${JSON.stringify(adapterImport)}`);
@@ -1036,7 +1058,17 @@ export function generateServerEntry(options) {
1036
1058
  lines.push('import { toFetchHandler } from "@nifrajs/core/server"');
1037
1059
  if (target === "node")
1038
1060
  lines.push('import { serve } from "@nifrajs/node"');
1039
- lines.push("", "const app = createWebApp({", " adapter,", ...(useImport !== undefined ? [" use,"] : []), ...importedOptions.map((name) => ` ${name},`), " manifest,", " clientEntry,", " styles,", " routeStyles,", " cssLoading,", ...(options.copyChunks === true ? [" copyChunks,"] : []), ...(backendImport !== undefined ? [" api: inProcessClient(backend),"] : []), ` title: ${JSON.stringify(title)},`,
1061
+ if (nodeFs)
1062
+ lines.push('import { fileURLToPath } from "node:url"');
1063
+ if (target === "deno") {
1064
+ lines.push('import { parseByteRange } from "@nifrajs/core/range"', 'import { contentTags, contentTypeOf, ifRangeAllowsPartial, isHiddenPath, isNotModified, lastModifiedOf } from "@nifrajs/core/static-file"', 'import { createHash } from "node:crypto"');
1065
+ }
1066
+ if (prerendered.length > 0) {
1067
+ const headers = Object.fromEntries(prerendered.map(([path, page]) => [path, page.headers]));
1068
+ const files = Object.fromEntries(prerendered.map(([path, page]) => [path, page.file]));
1069
+ lines.push("", `// The pages the build prerendered into ${PRERENDERED_DIR}/, answered from their file after \`use\`.`, "const prerenderedPages = {", ` pages: ${JSON.stringify(headers)},`, " read: prerenderedPageReader({", ` dir: fileURLToPath(new URL(${JSON.stringify(`./${PRERENDERED_DIR}/`)}, import.meta.url)),`, ` files: ${JSON.stringify(files)},`, " }),", "}");
1070
+ }
1071
+ lines.push("", "const app = createWebApp({", " adapter,", ...(useImport !== undefined ? [" use,"] : []), ...(prerendered.length > 0 ? [" prerenderedPages,"] : []), ...importedOptions.map((name) => ` ${name},`), " manifest,", " clientEntry,", " styles,", " routeStyles,", " cssLoading,", ...(options.copyChunks === true ? [" copyChunks,"] : []), ...(backendImport !== undefined ? [" api: inProcessClient(backend),"] : []), ` title: ${JSON.stringify(title)},`,
1040
1072
  // Core's own `{ header }` trust, so the mounted backend and every loader see the derived caller.
1041
1073
  ...(clientIpHeader !== undefined
1042
1074
  ? [` server: { clientIp: { header: ${JSON.stringify(clientIpHeader)} } },`]
@@ -1059,28 +1091,203 @@ export function generateServerEntry(options) {
1059
1091
  // bun / node / deno self-host AND serve the client bundle from disk (it sits next to this entry).
1060
1092
  lines.push("// The client bundle and public files live next to this entry; serve only manifest-approved paths.", 'const STATIC_ROOT = new URL("./", import.meta.url)', `const PUBLIC_FILES = new Set(${JSON.stringify(publicFiles)})`, ...(hashedFiles !== undefined
1061
1093
  ? [`const HASHED_FILES = new Set(${JSON.stringify(hashedFiles)})`]
1062
- : []), "const staticPath = (pathname: string): string | undefined => {", " if (PUBLIC_FILES.has(pathname)) return '.' + pathname", ' if (!pathname.startsWith("/assets/")) return undefined', ' const segments = pathname.slice(1).split("/")', " // `.` and `..` match the character class, and the path is joined onto a base URL that resolves", " // dot segments - so they are rejected explicitly rather than relying on the request URL having", " // been normalized upstream.", " const safe = (segment: string): boolean =>", ' segment !== "." && segment !== ".." && /^[A-Za-z0-9._-]+$/.test(segment)', " return segments.every(safe) ? '.' + pathname : undefined", "}",
1063
- // The type table and cache policy `servePublicDir` uses. Without a known type, Bun answers a file
1064
- // as an `application/octet-stream` attachment, which a browser downloads instead of showing.
1065
- `const TYPES: Record<string, string> = ${JSON.stringify(CONTENT_TYPES)}`, "const staticHeaders = (pathname: string): Record<string, string> => ({", ' "content-type": TYPES[pathname.slice(pathname.lastIndexOf(".")).toLowerCase()] ?? "application/octet-stream",', ` "cache-control": ${hashedFiles !== undefined ? "HASHED_FILES.has(pathname)" : 'pathname.startsWith("/assets/")'} ? ${JSON.stringify(IMMUTABLE)} : ${JSON.stringify(ONE_DAY)},`, ' "x-content-type-options": "nosniff",', "})");
1094
+ : []));
1095
+ if (target === "node") {
1096
+ // The app itself with the adapter's own reader, not a `{ fetch }` wrapper around app.fetch: the
1097
+ // wrapper hid the node-direct lane, so every page built an undici Request and drained a Response,
1098
+ // and every file went through one too. The root mount serves only the files the build listed.
1099
+ lines.push("await serve(app, {", " port: Number(process.env.PORT ?? 3000),", " static: [", " {", ' dir: fileURLToPath(new URL("./assets/", import.meta.url)),', ' prefix: "/assets",', ...(hashedFiles !== undefined ? [" hashedFiles: HASHED_FILES,"] : []), ` cacheControl: ${JSON.stringify(ONE_DAY)},`, " },", " {", " dir: fileURLToPath(STATIC_ROOT),", ' prefix: "/",', " files: PUBLIC_FILES,", " immutable: false,", ` cacheControl: ${JSON.stringify(ONE_DAY)},`, " },", " ],", "})");
1100
+ return `${lines.join("\n")}\n`;
1101
+ }
1102
+ lines.push("const staticPath = (pathname: string): string | undefined => {", " if (PUBLIC_FILES.has(pathname)) return '.' + pathname", ' if (!pathname.startsWith("/assets/")) return undefined', ' const segments = pathname.slice(1).split("/")', " // `.` and `..` match the character class, and the path is joined onto a base URL that resolves", " // dot segments - so they are rejected explicitly rather than relying on the request URL having", " // been normalized upstream.", " const safe = (segment: string): boolean =>", ' segment !== "." && segment !== ".." && /^[A-Za-z0-9._-]+$/.test(segment)', " return segments.every(safe) ? '.' + pathname : undefined", "}",
1103
+ // The handler `nifra dev` serves public/ with: its type table, nosniff and cache policy, content
1104
+ // tags, byte ranges and conditional requests, and a descriptor opened only once the real path is
1105
+ // inside the root, so a link planted in the deployed tree cannot reach a file outside it.
1106
+ ...(target === "bun"
1107
+ ? [
1108
+ "const serveStatic = servePublicDir({",
1109
+ " dir: fileURLToPath(STATIC_ROOT),",
1110
+ ...(hashedFiles !== undefined ? [" hashedFiles: HASHED_FILES,"] : []),
1111
+ "})",
1112
+ "const staticResponse = async (req: Request): Promise<Response> =>",
1113
+ ' (await serveStatic(req)) ?? new Response("not found", { status: 404 })',
1114
+ ]
1115
+ : denoStaticResponse(hashedFiles !== undefined)));
1066
1116
  // Bun.serve and Deno.serve delimit the body themselves, so its declared length is the transport
1067
1117
  // frame - the guarantee core's own listen() and the Deno adapter mark (see markTrustedBodyFraming).
1068
1118
  const markFramed = '(req as unknown as Record<symbol, unknown>)[Symbol.for("nifra.body.trustedFraming")] = true';
1069
1119
  if (target === "bun") {
1070
- lines.push("const server = Bun.serve({", " port: Number(Bun.env.PORT ?? 3000),", " async fetch(req, server) {", " const { pathname } = new URL(req.url)", " const filePath = staticPath(pathname)", " if (filePath !== undefined) {", " const file = Bun.file(new URL(filePath, STATIC_ROOT))", ' if (!(await file.exists())) return new Response("not found", { status: 404 })', " return new Response(file, { headers: staticHeaders(pathname) })", " }", ` ${markFramed}`, " // The socket peer, looked up only when something reads it (as core's own listen() does).", " return app.fetch(req, { get clientIp() { return server.requestIP(req)?.address } })", " },", "})",
1120
+ lines.push("const server = Bun.serve({", " port: Number(Bun.env.PORT ?? 3000),", " async fetch(req, server) {", " const { pathname } = new URL(req.url)",
1121
+ // Braced: the next emitted line opens with `(`, which would otherwise call the returned value.
1122
+ " if (staticPath(pathname) !== undefined) {", " return staticResponse(req)", " }", ` ${markFramed}`, " // The socket peer, looked up only when something reads it (as core's own listen() does).", " return app.fetch(req, { get clientIp() { return server.requestIP(req)?.address } })", " },", "})",
1071
1123
  // The `${...}` here is literal OUTPUT (a template in the GENERATED file), not a template in this
1072
1124
  // source - split so biome's noTemplateCurlyInString doesn't flag it; the emitted line is unchanged.
1073
1125
  `console.log(\`nifra (Bun) → http://localhost:$${"{server.port}"}\`)`);
1074
1126
  return `${lines.join("\n")}\n`;
1075
1127
  }
1076
- if (target === "node") {
1077
- lines.push('import { readFile } from "node:fs/promises"', "await serve(", " {", " async fetch(req, platform) {", " const { pathname } = new URL(req.url)", " const filePath = staticPath(pathname)", " if (filePath !== undefined) {", " try {", " const body = await readFile(new URL(filePath, STATIC_ROOT))", " return new Response(body, { headers: staticHeaders(pathname) })", " } catch {", ' return new Response("not found", { status: 404 })', " }", " }", " return app.fetch(req, platform)", " },", " },", " { port: Number(process.env.PORT ?? 3000) },", ")");
1078
- return `${lines.join("\n")}\n`;
1079
- }
1080
1128
  // deno
1081
- lines.push("// @ts-ignore - Deno global is present on the Deno runtime this output targets.", 'Deno.serve({ port: Number(Deno.env.get("PORT") ?? "3000") }, async (req, info) => {', " const { pathname } = new URL(req.url)", " const filePath = staticPath(pathname)", " if (filePath !== undefined) {", " try {", " // @ts-ignore - Deno.readFile is present on the Deno runtime.", " const body = await Deno.readFile(new URL(filePath, STATIC_ROOT))", " return new Response(body, { headers: staticHeaders(pathname) })", " } catch {", ' return new Response("not found", { status: 404 })', " }", " }", ` ${markFramed}`, " return app.fetch(req, { clientIp: info.remoteAddr.hostname })", "})");
1129
+ lines.push("// @ts-ignore - Deno global is present on the Deno runtime this output targets.", 'Deno.serve({ port: Number(Deno.env.get("PORT") ?? "3000") }, async (req, info) => {', " const { pathname } = new URL(req.url)", " const filePath = staticPath(pathname)", " if (filePath !== undefined) {", " return staticResponse(req, filePath, pathname)", " }", ` ${markFramed}`, " return app.fetch(req, { clientIp: info.remoteAddr.hostname })", "})");
1082
1130
  return `${lines.join("\n")}\n`;
1083
1131
  }
1132
+ /**
1133
+ * The Deno entry's static handler: `servePublicDir`'s checks and answers on Deno's file API. The real
1134
+ * path must sit inside the root and cross no hidden segment, and the opened file must still be the one
1135
+ * that path names, so a link planted in the deployed tree cannot reach a file outside it or a `.env`
1136
+ * inside it. The file is typed by its real path, tagged by its content and dated only by an mtime its
1137
+ * own last write set, through the same policy as servePublicDir: a current cached copy is a 304, and a
1138
+ * range survives only an `If-Range` naming that tag. A file is read once to tag it, and one small
1139
+ * enough is then served from those bytes; otherwise one byte range is served from a seek, several as
1140
+ * the whole file.
1141
+ */
1142
+ function denoStaticResponse(hashed) {
1143
+ return [
1144
+ "const staticHeaders = (pathname: string, file: string): Record<string, string> => ({",
1145
+ ' "content-type": contentTypeOf(file),',
1146
+ ` "cache-control": ${hashed ? "HASHED_FILES.has(pathname)" : 'pathname.startsWith("/assets/")'} ? ${JSON.stringify(IMMUTABLE)} : ${JSON.stringify(ONE_DAY)},`,
1147
+ ' "x-content-type-options": "nosniff",',
1148
+ ' "accept-ranges": "bytes",',
1149
+ "})",
1150
+ "// @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1151
+ "const STATIC_REAL: string = await Deno.realPath(STATIC_ROOT)",
1152
+ "// One per process: the tag of a file is remembered by its real path.",
1153
+ "const STATIC_TAGS = contentTags(createHash)",
1154
+ "// Up to this many bytes are read whole to tag a file, and the response is then served from them.",
1155
+ "const STATIC_INLINE_MAX = 256 * 1024",
1156
+ "const staticResponse = async (req: Request, filePath: string, pathname: string): Promise<Response> => {",
1157
+ ' const missing = () => new Response("not found", { status: 404 })',
1158
+ ' if (req.method !== "GET" && req.method !== "HEAD") return missing()',
1159
+ " let file, info",
1160
+ ' let real = ""',
1161
+ " try {",
1162
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1163
+ " real = await Deno.realPath(new URL(filePath, STATIC_ROOT))",
1164
+ ' if (!real.startsWith(STATIC_REAL + (STATIC_REAL.includes("\\\\") ? "\\\\" : "/"))) return missing()',
1165
+ " if (isHiddenPath(real.slice(STATIC_REAL.length + 1))) return missing()",
1166
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1167
+ " file = await Deno.open(real)",
1168
+ " // Deno.open follows links, so a directory swapped for one after the check above redirects it.",
1169
+ " // The opened file must be the one at `real`, which must still resolve to itself.",
1170
+ " const opened = await file.stat()",
1171
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1172
+ " const named = await Deno.stat(real)",
1173
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1174
+ " if (opened.dev !== named.dev || opened.ino !== named.ino || (await Deno.realPath(real)) !== real) {",
1175
+ " file.close()",
1176
+ " return missing()",
1177
+ " }",
1178
+ " info = opened",
1179
+ " } catch {",
1180
+ " file?.close()",
1181
+ " return missing()",
1182
+ " }",
1183
+ " if (!info.isFile) {",
1184
+ " file.close()",
1185
+ " return missing()",
1186
+ " }",
1187
+ " const size: number = info.size",
1188
+ " const headers = new Headers(staticHeaders(pathname, real))",
1189
+ " // Deno reports a field the platform does not keep as null; NaN never matches a remembered version.",
1190
+ " const version = {",
1191
+ " dev: info.dev,",
1192
+ " ino: info.ino ?? Number.NaN,",
1193
+ " size,",
1194
+ " mtimeMs: info.mtime?.getTime() ?? Number.NaN,",
1195
+ " ctimeMs: info.ctime?.getTime() ?? Number.NaN,",
1196
+ " }",
1197
+ " let whole: Uint8Array<ArrayBuffer> | undefined",
1198
+ " let moved = false",
1199
+ " let etag: string | undefined",
1200
+ " try {",
1201
+ " etag = STATIC_TAGS.cached(real, version)",
1202
+ " if (etag === undefined && size <= STATIC_INLINE_MAX) {",
1203
+ " whole = new Uint8Array(size)",
1204
+ " let filled = 0",
1205
+ " while (filled < size) {",
1206
+ " const read: number | null = await file.read(whole.subarray(filled))",
1207
+ " if (read === null || read === 0) break",
1208
+ " filled += read",
1209
+ " }",
1210
+ " if (filled < size) whole = whole.subarray(0, filled)",
1211
+ " etag = STATIC_TAGS.ofBytes(real, version, whole)",
1212
+ " }",
1213
+ " etag ??= await STATIC_TAGS.ofFile(real, version, async (bytes: Uint8Array, position: number) => {",
1214
+ " moved = true",
1215
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1216
+ " await file.seek(position, Deno.SeekMode.Start)",
1217
+ " return (await file.read(bytes)) ?? 0",
1218
+ " })",
1219
+ " } catch {",
1220
+ " file.close()",
1221
+ " return missing()",
1222
+ " }",
1223
+ ' if (etag !== undefined) headers.set("etag", etag)',
1224
+ " const lastModified = lastModifiedOf(version)",
1225
+ ' if (lastModified !== undefined) headers.set("last-modified", new Date(lastModified).toUTCString())',
1226
+ ' if (isNotModified(req.headers.get("if-none-match"), req.headers.get("if-modified-since"), etag, lastModified)) {',
1227
+ " file.close()",
1228
+ ' headers.delete("content-type")',
1229
+ " return new Response(null, { status: 304, headers })",
1230
+ " }",
1231
+ ' const rangeHeader = req.method === "GET" ? req.headers.get("range") : null',
1232
+ " const range =",
1233
+ ' rangeHeader !== null && ifRangeAllowsPartial(req.headers.get("if-range"), etag, lastModified)',
1234
+ " ? parseByteRange(rangeHeader, size)",
1235
+ ' : { kind: "none" as const }',
1236
+ ' if (req.method === "HEAD" || range.kind === "unsatisfiable") {',
1237
+ " file.close()",
1238
+ ' if (range.kind !== "unsatisfiable") {',
1239
+ ' headers.set("content-length", String(size))',
1240
+ " return new Response(null, { headers })",
1241
+ " }",
1242
+ ' headers.delete("content-type")',
1243
+ ' headers.set("content-range", "bytes */" + size)',
1244
+ " return new Response(null, { status: 416, headers })",
1245
+ " }",
1246
+ ' if (range.kind === "satisfiable" && range.ranges.length === 1) {',
1247
+ " const { start, end } = range.ranges[0]!",
1248
+ ' headers.set("content-range", "bytes " + start + "-" + end + "/" + size)',
1249
+ " if (whole !== undefined) {",
1250
+ " file.close()",
1251
+ " const part = whole.subarray(start, end + 1)",
1252
+ ' headers.set("content-length", String(part.byteLength))',
1253
+ " return new Response(part, { status: 206, headers })",
1254
+ " }",
1255
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1256
+ " await file.seek(start, Deno.SeekMode.Start)",
1257
+ " let left = end - start + 1",
1258
+ ' headers.set("content-length", String(left))',
1259
+ " const body = new ReadableStream<Uint8Array>({",
1260
+ " async pull(controller) {",
1261
+ " const chunk = new Uint8Array(Math.min(left, 65_536))",
1262
+ " const read: number | null = await file.read(chunk)",
1263
+ " if (read === null || read === 0) {",
1264
+ " file.close()",
1265
+ " controller.close()",
1266
+ " return",
1267
+ " }",
1268
+ " controller.enqueue(chunk.subarray(0, read))",
1269
+ " left -= read",
1270
+ " if (left <= 0) {",
1271
+ " file.close()",
1272
+ " controller.close()",
1273
+ " }",
1274
+ " },",
1275
+ " cancel: () => file.close(),",
1276
+ " })",
1277
+ " return new Response(body, { status: 206, headers })",
1278
+ " }",
1279
+ " if (whole !== undefined) {",
1280
+ " file.close()",
1281
+ ' headers.set("content-length", String(whole.byteLength))',
1282
+ " return new Response(whole, { headers })",
1283
+ " }",
1284
+ " // @ts-ignore - Deno global is present on the Deno runtime this output targets.",
1285
+ " if (moved) await file.seek(0, Deno.SeekMode.Start)",
1286
+ ' headers.set("content-length", String(size))',
1287
+ " return new Response(file.readable, { headers })",
1288
+ "}",
1289
+ ];
1290
+ }
1084
1291
  /** Measure each emitted output file's raw + gzip size. Reads the file off disk and gzips it with
1085
1292
  * `Bun.gzipSync` (the over-the-wire weight). Async only because it reads files; the aggregation is the
1086
1293
  * pure {@link aggregateSizeReport}. */
@@ -1161,6 +1368,36 @@ function assertNoPublicCollision(plan, publicFiles, outDir) {
1161
1368
  `build's own output in ${outDir}. Rename or move them out of the public directory.`);
1162
1369
  }
1163
1370
  }
1371
+ /** Prerender a self-hosting build's opted-in routes into `dir` and return the pages its server may
1372
+ * answer from their file: those whose render was not specific to its request. */
1373
+ async function prerenderForServer(prerenderApp, client, routesDir, dir, publicFiles, secrets) {
1374
+ const owned = `/${PRERENDERED_DIR}/`;
1375
+ const clashes = publicFiles.filter((file) => file.toLowerCase().startsWith(owned));
1376
+ if (clashes.length > 0) {
1377
+ throw new Error(`[nifra/web] public file(s) ${clashes.join(", ")} sit under ${owned}, where the build writes its ` +
1378
+ "prerendered pages. Rename or move them out of the public directory.");
1379
+ }
1380
+ let result;
1381
+ try {
1382
+ result = await prerenderRoutes({
1383
+ app: await prerenderApp(client),
1384
+ routes: discoverRoutes(routesDir).routes,
1385
+ outDir: dir,
1386
+ secrets,
1387
+ });
1388
+ }
1389
+ catch (error) {
1390
+ // Prerendering loads every route module at build time, which a server build never needed before:
1391
+ // one that cannot load there (a connection opened on import, an env var only production sets) costs
1392
+ // the speedup, not the build. A secret in a page still fails it.
1393
+ if (error instanceof SecretScanError)
1394
+ throw error;
1395
+ rmSync(dir, { recursive: true, force: true });
1396
+ console.warn(`[nifra/web] prerendering failed, so the server renders every page on demand: ${error instanceof Error ? error.message : String(error)}`);
1397
+ return {};
1398
+ }
1399
+ return servablePrerenderedPages(result.prerendered);
1400
+ }
1164
1401
  /** Build a full deploy dir for `target` using the default Bun bundler. See {@link buildTargetWith}. */
1165
1402
  export async function buildTarget(target, options) {
1166
1403
  return buildTargetWith(target, options, bunBundler);
@@ -1186,6 +1423,12 @@ export async function buildTargetWith(target, options, bundler) {
1186
1423
  ];
1187
1424
  assertDisposableDir("outDir", outDir, sources);
1188
1425
  assertDisposableDir("workDir", workDir, sources);
1426
+ // Before anything is cleared, so a refused build leaves the last deploy dir in place.
1427
+ if (targetPlan.target === "node") {
1428
+ const refusal = nodeAdapterRefusal(workDir);
1429
+ if (refusal !== undefined)
1430
+ throw new Error(refusal);
1431
+ }
1189
1432
  const { rmSync } = await import("node:fs");
1190
1433
  rmSync(outDir, { recursive: true, force: true });
1191
1434
  rmSync(workDir, { recursive: true, force: true });
@@ -1243,6 +1486,12 @@ export async function buildTargetWith(target, options, bundler) {
1243
1486
  if (requestedCssLoading !== undefined || publicFiles.length > 0) {
1244
1487
  writeFileSync(`${assetsDir}/manifest.json`, JSON.stringify(client, null, 2));
1245
1488
  }
1489
+ // A self-hosting server answers its prerendered pages from their file. The static target is all
1490
+ // prerendered pages; the edge targets keep rendering them on demand.
1491
+ const prerenderedPages = options.prerenderApp !== undefined &&
1492
+ (targetPlan.target === "bun" || targetPlan.target === "node")
1493
+ ? await prerenderForServer(options.prerenderApp, client, routesDir, `${staticRoot}/${PRERENDERED_DIR}`, publicFiles, secretScan)
1494
+ : undefined;
1246
1495
  if (targetPlan.kind === "static") {
1247
1496
  if (options.prerenderApp === undefined) {
1248
1497
  throw new Error("[nifra/web] buildTarget(static) requires `prerenderApp` (a factory `(client) => createWebApp`)");
@@ -1289,6 +1538,7 @@ export async function buildTargetWith(target, options, bundler) {
1289
1538
  hashedFiles: client.assets,
1290
1539
  ...(options.clientIp !== undefined ? { clientIp: options.clientIp } : {}),
1291
1540
  ...(client.copyChunks !== undefined ? { copyChunks: true } : {}),
1541
+ ...(prerenderedPages !== undefined ? { prerenderedPages } : {}),
1292
1542
  }));
1293
1543
  const { worker } = await bundler.buildServer({
1294
1544
  routesDir,