@nifrajs/web 3.3.0 → 4.0.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.
- package/dist/backend-only.d.ts +15 -0
- package/dist/backend-only.d.ts.map +1 -0
- package/dist/backend-only.js +16 -0
- package/dist/backend-only.js.map +1 -0
- package/dist/boundary.d.ts +5 -0
- package/dist/boundary.d.ts.map +1 -1
- package/dist/boundary.js +11 -2
- package/dist/boundary.js.map +1 -1
- package/dist/build-plan.d.ts +35 -6
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +89 -35
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +18 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +97 -17
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +72 -23
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +411 -98
- package/dist/build.js.map +1 -1
- package/dist/bun-dev-entry.d.ts +1 -1
- package/dist/bun-dev-entry.js +1 -1
- package/dist/cdn.d.ts +74 -0
- package/dist/cdn.d.ts.map +1 -0
- package/dist/cdn.js +132 -0
- package/dist/cdn.js.map +1 -0
- package/dist/client.d.ts +21 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +226 -15
- package/dist/client.js.map +1 -1
- package/dist/conformance.d.ts +4 -3
- package/dist/conformance.d.ts.map +1 -1
- package/dist/conformance.js +12 -3
- package/dist/conformance.js.map +1 -1
- package/dist/csp.d.ts +93 -0
- package/dist/csp.d.ts.map +1 -0
- package/dist/csp.js +122 -0
- package/dist/csp.js.map +1 -0
- package/dist/css-contract.d.ts +21 -0
- package/dist/css-contract.d.ts.map +1 -0
- package/dist/css-contract.js +28 -0
- package/dist/css-contract.js.map +1 -0
- package/dist/dev-client.d.ts +76 -0
- package/dist/dev-client.d.ts.map +1 -0
- package/dist/dev-client.js +280 -0
- package/dist/dev-client.js.map +1 -0
- package/dist/dev-diagnostics.d.ts +26 -2
- package/dist/dev-diagnostics.d.ts.map +1 -1
- package/dist/dev-diagnostics.js +38 -7
- package/dist/dev-diagnostics.js.map +1 -1
- package/dist/dev-error.d.ts +4 -13
- package/dist/dev-error.d.ts.map +1 -1
- package/dist/dev-error.js +46 -4
- package/dist/dev-error.js.map +1 -1
- package/dist/dev-feed.d.ts +298 -0
- package/dist/dev-feed.d.ts.map +1 -0
- package/dist/dev-feed.js +797 -0
- package/dist/dev-feed.js.map +1 -0
- package/dist/dev-indicator.d.ts +38 -0
- package/dist/dev-indicator.d.ts.map +1 -0
- package/dist/dev-indicator.js +93 -0
- package/dist/dev-indicator.js.map +1 -0
- package/dist/dev-session.d.ts +77 -0
- package/dist/dev-session.d.ts.map +1 -0
- package/dist/dev-session.js +580 -0
- package/dist/dev-session.js.map +1 -0
- package/dist/dev-sourcemap.d.ts +27 -0
- package/dist/dev-sourcemap.d.ts.map +1 -0
- package/dist/dev-sourcemap.js +235 -0
- package/dist/dev-sourcemap.js.map +1 -0
- package/dist/dev-ssr-graph.d.ts.map +1 -1
- package/dist/dev-ssr-graph.js +13 -5
- package/dist/dev-ssr-graph.js.map +1 -1
- package/dist/dev.d.ts +15 -2
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +69 -44
- package/dist/dev.js.map +1 -1
- package/dist/diagnostic-catalog.d.ts +35 -0
- package/dist/diagnostic-catalog.d.ts.map +1 -0
- package/dist/diagnostic-catalog.js +203 -0
- package/dist/diagnostic-catalog.js.map +1 -0
- package/dist/diagnostic-prompt.d.ts +49 -0
- package/dist/diagnostic-prompt.d.ts.map +1 -0
- package/dist/diagnostic-prompt.js +303 -0
- package/dist/diagnostic-prompt.js.map +1 -0
- package/dist/diagnostic.d.ts +17 -23
- package/dist/diagnostic.d.ts.map +1 -1
- package/dist/diagnostic.js +31 -39
- package/dist/diagnostic.js.map +1 -1
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +4 -1
- package/dist/draft.js.map +1 -1
- package/dist/fn.d.ts +5 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +13 -3
- package/dist/fn.js.map +1 -1
- package/dist/fs.d.ts.map +1 -1
- package/dist/fs.js +4 -2
- package/dist/fs.js.map +1 -1
- package/dist/index.d.ts +15 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -12
- package/dist/index.js.map +1 -1
- package/dist/internal/cdn/cloudflare.d.ts +48 -0
- package/dist/internal/cdn/cloudflare.d.ts.map +1 -0
- package/dist/internal/cdn/cloudflare.js +94 -0
- package/dist/internal/cdn/cloudflare.js.map +1 -0
- package/dist/internal/cdn/core.d.ts +91 -0
- package/dist/internal/cdn/core.d.ts.map +1 -0
- package/dist/internal/cdn/core.js +220 -0
- package/dist/internal/cdn/core.js.map +1 -0
- package/dist/internal/cdn/fastly.d.ts +17 -0
- package/dist/internal/cdn/fastly.d.ts.map +1 -0
- package/dist/internal/cdn/fastly.js +43 -0
- package/dist/internal/cdn/fastly.js.map +1 -0
- package/dist/internal/cdn/vercel.d.ts +31 -0
- package/dist/internal/cdn/vercel.d.ts.map +1 -0
- package/dist/internal/cdn/vercel.js +56 -0
- package/dist/internal/cdn/vercel.js.map +1 -0
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/codegen.d.ts +3 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +151 -30
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/dev-csp.d.ts +24 -0
- package/dist/internal/dev-csp.d.ts.map +1 -0
- package/dist/internal/dev-csp.js +160 -0
- package/dist/internal/dev-csp.js.map +1 -0
- package/dist/internal/dev-reserved.d.ts +25 -0
- package/dist/internal/dev-reserved.d.ts.map +1 -0
- package/dist/internal/dev-reserved.js +39 -0
- package/dist/internal/dev-reserved.js.map +1 -0
- package/dist/internal/html-spans.d.ts +10 -0
- package/dist/internal/html-spans.d.ts.map +1 -0
- package/dist/internal/html-spans.js +46 -0
- package/dist/internal/html-spans.js.map +1 -0
- package/dist/internal/loading-runtime.d.ts +44 -0
- package/dist/internal/loading-runtime.d.ts.map +1 -0
- package/dist/internal/loading-runtime.js +206 -0
- package/dist/internal/loading-runtime.js.map +1 -0
- package/dist/internal/matches-runtime.d.ts +8 -0
- package/dist/internal/matches-runtime.d.ts.map +1 -0
- package/dist/internal/matches-runtime.js +61 -0
- package/dist/internal/matches-runtime.js.map +1 -0
- package/dist/internal/mount-shadow.d.ts +36 -0
- package/dist/internal/mount-shadow.d.ts.map +1 -0
- package/dist/internal/mount-shadow.js +65 -0
- package/dist/internal/mount-shadow.js.map +1 -0
- package/dist/internal/node-builtins.d.ts +3 -0
- package/dist/internal/node-builtins.d.ts.map +1 -0
- package/dist/internal/node-builtins.js +79 -0
- package/dist/internal/node-builtins.js.map +1 -0
- package/dist/internal/output-guard.d.ts +65 -0
- package/dist/internal/output-guard.d.ts.map +1 -0
- package/dist/internal/output-guard.js +630 -0
- package/dist/internal/output-guard.js.map +1 -0
- package/dist/internal/page-execution.d.ts +69 -0
- package/dist/internal/page-execution.d.ts.map +1 -0
- package/dist/internal/page-execution.js +1021 -0
- package/dist/internal/page-execution.js.map +1 -0
- package/dist/internal/parity.d.ts +21 -0
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +104 -22
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +12 -0
- package/dist/internal/private-env.d.ts.map +1 -0
- package/dist/internal/private-env.js +335 -0
- package/dist/internal/private-env.js.map +1 -0
- package/dist/internal/public-url.d.ts +3 -0
- package/dist/internal/public-url.d.ts.map +1 -0
- package/dist/internal/public-url.js +18 -0
- package/dist/internal/public-url.js.map +1 -0
- package/dist/internal/render-document.d.ts +37 -21
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +104 -65
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/request-url.d.ts +3 -0
- package/dist/internal/request-url.d.ts.map +1 -0
- package/dist/internal/request-url.js +14 -0
- package/dist/internal/request-url.js.map +1 -0
- package/dist/internal/response-controls.d.ts +75 -0
- package/dist/internal/response-controls.d.ts.map +1 -0
- package/dist/internal/response-controls.js +256 -0
- package/dist/internal/response-controls.js.map +1 -0
- package/dist/internal/runtime-contract.d.ts +11 -16
- package/dist/internal/runtime-contract.d.ts.map +1 -1
- package/dist/internal/runtime-contract.js.map +1 -1
- package/dist/internal/secret-scan.d.ts +104 -0
- package/dist/internal/secret-scan.d.ts.map +1 -0
- package/dist/internal/secret-scan.js +485 -0
- package/dist/internal/secret-scan.js.map +1 -0
- package/dist/internal/server-boundary.d.ts +6 -29
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +6 -66
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/source-map.d.ts +47 -0
- package/dist/internal/source-map.d.ts.map +1 -0
- package/dist/internal/source-map.js +225 -0
- package/dist/internal/source-map.js.map +1 -0
- package/dist/internal/target-compat.d.ts +27 -0
- package/dist/internal/target-compat.d.ts.map +1 -0
- package/dist/internal/target-compat.js +77 -0
- package/dist/internal/target-compat.js.map +1 -0
- package/dist/internal/timing-safe-equal.d.ts +6 -0
- package/dist/internal/timing-safe-equal.d.ts.map +1 -1
- package/dist/internal/timing-safe-equal.js +10 -0
- package/dist/internal/timing-safe-equal.js.map +1 -1
- package/dist/internal/vitals-runtime.d.ts +18 -0
- package/dist/internal/vitals-runtime.d.ts.map +1 -0
- package/dist/internal/vitals-runtime.js +38 -0
- package/dist/internal/vitals-runtime.js.map +1 -0
- package/dist/internal/web-app.d.ts +56 -8
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +215 -772
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/zone-graph.d.ts +89 -0
- package/dist/internal/zone-graph.d.ts.map +1 -0
- package/dist/internal/zone-graph.js +492 -0
- package/dist/internal/zone-graph.js.map +1 -0
- package/dist/isr.d.ts +83 -6
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +301 -39
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +3 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +37 -9
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +221 -14
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +386 -9
- package/dist/manifest.js.map +1 -1
- package/dist/module-graph.d.ts +26 -5
- package/dist/module-graph.d.ts.map +1 -1
- package/dist/module-graph.js +17 -7
- package/dist/module-graph.js.map +1 -1
- package/dist/navigation.d.ts +17 -3
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +11 -5
- package/dist/navigation.js.map +1 -1
- package/dist/nonce.d.ts +47 -0
- package/dist/nonce.d.ts.map +1 -0
- package/dist/nonce.js +63 -0
- package/dist/nonce.js.map +1 -0
- package/dist/plugins/css-modules.d.ts.map +1 -1
- package/dist/plugins/css-modules.js +4 -3
- package/dist/plugins/css-modules.js.map +1 -1
- package/dist/plugins/kit.d.ts +9 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +18 -0
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/svg.d.ts +22 -3
- package/dist/plugins/svg.d.ts.map +1 -1
- package/dist/plugins/svg.js +112 -1
- package/dist/plugins/svg.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +107 -3
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +378 -17
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts +61 -0
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -0
- package/dist/plugins/vite-zone-guard.js +208 -0
- package/dist/plugins/vite-zone-guard.js.map +1 -0
- package/dist/plugins/zone-guard.d.ts +27 -0
- package/dist/plugins/zone-guard.d.ts.map +1 -0
- package/dist/plugins/zone-guard.js +72 -0
- package/dist/plugins/zone-guard.js.map +1 -0
- package/dist/prerender.d.ts +10 -0
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +35 -3
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +9 -0
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +131 -48
- package/dist/public-dir.js.map +1 -1
- package/dist/pwa-manifest.d.ts +68 -0
- package/dist/pwa-manifest.d.ts.map +1 -0
- package/dist/pwa-manifest.js +178 -0
- package/dist/pwa-manifest.js.map +1 -0
- package/dist/query.js +4 -4
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +42 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +6 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-manifest.d.ts +3 -0
- package/dist/route-manifest.d.ts.map +1 -1
- package/dist/route-manifest.js +26 -3
- package/dist/route-manifest.js.map +1 -1
- package/dist/route-types.d.ts +24 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +195 -0
- package/dist/route-types.js.map +1 -0
- package/dist/router.d.ts +28 -6
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +129 -40
- package/dist/router.js.map +1 -1
- package/dist/trusted-html.d.ts +34 -0
- package/dist/trusted-html.d.ts.map +1 -0
- package/dist/trusted-html.js +35 -0
- package/dist/trusted-html.js.map +1 -0
- package/dist/vitals.d.ts +33 -0
- package/dist/vitals.d.ts.map +1 -0
- package/dist/vitals.js +16 -0
- package/dist/vitals.js.map +1 -0
- package/dist/vite.d.ts +16 -1
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +187 -36
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +70 -0
- package/dist/zones.d.ts.map +1 -0
- package/dist/zones.js +365 -0
- package/dist/zones.js.map +1 -0
- package/package.json +67 -14
- package/src/backend-only.ts +16 -0
- package/src/boundary.ts +18 -5
- package/src/build-plan.ts +113 -36
- package/src/build-vite.ts +124 -15
- package/src/build.ts +535 -102
- package/src/bun-dev-entry.ts +1 -1
- package/src/cdn.ts +216 -0
- package/src/client.ts +251 -18
- package/src/conformance.ts +16 -3
- package/src/csp.ts +194 -0
- package/src/css-contract.ts +44 -0
- package/src/dev-client.ts +349 -0
- package/src/dev-diagnostics.ts +60 -7
- package/src/dev-error.ts +49 -5
- package/src/dev-feed.ts +1140 -0
- package/src/dev-indicator.ts +113 -0
- package/src/dev-session.ts +729 -0
- package/src/dev-sourcemap.ts +281 -0
- package/src/dev-ssr-graph.ts +16 -5
- package/src/dev.ts +86 -43
- package/src/diagnostic-catalog.ts +243 -0
- package/src/diagnostic-prompt.ts +355 -0
- package/src/diagnostic.ts +63 -66
- package/src/draft.ts +4 -1
- package/src/fn.ts +19 -3
- package/src/fs.ts +13 -2
- package/src/index.ts +57 -10
- package/src/internal/cdn/cloudflare.ts +146 -0
- package/src/internal/cdn/core.ts +316 -0
- package/src/internal/cdn/fastly.ts +62 -0
- package/src/internal/cdn/vercel.ts +95 -0
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/codegen.ts +180 -29
- package/src/internal/dev-csp.ts +176 -0
- package/src/internal/dev-reserved.ts +52 -0
- package/src/internal/html-spans.ts +50 -0
- package/src/internal/loading-runtime.ts +244 -0
- package/src/internal/matches-runtime.ts +62 -0
- package/src/internal/mount-shadow.ts +88 -0
- package/src/internal/node-builtins.ts +79 -0
- package/src/internal/output-guard.ts +701 -0
- package/src/internal/page-execution.ts +1371 -0
- package/src/internal/parity.ts +150 -36
- package/src/internal/private-env.ts +334 -0
- package/src/internal/public-url.ts +22 -0
- package/src/internal/render-document.ts +153 -73
- package/src/internal/request-url.ts +14 -0
- package/src/internal/response-controls.ts +284 -0
- package/src/internal/runtime-contract.ts +10 -15
- package/src/internal/secret-scan.ts +623 -0
- package/src/internal/server-boundary.ts +6 -69
- package/src/internal/source-map.ts +256 -0
- package/src/internal/target-compat.ts +92 -0
- package/src/internal/timing-safe-equal.ts +13 -0
- package/src/internal/vitals-runtime.ts +53 -0
- package/src/internal/web-app.ts +290 -879
- package/src/internal/zone-graph.ts +593 -0
- package/src/isr.ts +366 -39
- package/src/llms-txt.ts +44 -8
- package/src/manifest.ts +671 -17
- package/src/module-graph.ts +53 -9
- package/src/navigation.ts +29 -6
- package/src/nonce.ts +104 -0
- package/src/plugins/css-modules.ts +5 -3
- package/src/plugins/kit.ts +24 -0
- package/src/plugins/svg.ts +135 -4
- package/src/plugins/vite-leak-guard.ts +529 -16
- package/src/plugins/vite-zone-guard.ts +263 -0
- package/src/plugins/zone-guard.ts +110 -0
- package/src/prerender.ts +49 -3
- package/src/public-dir.ts +135 -49
- package/src/pwa-manifest.ts +226 -0
- package/src/query.ts +4 -4
- package/src/render-seam.ts +46 -1
- package/src/route-manifest.ts +15 -3
- package/src/route-types.ts +264 -0
- package/src/router.ts +163 -63
- package/src/trusted-html.ts +44 -0
- package/src/vitals.ts +44 -0
- package/src/vite.ts +207 -36
- package/src/zones.ts +430 -0
- package/dist/plugins/vite-server-only.d.ts +0 -49
- package/dist/plugins/vite-server-only.d.ts.map +0 -1
- package/dist/plugins/vite-server-only.js +0 -57
- package/dist/plugins/vite-server-only.js.map +0 -1
- package/dist/server-only.d.ts +0 -21
- package/dist/server-only.d.ts.map +0 -1
- package/dist/server-only.js +0 -25
- package/dist/server-only.js.map +0 -1
- package/src/plugins/vite-server-only.ts +0 -75
- package/src/server-only.ts +0 -25
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The zone guard for Vite's dev server. A dev server serves source by URL, so the guard has to hold
|
|
3
|
+
* before any byte is sent, whatever the URL form:
|
|
4
|
+
*
|
|
5
|
+
* - a middleware ahead of Vite's own answers 403 for any request that maps to a file the browser may
|
|
6
|
+
* not have: a direct path, `/@fs/`, `?raw`, `?url`, `?import`, a `.map`, a pre-bundled dependency;
|
|
7
|
+
* - `resolveId` refuses a browser import of backend code at the importer, so the overlay names the line
|
|
8
|
+
* instead of the browser hitting a bare 403;
|
|
9
|
+
* - `transform` refuses whatever reaches the client environment by another route.
|
|
10
|
+
*
|
|
11
|
+
* The SSR environment loads the same files legitimately and is never checked here.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync, statSync } from "node:fs"
|
|
14
|
+
import type { IncomingMessage, ServerResponse } from "node:http"
|
|
15
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
|
16
|
+
import { privateEnvDenial } from "../internal/private-env.ts"
|
|
17
|
+
import {
|
|
18
|
+
browserDenial,
|
|
19
|
+
type Classification,
|
|
20
|
+
createZoneClassifier,
|
|
21
|
+
SERVER_PACKAGES,
|
|
22
|
+
specifierPackage,
|
|
23
|
+
} from "../zones.ts"
|
|
24
|
+
|
|
25
|
+
export interface ViteZoneGuardOptions {
|
|
26
|
+
/** The app root: the directory holding `routes/`, `frontend/`, `backend/` and `shared/`. */
|
|
27
|
+
readonly appRoot: string
|
|
28
|
+
/** The routes directory (default `<appRoot>/routes`). */
|
|
29
|
+
readonly routesDir?: string
|
|
30
|
+
/** Generated client modules (the dev entry). */
|
|
31
|
+
readonly generatedFiles?: readonly string[]
|
|
32
|
+
/** The public-env prefix browser code may read (default `"PUBLIC_"`). */
|
|
33
|
+
readonly publicEnvPrefix?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Zones whose code runs in a browser, so may read only public environment variables. */
|
|
37
|
+
const BROWSER_CODE = new Set<string>(["route-frontend", "frontend", "shared"])
|
|
38
|
+
|
|
39
|
+
type Next = (error?: unknown) => void
|
|
40
|
+
type Middleware = (req: IncomingMessage, res: ServerResponse, next: Next) => void
|
|
41
|
+
|
|
42
|
+
interface PluginContext {
|
|
43
|
+
readonly environment?: { readonly name?: string }
|
|
44
|
+
error(error: Error | string): never
|
|
45
|
+
resolve(
|
|
46
|
+
source: string,
|
|
47
|
+
importer: string | undefined,
|
|
48
|
+
options: { readonly skipSelf: boolean } & Record<string, unknown>,
|
|
49
|
+
): Promise<{ readonly id: string; readonly external?: boolean | string } | null>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The slice of a Vite plugin this returns. Structural, so `vite` stays an optional peer. */
|
|
53
|
+
export interface ViteZoneGuardPlugin {
|
|
54
|
+
readonly name: string
|
|
55
|
+
readonly enforce: "pre"
|
|
56
|
+
config(): { optimizeDeps: { exclude: string[] } }
|
|
57
|
+
configResolved(config: { readonly root: string }): void
|
|
58
|
+
configureServer(server: { readonly middlewares: { use(middleware: Middleware): void } }): void
|
|
59
|
+
resolveId(
|
|
60
|
+
this: PluginContext,
|
|
61
|
+
source: string,
|
|
62
|
+
importer: string | undefined,
|
|
63
|
+
options?: { readonly ssr?: boolean; readonly scan?: boolean },
|
|
64
|
+
): Promise<null>
|
|
65
|
+
transform(
|
|
66
|
+
this: PluginContext,
|
|
67
|
+
code: string,
|
|
68
|
+
id: string,
|
|
69
|
+
options?: { readonly ssr?: boolean },
|
|
70
|
+
): null
|
|
71
|
+
/**
|
|
72
|
+
* Why the module a request URL names failed to serve, when the guard refused one of its imports.
|
|
73
|
+
* Vite in middleware mode reports a transform error to the overlay and then passes the request on
|
|
74
|
+
* instead of answering it; the dev server answers with this.
|
|
75
|
+
*/
|
|
76
|
+
refusalFor(url: string): string | undefined
|
|
77
|
+
/** The source file a request URL names, when it names one. */
|
|
78
|
+
fileForUrl(url: string): string | undefined
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const stripQuery = (id: string): string => id.replace(/[?#].*$/, "")
|
|
82
|
+
/** Query forms that hand a module's source, or a URL to it, to the browser untransformed. */
|
|
83
|
+
const RAW_QUERY = /(?:^|&)(?:raw|url|inline)(?:[&=]|$)/
|
|
84
|
+
|
|
85
|
+
const isFile = (path: string): boolean => {
|
|
86
|
+
try {
|
|
87
|
+
return statSync(path).isFile()
|
|
88
|
+
} catch {
|
|
89
|
+
return false
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugin {
|
|
94
|
+
const classifier = createZoneClassifier(options)
|
|
95
|
+
let root = resolve(options.appRoot)
|
|
96
|
+
const display = (file: string): string => {
|
|
97
|
+
const rel = relative(classifier.appRoot, file).replaceAll("\\", "/")
|
|
98
|
+
return rel.startsWith("..") || isAbsolute(rel) ? file : rel
|
|
99
|
+
}
|
|
100
|
+
const isClient = (context: PluginContext, ssr: boolean | undefined): boolean =>
|
|
101
|
+
ssr !== true && (context.environment?.name ?? "client") === "client"
|
|
102
|
+
|
|
103
|
+
/** What a pre-bundled dependency file was built from, per Vite's `_metadata.json`. */
|
|
104
|
+
const depsSources = new Map<string, { mtime: number; sources: Map<string, string> }>()
|
|
105
|
+
const depSource = (file: string): string | undefined => {
|
|
106
|
+
const dir = dirname(file)
|
|
107
|
+
const metadata = join(dir, "_metadata.json")
|
|
108
|
+
let mtime: number
|
|
109
|
+
try {
|
|
110
|
+
mtime = statSync(metadata).mtimeMs
|
|
111
|
+
} catch {
|
|
112
|
+
return undefined
|
|
113
|
+
}
|
|
114
|
+
let cached = depsSources.get(dir)
|
|
115
|
+
if (cached === undefined || cached.mtime !== mtime) {
|
|
116
|
+
const sources = new Map<string, string>()
|
|
117
|
+
try {
|
|
118
|
+
const parsed = JSON.parse(readFileSync(metadata, "utf8")) as {
|
|
119
|
+
optimized?: Record<string, { src?: string; file?: string }>
|
|
120
|
+
}
|
|
121
|
+
for (const dep of Object.values(parsed.optimized ?? {})) {
|
|
122
|
+
if (dep.src !== undefined && dep.file !== undefined) {
|
|
123
|
+
sources.set(resolve(dir, dep.file), resolve(dir, dep.src))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
} catch {
|
|
127
|
+
// An unreadable metadata file maps nothing; the request then classifies as the cache file.
|
|
128
|
+
}
|
|
129
|
+
cached = { mtime, sources }
|
|
130
|
+
depsSources.set(dir, cached)
|
|
131
|
+
}
|
|
132
|
+
return cached.sources.get(file)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** The file a request URL serves, or `undefined` when it names none (an app route, a Vite internal). */
|
|
136
|
+
const fileForUrl = (url: string): string | undefined => {
|
|
137
|
+
let path: string
|
|
138
|
+
try {
|
|
139
|
+
path = decodeURIComponent(stripQuery(url))
|
|
140
|
+
} catch {
|
|
141
|
+
return undefined
|
|
142
|
+
}
|
|
143
|
+
let file: string
|
|
144
|
+
if (path.startsWith("/@fs/")) file = path.slice("/@fs".length).replace(/^\/([a-zA-Z]:)/, "$1")
|
|
145
|
+
else if (path.startsWith("/@id/")) {
|
|
146
|
+
const id = path.slice("/@id/".length)
|
|
147
|
+
if (!isAbsolute(id)) return undefined
|
|
148
|
+
file = id
|
|
149
|
+
} else if (path.startsWith("/@")) return undefined
|
|
150
|
+
else file = join(root, path)
|
|
151
|
+
for (const candidate of [file, file.replace(/\.map$/, "")]) {
|
|
152
|
+
// One spelling per file: Vite names it `C:/app/x.ts` on Windows, `join` names it `C:\app\x.ts`.
|
|
153
|
+
if (isFile(candidate)) return resolve(candidate)
|
|
154
|
+
}
|
|
155
|
+
return undefined
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const middleware: Middleware = (req, res, next) => {
|
|
159
|
+
const url = req.url ?? "/"
|
|
160
|
+
const file = fileForUrl(url)
|
|
161
|
+
if (file === undefined) return next()
|
|
162
|
+
const source = file.replaceAll("\\", "/").includes("/node_modules/.vite/")
|
|
163
|
+
? (depSource(file) ?? file)
|
|
164
|
+
: file
|
|
165
|
+
const classification = classifier.classify(source)
|
|
166
|
+
let reason = browserDenial(classification)
|
|
167
|
+
const query = url.includes("?") ? url.slice(url.indexOf("?") + 1) : ""
|
|
168
|
+
if (reason === undefined && classification.zone === "fn" && RAW_QUERY.test(query)) {
|
|
169
|
+
reason = "a server-function module reaches the browser only as its generated stub"
|
|
170
|
+
}
|
|
171
|
+
if (reason === undefined) return next()
|
|
172
|
+
res.statusCode = 403
|
|
173
|
+
res.setHeader("content-type", "text/plain; charset=utf-8")
|
|
174
|
+
res.setHeader("cache-control", "no-store")
|
|
175
|
+
res.end(`[nifra] ${display(source)} may not reach the browser: ${reason}\n`)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const refusals = new Map<string, string>()
|
|
179
|
+
const refuse = (
|
|
180
|
+
context: PluginContext,
|
|
181
|
+
file: string,
|
|
182
|
+
reason: string,
|
|
183
|
+
importer?: string,
|
|
184
|
+
): never => {
|
|
185
|
+
const message = `[nifra/web] ${display(file)} may not reach the browser${importer === undefined ? "" : ` (imported by ${display(importer)})`}: ${reason}`
|
|
186
|
+
refusals.set(resolve(importer ?? file), message)
|
|
187
|
+
return context.error(new Error(message))
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** A bare specifier's package, classified through its manifest so a workspace link is first-party. */
|
|
191
|
+
const packageClassification = (name: string, importer: string): Classification => {
|
|
192
|
+
let dir = dirname(importer)
|
|
193
|
+
for (;;) {
|
|
194
|
+
const manifest = join(dir, "node_modules", name, "package.json")
|
|
195
|
+
if (existsSync(manifest)) return classifier.classify(manifest)
|
|
196
|
+
const parent = dirname(dir)
|
|
197
|
+
if (parent === dir) return { zone: "library", packageName: name }
|
|
198
|
+
dir = parent
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
name: "nifra:zone-guard",
|
|
204
|
+
enforce: "pre",
|
|
205
|
+
refusalFor(url) {
|
|
206
|
+
const file = fileForUrl(url)
|
|
207
|
+
return file === undefined ? undefined : refusals.get(file)
|
|
208
|
+
},
|
|
209
|
+
fileForUrl,
|
|
210
|
+
// A pre-bundled copy would serve a server package under `.vite/deps/` instead of its own path.
|
|
211
|
+
config: () => ({ optimizeDeps: { exclude: [...SERVER_PACKAGES] } }),
|
|
212
|
+
configResolved(config) {
|
|
213
|
+
root = config.root
|
|
214
|
+
},
|
|
215
|
+
configureServer(server) {
|
|
216
|
+
// Registered directly (not in a returned post hook), so it runs ahead of Vite's own middlewares.
|
|
217
|
+
server.middlewares.use(middleware)
|
|
218
|
+
},
|
|
219
|
+
async resolveId(source, importer, resolveOptions) {
|
|
220
|
+
if (importer === undefined || resolveOptions?.scan === true) return null
|
|
221
|
+
if (!isClient(this, resolveOptions?.ssr)) return null
|
|
222
|
+
const importerFile = stripQuery(importer)
|
|
223
|
+
if (!isAbsolute(importerFile)) return null
|
|
224
|
+
if (source.startsWith(".")) {
|
|
225
|
+
const target = resolve(dirname(importerFile), stripQuery(source))
|
|
226
|
+
if (isFile(target)) {
|
|
227
|
+
const reason = browserDenial(classifier.classify(target))
|
|
228
|
+
if (reason !== undefined) refuse(this, target, reason, importerFile)
|
|
229
|
+
}
|
|
230
|
+
return null
|
|
231
|
+
}
|
|
232
|
+
const name = specifierPackage(source)
|
|
233
|
+
if (name !== undefined) {
|
|
234
|
+
const reason = browserDenial(packageClassification(name, importerFile), source)
|
|
235
|
+
if (reason !== undefined) refuse(this, source, reason, importerFile)
|
|
236
|
+
}
|
|
237
|
+
// An alias, a tsconfig path or a package export: what the specifier resolves to decides.
|
|
238
|
+
const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true })
|
|
239
|
+
const target = resolved === null || resolved.external ? undefined : stripQuery(resolved.id)
|
|
240
|
+
if (target !== undefined && isAbsolute(target) && isFile(target)) {
|
|
241
|
+
const reason = browserDenial(classifier.classify(target), source)
|
|
242
|
+
if (reason !== undefined) refuse(this, target, reason, importerFile)
|
|
243
|
+
}
|
|
244
|
+
return null
|
|
245
|
+
},
|
|
246
|
+
transform(code, id, transformOptions) {
|
|
247
|
+
if (!isClient(this, transformOptions?.ssr)) return null
|
|
248
|
+
const file = stripQuery(id)
|
|
249
|
+
if (!isAbsolute(file) || !isFile(file)) return null
|
|
250
|
+
// A fresh transform re-resolves every import, so an earlier refusal is stale until it recurs.
|
|
251
|
+
refusals.delete(resolve(file))
|
|
252
|
+
const classification = classifier.classify(file)
|
|
253
|
+
// A `pre` transform sees the file as written, so the env check reads the source the author wrote.
|
|
254
|
+
const reason =
|
|
255
|
+
browserDenial(classification) ??
|
|
256
|
+
(BROWSER_CODE.has(classification.zone) && id === file
|
|
257
|
+
? privateEnvDenial(file, code, options.publicEnvPrefix ?? "PUBLIC_")
|
|
258
|
+
: undefined)
|
|
259
|
+
if (reason !== undefined) refuse(this, file, reason)
|
|
260
|
+
return null
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The zone guard for Bun's bundler: every file the browser bundle loads is classified, and one that may
|
|
3
|
+
* not reach a browser stops the build (or the dev request) before its code is bundled.
|
|
4
|
+
*
|
|
5
|
+
* The client build runs it in `record` mode and reports every refusal together with its import chain,
|
|
6
|
+
* read from the finished graph; the dev server runs it in `throw` mode, where there is no finished graph
|
|
7
|
+
* and the refusal must stop the response.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path"
|
|
11
|
+
import type { BunPlugin } from "bun"
|
|
12
|
+
import { privateEnvDenial } from "../internal/private-env.ts"
|
|
13
|
+
import {
|
|
14
|
+
BACKEND_ONLY_MARKER,
|
|
15
|
+
browserDenial,
|
|
16
|
+
createZoneClassifier,
|
|
17
|
+
type ZoneClassifier,
|
|
18
|
+
} from "../zones.ts"
|
|
19
|
+
|
|
20
|
+
const SOURCE_EXTENSIONS =
|
|
21
|
+
"[cm]?[jt]sx?|svelte|vue|mdx|astro|css|scss|sass|less|json|jsonc|toml|ya?ml|txt|md"
|
|
22
|
+
/** Source files Bun loads through a loader that tolerates a declining `onLoad`. Every other file
|
|
23
|
+
* (images, fonts, wasm, and a `.pem` or `.sql` imported `with { type: "text" }`) goes through
|
|
24
|
+
* `onResolve`: a file-loader asset panics Bun 1.4 on a declining `onLoad`. */
|
|
25
|
+
const SOURCE_FILE = new RegExp(`\\.(?:${SOURCE_EXTENSIONS})$`)
|
|
26
|
+
/**
|
|
27
|
+
* A relative or absolute import whose last segment has an extension, other than a source file.
|
|
28
|
+
* Source files are excluded by the FILTER, not declined by the handler: on Bun's dev server a filter
|
|
29
|
+
* match alone changes how the import resolves, even when the handler declines (see
|
|
30
|
+
* `internal/dev-reserved.ts`), and a source file is the onLoad hook's to judge anyway.
|
|
31
|
+
*/
|
|
32
|
+
const NON_SOURCE_PATH = new RegExp(
|
|
33
|
+
`^(?:\\.|\\/|[A-Za-z]:[\\\\/])(?![^?#]*\\.(?:${SOURCE_EXTENSIONS})(?:[?#]|$)).*\\.[^./\\\\?#]+(?:[?#].*)?$`,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
export interface ZoneGuardOptions {
|
|
37
|
+
/** The app root: the directory holding `routes/`, `frontend/`, `backend/` and `shared/`. */
|
|
38
|
+
readonly appRoot: string
|
|
39
|
+
/** The routes directory (default `<appRoot>/routes`). */
|
|
40
|
+
readonly routesDir?: string
|
|
41
|
+
/** The build's own generated entry modules. */
|
|
42
|
+
readonly generatedFiles?: readonly string[]
|
|
43
|
+
/** Collect refusals instead of throwing; the caller reports them with chains after the build. */
|
|
44
|
+
readonly onDenied?: (file: string, reason: string) => void
|
|
45
|
+
/** Share a classifier with post-build verification. */
|
|
46
|
+
readonly classifier?: ZoneClassifier
|
|
47
|
+
/** The public-env prefix browser code may read (default `"PUBLIC_"`). Checked here only in throw
|
|
48
|
+
* mode; a build checks the finished graph instead. */
|
|
49
|
+
readonly publicEnvPrefix?: string
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Zones whose code runs in a browser, so may read only public environment variables. */
|
|
53
|
+
const BROWSER_CODE = new Set<string>(["route-frontend", "frontend", "shared"])
|
|
54
|
+
|
|
55
|
+
export function zoneGuardPlugin(options: ZoneGuardOptions): BunPlugin {
|
|
56
|
+
const classifier = options.classifier ?? createZoneClassifier(options)
|
|
57
|
+
const display = (file: string): string => {
|
|
58
|
+
const rel = relative(classifier.appRoot, file).replaceAll("\\", "/")
|
|
59
|
+
return rel.startsWith("..") || isAbsolute(rel) ? file : rel
|
|
60
|
+
}
|
|
61
|
+
const refuse = (file: string, reason: string, importer?: string): undefined => {
|
|
62
|
+
if (options.onDenied !== undefined) {
|
|
63
|
+
options.onDenied(file, reason)
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
66
|
+
const via =
|
|
67
|
+
importer === undefined || importer === "" ? "" : ` (imported by ${display(importer)})`
|
|
68
|
+
throw new Error(`[nifra/web] ${display(file)} may not reach the browser${via}: ${reason}`)
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
name: "nifra-zone-guard",
|
|
72
|
+
setup(build) {
|
|
73
|
+
build.onLoad({ filter: SOURCE_FILE }, async (args) => {
|
|
74
|
+
if (args.namespace !== "file") return undefined
|
|
75
|
+
const classification = classifier.classify(args.path)
|
|
76
|
+
let reason = browserDenial(classification)
|
|
77
|
+
if (
|
|
78
|
+
reason === undefined &&
|
|
79
|
+
options.onDenied === undefined &&
|
|
80
|
+
BROWSER_CODE.has(classification.zone)
|
|
81
|
+
) {
|
|
82
|
+
const source = await Bun.file(args.path).text()
|
|
83
|
+
reason = privateEnvDenial(args.path, source, options.publicEnvPrefix ?? "PUBLIC_")
|
|
84
|
+
}
|
|
85
|
+
return reason === undefined ? undefined : refuse(args.path, reason)
|
|
86
|
+
})
|
|
87
|
+
build.onResolve({ filter: NON_SOURCE_PATH }, (args) => {
|
|
88
|
+
// An entry point (a dev server's HTML page) is the build's own choice, not an import.
|
|
89
|
+
if (args.importer === "") return undefined
|
|
90
|
+
const file = resolve(dirname(args.importer), args.path.replace(/[?#].*$/, ""))
|
|
91
|
+
const reason = browserDenial(classifier.classify(file))
|
|
92
|
+
return reason === undefined ? undefined : refuse(file, reason, args.importer)
|
|
93
|
+
})
|
|
94
|
+
// In record mode the finished graph reports these, with chains; only the dev server needs them here.
|
|
95
|
+
if (options.onDenied !== undefined) return
|
|
96
|
+
build.onResolve({ filter: /^(?:node|bun):/ }, (args) =>
|
|
97
|
+
refuse(
|
|
98
|
+
args.path,
|
|
99
|
+
"Node and Bun built-ins run on the server only; use them in a route's backend half or under backend/",
|
|
100
|
+
args.importer,
|
|
101
|
+
),
|
|
102
|
+
)
|
|
103
|
+
build.onResolve(
|
|
104
|
+
{ filter: new RegExp(`^${BACKEND_ONLY_MARKER.replaceAll("/", "\\/")}$`) },
|
|
105
|
+
(args) =>
|
|
106
|
+
refuse(args.importer, `it imports "${BACKEND_ONLY_MARKER}", which marks it backend-only`),
|
|
107
|
+
)
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/prerender.ts
CHANGED
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { mkdirSync, writeFileSync } from "node:fs"
|
|
12
12
|
import { dirname, resolve, sep } from "node:path"
|
|
13
|
+
import { assertNoSecrets, buildEnvironment, type SecretExemption } from "./internal/secret-scan.ts"
|
|
13
14
|
import { fillRoutePattern, type RouteEntry } from "./manifest.ts"
|
|
14
|
-
import { DATA_HEADER } from "./router.ts"
|
|
15
|
+
import { DATA_HEADER, PRERENDERED_GLOBAL, PRERENDERED_LIST_PATH } from "./router.ts"
|
|
15
16
|
|
|
16
17
|
/** Minimal app surface the driver needs - just a fetch handler (a built `createWebApp`). */
|
|
17
18
|
export interface PrerenderApp {
|
|
@@ -30,6 +31,15 @@ export interface PrerenderOptions {
|
|
|
30
31
|
/** Origin for the synthetic request URL. Default `http://localhost`. Only the path is meaningful;
|
|
31
32
|
* loaders see this as `request.url`, so set it if a loader builds absolute URLs from the origin. */
|
|
32
33
|
readonly origin?: string
|
|
34
|
+
/** The secret scan every page and `_data.json` passes before it is written: what looks like a
|
|
35
|
+
* credential, or the value of a non-public build environment variable, fails the prerender. */
|
|
36
|
+
readonly secrets?: {
|
|
37
|
+
/** Default: the build environment (`Bun.env`, else `process.env`). */
|
|
38
|
+
readonly env?: Readonly<Record<string, string | undefined>>
|
|
39
|
+
/** Default `"PUBLIC_"`. */
|
|
40
|
+
readonly publicEnvPrefix?: string
|
|
41
|
+
readonly exemptions?: readonly SecretExemption[]
|
|
42
|
+
}
|
|
33
43
|
}
|
|
34
44
|
|
|
35
45
|
export interface PrerenderEntry {
|
|
@@ -66,6 +76,11 @@ export function htmlFileFor(pattern: string): string {
|
|
|
66
76
|
return trimmed === "" ? "index.html" : `${trimmed}/index.html`
|
|
67
77
|
}
|
|
68
78
|
|
|
79
|
+
// The handover of a page whose prerendered set is served as a list rather than inlined.
|
|
80
|
+
const PRERENDERED_LIST_URL = new RegExp(
|
|
81
|
+
`"${PRERENDERED_GLOBAL}":"(${PRERENDERED_LIST_PATH.replaceAll(".", "\\.")}\\?v=[0-9a-f]+)"`,
|
|
82
|
+
)
|
|
83
|
+
|
|
69
84
|
/** The static loader-data file next to a route's `index.html`: `/` → `_data.json`, `/a/b` →
|
|
70
85
|
* `a/b/_data.json`. The client fetches it on soft-nav into a prerendered route (no worker). */
|
|
71
86
|
export function dataFileFor(pattern: string): string {
|
|
@@ -90,9 +105,19 @@ export async function prerenderRoutes(options: PrerenderOptions): Promise<Preren
|
|
|
90
105
|
const skipped: { path: string; reason: string }[] = []
|
|
91
106
|
const fallbacks: Record<string, "ssr" | "404"> = {}
|
|
92
107
|
const outRoot = resolve(options.outDir)
|
|
108
|
+
let listUrl: string | undefined
|
|
109
|
+
|
|
110
|
+
const secretScan = {
|
|
111
|
+
env: options.secrets?.env ?? buildEnvironment(),
|
|
112
|
+
publicEnvPrefix: options.secrets?.publicEnvPrefix ?? "PUBLIC_",
|
|
113
|
+
...(options.secrets?.exemptions ? { exemptions: options.secrets.exemptions } : {}),
|
|
114
|
+
}
|
|
115
|
+
const assertClean = (name: string, text: string): void =>
|
|
116
|
+
assertNoSecrets({ ...secretScan, artifacts: [{ name, text }] })
|
|
93
117
|
|
|
94
118
|
// Render one concrete path → drain the streamed document to bytes → write its index.html. Shared by
|
|
95
|
-
// static (one path) and dynamic (one path per getStaticPaths entry) routes
|
|
119
|
+
// static (one path) and dynamic (one path per getStaticPaths entry) routes. A failed render is a skip;
|
|
120
|
+
// a credential in the output fails the whole prerender.
|
|
96
121
|
const renderPath = async (path: string): Promise<void> => {
|
|
97
122
|
const res = await options.app.fetch(new Request(`${origin}${path}`))
|
|
98
123
|
if (!res.ok) {
|
|
@@ -101,12 +126,14 @@ export async function prerenderRoutes(options: PrerenderOptions): Promise<Preren
|
|
|
101
126
|
return
|
|
102
127
|
}
|
|
103
128
|
const html = await res.text()
|
|
129
|
+
listUrl ??= PRERENDERED_LIST_URL.exec(html)?.[1]
|
|
104
130
|
const file = htmlFileFor(path)
|
|
105
131
|
const abs = outputPath(outRoot, file)
|
|
106
132
|
if (abs === undefined) {
|
|
107
133
|
skipped.push({ path, reason: "unsafe output path" })
|
|
108
134
|
return
|
|
109
135
|
}
|
|
136
|
+
assertClean(file, html)
|
|
110
137
|
mkdirSync(dirname(abs), { recursive: true })
|
|
111
138
|
writeFileSync(abs, html)
|
|
112
139
|
|
|
@@ -128,7 +155,9 @@ export async function prerenderRoutes(options: PrerenderOptions): Promise<Preren
|
|
|
128
155
|
await dataRes.body?.cancel()
|
|
129
156
|
dataFile = undefined
|
|
130
157
|
} else {
|
|
131
|
-
|
|
158
|
+
const data = await dataRes.text()
|
|
159
|
+
assertClean(dataFile, data)
|
|
160
|
+
writeFileSync(dataAbs, data)
|
|
132
161
|
}
|
|
133
162
|
} else {
|
|
134
163
|
await dataRes.body?.cancel() // not emitting (non-OK or NDJSON) - release the stream
|
|
@@ -177,6 +206,23 @@ export async function prerenderRoutes(options: PrerenderOptions): Promise<Preren
|
|
|
177
206
|
await renderPath(route.pattern)
|
|
178
207
|
}
|
|
179
208
|
|
|
209
|
+
// A set too large to inline is handed over as the URL of a list the app serves; the static output
|
|
210
|
+
// has to carry the file those pages fetch.
|
|
211
|
+
if (listUrl !== undefined) {
|
|
212
|
+
const listRes = await options.app.fetch(new Request(`${origin}${listUrl}`))
|
|
213
|
+
const listFile = PRERENDERED_LIST_PATH.slice(1)
|
|
214
|
+
const listAbs = outputPath(outRoot, listFile)
|
|
215
|
+
if (listRes.ok && listAbs !== undefined) {
|
|
216
|
+
const text = await listRes.text()
|
|
217
|
+
assertClean(listFile, text)
|
|
218
|
+
mkdirSync(dirname(listAbs), { recursive: true })
|
|
219
|
+
writeFileSync(listAbs, text)
|
|
220
|
+
} else {
|
|
221
|
+
await listRes.body?.cancel()
|
|
222
|
+
skipped.push({ path: PRERENDERED_LIST_PATH, reason: `list returned HTTP ${listRes.status}` })
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
180
226
|
return { prerendered, skipped, fallbacks }
|
|
181
227
|
}
|
|
182
228
|
|