@nifrajs/web 3.5.0 → 4.0.2
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 +18 -6
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +82 -34
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +4 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +68 -13
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +57 -23
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +356 -85
- 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 +3 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +136 -35
- 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/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 +13 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -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.map +1 -1
- package/dist/internal/codegen.js +141 -25
- 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 +20 -8
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +571 -105
- package/dist/internal/page-execution.js.map +1 -1
- 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 +25 -19
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +77 -61
- package/dist/internal/render-document.js.map +1 -1
- 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 +503 -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 +38 -9
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +199 -59
- 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 +263 -36
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +31 -6
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +220 -13
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +394 -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 +80 -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 +67 -9
- 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/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 +248 -43
- 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 +90 -35
- package/src/build-vite.ts +77 -11
- package/src/build.ts +466 -96
- package/src/bun-dev-entry.ts +1 -1
- package/src/cdn.ts +216 -0
- package/src/client.ts +135 -36
- package/src/conformance.ts +16 -3
- package/src/csp.ts +194 -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 +42 -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 +161 -24
- 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 +795 -185
- 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 +114 -67
- package/src/internal/response-controls.ts +284 -0
- package/src/internal/runtime-contract.ts +10 -15
- package/src/internal/secret-scan.ts +638 -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 +262 -84
- package/src/internal/zone-graph.ts +593 -0
- package/src/isr.ts +325 -37
- package/src/llms-txt.ts +28 -4
- package/src/manifest.ts +682 -16
- 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 +119 -0
- package/src/prerender.ts +49 -3
- package/src/public-dir.ts +69 -8
- 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/vitals.ts +44 -0
- package/src/vite.ts +257 -42
- 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
package/dist/build.js
CHANGED
|
@@ -5,22 +5,30 @@
|
|
|
5
5
|
* are Bun-specific and never on the request path (own subpath, like `@nifrajs/web/fs`); the *output*
|
|
6
6
|
* runs on any runtime.
|
|
7
7
|
*/
|
|
8
|
-
import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { cp, lstat, mkdir, realpath } from "node:fs/promises";
|
|
10
|
-
import { dirname, join, basename as pathBasename, relative, resolve as resolvePath, sep, } from "node:path";
|
|
10
|
+
import { dirname, join, basename as pathBasename, posix, relative, resolve as resolvePath, sep, } from "node:path";
|
|
11
11
|
import { singleCopyPlugin as declaredSingleCopyPlugin, readSingleCopyDeclaration, } from "@nifrajs/core/single-copy";
|
|
12
12
|
import { Glob } from "bun";
|
|
13
|
-
import { aggregateSizeReport, detectNodeBuiltinsInClient, detectServerOnlyInClient, formatNodeBuiltinLeak, formatServerOnlyLeak, parseManifestClientEntry, parseManifestCssLoading, parseManifestRouteStyles, parseManifestStyles, planBuildTarget, } from "./build-plan.js";
|
|
13
|
+
import { aggregateSizeReport, detectNodeBuiltinsInClient, detectServerOnlyInClient, formatNodeBuiltinLeak, formatServerOnlyLeak, parseBuildTarget, parseManifestClientEntry, parseManifestCssLoading, parseManifestRouteStyles, parseManifestStyles, planBuildTarget, } from "./build-plan.js";
|
|
14
14
|
import { sanitizeOutputNames } from "./chunk-names.js";
|
|
15
15
|
import { normalizeCssCodeSplit, normalizeCssLoading } from "./css-contract.js";
|
|
16
16
|
import { discoverRoutes } from "./fs.js";
|
|
17
17
|
import { generateClientEntry, generateServerManifest } from "./index.js";
|
|
18
18
|
import { dedupePolicyFor } from "./internal/identity-policy.js";
|
|
19
19
|
import { assertDevelopmentProductionParity, assertIdentityParity, collectDevelopmentParityInput, } from "./internal/parity.js";
|
|
20
|
-
import {
|
|
21
|
-
|
|
20
|
+
import { privateEnvCheck } from "./internal/private-env.js";
|
|
21
|
+
import { publicUrlPath } from "./internal/public-url.js";
|
|
22
|
+
import { assertNoSecrets, buildEnvironment, emittedScanFiles, formatSecretFindings, graphScanInput, originName, publicScanFiles, scanForSecrets, textOf, } from "./internal/secret-scan.js";
|
|
23
|
+
import { generateServerFnStub, SERVER_FN_MODULE, serverFnNamespace, } from "./internal/server-boundary.js";
|
|
24
|
+
import { formatUnsupportedBuiltins, unsupportedBuiltins } from "./internal/target-compat.js";
|
|
25
|
+
import { accountEmittedFiles, bunModuleSource, formatClientGraphVerdict, formatServerGraphVerdict, formatUnaccountedOutput, verifyClientGraph, verifyServerGraph, } from "./internal/zone-graph.js";
|
|
22
26
|
import { fromBunMetafile } from "./module-graph.js";
|
|
27
|
+
import { zoneGuardPlugin } from "./plugins/zone-guard.js";
|
|
28
|
+
// `buildTarget(static)` drives the SSG prerender engine directly (it's also re-exported below).
|
|
23
29
|
import { prerenderRoutes } from "./prerender.js";
|
|
30
|
+
import { CONTENT_TYPES, IMMUTABLE, ONE_DAY } from "./public-dir.js";
|
|
31
|
+
import { createZoneClassifier } from "./zones.js";
|
|
24
32
|
export * from "./build-plan.js";
|
|
25
33
|
const basename = (path) => pathBasename(path);
|
|
26
34
|
// The lazy shape declares `const loaders`, the eager shape `const modules`. Anchored at a line start,
|
|
@@ -55,18 +63,6 @@ export function publicEnvDefines(prefix, env) {
|
|
|
55
63
|
}
|
|
56
64
|
return defines;
|
|
57
65
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Percent-encode one path segment exactly the way a browser encodes it into a request URL.
|
|
60
|
-
*
|
|
61
|
-
* NOT `encodeURIComponent`. That escapes the sub-delimiters `, @ + = & ; $`, which a browser sends raw -
|
|
62
|
-
* so a file named `report,2026.csv` would be recorded as `/report%2C2026.csv` while the request arrives
|
|
63
|
-
* as `/report,2026.csv`, the allowlist lookup misses, and the file 404s in production only. `encodeURI`
|
|
64
|
-
* agrees with `URL.pathname` on every character except `?` and `#`, which terminate a path and so must
|
|
65
|
-
* be escaped explicitly here.
|
|
66
|
-
*/
|
|
67
|
-
function encodePathSegment(segment) {
|
|
68
|
-
return encodeURI(segment).replace(/[?#]/g, (char) => `%${char.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
69
|
-
}
|
|
70
66
|
/**
|
|
71
67
|
* Cloudflare Pages rejects a `_routes.json` with more than 100 include+exclude rules combined, or any
|
|
72
68
|
* single rule longer than 100 characters.
|
|
@@ -117,7 +113,7 @@ function collapsibleDirs(publicFiles, routePatterns) {
|
|
|
117
113
|
return dirs;
|
|
118
114
|
}
|
|
119
115
|
/**
|
|
120
|
-
* Build the
|
|
116
|
+
* Build the cloudflare `_routes.json` rules for a set of copied public files, within Cloudflare's budget.
|
|
121
117
|
*
|
|
122
118
|
* `exclude` is what Pages serves straight from the CDN instead of invoking the worker, so naming every
|
|
123
119
|
* public file is ideal - and impossible past ~99 of them. A `public/` of icons, fonts and share images
|
|
@@ -201,7 +197,7 @@ export async function copyPublicDir(from, to) {
|
|
|
201
197
|
const target = join(to, rel);
|
|
202
198
|
await mkdir(join(target, ".."), { recursive: true });
|
|
203
199
|
await cp(candidateReal, target);
|
|
204
|
-
copied.push(
|
|
200
|
+
copied.push(publicUrlPath(rel));
|
|
205
201
|
}
|
|
206
202
|
return copied.sort();
|
|
207
203
|
}
|
|
@@ -267,6 +263,7 @@ export async function buildClient(options) {
|
|
|
267
263
|
// layered after these in the `define` object). `Bun` may be absent under non-Bun typecheck - guard it.
|
|
268
264
|
const buildEnv = (typeof Bun !== "undefined" ? Bun.env : undefined) ?? process.env;
|
|
269
265
|
const publicDefines = publicEnvDefines(options.publicEnvPrefix ?? "PUBLIC_", buildEnv);
|
|
266
|
+
const privateEnv = privateEnvCheck(options.publicEnvPrefix ?? "PUBLIC_");
|
|
270
267
|
// Keep the generated source beside the project, not inside `outDir`: module resolution starts at the
|
|
271
268
|
// importing file, so an absolute `--out /tmp/deploy` must still resolve the app's dependencies.
|
|
272
269
|
// A unique directory also avoids clobbering a user file or colliding with parallel builds.
|
|
@@ -284,6 +281,10 @@ export async function buildClient(options) {
|
|
|
284
281
|
...routeManifest.routes.map((r) => r.file),
|
|
285
282
|
...Object.values(routeManifest.layouts).map((l) => l.file),
|
|
286
283
|
...(routeManifest.notFound ? [routeManifest.notFound.file] : []),
|
|
284
|
+
// A nested `_404` is an entry only for its stylesheet: it renders on the server, unhydrated.
|
|
285
|
+
...Object.values(routeManifest.notFounds ?? {}).map((page) => page.file),
|
|
286
|
+
// A `_loading` page is an entry for its stylesheet too: the bootstrap imports it lazily.
|
|
287
|
+
...Object.values(routeManifest.loadings ?? {}).map((page) => page.file),
|
|
287
288
|
]),
|
|
288
289
|
].sort();
|
|
289
290
|
// `metafile: true` asks Bun for the input/output graph - specifically `outputs[js].entryPoint`
|
|
@@ -291,12 +292,14 @@ export async function buildClient(options) {
|
|
|
291
292
|
// entry→CSS link for per-route splitting: keyed by the unique source path, so it survives
|
|
292
293
|
// same-basename collisions (`index.tsx` + `blog/index.tsx`) that a filename match can't. Not yet in
|
|
293
294
|
// `@types/bun`'s `BuildConfig`, so spread it in (spread props skip the excess-property check).
|
|
294
|
-
const buildExtras = { metafile: true };
|
|
295
|
+
const buildExtras = { metafile: true, throw: false };
|
|
296
|
+
const classifier = createZoneClassifier({ appRoot: root, routesDir, generatedFiles: [entryFile] });
|
|
297
|
+
const refused = new Map();
|
|
298
|
+
// No `outdir`: the bundle stays in memory until the graph proves it holds browser code only.
|
|
295
299
|
const result = await (async () => {
|
|
296
300
|
try {
|
|
297
301
|
return await Bun.build({
|
|
298
302
|
entrypoints: [entryFile, ...routeFiles.map(resolve)],
|
|
299
|
-
outdir: outDir,
|
|
300
303
|
target: "browser",
|
|
301
304
|
naming: "[name]-[hash].[ext]",
|
|
302
305
|
publicPath,
|
|
@@ -307,11 +310,16 @@ export async function buildClient(options) {
|
|
|
307
310
|
...buildExtras,
|
|
308
311
|
minify: options.minify ?? true,
|
|
309
312
|
plugins: [
|
|
313
|
+
// First, so it sees every file before another plugin's `onLoad` claims it.
|
|
314
|
+
zoneGuardPlugin({
|
|
315
|
+
appRoot: root,
|
|
316
|
+
classifier,
|
|
317
|
+
onDenied: (file, reason) => refused.set(file, reason),
|
|
318
|
+
}),
|
|
310
319
|
...declaredSingleCopyPlugins(root),
|
|
311
320
|
reactDedupePlugin(routesDir),
|
|
312
321
|
preactDedupePlugin(routesDir),
|
|
313
322
|
svelteDedupePlugin(routesDir),
|
|
314
|
-
serverOnlyEmptyPlugin(),
|
|
315
323
|
serverFnStubPlugin(),
|
|
316
324
|
...(options.plugins ?? []),
|
|
317
325
|
],
|
|
@@ -319,10 +327,11 @@ export async function buildClient(options) {
|
|
|
319
327
|
// Replace `process.env.*` at compile time so an app module reading config off `process.env` doesn't
|
|
320
328
|
// hit a `process is not defined` crash in the browser. Bun does longest-match: NODE_ENV resolves to
|
|
321
329
|
// the build mode (React's prod/dev branch); each PUBLIC_* var resolves to its baked VALUE; every
|
|
322
|
-
// other `process.env.X` becomes undefined (the bare `process.env` → `
|
|
323
|
-
// never leak). Callers can override any of these via `options.define` (layered last).
|
|
330
|
+
// other `process.env.X` becomes undefined (the bare `process.env` → `{}` fallback - so secrets
|
|
331
|
+
// never leak). Callers can override any of these via `options.define` (layered last). The value
|
|
332
|
+
// must be JSON: Bun inlines anything else (`({})`) as a string literal.
|
|
324
333
|
define: {
|
|
325
|
-
"process.env": "
|
|
334
|
+
"process.env": "{}",
|
|
326
335
|
"process.env.NODE_ENV": JSON.stringify(mode),
|
|
327
336
|
...publicDefines,
|
|
328
337
|
...options.define,
|
|
@@ -333,36 +342,110 @@ export async function buildClient(options) {
|
|
|
333
342
|
rmSync(entryDir, { recursive: true, force: true });
|
|
334
343
|
}
|
|
335
344
|
})();
|
|
345
|
+
const cwd = process.cwd();
|
|
346
|
+
const clientMeta = result.metafile;
|
|
336
347
|
if (!result.success) {
|
|
348
|
+
// A refused file can be what broke the build (a backend module importing a server-only built-in),
|
|
349
|
+
// so the refusal is the error to show, not the bundler's symptom.
|
|
350
|
+
const verdict = refused.size > 0 && clientMeta !== undefined
|
|
351
|
+
? formatClientGraphVerdict(verifyClientGraph(fromBunMetafile(clientMeta), {
|
|
352
|
+
classifier,
|
|
353
|
+
sourceOf: bunModuleSource(cwd),
|
|
354
|
+
privateEnv,
|
|
355
|
+
refused: new Set(refused.keys()),
|
|
356
|
+
}))
|
|
357
|
+
: undefined;
|
|
358
|
+
if (verdict !== undefined)
|
|
359
|
+
throw new Error(verdict);
|
|
360
|
+
if (refused.size > 0)
|
|
361
|
+
throw new Error(formatRefusedFiles(refused, root));
|
|
337
362
|
throw new Error(`[nifra/web] client build failed:\n${result.logs.map((l) => String(l)).join("\n")}`);
|
|
338
363
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
364
|
+
const emitted = await Promise.all(result.outputs.map(async (out) => {
|
|
365
|
+
if (out.kind === "sourcemap")
|
|
366
|
+
return { name: out.path, kind: "map", text: await out.text() };
|
|
367
|
+
if (out.kind === "asset") {
|
|
368
|
+
if (out.path.endsWith(".css"))
|
|
369
|
+
return { name: out.path, kind: "css", text: await out.text() };
|
|
370
|
+
const text = textOf(new Uint8Array(await out.arrayBuffer()));
|
|
371
|
+
return { name: out.path, kind: "asset", ...(text === undefined ? {} : { text }) };
|
|
372
|
+
}
|
|
373
|
+
return { name: out.path, kind: "code", text: await out.text() };
|
|
374
|
+
}));
|
|
375
|
+
const clientGraph = withEmittedImports(fromBunMetafile(clientMeta), emitted, publicPath);
|
|
376
|
+
const verdict = verifyClientGraph(clientGraph, {
|
|
377
|
+
classifier,
|
|
378
|
+
sourceOf: bunModuleSource(cwd),
|
|
379
|
+
privateEnv,
|
|
380
|
+
refused: new Set(refused.keys()),
|
|
381
|
+
});
|
|
382
|
+
// Two independent records of one fact: a file the plugin refused that the graph never shows means the
|
|
383
|
+
// graph is missing evidence, and the build cannot prove anything else about it either.
|
|
384
|
+
const reported = new Set(verdict.leaks.map((leak) => resolvePath(root, leak.module)));
|
|
385
|
+
const unseen = [...refused.keys()].filter((file) => !reported.has(file));
|
|
386
|
+
const graphMessage = formatClientGraphVerdict(unseen.length === 0
|
|
387
|
+
? verdict
|
|
388
|
+
: {
|
|
389
|
+
...verdict,
|
|
390
|
+
gaps: [
|
|
391
|
+
...verdict.gaps,
|
|
392
|
+
...unseen.map((file) => `${file} was refused while loading but is missing from the graph`),
|
|
393
|
+
],
|
|
394
|
+
});
|
|
395
|
+
if (graphMessage !== undefined)
|
|
396
|
+
throw new Error(graphMessage);
|
|
346
397
|
// #4: a `node:` builtin (e.g. `node:crypto`) pulled into a CLIENT chunk builds fine (Bun substitutes a
|
|
347
398
|
// browser polyfill) but breaks/leaks at runtime. Fail with the chain (entry → … → builtin), through the
|
|
348
399
|
// SHARED formatter so the Vite pipeline's identical guard reads byte-for-byte the same.
|
|
349
400
|
const nodeBuiltinLeak = formatNodeBuiltinLeak(detectNodeBuiltinsInClient(clientGraph));
|
|
350
401
|
if (nodeBuiltinLeak !== undefined)
|
|
351
402
|
throw new Error(nodeBuiltinLeak);
|
|
352
|
-
//
|
|
353
|
-
//
|
|
354
|
-
// `*.server`, so neither other guard fires. Same shared formatter as above.
|
|
403
|
+
// A module that imports the `backend-only` marker yet reached a client chunk: library code the zones
|
|
404
|
+
// cannot place on a side by path alone. Same shared formatter as the Vite pipeline.
|
|
355
405
|
const serverOnlyLeak = formatServerOnlyLeak(detectServerOnlyInClient(clientGraph));
|
|
356
406
|
if (serverOnlyLeak !== undefined)
|
|
357
407
|
throw new Error(serverOnlyLeak);
|
|
408
|
+
const unaccounted = formatUnaccountedOutput(accountEmittedFiles(emitted, clientGraph, {
|
|
409
|
+
classifier,
|
|
410
|
+
sourceOf: bunModuleSource(cwd),
|
|
411
|
+
outDir: resolvePath(outDir),
|
|
412
|
+
}));
|
|
413
|
+
if (unaccounted !== undefined)
|
|
414
|
+
throw new Error(unaccounted);
|
|
415
|
+
const publicDir = options.publicDir === false ? undefined : (options.publicDir ?? "public");
|
|
416
|
+
const { sources, originsOf } = graphScanInput(clientGraph, bunModuleSource(cwd), classifier);
|
|
417
|
+
const secrets = formatSecretFindings(scanForSecrets({
|
|
418
|
+
sources,
|
|
419
|
+
artifacts: [
|
|
420
|
+
...emitted.flatMap((file) => {
|
|
421
|
+
if (file.text === undefined)
|
|
422
|
+
return [];
|
|
423
|
+
const name = file.name.replace(/^\.\//, "");
|
|
424
|
+
return emittedScanFiles(name, file.text, originsOf(name));
|
|
425
|
+
}),
|
|
426
|
+
...(publicDir !== undefined && existsSync(publicDir)
|
|
427
|
+
? publicScanFiles(publicDir, originName(root, resolvePath(publicDir)))
|
|
428
|
+
: []),
|
|
429
|
+
],
|
|
430
|
+
env: buildEnv,
|
|
431
|
+
publicEnvPrefix: options.publicEnvPrefix ?? "PUBLIC_",
|
|
432
|
+
...(options.secretExemptions ? { exemptions: options.secretExemptions } : {}),
|
|
433
|
+
}));
|
|
434
|
+
if (secrets !== undefined)
|
|
435
|
+
throw new Error(secrets);
|
|
436
|
+
const outputs = await Promise.all(result.outputs.map(async (out) => {
|
|
437
|
+
const path = join(outDir, out.path);
|
|
438
|
+
await Bun.write(path, out);
|
|
439
|
+
return { kind: out.kind, path };
|
|
440
|
+
}));
|
|
358
441
|
// Rename any chunk whose basename isn't URL-safe (dynamic-route files become `[slug]-hash.js`) and
|
|
359
442
|
// rewrite the references - otherwise the lazy import 404s and the route silently never hydrates.
|
|
360
|
-
const renamed = sanitizeOutputNames(
|
|
443
|
+
const renamed = sanitizeOutputNames(outputs);
|
|
361
444
|
const toUrl = (path) => `${publicPath}${renamed.get(basename(path)) ?? basename(path)}`;
|
|
362
445
|
// Entry-point outputs come back in entrypoint order: [bootstrap, ...routeFiles]. Map each route file
|
|
363
446
|
// to its chunk URL by that order (guarded against drift), then a route's chunks = its layout chain +
|
|
364
447
|
// own file.
|
|
365
|
-
const entryPoints =
|
|
448
|
+
const entryPoints = outputs.filter((o) => o.kind === "entry-point");
|
|
366
449
|
const bootstrap = entryPoints[0];
|
|
367
450
|
if (bootstrap === undefined)
|
|
368
451
|
throw new Error("[nifra/web] build produced no entry-point output");
|
|
@@ -400,7 +483,7 @@ export async function buildClient(options) {
|
|
|
400
483
|
// per-route `cssBundle` outputs follow. Dropping those per-route outputs (the old `aggregate`-only
|
|
401
484
|
// shape) left them in `manifest.assets` but not `css`, so a route-scoped stylesheet normalized to
|
|
402
485
|
// `asset:css` and tripped the module-graph parity contract - a bundler-dependent divergence.
|
|
403
|
-
const cssAssets =
|
|
486
|
+
const cssAssets = outputs.filter((o) => o.kind === "asset" && o.path.endsWith(".css"));
|
|
404
487
|
const css = [
|
|
405
488
|
...cssAssets.filter((o) => cssNameOf(o.path) === bootstrapName),
|
|
406
489
|
...cssAssets.filter((o) => cssNameOf(o.path) !== bootstrapName),
|
|
@@ -411,7 +494,6 @@ export async function buildClient(options) {
|
|
|
411
494
|
// same-basename collisions (`index.tsx` + `blog/index.tsx`) that a filename match can't. A page then
|
|
412
495
|
// links only its layout chain + own CSS (deduped); an empty array means the page needs no CSS at all.
|
|
413
496
|
// Absent (→ aggregate fallback) only if Bun emits no metafile/cssBundle - never silently incomplete.
|
|
414
|
-
const cwd = process.cwd();
|
|
415
497
|
const cssByEntry = new Map();
|
|
416
498
|
for (const out of Object.values(clientMeta?.outputs ?? {})) {
|
|
417
499
|
if (out.entryPoint !== undefined && out.cssBundle !== undefined) {
|
|
@@ -431,22 +513,32 @@ export async function buildClient(options) {
|
|
|
431
513
|
// SSR pages unstyled. Keep routeStyles absent until at least one authored route entry owns CSS.
|
|
432
514
|
const routeCssEntries = [...cssByEntry.keys()].filter((entry) => entry !== resolvePath(entryFile));
|
|
433
515
|
if (css.length > 0 && routeCssEntries.length > 0) {
|
|
516
|
+
// A `_loading` page can show on a navigation from ANY page, and a document links only its own
|
|
517
|
+
// route's stylesheets - so every page that can start a navigation carries the loading pages' too.
|
|
518
|
+
const loadingFiles = Object.values(routeManifest.loadings ?? {}).map((page) => page.file);
|
|
434
519
|
for (const route of routeManifest.routes) {
|
|
435
520
|
routeStyles[route.id] = stylesFor([
|
|
436
521
|
...route.layoutIds.map((id) => routeManifest.layouts[id]?.file ?? ""),
|
|
437
522
|
route.file,
|
|
523
|
+
...loadingFiles,
|
|
524
|
+
]);
|
|
525
|
+
}
|
|
526
|
+
if (routeManifest.notFound) {
|
|
527
|
+
routeStyles._404 = stylesFor([routeManifest.notFound.file, ...loadingFiles]);
|
|
528
|
+
}
|
|
529
|
+
for (const [id, page] of Object.entries(routeManifest.notFounds ?? {})) {
|
|
530
|
+
routeStyles[id] = stylesFor([
|
|
531
|
+
...page.layoutIds.map((layoutId) => routeManifest.layouts[layoutId]?.file ?? ""),
|
|
532
|
+
page.file,
|
|
438
533
|
]);
|
|
439
534
|
}
|
|
440
|
-
if (routeManifest.notFound)
|
|
441
|
-
routeStyles._404 = stylesFor([routeManifest.notFound.file]);
|
|
442
535
|
}
|
|
443
536
|
// Copy `public/` into the output next to the hashed assets. A missing directory is normal (most
|
|
444
537
|
// apps have none) and must not fail the build.
|
|
445
|
-
const publicDir = options.publicDir === false ? undefined : (options.publicDir ?? "public");
|
|
446
538
|
const publicFiles = publicDir !== undefined && existsSync(publicDir) ? await copyPublicDir(publicDir, outDir) : [];
|
|
447
539
|
const manifest = {
|
|
448
540
|
entry: toUrl(bootstrap.path),
|
|
449
|
-
assets:
|
|
541
|
+
assets: outputs.map((o) => toUrl(o.path)),
|
|
450
542
|
routes,
|
|
451
543
|
...(publicFiles.length > 0 ? { publicFiles } : {}),
|
|
452
544
|
...(css.length > 0 ? { css } : {}),
|
|
@@ -543,12 +635,55 @@ const SVELTE_DEDUPE_PATTERN = dedupePolicyFor("svelte").bunPattern ?? /^svelte($
|
|
|
543
635
|
* fixed subpath list), Svelte has many internal subpaths, so each matched import is resolved dynamically.
|
|
544
636
|
* `svelte/compiler` (build-time only, not in the bundle) doesn't match the filter and is left alone. No-op
|
|
545
637
|
* when Svelte isn't used / isn't resolvable from `from`.
|
|
638
|
+
*
|
|
639
|
+
* Svelte is NOT condition-agnostic, unlike React and Preact: its bare entry maps `browser` to the client
|
|
640
|
+
* runtime and `default` to the server one. `Bun.resolveSync` answers for the process running the build -
|
|
641
|
+
* a server runtime - so pinning its answer put the SERVER entry into a browser bundle, where `hydrate`
|
|
642
|
+
* throws and the page never becomes interactive. For a browser bundle the package is therefore pinned by
|
|
643
|
+
* DIRECTORY and its export map is read with the bundle's own conditions.
|
|
546
644
|
*/
|
|
547
645
|
export const svelteDedupePlugin = (from) => ({
|
|
548
646
|
name: "nifra-svelte-dedupe",
|
|
549
647
|
setup(build) {
|
|
648
|
+
// Absent when the plugin runs as a runtime plugin rather than inside `Bun.build`; a build with no
|
|
649
|
+
// `target` is a browser build (Bun's default).
|
|
650
|
+
const config = build.config;
|
|
651
|
+
const browser = config !== undefined && (config.target ?? "browser") === "browser";
|
|
652
|
+
const conditions = new Set([
|
|
653
|
+
"browser",
|
|
654
|
+
"import",
|
|
655
|
+
...(typeof config?.conditions === "string"
|
|
656
|
+
? [config.conditions]
|
|
657
|
+
: (config?.conditions ?? [])),
|
|
658
|
+
]);
|
|
659
|
+
// The pinned copy's export map, read once. `null` when Svelte is not resolvable from the app root.
|
|
660
|
+
let pinned;
|
|
661
|
+
const pin = () => {
|
|
662
|
+
if (pinned !== undefined)
|
|
663
|
+
return pinned;
|
|
664
|
+
try {
|
|
665
|
+
const manifest = Bun.resolveSync("svelte/package.json", from);
|
|
666
|
+
const map = JSON.parse(readFileSync(manifest, "utf8")).exports;
|
|
667
|
+
pinned =
|
|
668
|
+
typeof map === "object" && map !== null
|
|
669
|
+
? { root: dirname(manifest), map: map }
|
|
670
|
+
: null;
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
pinned = null;
|
|
674
|
+
}
|
|
675
|
+
return pinned;
|
|
676
|
+
};
|
|
550
677
|
build.onResolve({ filter: SVELTE_DEDUPE_PATTERN }, (args) => {
|
|
551
678
|
try {
|
|
679
|
+
if (browser) {
|
|
680
|
+
const copy = pin();
|
|
681
|
+
const target = copy === null
|
|
682
|
+
? undefined
|
|
683
|
+
: exportTarget(copy.map[`.${args.path.slice("svelte".length)}`], conditions);
|
|
684
|
+
if (copy !== null && target !== undefined)
|
|
685
|
+
return { path: join(copy.root, target) };
|
|
686
|
+
}
|
|
552
687
|
return { path: Bun.resolveSync(args.path, from) };
|
|
553
688
|
}
|
|
554
689
|
catch {
|
|
@@ -557,6 +692,22 @@ export const svelteDedupePlugin = (from) => ({
|
|
|
557
692
|
});
|
|
558
693
|
},
|
|
559
694
|
});
|
|
695
|
+
/** Walk one `exports` entry the way a resolver does: the first key, in the package's own order, that is
|
|
696
|
+
* an active condition (or `default`) wins. */
|
|
697
|
+
function exportTarget(entry, conditions) {
|
|
698
|
+
if (typeof entry === "string")
|
|
699
|
+
return entry;
|
|
700
|
+
if (typeof entry !== "object" || entry === null)
|
|
701
|
+
return undefined;
|
|
702
|
+
for (const [key, value] of Object.entries(entry)) {
|
|
703
|
+
if (key !== "default" && !conditions.has(key))
|
|
704
|
+
continue;
|
|
705
|
+
const target = exportTarget(value, conditions);
|
|
706
|
+
if (target !== undefined)
|
|
707
|
+
return target;
|
|
708
|
+
}
|
|
709
|
+
return undefined;
|
|
710
|
+
}
|
|
560
711
|
/**
|
|
561
712
|
* The app's declared single-copy rule, applied to the bundle.
|
|
562
713
|
*
|
|
@@ -571,32 +722,50 @@ export const svelteDedupePlugin = (from) => ({
|
|
|
571
722
|
*/
|
|
572
723
|
const declaredSingleCopyPlugins = (root) => readSingleCopyDeclaration(root) === undefined ? [] : [declaredSingleCopyPlugin({ cwd: root })];
|
|
573
724
|
/**
|
|
574
|
-
*
|
|
575
|
-
* `
|
|
576
|
-
*
|
|
577
|
-
* import resolves to `undefined` rather than a "missing export" bundle error (verified), and the real
|
|
578
|
-
* import subtree is gone. The complement to the node-builtin guard: when a server-only import is co-located
|
|
579
|
-
* in a route file (so it can't be tree-shaken out and the guard fails loud), moving it into a `*.server`
|
|
580
|
-
* module is the fix. CLIENT-only - buildServer keeps the real module, which runs server-side.
|
|
725
|
+
* Give each output chunk the specifiers its emitted code imports. Bun's metafile does not record them
|
|
726
|
+
* (its output `imports` stay empty even for an external the code keeps), so they are read from the code.
|
|
727
|
+
* A specifier under `publicPath` or relative to the chunk is mapped back to the output it names.
|
|
581
728
|
*/
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
729
|
+
function withEmittedImports(graph, emitted, publicPath) {
|
|
730
|
+
const scanner = new Bun.Transpiler({ loader: "js" });
|
|
731
|
+
const imports = new Map();
|
|
732
|
+
for (const file of emitted) {
|
|
733
|
+
if (file.kind !== "code" || file.text === undefined) {
|
|
734
|
+
imports.set(posix.normalize(file.name), []);
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
imports.set(posix.normalize(file.name), scanner.scanImports(file.text).map(({ path }) => {
|
|
738
|
+
if (publicPath !== undefined && path.startsWith(publicPath))
|
|
739
|
+
return path.slice(publicPath.length);
|
|
740
|
+
if (path.startsWith("./") || path.startsWith("../"))
|
|
741
|
+
return posix.join(posix.dirname(posix.normalize(file.name)), path);
|
|
742
|
+
return path;
|
|
588
743
|
}));
|
|
589
|
-
}
|
|
590
|
-
}
|
|
744
|
+
}
|
|
745
|
+
const chunks = {};
|
|
746
|
+
// A chunk with no emitted file keeps no `imports`, which the verifiers treat as missing evidence.
|
|
747
|
+
for (const [path, chunk] of Object.entries(graph.chunks)) {
|
|
748
|
+
const kept = imports.get(posix.normalize(path));
|
|
749
|
+
chunks[path] = kept === undefined ? chunk : { ...chunk, imports: kept };
|
|
750
|
+
}
|
|
751
|
+
return { ...graph, chunks };
|
|
752
|
+
}
|
|
753
|
+
/** Refusals the bundler stopped on before it produced a graph, so without import chains. */
|
|
754
|
+
function formatRefusedFiles(refused, root) {
|
|
755
|
+
const lines = [...refused].map(([file, reason]) => {
|
|
756
|
+
const rel = relative(root, file).replaceAll("\\", "/");
|
|
757
|
+
return ` - ${rel.startsWith("..") ? file : rel}: ${reason}`;
|
|
758
|
+
});
|
|
759
|
+
return `[nifra/web] the browser build reached code that may not ship to a browser:\n${lines.join("\n")}`;
|
|
760
|
+
}
|
|
761
|
+
export { zoneGuardPlugin } from "./plugins/zone-guard.js";
|
|
591
762
|
/**
|
|
592
763
|
* Server functions in the CLIENT build: replace each `*.fn.ts` module with stubs that call the routes
|
|
593
764
|
* the server mounted, so the function bodies - and everything they import - never reach a browser.
|
|
594
765
|
*
|
|
595
|
-
* The
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
* `internal/server-boundary.ts` so the Vite pipeline emits identical stubs from the same code; two
|
|
599
|
-
* hand-written copies would be a client that works in dev and 404s in production.
|
|
766
|
+
* The generation itself is in `internal/server-boundary.ts` so the Vite pipeline emits identical stubs
|
|
767
|
+
* from the same code; two hand-written copies would be a client that works in dev and 404s in
|
|
768
|
+
* production.
|
|
600
769
|
*
|
|
601
770
|
* CLIENT-only. The server build keeps the real module, which is what `serverFunctions()` mounts.
|
|
602
771
|
*/
|
|
@@ -665,6 +834,7 @@ export async function buildServer(options) {
|
|
|
665
834
|
entrypoints: [serverEntry],
|
|
666
835
|
outdir: outDir,
|
|
667
836
|
target,
|
|
837
|
+
metafile: true,
|
|
668
838
|
conditions: [...conditions],
|
|
669
839
|
define: {
|
|
670
840
|
...(options.define ?? { "process.env.NODE_ENV": '"production"' }),
|
|
@@ -692,6 +862,38 @@ export async function buildServer(options) {
|
|
|
692
862
|
if (!result.success) {
|
|
693
863
|
throw new Error(`[nifra/web] server build failed:\n${result.logs.map((l) => String(l)).join("\n")}`);
|
|
694
864
|
}
|
|
865
|
+
// Named as the metafile names outputs: relative to `outdir`.
|
|
866
|
+
const nameOf = (path) => relative(outDir, path).replaceAll("\\", "/");
|
|
867
|
+
const emitted = await Promise.all(result.outputs.map(async (out) => out.kind === "sourcemap" || out.kind === "asset"
|
|
868
|
+
? { name: nameOf(out.path), kind: out.kind === "sourcemap" ? "map" : "asset" }
|
|
869
|
+
: { name: nameOf(out.path), kind: "code", text: await out.text() }));
|
|
870
|
+
const serverGraph = withEmittedImports(fromBunMetafile(result.metafile), emitted);
|
|
871
|
+
const appRoot = resolvePath(options.root ?? dirname(routesDir));
|
|
872
|
+
const classifier = createZoneClassifier({
|
|
873
|
+
appRoot,
|
|
874
|
+
routesDir,
|
|
875
|
+
generatedFiles: [
|
|
876
|
+
serverEntry,
|
|
877
|
+
join(entryDir, manifestFile),
|
|
878
|
+
...(options.generatedFiles ?? []),
|
|
879
|
+
].map((file) => resolvePath(file)),
|
|
880
|
+
});
|
|
881
|
+
const sourceOf = bunModuleSource(process.cwd());
|
|
882
|
+
const labelOf = (id) => {
|
|
883
|
+
const source = sourceOf(id);
|
|
884
|
+
return source.kind === "file" ? relative(appRoot, source.file).replaceAll("\\", "/") : id;
|
|
885
|
+
};
|
|
886
|
+
const refusal = formatServerGraphVerdict(verifyServerGraph(serverGraph, {
|
|
887
|
+
classifier,
|
|
888
|
+
sourceOf,
|
|
889
|
+
privateEnv: privateEnvCheck(options.publicEnvPrefix ?? "PUBLIC_"),
|
|
890
|
+
})) ?? formatUnsupportedBuiltins(unsupportedBuiltins(serverGraph, target, labelOf), target);
|
|
891
|
+
if (refusal !== undefined) {
|
|
892
|
+
// Bun wrote the bundle already; a refused build leaves nothing a deploy step could pick up.
|
|
893
|
+
for (const output of result.outputs)
|
|
894
|
+
rmSync(output.path, { force: true });
|
|
895
|
+
throw new Error(refusal);
|
|
896
|
+
}
|
|
695
897
|
const entryOutput = result.outputs.find((o) => o.kind === "entry-point");
|
|
696
898
|
if (entryOutput === undefined) {
|
|
697
899
|
throw new Error("[nifra/web] server build produced no entry-point output");
|
|
@@ -706,12 +908,24 @@ export async function buildServer(options) {
|
|
|
706
908
|
// The ONLY thing apps used to hand-write per target was the server entry (`_worker.ts`, `server-bun.ts`,
|
|
707
909
|
// …) - so we GENERATE it here (per target) instead of asking each app to ship five near-identical files.
|
|
708
910
|
// ===================================================================================================
|
|
911
|
+
/** The `createWebApp` options a generated server entry can import from the app's framework module. */
|
|
912
|
+
export const SERVER_ENTRY_OPTIONS = ["apiPrefix", "apiStrip", "mounts", "csp", "nonce"];
|
|
913
|
+
/**
|
|
914
|
+
* The header each edge platform sets to the caller's address, replacing any value the client sent.
|
|
915
|
+
* That replacement is the whole trust: the same bundle served without the platform's edge in front
|
|
916
|
+
* (`wrangler pages dev`, a self-hosted workerd) hands the header straight from the client, which is
|
|
917
|
+
* why it is only believed on an explicit `clientIp: "platform"`.
|
|
918
|
+
*/
|
|
919
|
+
const PLATFORM_CLIENT_IP_HEADERS = {
|
|
920
|
+
cloudflare: "cf-connecting-ip",
|
|
921
|
+
vercel: "x-real-ip",
|
|
922
|
+
};
|
|
709
923
|
/**
|
|
710
924
|
* Codegen the per-target **server entry** module (source text) for `buildServer` to bundle. It imports
|
|
711
925
|
* the app's `adapter` (from `framework.ts`), the optional `backend` (from `backend.ts`), and the
|
|
712
926
|
* generated `{ manifest, clientEntry }` (from `./server-manifest`), builds `createWebApp`, then wires
|
|
713
927
|
* the right host:
|
|
714
|
-
* - `
|
|
928
|
+
* - `cloudflare` / `vercel`: `export default` the fetch handler (the platform serves /assets/* itself).
|
|
715
929
|
* - `deno`: same fetch-handler default, plus `Deno.serve` self-host when run directly.
|
|
716
930
|
* - `bun` / `node`: a self-hosting server that ALSO serves the client bundle from disk (those
|
|
717
931
|
* runtimes have a filesystem; the static `/assets/*` sit next to the entry).
|
|
@@ -720,7 +934,23 @@ export async function buildServer(options) {
|
|
|
720
934
|
* string out) so the generation is unit-testable without a real build.
|
|
721
935
|
*/
|
|
722
936
|
export function generateServerEntry(options) {
|
|
723
|
-
const { target, adapterImport, backendImport, useImport, title = "nifra", publicFiles = [], } = options;
|
|
937
|
+
const { target, adapterImport, backendImport, useImport, optionImports = {}, title = "nifra", publicFiles = [], clientIp, } = options;
|
|
938
|
+
if (clientIp !== undefined && clientIp !== "platform") {
|
|
939
|
+
throw new Error(`[nifra/web] generateServerEntry: clientIp must be "platform" or left out, got ${JSON.stringify(clientIp)}`);
|
|
940
|
+
}
|
|
941
|
+
const clientIpHeader = clientIp === "platform" ? PLATFORM_CLIENT_IP_HEADERS[target] : undefined;
|
|
942
|
+
// Identifiers come only from the fixed list, never from the caller's keys, so generated code names
|
|
943
|
+
// nothing the list does not.
|
|
944
|
+
const importedOptions = SERVER_ENTRY_OPTIONS.filter((name) => optionImports[name] !== undefined);
|
|
945
|
+
const optionModules = new Map();
|
|
946
|
+
for (const name of importedOptions) {
|
|
947
|
+
const from = optionImports[name];
|
|
948
|
+
const names = optionModules.get(from);
|
|
949
|
+
if (names === undefined)
|
|
950
|
+
optionModules.set(from, [name]);
|
|
951
|
+
else
|
|
952
|
+
names.push(name);
|
|
953
|
+
}
|
|
724
954
|
if (target === "static") {
|
|
725
955
|
throw new Error("[nifra/web] generateServerEntry: `static` has no server entry (SSG only)");
|
|
726
956
|
}
|
|
@@ -730,18 +960,24 @@ export function generateServerEntry(options) {
|
|
|
730
960
|
lines.push(`import { adapter } from ${JSON.stringify(adapterImport)}`);
|
|
731
961
|
if (useImport !== undefined)
|
|
732
962
|
lines.push(`import { use } from ${JSON.stringify(useImport)}`);
|
|
963
|
+
for (const [from, names] of optionModules) {
|
|
964
|
+
lines.push(`import { ${names.join(", ")} } from ${JSON.stringify(from)}`);
|
|
965
|
+
}
|
|
733
966
|
if (backendImport !== undefined) {
|
|
734
967
|
lines.push(`import { backend } from ${JSON.stringify(backendImport)}`);
|
|
735
968
|
}
|
|
736
969
|
lines.push('import { clientEntry, cssLoading, manifest, styles, routeStyles } from "./server-manifest"');
|
|
737
|
-
//
|
|
738
|
-
|
|
739
|
-
if (usesToFetch)
|
|
970
|
+
// Cloudflare needs the fetch-handler shape; the others call app.fetch directly.
|
|
971
|
+
if (target === "cloudflare")
|
|
740
972
|
lines.push('import { toFetchHandler } from "@nifrajs/core/server"');
|
|
741
973
|
if (target === "node")
|
|
742
974
|
lines.push('import { serve } from "@nifrajs/node"');
|
|
743
|
-
lines.push("", "const app = createWebApp({", " adapter,", ...(useImport !== undefined ? [" use,"] : []), " manifest,", " clientEntry,", " styles,", " routeStyles,", " cssLoading,", ...(backendImport !== undefined ? [" api: inProcessClient(backend),"] : []), ` title: ${JSON.stringify(title)},`,
|
|
744
|
-
|
|
975
|
+
lines.push("", "const app = createWebApp({", " adapter,", ...(useImport !== undefined ? [" use,"] : []), ...importedOptions.map((name) => ` ${name},`), " manifest,", " clientEntry,", " styles,", " routeStyles,", " cssLoading,", ...(backendImport !== undefined ? [" api: inProcessClient(backend),"] : []), ` title: ${JSON.stringify(title)},`,
|
|
976
|
+
// Core's own `{ header }` trust, so the mounted backend and every loader see the derived caller.
|
|
977
|
+
...(clientIpHeader !== undefined
|
|
978
|
+
? [` server: { clientIp: { header: ${JSON.stringify(clientIpHeader)} } },`]
|
|
979
|
+
: []), "})", "");
|
|
980
|
+
if (target === "cloudflare") {
|
|
745
981
|
// Cloudflare Pages advanced mode: `_routes.json` keeps static paths off the worker entirely, and
|
|
746
982
|
// everything else falls through to this handler (SSR).
|
|
747
983
|
//
|
|
@@ -757,20 +993,26 @@ export function generateServerEntry(options) {
|
|
|
757
993
|
return `${lines.join("\n")}\n`;
|
|
758
994
|
}
|
|
759
995
|
// bun / node / deno self-host AND serve the client bundle from disk (it sits next to this entry).
|
|
760
|
-
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)})`, "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", "}",
|
|
996
|
+
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)})`, "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", "}",
|
|
997
|
+
// The type table and cache policy `servePublicDir` uses. Without a known type, Bun answers a file
|
|
998
|
+
// as an `application/octet-stream` attachment, which a browser downloads instead of showing.
|
|
999
|
+
`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": pathname.startsWith("/assets/") ? ${JSON.stringify(IMMUTABLE)} : ${JSON.stringify(ONE_DAY)},`, ' "x-content-type-options": "nosniff",', "})");
|
|
1000
|
+
// Bun.serve and Deno.serve delimit the body themselves, so its declared length is the transport
|
|
1001
|
+
// frame - the guarantee core's own listen() and the Deno adapter mark (see markTrustedBodyFraming).
|
|
1002
|
+
const markFramed = '(req as unknown as Record<symbol, unknown>)[Symbol.for("nifra.body.trustedFraming")] = true';
|
|
761
1003
|
if (target === "bun") {
|
|
762
|
-
lines.push("const server = Bun.serve({", " port: Number(Bun.env.PORT ?? 3000),", " async fetch(req) {", " 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 })',
|
|
1004
|
+
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 } })", " },", "})",
|
|
763
1005
|
// The `${...}` here is literal OUTPUT (a template in the GENERATED file), not a template in this
|
|
764
1006
|
// source - split so biome's noTemplateCurlyInString doesn't flag it; the emitted line is unchanged.
|
|
765
1007
|
`console.log(\`nifra (Bun) → http://localhost:$${"{server.port}"}\`)`);
|
|
766
1008
|
return `${lines.join("\n")}\n`;
|
|
767
1009
|
}
|
|
768
1010
|
if (target === "node") {
|
|
769
|
-
lines.push('import { readFile } from "node:fs/promises"', "await serve(", " {", " async fetch(req) {", " const { pathname } = new URL(req.url)", " const filePath = staticPath(pathname)", " if (filePath !== undefined) {", " try {", " const body = await readFile(new URL(filePath, STATIC_ROOT))",
|
|
1011
|
+
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) },", ")");
|
|
770
1012
|
return `${lines.join("\n")}\n`;
|
|
771
1013
|
}
|
|
772
1014
|
// deno
|
|
773
|
-
lines.push("
|
|
1015
|
+
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 })", "})");
|
|
774
1016
|
return `${lines.join("\n")}\n`;
|
|
775
1017
|
}
|
|
776
1018
|
/** Measure each emitted output file's raw + gzip size. Reads the file off disk and gzips it with
|
|
@@ -793,15 +1035,13 @@ async function measureOutputs(paths) {
|
|
|
793
1035
|
* `<outDir>/assets/*`, then per target:
|
|
794
1036
|
* - `static`: prerenders opted-in routes (`prerenderRoutes`) to `<outDir>/<path>/index.html` (+
|
|
795
1037
|
* `_data.json`); needs `prerenderApp`. No server.
|
|
796
|
-
* - `
|
|
797
|
-
* - `vercel`:
|
|
798
|
-
*
|
|
1038
|
+
* - `cloudflare`: a `_worker.js` (edge bundle) + a `_routes.json` excluding /assets/* from the worker.
|
|
1039
|
+
* - `vercel`: Vercel's Build Output API v3 - `config.json`, the client bundle and public files under
|
|
1040
|
+
* `static/`, and the edge function at `functions/index.func/index.js`; `vercel deploy --prebuilt`
|
|
1041
|
+
* uploads it as it is when `outDir` is `.vercel/output`.
|
|
799
1042
|
* - `deno`/`node`/`bun`: the self-hosting server bundle (`server.js`) next to the assets.
|
|
800
1043
|
* The server entry is GENERATED (`generateServerEntry`) and bundled (`buildServer`); the app supplies
|
|
801
1044
|
* only adapter/backend/routes. Returns the manifest + a size report. Throws on any build failure.
|
|
802
|
-
*
|
|
803
|
-
* Note: the heavier platform wrappers (`.vercel/output` v3 layout, wrangler ISR `find_additional_modules`)
|
|
804
|
-
* remain app-owned scripts; this command targets the common single-bundle deploys. See the CLI docs.
|
|
805
1045
|
*/
|
|
806
1046
|
/** The default (Bun) strategy - `buildClient`/`buildServer` from this module. */
|
|
807
1047
|
export const bunBundler = {
|
|
@@ -820,6 +1060,7 @@ export const bunBundler = {
|
|
|
820
1060
|
...(input.cssCodeSplit !== undefined ? { cssCodeSplit } : {}),
|
|
821
1061
|
...(input.publicDir !== undefined ? { publicDir: input.publicDir } : {}),
|
|
822
1062
|
...(input.publicEnvPrefix !== undefined ? { publicEnvPrefix: input.publicEnvPrefix } : {}),
|
|
1063
|
+
...(input.secretExemptions !== undefined ? { secretExemptions: input.secretExemptions } : {}),
|
|
823
1064
|
});
|
|
824
1065
|
},
|
|
825
1066
|
buildServer: (input) => buildServer({
|
|
@@ -833,6 +1074,9 @@ export const bunBundler = {
|
|
|
833
1074
|
...(input.plugins ? { plugins: input.plugins } : {}),
|
|
834
1075
|
...(input.define ? { define: input.define } : {}),
|
|
835
1076
|
...(input.cssLoading !== undefined ? { cssLoading: input.cssLoading } : {}),
|
|
1077
|
+
...(input.root !== undefined ? { root: input.root } : {}),
|
|
1078
|
+
...(input.generatedFiles !== undefined ? { generatedFiles: input.generatedFiles } : {}),
|
|
1079
|
+
...(input.publicEnvPrefix !== undefined ? { publicEnvPrefix: input.publicEnvPrefix } : {}),
|
|
836
1080
|
}),
|
|
837
1081
|
};
|
|
838
1082
|
/** Build a full deploy dir for `target` using the default Bun bundler. See {@link buildTargetWith}. */
|
|
@@ -849,14 +1093,15 @@ export async function buildTargetWith(target, options, bundler) {
|
|
|
849
1093
|
const { routesDir, outDir, workDir } = options;
|
|
850
1094
|
const requestedCssCodeSplit = options.cssCodeSplit === undefined ? undefined : normalizeCssCodeSplit(options.cssCodeSplit);
|
|
851
1095
|
const requestedCssLoading = options.cssLoading === undefined ? undefined : normalizeCssLoading(options.cssLoading);
|
|
852
|
-
const targetPlan = planBuildTarget(target, outDir);
|
|
1096
|
+
const targetPlan = planBuildTarget(parseBuildTarget(target), outDir);
|
|
853
1097
|
const { rmSync } = await import("node:fs");
|
|
854
1098
|
rmSync(outDir, { recursive: true, force: true });
|
|
855
1099
|
rmSync(workDir, { recursive: true, force: true });
|
|
856
|
-
const
|
|
1100
|
+
const staticRoot = targetPlan.staticDir === "" ? outDir : `${outDir}/${targetPlan.staticDir}`;
|
|
1101
|
+
const assetsDir = `${staticRoot}/assets`;
|
|
857
1102
|
mkdirSync(assetsDir, { recursive: true });
|
|
858
1103
|
mkdirSync(workDir, { recursive: true });
|
|
859
|
-
// (1) Client bundle → <
|
|
1104
|
+
// (1) Client bundle → <static root>/assets/* (every target ships the same hashed client bundle).
|
|
860
1105
|
let client = await bundler.buildClient({
|
|
861
1106
|
routesDir,
|
|
862
1107
|
outDir: assetsDir,
|
|
@@ -866,6 +1111,9 @@ export async function buildTargetWith(target, options, bundler) {
|
|
|
866
1111
|
define: { "process.env.NODE_ENV": '"production"', ...(options.define ?? {}) },
|
|
867
1112
|
publicDir: false,
|
|
868
1113
|
...(options.publicEnvPrefix !== undefined ? { publicEnvPrefix: options.publicEnvPrefix } : {}),
|
|
1114
|
+
...(options.secretExemptions !== undefined
|
|
1115
|
+
? { secretExemptions: options.secretExemptions }
|
|
1116
|
+
: {}),
|
|
869
1117
|
...(requestedCssCodeSplit !== undefined ? { cssCodeSplit: requestedCssCodeSplit } : {}),
|
|
870
1118
|
...(requestedCssLoading !== undefined ? { cssLoading: requestedCssLoading } : {}),
|
|
871
1119
|
root: resolvePath(dirname(routesDir)),
|
|
@@ -879,7 +1127,20 @@ export async function buildTargetWith(target, options, bundler) {
|
|
|
879
1127
|
const publicDir = options.publicDir === false
|
|
880
1128
|
? undefined
|
|
881
1129
|
: resolvePath(options.publicDir ?? join(dirname(routesDir), "public"));
|
|
882
|
-
const
|
|
1130
|
+
const secretScan = {
|
|
1131
|
+
env: buildEnvironment(),
|
|
1132
|
+
publicEnvPrefix: options.publicEnvPrefix ?? "PUBLIC_",
|
|
1133
|
+
...(options.secretExemptions !== undefined ? { exemptions: options.secretExemptions } : {}),
|
|
1134
|
+
};
|
|
1135
|
+
if (publicDir !== undefined && existsSync(publicDir)) {
|
|
1136
|
+
assertNoSecrets({
|
|
1137
|
+
...secretScan,
|
|
1138
|
+
artifacts: publicScanFiles(publicDir, originName(dirname(routesDir), publicDir)),
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
const publicFiles = publicDir !== undefined && existsSync(publicDir)
|
|
1142
|
+
? await copyPublicDir(publicDir, staticRoot)
|
|
1143
|
+
: [];
|
|
883
1144
|
if (publicFiles.length > 0) {
|
|
884
1145
|
client = { ...client, publicFiles };
|
|
885
1146
|
}
|
|
@@ -902,6 +1163,7 @@ export async function buildTargetWith(target, options, bundler) {
|
|
|
902
1163
|
app,
|
|
903
1164
|
routes: manifest.routes,
|
|
904
1165
|
outDir,
|
|
1166
|
+
secrets: secretScan,
|
|
905
1167
|
});
|
|
906
1168
|
if (result.prerendered.length === 0) {
|
|
907
1169
|
// A static build that renders nothing is almost always a misconfig (no `prerender = true` / no
|
|
@@ -927,9 +1189,11 @@ export async function buildTargetWith(target, options, bundler) {
|
|
|
927
1189
|
target,
|
|
928
1190
|
adapterImport: options.adapterImport,
|
|
929
1191
|
...(options.useImport !== undefined ? { useImport: options.useImport } : {}),
|
|
1192
|
+
...(options.optionImports !== undefined ? { optionImports: options.optionImports } : {}),
|
|
930
1193
|
...(options.backendImport !== undefined ? { backendImport: options.backendImport } : {}),
|
|
931
1194
|
...(options.title !== undefined ? { title: options.title } : {}),
|
|
932
1195
|
...(publicFiles.length > 0 ? { publicFiles } : {}),
|
|
1196
|
+
...(options.clientIp !== undefined ? { clientIp: options.clientIp } : {}),
|
|
933
1197
|
}));
|
|
934
1198
|
const { worker } = await bundler.buildServer({
|
|
935
1199
|
routesDir,
|
|
@@ -947,10 +1211,12 @@ export async function buildTargetWith(target, options, bundler) {
|
|
|
947
1211
|
? { cssLoading: requestedCssLoading }
|
|
948
1212
|
: {}),
|
|
949
1213
|
root: resolvePath(dirname(routesDir)),
|
|
1214
|
+
generatedFiles: [resolvePath(workDir, options.adapterImport)],
|
|
1215
|
+
...(options.publicEnvPrefix !== undefined ? { publicEnvPrefix: options.publicEnvPrefix } : {}),
|
|
950
1216
|
});
|
|
951
1217
|
// (3) Assemble the deploy dir for the target.
|
|
952
1218
|
const { cpSync } = await import("node:fs");
|
|
953
|
-
if (targetPlan.target === "
|
|
1219
|
+
if (targetPlan.target === "cloudflare") {
|
|
954
1220
|
cpSync(worker, `${outDir}/${targetPlan.outputFile}`);
|
|
955
1221
|
// The app's real patterns, so a directory is only collapsed into a glob once the route table
|
|
956
1222
|
// proves nothing can be served beneath it.
|
|
@@ -965,7 +1231,12 @@ export async function buildTargetWith(target, options, bundler) {
|
|
|
965
1231
|
}
|
|
966
1232
|
}
|
|
967
1233
|
else if (targetPlan.target === "vercel") {
|
|
1234
|
+
const fn = dirname(`${outDir}/${targetPlan.outputFile}`);
|
|
1235
|
+
mkdirSync(fn, { recursive: true });
|
|
968
1236
|
cpSync(worker, `${outDir}/${targetPlan.outputFile}`);
|
|
1237
|
+
writeFileSync(`${fn}/.vc-config.json`, `${JSON.stringify({ runtime: "edge", entrypoint: "index.js" }, null, 2)}\n`);
|
|
1238
|
+
// Files the build wrote are served first; every other path is the SSR function's.
|
|
1239
|
+
writeFileSync(`${outDir}/config.json`, `${JSON.stringify({ version: 3, routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/index" }] }, null, 2)}\n`);
|
|
969
1240
|
}
|
|
970
1241
|
else {
|
|
971
1242
|
cpSync(worker, `${outDir}/${targetPlan.outputFile}`);
|