@nifrajs/web 4.0.2 → 5.1.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/README.md +19 -0
- package/dist/build-vite.d.ts +19 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +31 -10
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +8 -10
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +90 -31
- package/dist/build.js.map +1 -1
- package/dist/cdn.d.ts +8 -0
- package/dist/cdn.d.ts.map +1 -1
- package/dist/cdn.js +14 -15
- package/dist/cdn.js.map +1 -1
- package/dist/client.d.ts +6 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +46 -18
- package/dist/client.js.map +1 -1
- package/dist/deferred.d.ts +2 -2
- package/dist/deferred.d.ts.map +1 -1
- package/dist/deferred.js +28 -7
- package/dist/deferred.js.map +1 -1
- package/dist/dev-feed.d.ts.map +1 -1
- package/dist/dev-feed.js +24 -5
- package/dist/dev-feed.js.map +1 -1
- package/dist/dev-port.d.ts +3 -2
- package/dist/dev-port.d.ts.map +1 -1
- package/dist/dev-port.js +2 -6
- package/dist/dev-port.js.map +1 -1
- package/dist/dev-session.d.ts.map +1 -1
- package/dist/dev-session.js +4 -14
- package/dist/dev-session.js.map +1 -1
- package/dist/dev.d.ts +7 -17
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.js +13 -1
- package/dist/dev.js.map +1 -1
- package/dist/draft.d.ts +8 -5
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +30 -13
- package/dist/draft.js.map +1 -1
- package/dist/fonts-google.d.ts.map +1 -1
- package/dist/fonts-google.js +7 -0
- package/dist/fonts-google.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/internal/codegen.d.ts +1 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +56 -29
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/disposable-dir.d.ts +8 -0
- package/dist/internal/disposable-dir.d.ts.map +1 -0
- package/dist/internal/disposable-dir.js +25 -0
- package/dist/internal/disposable-dir.js.map +1 -0
- package/dist/internal/entry-chunks.d.ts +9 -0
- package/dist/internal/entry-chunks.d.ts.map +1 -0
- package/dist/internal/entry-chunks.js +15 -0
- package/dist/internal/entry-chunks.js.map +1 -0
- package/dist/internal/evidence-traversal.d.ts +12 -0
- package/dist/internal/evidence-traversal.d.ts.map +1 -0
- package/dist/internal/evidence-traversal.js +48 -0
- package/dist/internal/evidence-traversal.js.map +1 -0
- package/dist/internal/head-merge.d.ts +6 -0
- package/dist/internal/head-merge.d.ts.map +1 -1
- package/dist/internal/head-merge.js +9 -0
- package/dist/internal/head-merge.js.map +1 -1
- package/dist/internal/js-string.d.ts +2 -0
- package/dist/internal/js-string.d.ts.map +1 -1
- package/dist/internal/js-string.js +5 -1
- package/dist/internal/js-string.js.map +1 -1
- package/dist/internal/mount-shadow.js +1 -1
- package/dist/internal/mount-shadow.js.map +1 -1
- package/dist/internal/output-guard.d.ts +7 -0
- package/dist/internal/output-guard.d.ts.map +1 -1
- package/dist/internal/output-guard.js +80 -8
- package/dist/internal/output-guard.js.map +1 -1
- package/dist/internal/package-entries.d.ts +34 -0
- package/dist/internal/package-entries.d.ts.map +1 -0
- package/dist/internal/package-entries.js +324 -0
- package/dist/internal/package-entries.js.map +1 -0
- package/dist/internal/page-execution.d.ts +6 -0
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +437 -114
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +15 -2
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +8 -0
- package/dist/internal/private-env.d.ts.map +1 -1
- package/dist/internal/private-env.js +25 -10
- package/dist/internal/private-env.js.map +1 -1
- package/dist/internal/progressive-cookies.d.ts +14 -0
- package/dist/internal/progressive-cookies.d.ts.map +1 -0
- package/dist/internal/progressive-cookies.js +43 -0
- package/dist/internal/progressive-cookies.js.map +1 -0
- package/dist/internal/public-env.d.ts +2 -0
- package/dist/internal/public-env.d.ts.map +1 -0
- package/dist/internal/public-env.js +13 -0
- package/dist/internal/public-env.js.map +1 -0
- package/dist/internal/render-document.d.ts +5 -44
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +51 -63
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/response-controls.d.ts +2 -0
- package/dist/internal/response-controls.d.ts.map +1 -1
- package/dist/internal/response-controls.js +26 -16
- package/dist/internal/response-controls.js.map +1 -1
- package/dist/internal/revive-dates.d.ts +16 -0
- package/dist/internal/revive-dates.d.ts.map +1 -0
- package/dist/internal/revive-dates.js +32 -0
- package/dist/internal/revive-dates.js.map +1 -0
- package/dist/internal/route-path.d.ts +14 -0
- package/dist/internal/route-path.d.ts.map +1 -0
- package/dist/internal/route-path.js +19 -0
- package/dist/internal/route-path.js.map +1 -0
- package/dist/internal/secret-scan.d.ts.map +1 -1
- package/dist/internal/secret-scan.js +190 -20
- package/dist/internal/secret-scan.js.map +1 -1
- package/dist/internal/server-boundary.d.ts.map +1 -1
- package/dist/internal/server-boundary.js +33 -5
- package/dist/internal/server-boundary.js.map +1 -1
- package/dist/internal/web-app.d.ts +25 -18
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +81 -24
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/wire-date.d.ts +3 -0
- package/dist/internal/wire-date.d.ts.map +1 -0
- package/dist/internal/wire-date.js +14 -0
- package/dist/internal/wire-date.js.map +1 -0
- package/dist/internal/without-query.d.ts +4 -0
- package/dist/internal/without-query.d.ts.map +1 -0
- package/dist/internal/without-query.js +9 -0
- package/dist/internal/without-query.js.map +1 -0
- package/dist/internal/zone-graph.d.ts +4 -0
- package/dist/internal/zone-graph.d.ts.map +1 -1
- package/dist/internal/zone-graph.js +71 -20
- package/dist/internal/zone-graph.js.map +1 -1
- package/dist/isr.d.ts +59 -3
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +311 -41
- package/dist/isr.js.map +1 -1
- package/dist/llms-txt.d.ts +2 -0
- package/dist/llms-txt.d.ts.map +1 -1
- package/dist/llms-txt.js +29 -3
- package/dist/llms-txt.js.map +1 -1
- package/dist/manifest.d.ts +37 -4
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +4 -0
- package/dist/manifest.js.map +1 -1
- package/dist/open-resolved.d.ts +18 -0
- package/dist/open-resolved.d.ts.map +1 -0
- package/dist/open-resolved.js +62 -0
- package/dist/open-resolved.js.map +1 -0
- package/dist/plugins/kit.d.ts +1 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +26 -11
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +9 -1
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +84 -7
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
- package/dist/plugins/vite-zone-guard.js +17 -21
- package/dist/plugins/vite-zone-guard.js.map +1 -1
- package/dist/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +44 -3
- package/dist/plugins/zone-guard.js.map +1 -1
- package/dist/prerender.d.ts +5 -1
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +41 -19
- package/dist/prerender.js.map +1 -1
- package/dist/public-dir.d.ts +8 -1
- package/dist/public-dir.d.ts.map +1 -1
- package/dist/public-dir.js +17 -6
- package/dist/public-dir.js.map +1 -1
- package/dist/query.d.ts +13 -4
- package/dist/query.d.ts.map +1 -1
- package/dist/query.js +89 -26
- package/dist/query.js.map +1 -1
- package/dist/render-seam.d.ts +25 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +1 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/route-types.d.ts.map +1 -1
- package/dist/route-types.js +20 -9
- package/dist/route-types.js.map +1 -1
- package/dist/router.d.ts +9 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +47 -50
- package/dist/router.js.map +1 -1
- package/dist/seo.d.ts +49 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +48 -0
- package/dist/seo.js.map +1 -0
- package/dist/server-components/build-info.d.ts +3 -0
- package/dist/server-components/build-info.d.ts.map +1 -0
- package/dist/server-components/build-info.js +7 -0
- package/dist/server-components/build-info.js.map +1 -0
- package/dist/server-components/build-target.d.ts +12 -0
- package/dist/server-components/build-target.d.ts.map +1 -0
- package/dist/server-components/build-target.js +153 -0
- package/dist/server-components/build-target.js.map +1 -0
- package/dist/server-components/build.d.ts +24 -0
- package/dist/server-components/build.d.ts.map +1 -0
- package/dist/server-components/build.js +366 -0
- package/dist/server-components/build.js.map +1 -0
- package/dist/server-components/bundle-proof.d.ts +3 -0
- package/dist/server-components/bundle-proof.d.ts.map +1 -0
- package/dist/server-components/bundle-proof.js +23 -0
- package/dist/server-components/bundle-proof.js.map +1 -0
- package/dist/server-components/client.d.ts +46 -0
- package/dist/server-components/client.d.ts.map +1 -0
- package/dist/server-components/client.js +594 -0
- package/dist/server-components/client.js.map +1 -0
- package/dist/server-components/definition.d.ts +11 -0
- package/dist/server-components/definition.d.ts.map +1 -0
- package/dist/server-components/definition.js +94 -0
- package/dist/server-components/definition.js.map +1 -0
- package/dist/server-components/dev-http.d.ts +5 -0
- package/dist/server-components/dev-http.d.ts.map +1 -0
- package/dist/server-components/dev-http.js +71 -0
- package/dist/server-components/dev-http.js.map +1 -0
- package/dist/server-components/error.d.ts +9 -0
- package/dist/server-components/error.d.ts.map +1 -0
- package/dist/server-components/error.js +24 -0
- package/dist/server-components/error.js.map +1 -0
- package/dist/server-components/frames.d.ts +7 -0
- package/dist/server-components/frames.d.ts.map +1 -0
- package/dist/server-components/frames.js +169 -0
- package/dist/server-components/frames.js.map +1 -0
- package/dist/server-components/html-policy.d.ts +6 -0
- package/dist/server-components/html-policy.d.ts.map +1 -0
- package/dist/server-components/html-policy.js +75 -0
- package/dist/server-components/html-policy.js.map +1 -0
- package/dist/server-components/html.d.ts +4 -0
- package/dist/server-components/html.d.ts.map +1 -0
- package/dist/server-components/html.js +67 -0
- package/dist/server-components/html.js.map +1 -0
- package/dist/server-components/islands.d.ts +44 -0
- package/dist/server-components/islands.d.ts.map +1 -0
- package/dist/server-components/islands.js +42 -0
- package/dist/server-components/islands.js.map +1 -0
- package/dist/server-components/native.d.ts +4 -0
- package/dist/server-components/native.d.ts.map +1 -0
- package/dist/server-components/native.js +81 -0
- package/dist/server-components/native.js.map +1 -0
- package/dist/server-components/portal.d.ts +6 -0
- package/dist/server-components/portal.d.ts.map +1 -0
- package/dist/server-components/portal.js +35 -0
- package/dist/server-components/portal.js.map +1 -0
- package/dist/server-components/props.d.ts +3 -0
- package/dist/server-components/props.d.ts.map +1 -0
- package/dist/server-components/props.js +14 -0
- package/dist/server-components/props.js.map +1 -0
- package/dist/server-components/protocol.d.ts +44 -0
- package/dist/server-components/protocol.d.ts.map +1 -0
- package/dist/server-components/protocol.js +139 -0
- package/dist/server-components/protocol.js.map +1 -0
- package/dist/server-components/range.d.ts +24 -0
- package/dist/server-components/range.d.ts.map +1 -0
- package/dist/server-components/range.js +263 -0
- package/dist/server-components/range.js.map +1 -0
- package/dist/server-components/registry.d.ts +6 -0
- package/dist/server-components/registry.d.ts.map +1 -0
- package/dist/server-components/registry.js +30 -0
- package/dist/server-components/registry.js.map +1 -0
- package/dist/server-components/response.d.ts +4 -0
- package/dist/server-components/response.d.ts.map +1 -0
- package/dist/server-components/response.js +32 -0
- package/dist/server-components/response.js.map +1 -0
- package/dist/server-components/router-integration.d.ts +9 -0
- package/dist/server-components/router-integration.d.ts.map +1 -0
- package/dist/server-components/router-integration.js +29 -0
- package/dist/server-components/router-integration.js.map +1 -0
- package/dist/server-components/runtime.d.ts +14 -0
- package/dist/server-components/runtime.d.ts.map +1 -0
- package/dist/server-components/runtime.js +276 -0
- package/dist/server-components/runtime.js.map +1 -0
- package/dist/server-components/scheduler.d.ts +14 -0
- package/dist/server-components/scheduler.d.ts.map +1 -0
- package/dist/server-components/scheduler.js +74 -0
- package/dist/server-components/scheduler.js.map +1 -0
- package/dist/server-components/schema.d.ts +4 -0
- package/dist/server-components/schema.d.ts.map +1 -0
- package/dist/server-components/schema.js +77 -0
- package/dist/server-components/schema.js.map +1 -0
- package/dist/server-components/server-entry.d.ts +12 -0
- package/dist/server-components/server-entry.d.ts.map +1 -0
- package/dist/server-components/server-entry.js +90 -0
- package/dist/server-components/server-entry.js.map +1 -0
- package/dist/server-components/source.d.ts +15 -0
- package/dist/server-components/source.d.ts.map +1 -0
- package/dist/server-components/source.js +269 -0
- package/dist/server-components/source.js.map +1 -0
- package/dist/server-components/state.d.ts +4 -0
- package/dist/server-components/state.d.ts.map +1 -0
- package/dist/server-components/state.js +123 -0
- package/dist/server-components/state.js.map +1 -0
- package/dist/server-components/stream.d.ts +6 -0
- package/dist/server-components/stream.d.ts.map +1 -0
- package/dist/server-components/stream.js +70 -0
- package/dist/server-components/stream.js.map +1 -0
- package/dist/server-components/trusted-html.d.ts +3 -0
- package/dist/server-components/trusted-html.d.ts.map +1 -0
- package/dist/server-components/trusted-html.js +14 -0
- package/dist/server-components/trusted-html.js.map +1 -0
- package/dist/server-components/types.d.ts +107 -0
- package/dist/server-components/types.d.ts.map +1 -0
- package/dist/server-components/types.js +2 -0
- package/dist/server-components/types.js.map +1 -0
- package/dist/server-components/typescript.d.ts +11 -0
- package/dist/server-components/typescript.d.ts.map +1 -0
- package/dist/server-components/typescript.js +28 -0
- package/dist/server-components/typescript.js.map +1 -0
- package/dist/server-components/value.d.ts +5 -0
- package/dist/server-components/value.d.ts.map +1 -0
- package/dist/server-components/value.js +77 -0
- package/dist/server-components/value.js.map +1 -0
- package/dist/server-components/vite.d.ts +12 -0
- package/dist/server-components/vite.d.ts.map +1 -0
- package/dist/server-components/vite.js +197 -0
- package/dist/server-components/vite.js.map +1 -0
- package/dist/server-components.d.ts +5 -0
- package/dist/server-components.d.ts.map +1 -0
- package/dist/server-components.js +4 -0
- package/dist/server-components.js.map +1 -0
- package/dist/service-worker.d.ts +8 -1
- package/dist/service-worker.d.ts.map +1 -1
- package/dist/service-worker.js +20 -6
- package/dist/service-worker.js.map +1 -1
- package/dist/vite.d.ts +12 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +7 -1
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +64 -2
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +331 -28
- package/dist/zones.js.map +1 -1
- package/package.json +102 -4
- package/src/build-vite.ts +31 -9
- package/src/build.ts +109 -31
- package/src/cdn.ts +18 -12
- package/src/client.ts +48 -17
- package/src/deferred.ts +27 -9
- package/src/dev-feed.ts +24 -5
- package/src/dev-port.ts +4 -10
- package/src/dev-session.ts +4 -13
- package/src/dev.ts +29 -1
- package/src/draft.ts +35 -14
- package/src/fonts-google.ts +10 -0
- package/src/index.ts +5 -6
- package/src/internal/codegen.ts +60 -29
- package/src/internal/disposable-dir.ts +24 -0
- package/src/internal/entry-chunks.ts +18 -0
- package/src/internal/evidence-traversal.ts +57 -0
- package/src/internal/head-merge.ts +16 -0
- package/src/internal/js-string.ts +6 -1
- package/src/internal/mount-shadow.ts +1 -1
- package/src/internal/output-guard.ts +88 -7
- package/src/internal/package-entries.ts +331 -0
- package/src/internal/page-execution.ts +573 -114
- package/src/internal/parity.ts +15 -2
- package/src/internal/private-env.ts +27 -13
- package/src/internal/progressive-cookies.ts +60 -0
- package/src/internal/public-env.ts +14 -0
- package/src/internal/render-document.ts +66 -89
- package/src/internal/response-controls.ts +31 -14
- package/src/internal/revive-dates.ts +35 -0
- package/src/internal/route-path.ts +24 -0
- package/src/internal/secret-scan.ts +190 -18
- package/src/internal/server-boundary.ts +29 -5
- package/src/internal/web-app.ts +126 -42
- package/src/internal/wire-date.ts +16 -0
- package/src/internal/without-query.ts +8 -0
- package/src/internal/zone-graph.ts +78 -25
- package/src/isr.ts +364 -45
- package/src/llms-txt.ts +32 -3
- package/src/manifest.ts +38 -4
- package/src/open-resolved.ts +64 -0
- package/src/plugins/kit.ts +31 -11
- package/src/plugins/vite-leak-guard.ts +104 -8
- package/src/plugins/vite-zone-guard.ts +21 -19
- package/src/plugins/zone-guard.ts +41 -2
- package/src/prerender.ts +39 -18
- package/src/public-dir.ts +27 -8
- package/src/query.ts +107 -26
- package/src/render-seam.ts +30 -2
- package/src/route-types.ts +34 -7
- package/src/router.ts +66 -59
- package/src/seo.ts +68 -0
- package/src/server-components/build-info.ts +11 -0
- package/src/server-components/build-target.ts +194 -0
- package/src/server-components/build.ts +419 -0
- package/src/server-components/bundle-proof.ts +33 -0
- package/src/server-components/client.ts +663 -0
- package/src/server-components/definition.ts +125 -0
- package/src/server-components/dev-http.ts +75 -0
- package/src/server-components/error.ts +24 -0
- package/src/server-components/frames.ts +181 -0
- package/src/server-components/html-policy.ts +94 -0
- package/src/server-components/html.ts +81 -0
- package/src/server-components/islands.ts +111 -0
- package/src/server-components/native.ts +101 -0
- package/src/server-components/portal.ts +42 -0
- package/src/server-components/props.ts +19 -0
- package/src/server-components/protocol.ts +167 -0
- package/src/server-components/range.ts +313 -0
- package/src/server-components/registry.ts +34 -0
- package/src/server-components/response.ts +44 -0
- package/src/server-components/router-integration.ts +40 -0
- package/src/server-components/runtime.ts +376 -0
- package/src/server-components/scheduler.ts +84 -0
- package/src/server-components/schema.ts +69 -0
- package/src/server-components/server-entry.ts +98 -0
- package/src/server-components/source.ts +324 -0
- package/src/server-components/state.ts +143 -0
- package/src/server-components/stream.ts +71 -0
- package/src/server-components/trusted-html.ts +21 -0
- package/src/server-components/types.ts +138 -0
- package/src/server-components/typescript.ts +39 -0
- package/src/server-components/value.ts +82 -0
- package/src/server-components/vite.ts +242 -0
- package/src/server-components.ts +32 -0
- package/src/service-worker.ts +24 -7
- package/src/vite.ts +8 -1
- package/src/zones.ts +418 -32
package/src/llms-txt.ts
CHANGED
|
@@ -3,9 +3,12 @@ import {
|
|
|
3
3
|
reflectedRoutesFromEvidence,
|
|
4
4
|
snapshotProjectEvidence,
|
|
5
5
|
} from "@nifrajs/core/evidence"
|
|
6
|
+
import { type BackendEvidenceProvider, NIFRA_BACKEND_EVIDENCE } from "@nifrajs/core/mount"
|
|
6
7
|
import { compileRoutePattern, type RoutePatternSegment } from "@nifrajs/core/pattern"
|
|
7
8
|
|
|
8
9
|
const IDENT = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
|
10
|
+
const RESERVED_CLIENT_SEGMENT = /^(?:get|post|put|patch|delete|head|options)$/i
|
|
11
|
+
const RESERVED_EXACT_SEGMENTS = new Set(["subscribe", "ws", "index", "then"])
|
|
9
12
|
|
|
10
13
|
/** The subset of JSON Schema fields {@link tsTypeOf} reads to render a TypeScript type string. */
|
|
11
14
|
interface JsonSchemaNode {
|
|
@@ -93,7 +96,13 @@ function clientCall(method: string, path: string, schema: unknown): string {
|
|
|
93
96
|
if (segs.length === 0) chain += ".index"
|
|
94
97
|
else
|
|
95
98
|
for (const seg of segs) {
|
|
96
|
-
chain +=
|
|
99
|
+
chain +=
|
|
100
|
+
paramCall(seg) ??
|
|
101
|
+
(RESERVED_CLIENT_SEGMENT.test(seg) || RESERVED_EXACT_SEGMENTS.has(seg)
|
|
102
|
+
? `(${JSON.stringify(seg)})`
|
|
103
|
+
: IDENT.test(seg)
|
|
104
|
+
? `.${seg}`
|
|
105
|
+
: `[${JSON.stringify(seg)}]`)
|
|
97
106
|
}
|
|
98
107
|
const isBodyVerb = verb === "post" || verb === "put" || verb === "patch"
|
|
99
108
|
let call: string
|
|
@@ -101,12 +110,27 @@ function clientCall(method: string, path: string, schema: unknown): string {
|
|
|
101
110
|
const bodyArg = s?.body ? "body" : s?.query ? "undefined" : ""
|
|
102
111
|
const opts = s?.query ? (bodyArg ? ", { query }" : "{ query }") : ""
|
|
103
112
|
call = `.${verb}(${bodyArg}${opts})`
|
|
113
|
+
} else if (verb === "delete") {
|
|
114
|
+
const fields = [...(s?.body ? ["body"] : []), ...(s?.query ? ["query"] : [])]
|
|
115
|
+
call = `.delete(${fields.length === 0 ? "" : `{ ${fields.join(", ")} }`})`
|
|
104
116
|
} else {
|
|
105
117
|
call = `.${verb}(${s?.query ? "{ query }" : ""})`
|
|
106
118
|
}
|
|
107
119
|
return `await ${chain}${call}`
|
|
108
120
|
}
|
|
109
121
|
|
|
122
|
+
async function backendEvidence(backend: unknown): Promise<ProjectEvidenceSnapshot> {
|
|
123
|
+
if (backend !== null && (typeof backend === "object" || typeof backend === "function")) {
|
|
124
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: Object/function check allows reading the optional symbol-keyed provider without touching the client path proxy.
|
|
125
|
+
const provider = (backend as { readonly [NIFRA_BACKEND_EVIDENCE]?: unknown })[
|
|
126
|
+
NIFRA_BACKEND_EVIDENCE
|
|
127
|
+
]
|
|
128
|
+
// biome-ignore lint/plugin/requireSafetyCommentForTypeAssertion: A callable at NIFRA_BACKEND_EVIDENCE implements the framework's trusted evidence-provider seam.
|
|
129
|
+
if (typeof provider === "function") return (provider as BackendEvidenceProvider).call(backend)
|
|
130
|
+
}
|
|
131
|
+
return snapshotProjectEvidence(backend)
|
|
132
|
+
}
|
|
133
|
+
|
|
110
134
|
/** Read the project's `AGENTS.md`, or `""` if there isn't one (or we're on an edge runtime with no
|
|
111
135
|
* filesystem). Never throws - a missing file just means no guidelines section. */
|
|
112
136
|
async function readAgentsMd(): Promise<string> {
|
|
@@ -134,6 +158,8 @@ export async function generateLlmsTxt(
|
|
|
134
158
|
readonly includeLocalGuidelines?: boolean
|
|
135
159
|
/** Reuse the project's canonical route-evidence pass when one is already available. */
|
|
136
160
|
readonly evidence?: ProjectEvidenceSnapshot
|
|
161
|
+
/** The mount prefix a visitor's URL carries but the backend's own routes do not (`apiStrip`). */
|
|
162
|
+
readonly apiPathPrefix?: string
|
|
137
163
|
} = {},
|
|
138
164
|
): Promise<string> {
|
|
139
165
|
let output = ""
|
|
@@ -160,14 +186,17 @@ export async function generateLlmsTxt(
|
|
|
160
186
|
// 3. API Routes
|
|
161
187
|
output += `## API Routes\n\n`
|
|
162
188
|
const apiRoutes = reflectedRoutesFromEvidence(
|
|
163
|
-
options.evidence ??
|
|
189
|
+
options.evidence ?? (await backendEvidence(backend)),
|
|
164
190
|
)
|
|
165
191
|
|
|
166
192
|
if (apiRoutes.length === 0) {
|
|
167
193
|
output += `No API routes registered.\n`
|
|
168
194
|
} else {
|
|
195
|
+
const prefix = options.apiPathPrefix ?? ""
|
|
169
196
|
for (const route of apiRoutes) {
|
|
170
|
-
|
|
197
|
+
// The stripped backend's root is the bare prefix, as the mount matches it.
|
|
198
|
+
const path = prefix !== "" && route.path === "/" ? prefix : prefix + route.path
|
|
199
|
+
output += `- **${route.method}** \`${path}\`\n`
|
|
171
200
|
if (full) {
|
|
172
201
|
output += ` - Client Call: \`${clientCall(route.method, route.path, route.schema)}\`\n`
|
|
173
202
|
const s = route.schema
|
package/src/manifest.ts
CHANGED
|
@@ -271,13 +271,24 @@ export interface Meta {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
/**
|
|
274
|
-
* Args for a route's `meta` function:
|
|
275
|
-
* `meta()` runs in BOTH SSR and client navigation, so it has
|
|
276
|
-
* `origin` is the only server-resolved fact it gets (so you
|
|
277
|
-
* for absolute `og:url`/`canonical`/`og:image` URLs). See
|
|
274
|
+
* Args for a route's or layout's `meta` function: its own loader's `data` + the page's `pageData` + the
|
|
275
|
+
* route `params` + the request `origin`. `meta()` runs in BOTH SSR and client navigation, so it has
|
|
276
|
+
* **no `request`/`process.env`/server access** - `origin` is the only server-resolved fact it gets (so you
|
|
277
|
+
* needn't thread `siteUrl` through loader data for absolute `og:url`/`canonical`/`og:image` URLs). See
|
|
278
|
+
* {@link origin}.
|
|
278
279
|
*/
|
|
279
280
|
export interface MetaArgs<Data = unknown> {
|
|
281
|
+
/**
|
|
282
|
+
* The loader data of the module that exports this `meta`: the page's for a page, the layout's own for a
|
|
283
|
+
* layout (`null` when the layout has no loader), the same value its component receives. A layout that
|
|
284
|
+
* loads the locale sets `lang` from here, so a soft navigation keeps it.
|
|
285
|
+
*/
|
|
280
286
|
readonly data: Data
|
|
287
|
+
/**
|
|
288
|
+
* The page's loader data, for a layout's `meta` that builds on the page (a title template). In a page's
|
|
289
|
+
* own `meta` it is the same value as {@link data}. Always set by the framework.
|
|
290
|
+
*/
|
|
291
|
+
readonly pageData?: unknown
|
|
281
292
|
readonly params: Record<string, string>
|
|
282
293
|
/** The resolved document CSP nonce, when this render uses per-request nonce protection. */
|
|
283
294
|
readonly nonce?: string
|
|
@@ -323,7 +334,18 @@ export type GetStaticPaths = () => StaticPaths | Promise<StaticPaths>
|
|
|
323
334
|
export interface RouteModule {
|
|
324
335
|
readonly default: unknown
|
|
325
336
|
readonly loader?: Loader
|
|
337
|
+
/** Layout loader that must run before actions and is not retained across the action's render. */
|
|
338
|
+
readonly gate?: boolean
|
|
326
339
|
readonly action?: Action
|
|
340
|
+
/**
|
|
341
|
+
* Backend half only: which browser origins may POST to {@link action}. When omitted, a request
|
|
342
|
+
* whose `Origin` is another site's is refused with 403 before middleware, gates or the action run;
|
|
343
|
+
* requests without an `Origin` (non-browser clients) pass. An array allows exactly those origins
|
|
344
|
+
* (and refuses a request without one); a function decides, receiving `null` when there is none.
|
|
345
|
+
* Use it for a callback a third party posts to, such as an identity provider's `form_post`.
|
|
346
|
+
* `Origin` is browser-enforced, not authentication: keep the action's own auth checks.
|
|
347
|
+
*/
|
|
348
|
+
readonly allowedOrigins?: ReadonlyArray<string> | ((origin: string | null) => boolean)
|
|
327
349
|
/**
|
|
328
350
|
* Backend half only: the schema of what {@link loader} sends to the browser. Required whenever the
|
|
329
351
|
* loader returns data. Keys it does not declare are dropped before the page renders; a declared
|
|
@@ -384,6 +406,14 @@ export interface RouteModule {
|
|
|
384
406
|
* the module on the server and in the browser, so it must be the same value on both.
|
|
385
407
|
*/
|
|
386
408
|
readonly handle?: unknown
|
|
409
|
+
/**
|
|
410
|
+
* On a root status page (`_404.tsx`, `_<status>.tsx`) or an `_error.tsx`. By default (`true`) the
|
|
411
|
+
* page renders inside the layouts above it with their loader data and `meta`, as a nested `_404`
|
|
412
|
+
* renders. When those layouts cannot render it - a loader throws, a gate or the middleware refuses -
|
|
413
|
+
* a root status page renders alone and an `_error` inside its layouts without their data, with the
|
|
414
|
+
* same status. `false` always renders them that way, and runs no layout loader for them.
|
|
415
|
+
*/
|
|
416
|
+
readonly inLayouts?: boolean
|
|
387
417
|
/**
|
|
388
418
|
* Opt a **static** route (no `:param`/`*`) into build-time prerendering (SSG): `prerenderRoutes`
|
|
389
419
|
* (from `@nifrajs/web/build`) renders it to a static `index.html` at build. The loader runs at build
|
|
@@ -621,6 +651,7 @@ export const BACKEND_ROUTE_EXPORTS: ReadonlySet<string> = new Set([
|
|
|
621
651
|
"middleware",
|
|
622
652
|
"gate",
|
|
623
653
|
"shouldRevalidate",
|
|
654
|
+
"allowedOrigins",
|
|
624
655
|
])
|
|
625
656
|
|
|
626
657
|
const routeError = (message: string): Error => new Error(`[nifra/web] ${message}`)
|
|
@@ -998,6 +1029,9 @@ const ancestorDirs = (file: string): string[] => {
|
|
|
998
1029
|
* turns a path into a lazy module loader. Pure - no fs. Throws at boot (the loud-and-early
|
|
999
1030
|
* RouteConfigError ethos) on duplicate patterns. `_layout`/`_404`/`_error`/`_loading`/`_middleware`
|
|
1000
1031
|
* files are special; other `_`-prefixed files are ignored (private/colocated, never routed).
|
|
1032
|
+
* A page pairs with its `.backend.*` half only when both are in `files`, so a hand-written list that
|
|
1033
|
+
* omits the half serves the page without its loader and guards; `discoverRoutes` from
|
|
1034
|
+
* `@nifrajs/web/fs` scans a routes dir and never does.
|
|
1001
1035
|
*/
|
|
1002
1036
|
export function buildManifest(
|
|
1003
1037
|
files: readonly string[],
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { constants as FS } from "node:fs"
|
|
2
|
+
import { type FileHandle, open, readlink, realpath, stat } from "node:fs/promises"
|
|
3
|
+
|
|
4
|
+
// Darwin's open(2) flag refusing a symlink in ANY component; `O_NOFOLLOW` covers only the last one.
|
|
5
|
+
const O_NOFOLLOW_ANY = 0x20000000
|
|
6
|
+
// Deno's node:fs rejects flags it does not know with EINVAL; the first refusal turns the flag off.
|
|
7
|
+
let noFollowAny: boolean | undefined
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Open a name `realpath` already resolved, refusing it when any component became a symlink since.
|
|
11
|
+
* A local writer inside the served tree can swap an intermediate directory for a link after the
|
|
12
|
+
* containment check; `O_NOFOLLOW` alone would follow it. Darwin refuses such a path in the kernel;
|
|
13
|
+
* Linux reads back the name the descriptor holds. Elsewhere, Windows included, no file API can open
|
|
14
|
+
* without following a middle link or name an open descriptor's path, so the check in
|
|
15
|
+
* {@link opensAt} is a platform limit: it narrows the race to repeated swaps but cannot close it.
|
|
16
|
+
*/
|
|
17
|
+
export async function openResolved(realFile: string): Promise<FileHandle> {
|
|
18
|
+
// Read per open, not at load: this module can sit in a client bundle graph, where `process` is
|
|
19
|
+
// undefined.
|
|
20
|
+
if (noFollowAny ?? process.platform === "darwin") {
|
|
21
|
+
try {
|
|
22
|
+
return await open(realFile, FS.O_RDONLY | O_NOFOLLOW_ANY)
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (!(error instanceof Error && "code" in error && error.code === "EINVAL")) throw error
|
|
25
|
+
noFollowAny = false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const handle = await open(realFile, FS.O_RDONLY | FS.O_NOFOLLOW)
|
|
29
|
+
try {
|
|
30
|
+
if (await opensAt(handle, realFile)) return handle
|
|
31
|
+
} catch {}
|
|
32
|
+
await handle.close()
|
|
33
|
+
throw Object.assign(new Error(`${realFile} moved while it was opened`), { code: "ELOOP" })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Whether `handle` is the file at `realFile` with no symlink in its path. */
|
|
37
|
+
export async function opensAt(
|
|
38
|
+
handle: Pick<FileHandle, "fd" | "stat">,
|
|
39
|
+
realFile: string,
|
|
40
|
+
): Promise<boolean> {
|
|
41
|
+
try {
|
|
42
|
+
return (await readlink(`/proc/self/fd/${handle.fd}`)) === realFile
|
|
43
|
+
} catch {
|
|
44
|
+
return sameFileAt(handle, realFile)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* {@link opensAt} where no descriptor's path can be read. Exported for its test, which runs it on
|
|
50
|
+
* every host.
|
|
51
|
+
*/
|
|
52
|
+
export async function sameFileAt(
|
|
53
|
+
handle: Pick<FileHandle, "stat">,
|
|
54
|
+
realFile: string,
|
|
55
|
+
): Promise<boolean> {
|
|
56
|
+
// One step after another, so passing takes a swap back and a swap again between steps: the name
|
|
57
|
+
// resolves to itself, then to the opened file, then to itself again. Identities are compared as
|
|
58
|
+
// bigints because a Windows file index can exceed 2^53.
|
|
59
|
+
if ((await realpath(realFile)) !== realFile) return false
|
|
60
|
+
const opened = await handle.stat({ bigint: true })
|
|
61
|
+
const named = await stat(realFile, { bigint: true })
|
|
62
|
+
if (opened.dev !== named.dev || opened.ino !== named.ino) return false
|
|
63
|
+
return (await realpath(realFile)) === realFile
|
|
64
|
+
}
|
package/src/plugins/kit.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* CSS→client-bundle wiring, the deterministic hash, or the optional-peer guard. Internal to
|
|
5
5
|
* `@nifrajs/web` (imported by the sibling plugin modules); not a public subpath.
|
|
6
6
|
*/
|
|
7
|
-
import { existsSync } from "node:fs"
|
|
7
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
8
8
|
import { dirname, join, relative } from "node:path"
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from "node:url"
|
|
10
10
|
import type { BunPlugin } from "bun"
|
|
@@ -23,7 +23,7 @@ export type PluginBuilder = Parameters<BunPlugin["setup"]>[0]
|
|
|
23
23
|
*/
|
|
24
24
|
export { rewriteSsrImports } from "../dev-ssr-graph.ts"
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const packageCache = new Map<string, PackageOf>()
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Convert a plugin/runtime path into a path the host filesystem can open.
|
|
@@ -57,14 +57,31 @@ export function portablePath(path: string): string {
|
|
|
57
57
|
return normalizeFilePath(path).replaceAll("\\", "/")
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
interface PackageOf {
|
|
61
|
+
readonly root: string
|
|
62
|
+
readonly name: string
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The nearest ancestor directory of `startDir` that holds a `package.json` (the file's package root)
|
|
66
|
+
* and that package's `name` (`""` when it has none), cached. Falls back to `startDir` if none is found,
|
|
67
|
+
* so it never throws. */
|
|
68
|
+
function packageOf(startDir: string): PackageOf {
|
|
69
|
+
const cached = packageCache.get(startDir)
|
|
64
70
|
if (cached !== undefined) return cached
|
|
65
71
|
let dir = startDir
|
|
72
|
+
let name = ""
|
|
66
73
|
for (;;) {
|
|
67
|
-
|
|
74
|
+
const manifest = join(dir, "package.json")
|
|
75
|
+
if (existsSync(manifest)) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed: unknown = JSON.parse(readFileSync(manifest, "utf8"))
|
|
78
|
+
if (typeof parsed === "object" && parsed !== null && "name" in parsed)
|
|
79
|
+
name = typeof parsed.name === "string" ? parsed.name : ""
|
|
80
|
+
} catch {
|
|
81
|
+
// An unreadable manifest still marks the package root; the name is only a disambiguator.
|
|
82
|
+
}
|
|
83
|
+
break
|
|
84
|
+
}
|
|
68
85
|
const parent = dirname(dir)
|
|
69
86
|
if (parent === dir) {
|
|
70
87
|
dir = startDir // reached the filesystem root with no package.json - anchor on the file's own dir
|
|
@@ -72,8 +89,9 @@ function packageRootOf(startDir: string): string {
|
|
|
72
89
|
}
|
|
73
90
|
dir = parent
|
|
74
91
|
}
|
|
75
|
-
|
|
76
|
-
|
|
92
|
+
const found = { root: dir, name }
|
|
93
|
+
packageCache.set(startDir, found)
|
|
94
|
+
return found
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
/**
|
|
@@ -84,11 +102,13 @@ function packageRootOf(startDir: string): string {
|
|
|
84
102
|
* different machines or from different cwds - derive the SAME relative path for a given file, so their
|
|
85
103
|
* scoped class maps always agree. (Hashing the absolute path would differ across CI/host; a
|
|
86
104
|
* cwd-relative path would silently desync if the SSR server started from a non-project-root cwd.)
|
|
105
|
+
* The package's `name` leads the path, so `src/Card.vue` in two workspace packages hashes apart.
|
|
87
106
|
*/
|
|
88
107
|
export function reproduciblePath(absolutePath: string): string {
|
|
89
108
|
const filePath = normalizeFilePath(absolutePath)
|
|
90
|
-
const root =
|
|
91
|
-
|
|
109
|
+
const { root, name } = packageOf(dirname(filePath))
|
|
110
|
+
const path = relative(root, filePath).replaceAll("\\", "/")
|
|
111
|
+
return name === "" ? path : `${name}/${path}`
|
|
92
112
|
}
|
|
93
113
|
|
|
94
114
|
/**
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
formatServerOnlyLeak,
|
|
47
47
|
} from "../build-plan.ts"
|
|
48
48
|
import { isBareNodeBuiltin } from "../internal/node-builtins.ts"
|
|
49
|
+
import { installedManifest } from "../internal/package-entries.ts"
|
|
49
50
|
import { privateEnvCheck } from "../internal/private-env.ts"
|
|
50
51
|
import {
|
|
51
52
|
emittedScanFiles,
|
|
@@ -58,6 +59,7 @@ import {
|
|
|
58
59
|
scanForSecrets,
|
|
59
60
|
textOf,
|
|
60
61
|
} from "../internal/secret-scan.ts"
|
|
62
|
+
import { withoutQuery } from "../internal/without-query.ts"
|
|
61
63
|
import {
|
|
62
64
|
accountEmittedFiles,
|
|
63
65
|
assetDenial,
|
|
@@ -75,7 +77,7 @@ import {
|
|
|
75
77
|
type GraphModule,
|
|
76
78
|
type RollupBundleLike,
|
|
77
79
|
} from "../module-graph.ts"
|
|
78
|
-
import { createZoneClassifier, type ZoneClassifier } from "../zones.ts"
|
|
80
|
+
import { createZoneClassifier, specifierPackage, type ZoneClassifier } from "../zones.ts"
|
|
79
81
|
|
|
80
82
|
/**
|
|
81
83
|
* The Rollup plugin-context slice this uses: `getModuleInfo` for a module's resolved imports, and `error`
|
|
@@ -160,9 +162,80 @@ export interface LeakGuardPlugin {
|
|
|
160
162
|
* reached the browser.
|
|
161
163
|
*/
|
|
162
164
|
leak?: string
|
|
165
|
+
buildStart(): void
|
|
166
|
+
/** Ordered first: the bundler's own resolver answers before a plugin listed after it is asked. */
|
|
167
|
+
readonly resolveId: {
|
|
168
|
+
readonly order: "pre"
|
|
169
|
+
handler(
|
|
170
|
+
this: ResolveContext,
|
|
171
|
+
source: string,
|
|
172
|
+
importer: string | undefined,
|
|
173
|
+
options?: Readonly<Record<string, unknown>>,
|
|
174
|
+
): Promise<null>
|
|
175
|
+
}
|
|
163
176
|
generateBundle(this: RollupPluginContext, options: unknown, bundle: RollupBundleLike): void
|
|
164
177
|
}
|
|
165
178
|
|
|
179
|
+
/**
|
|
180
|
+
* Rollup keeps no import's written specifier, yet a package that gives each entry its own side is judged
|
|
181
|
+
* by the entry an import names, since two entries may share one file. This records the specifier of
|
|
182
|
+
* each import of such a package by the edge it resolved to, and puts it back on the finished graph.
|
|
183
|
+
*/
|
|
184
|
+
function entrySpecifiers(appRoot: string, options: ServerZoneGuardOptions) {
|
|
185
|
+
// One edge may carry several specifiers: `pkg` and `pkg/react` can resolve to one shared file.
|
|
186
|
+
const written = new Map<string, Set<string>>()
|
|
187
|
+
let classifier: ZoneClassifier | undefined
|
|
188
|
+
const edge = (from: string, to: string): string =>
|
|
189
|
+
`${from.replaceAll("\\", "/")}\0${to.replaceAll("\\", "/")}`
|
|
190
|
+
return {
|
|
191
|
+
reset(): void {
|
|
192
|
+
written.clear()
|
|
193
|
+
classifier = undefined
|
|
194
|
+
},
|
|
195
|
+
async resolveId(
|
|
196
|
+
context: ResolveContext,
|
|
197
|
+
source: string,
|
|
198
|
+
importer: string | undefined,
|
|
199
|
+
resolveOptions: Readonly<Record<string, unknown>> | undefined,
|
|
200
|
+
): Promise<null> {
|
|
201
|
+
const name = specifierPackage(source)
|
|
202
|
+
const from = importer === undefined ? undefined : withoutQuery(importer)
|
|
203
|
+
if (name === undefined || importer === undefined || from === undefined || !isAbsolute(from))
|
|
204
|
+
return null
|
|
205
|
+
const manifest = installedManifest(dirname(from), name)
|
|
206
|
+
if (manifest === undefined) return null
|
|
207
|
+
classifier ??= classifierFor(appRoot, options)
|
|
208
|
+
if (classifier.classify(manifest, source).entry === undefined) return null
|
|
209
|
+
const resolved = await context.resolve(source, importer, {
|
|
210
|
+
...resolveOptions,
|
|
211
|
+
skipSelf: true,
|
|
212
|
+
})
|
|
213
|
+
if (resolved === null) return null
|
|
214
|
+
const key = edge(importer, resolved.id)
|
|
215
|
+
written.set(key, (written.get(key) ?? new Set<string>()).add(source))
|
|
216
|
+
return null
|
|
217
|
+
},
|
|
218
|
+
annotate(graph: ClientModuleGraph): ClientModuleGraph {
|
|
219
|
+
if (written.size === 0) return graph
|
|
220
|
+
const modules: Record<string, GraphModule> = {}
|
|
221
|
+
for (const [id, module] of Object.entries(graph.modules)) {
|
|
222
|
+
modules[id] = {
|
|
223
|
+
imports: module.imports.flatMap((im) => {
|
|
224
|
+
const originals =
|
|
225
|
+
im.original !== undefined || im.path === undefined
|
|
226
|
+
? undefined
|
|
227
|
+
: written.get(edge(id, im.path))
|
|
228
|
+
return originals === undefined
|
|
229
|
+
? [im]
|
|
230
|
+
: [...originals].map((original) => ({ ...im, original }))
|
|
231
|
+
}),
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return { ...graph, modules }
|
|
235
|
+
},
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
166
239
|
const CSS_FILE = /\.(?:css|scss|sass|less|styl)(?:$|\?)/
|
|
167
240
|
|
|
168
241
|
/**
|
|
@@ -175,8 +248,16 @@ export function viteLeakGuard(options: LeakGuardOptions = {}): LeakGuardPlugin {
|
|
|
175
248
|
const appRoot = resolve(options.appRoot ?? process.cwd())
|
|
176
249
|
const root = resolve(options.root ?? appRoot)
|
|
177
250
|
const outDir = resolve(root, options.outDir ?? "dist")
|
|
251
|
+
const specifiers = entrySpecifiers(appRoot, options)
|
|
178
252
|
const plugin: LeakGuardPlugin = {
|
|
179
253
|
name: "nifra:leak-guard",
|
|
254
|
+
buildStart: () => specifiers.reset(),
|
|
255
|
+
resolveId: {
|
|
256
|
+
order: "pre",
|
|
257
|
+
handler(source, importer, resolveOptions) {
|
|
258
|
+
return specifiers.resolveId(this, source, importer, resolveOptions)
|
|
259
|
+
},
|
|
260
|
+
},
|
|
180
261
|
generateBundle(_options, bundle) {
|
|
181
262
|
const classifier = classifierFor(appRoot, options)
|
|
182
263
|
// Resolved import edges per module, straight from Rollup's graph. Dynamic imports count too: a
|
|
@@ -186,7 +267,9 @@ export function viteLeakGuard(options: LeakGuardOptions = {}): LeakGuardPlugin {
|
|
|
186
267
|
if (info === null) return []
|
|
187
268
|
return [...(info.importedIds ?? []), ...(info.dynamicallyImportedIds ?? [])]
|
|
188
269
|
}
|
|
189
|
-
const graph =
|
|
270
|
+
const graph = specifiers.annotate(
|
|
271
|
+
completeGraph(fromRollupBundle(bundle, importsOf), this, importsOf),
|
|
272
|
+
)
|
|
190
273
|
const sources = { classifier, sourceOf: rollupModuleSource }
|
|
191
274
|
const privateEnv = privateEnvCheck(options.publicEnvPrefix ?? "PUBLIC_")
|
|
192
275
|
const outputs = Object.values(bundle as Readonly<Record<string, RollupOutputLike>>)
|
|
@@ -251,6 +334,7 @@ function secretLeak(
|
|
|
251
334
|
artifacts,
|
|
252
335
|
env: options.secrets?.env ?? process.env,
|
|
253
336
|
publicEnvPrefix: options.publicEnvPrefix ?? "PUBLIC_",
|
|
337
|
+
// biome-ignore lint/plugin/noConditionalEmptyObjectSpread: Explicitly configured secret-scan exemptions preserve their absence by default.
|
|
254
338
|
...(options.secrets?.exemptions ? { exemptions: options.secrets.exemptions } : {}),
|
|
255
339
|
}),
|
|
256
340
|
)
|
|
@@ -260,7 +344,7 @@ function secretLeak(
|
|
|
260
344
|
export type ServerZoneGuardOptions = Pick<
|
|
261
345
|
LeakGuardOptions,
|
|
262
346
|
"appRoot" | "routesDir" | "generatedFiles" | "publicEnvPrefix"
|
|
263
|
-
>
|
|
347
|
+
> & { readonly componentModules?: ReadonlySet<string> }
|
|
264
348
|
|
|
265
349
|
/**
|
|
266
350
|
* The server build's half of the zone rules, with the same checks and message as nifra's Bun server
|
|
@@ -270,8 +354,16 @@ export type ServerZoneGuardOptions = Pick<
|
|
|
270
354
|
*/
|
|
271
355
|
export function viteServerZoneGuard(options: ServerZoneGuardOptions = {}): LeakGuardPlugin {
|
|
272
356
|
const appRoot = resolve(options.appRoot ?? process.cwd())
|
|
357
|
+
const specifiers = entrySpecifiers(appRoot, options)
|
|
273
358
|
const plugin: LeakGuardPlugin = {
|
|
274
359
|
name: "nifra:server-zone-guard",
|
|
360
|
+
buildStart: () => specifiers.reset(),
|
|
361
|
+
resolveId: {
|
|
362
|
+
order: "pre",
|
|
363
|
+
handler(source, importer, resolveOptions) {
|
|
364
|
+
return specifiers.resolveId(this, source, importer, resolveOptions)
|
|
365
|
+
},
|
|
366
|
+
},
|
|
275
367
|
generateBundle(_options, bundle) {
|
|
276
368
|
const classifier = classifierFor(appRoot, options)
|
|
277
369
|
const importsOf = (id: string): readonly string[] => {
|
|
@@ -279,12 +371,17 @@ export function viteServerZoneGuard(options: ServerZoneGuardOptions = {}): LeakG
|
|
|
279
371
|
if (info === null) return []
|
|
280
372
|
return [...(info.importedIds ?? []), ...(info.dynamicallyImportedIds ?? [])]
|
|
281
373
|
}
|
|
282
|
-
const graph =
|
|
374
|
+
const graph = specifiers.annotate(
|
|
375
|
+
completeGraph(fromRollupBundle(bundle, importsOf), this, importsOf),
|
|
376
|
+
)
|
|
283
377
|
const leak = formatServerGraphVerdict(
|
|
284
378
|
verifyServerGraph(graph, {
|
|
285
379
|
classifier,
|
|
286
380
|
sourceOf: rollupModuleSource,
|
|
287
381
|
privateEnv: privateEnvCheck(options.publicEnvPrefix ?? "PUBLIC_"),
|
|
382
|
+
...(options.componentModules === undefined
|
|
383
|
+
? {}
|
|
384
|
+
: { componentModules: options.componentModules }),
|
|
288
385
|
}),
|
|
289
386
|
)
|
|
290
387
|
if (leak === undefined) return
|
|
@@ -461,7 +558,6 @@ export interface AssetUrlGuardPlugin {
|
|
|
461
558
|
): Promise<null>
|
|
462
559
|
}
|
|
463
560
|
|
|
464
|
-
const POSTFIX = /[?#].*$/s
|
|
465
561
|
const URL_SCHEME = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i
|
|
466
562
|
|
|
467
563
|
/**
|
|
@@ -503,7 +599,7 @@ export function viteAssetUrlGuard(options: ServerZoneGuardOptions = {}): AssetUr
|
|
|
503
599
|
},
|
|
504
600
|
async transform(code, id, transformOptions) {
|
|
505
601
|
if (transformOptions?.ssr === true || !code.includes("import.meta.url")) return null
|
|
506
|
-
const importer = id
|
|
602
|
+
const importer = withoutQuery(id)
|
|
507
603
|
if (!isAbsolute(importer) || importer.includes("/node_modules/") || CSS_FILE.test(importer))
|
|
508
604
|
return null
|
|
509
605
|
let program: unknown
|
|
@@ -549,11 +645,11 @@ async function assetUrlFiles(
|
|
|
549
645
|
): Promise<string[]> {
|
|
550
646
|
const files = new Set<string>()
|
|
551
647
|
const add = (path: string | undefined): void => {
|
|
552
|
-
const file = path
|
|
648
|
+
const file = path === undefined ? undefined : withoutQuery(path)
|
|
553
649
|
if (file === undefined || !isAbsolute(file) || file.includes("\0")) return
|
|
554
650
|
if (statSync(file, { throwIfNoEntry: false })?.isFile() === true) files.add(file)
|
|
555
651
|
}
|
|
556
|
-
if (!url.startsWith("/")) add(resolve(dirname(importer), url
|
|
652
|
+
if (!url.startsWith("/")) add(resolve(dirname(importer), withoutQuery(url)))
|
|
557
653
|
const sources = url.startsWith(".") || url.startsWith("/") ? [url] : [url, `./${url}`]
|
|
558
654
|
for (const source of sources) {
|
|
559
655
|
try {
|
|
@@ -10,10 +10,12 @@
|
|
|
10
10
|
*
|
|
11
11
|
* The SSR environment loads the same files legitimately and is never checked here.
|
|
12
12
|
*/
|
|
13
|
-
import {
|
|
13
|
+
import { readFileSync, statSync } from "node:fs"
|
|
14
14
|
import type { IncomingMessage, ServerResponse } from "node:http"
|
|
15
15
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path"
|
|
16
|
+
import { installedManifest } from "../internal/package-entries.ts"
|
|
16
17
|
import { privateEnvDenial } from "../internal/private-env.ts"
|
|
18
|
+
import { withoutQuery } from "../internal/without-query.ts"
|
|
17
19
|
import {
|
|
18
20
|
browserDenial,
|
|
19
21
|
type Classification,
|
|
@@ -78,7 +80,6 @@ export interface ViteZoneGuardPlugin {
|
|
|
78
80
|
fileForUrl(url: string): string | undefined
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
const stripQuery = (id: string): string => id.replace(/[?#].*$/, "")
|
|
82
83
|
/** Query forms that hand a module's source, or a URL to it, to the browser untransformed. */
|
|
83
84
|
const RAW_QUERY = /(?:^|&)(?:raw|url|inline)(?:[&=]|$)/
|
|
84
85
|
|
|
@@ -136,7 +137,7 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
|
|
|
136
137
|
const fileForUrl = (url: string): string | undefined => {
|
|
137
138
|
let path: string
|
|
138
139
|
try {
|
|
139
|
-
path = decodeURIComponent(
|
|
140
|
+
path = decodeURIComponent(withoutQuery(url))
|
|
140
141
|
} catch {
|
|
141
142
|
return undefined
|
|
142
143
|
}
|
|
@@ -187,16 +188,17 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
|
|
|
187
188
|
return context.error(new Error(message))
|
|
188
189
|
}
|
|
189
190
|
|
|
190
|
-
/** A bare
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
191
|
+
/** A bare import's package, classified through its manifest so a workspace link is first-party,
|
|
192
|
+
* and by the entry it names when the package declares a side per entry. */
|
|
193
|
+
const packageClassification = (
|
|
194
|
+
name: string,
|
|
195
|
+
importer: string,
|
|
196
|
+
specifier: string,
|
|
197
|
+
): Classification => {
|
|
198
|
+
const manifest = installedManifest(dirname(importer), name)
|
|
199
|
+
return manifest === undefined
|
|
200
|
+
? { zone: "library", packageName: name }
|
|
201
|
+
: classifier.classify(manifest, specifier)
|
|
200
202
|
}
|
|
201
203
|
|
|
202
204
|
return {
|
|
@@ -219,10 +221,10 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
|
|
|
219
221
|
async resolveId(source, importer, resolveOptions) {
|
|
220
222
|
if (importer === undefined || resolveOptions?.scan === true) return null
|
|
221
223
|
if (!isClient(this, resolveOptions?.ssr)) return null
|
|
222
|
-
const importerFile =
|
|
224
|
+
const importerFile = withoutQuery(importer)
|
|
223
225
|
if (!isAbsolute(importerFile)) return null
|
|
224
226
|
if (source.startsWith(".")) {
|
|
225
|
-
const target = resolve(dirname(importerFile),
|
|
227
|
+
const target = resolve(dirname(importerFile), withoutQuery(source))
|
|
226
228
|
if (isFile(target)) {
|
|
227
229
|
const reason = browserDenial(classifier.classify(target))
|
|
228
230
|
if (reason !== undefined) refuse(this, target, reason, importerFile)
|
|
@@ -231,21 +233,21 @@ export function viteZoneGuard(options: ViteZoneGuardOptions): ViteZoneGuardPlugi
|
|
|
231
233
|
}
|
|
232
234
|
const name = specifierPackage(source)
|
|
233
235
|
if (name !== undefined) {
|
|
234
|
-
const reason = browserDenial(packageClassification(name, importerFile), source)
|
|
236
|
+
const reason = browserDenial(packageClassification(name, importerFile, source), source)
|
|
235
237
|
if (reason !== undefined) refuse(this, source, reason, importerFile)
|
|
236
238
|
}
|
|
237
239
|
// An alias, a tsconfig path or a package export: what the specifier resolves to decides.
|
|
238
240
|
const resolved = await this.resolve(source, importer, { ...resolveOptions, skipSelf: true })
|
|
239
|
-
const target = resolved === null || resolved.external ? undefined :
|
|
241
|
+
const target = resolved === null || resolved.external ? undefined : withoutQuery(resolved.id)
|
|
240
242
|
if (target !== undefined && isAbsolute(target) && isFile(target)) {
|
|
241
|
-
const reason = browserDenial(classifier.classify(target), source)
|
|
243
|
+
const reason = browserDenial(classifier.classify(target, source), source)
|
|
242
244
|
if (reason !== undefined) refuse(this, target, reason, importerFile)
|
|
243
245
|
}
|
|
244
246
|
return null
|
|
245
247
|
},
|
|
246
248
|
transform(code, id, transformOptions) {
|
|
247
249
|
if (!isClient(this, transformOptions?.ssr)) return null
|
|
248
|
-
const file =
|
|
250
|
+
const file = withoutQuery(id)
|
|
249
251
|
if (!isAbsolute(file) || !isFile(file)) return null
|
|
250
252
|
// A fresh transform re-resolves every import, so an earlier refusal is stale until it recurs.
|
|
251
253
|
refusals.delete(resolve(file))
|