@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/zones.ts
CHANGED
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { closeSync, existsSync, openSync, readFileSync, readSync, realpathSync } from "node:fs"
|
|
13
13
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path"
|
|
14
|
+
import {
|
|
15
|
+
type EntryOwnership,
|
|
16
|
+
type EnvironmentDeclaration,
|
|
17
|
+
entryKey,
|
|
18
|
+
entryOwnership,
|
|
19
|
+
parseEnvironment,
|
|
20
|
+
} from "./internal/package-entries.ts"
|
|
14
21
|
import { SERVER_FN_MODULE } from "./internal/server-boundary.ts"
|
|
22
|
+
import { withoutQuery } from "./internal/without-query.ts"
|
|
15
23
|
|
|
16
24
|
export type Zone =
|
|
17
25
|
/** A page or special file under `routes/` - the frontend half of a route. */
|
|
@@ -23,6 +31,8 @@ export type Zone =
|
|
|
23
31
|
| "shared"
|
|
24
32
|
/** A server-function module (`*.fn.ts`); the browser only ever receives its generated RPC stub. */
|
|
25
33
|
| "fn"
|
|
34
|
+
/** A compiler-owned server UI module; only opaque reference stubs may reach a browser. */
|
|
35
|
+
| "component"
|
|
26
36
|
/** Framework output (the client entry, the server composition, generated types). */
|
|
27
37
|
| "generated"
|
|
28
38
|
/** A third-party package, or a workspace package declaring `"nifra": { "environment": "library" }`. */
|
|
@@ -37,8 +47,17 @@ export type Classification =
|
|
|
37
47
|
readonly declared?: PackageEnvironment
|
|
38
48
|
/** A third-party file's path inside `node_modules`: `drizzle-orm/node-postgres/index.js`. */
|
|
39
49
|
readonly modulePath?: string
|
|
50
|
+
/** The export subpath (`"./react"`) whose own declared side decided this import. */
|
|
51
|
+
readonly entry?: string
|
|
52
|
+
}
|
|
53
|
+
| {
|
|
54
|
+
readonly zone: "error"
|
|
55
|
+
readonly reason: string
|
|
56
|
+
/** The undeclared package this error is about: every file in it shares the one fix. */
|
|
57
|
+
readonly package?: string
|
|
58
|
+
/** The export subpath the import named, when the package's map gives it no side. */
|
|
59
|
+
readonly entry?: string
|
|
40
60
|
}
|
|
41
|
-
| { readonly zone: "error"; readonly reason: string }
|
|
42
61
|
|
|
43
62
|
export type PackageEnvironment = "frontend" | "backend" | "shared" | "library"
|
|
44
63
|
const PACKAGE_ENVIRONMENTS: ReadonlySet<string> = new Set([
|
|
@@ -113,6 +132,7 @@ const SOURCE = "(?:[cm]?[jt]sx?|svelte|vue|mdx)"
|
|
|
113
132
|
export const ROUTE_BACKEND_FILE = /\.backend\.(?:[cm]?[jt]s)$/
|
|
114
133
|
const ZONE_SUFFIX = new RegExp(`\\.(frontend|backend|shared|fn)\\.${SOURCE}$`)
|
|
115
134
|
const LEGACY_SERVER_SUFFIX = new RegExp(`\\.server\\.${SOURCE}$`)
|
|
135
|
+
export const SERVER_COMPONENT_FILE = /\.component\.server\.[cm]?[jt]sx?$/
|
|
116
136
|
|
|
117
137
|
const ZONE_FOLDERS: Readonly<Record<string, Zone>> = {
|
|
118
138
|
frontend: "frontend",
|
|
@@ -128,12 +148,182 @@ export interface ZoneClassifierOptions {
|
|
|
128
148
|
readonly routesDir?: string
|
|
129
149
|
/** Extra absolute files treated as `generated` - a build's own entry modules. */
|
|
130
150
|
readonly generatedFiles?: readonly string[]
|
|
151
|
+
/** The app's zone settings. Default: `"nifra": { "zones": ... }` in the app's package.json. */
|
|
152
|
+
readonly zones?: ZoneConfig
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* App-side zone settings, read from `"nifra": { "zones": ... }` in the app's package.json so every
|
|
157
|
+
* build, dev server, `nifra check` and the editor plugin see the same ones without running app code.
|
|
158
|
+
*/
|
|
159
|
+
export interface ZoneConfig {
|
|
160
|
+
/**
|
|
161
|
+
* The side of a package that does not declare one in its own package.json, by name: exact
|
|
162
|
+
* (`"@shop/db"`) or a trailing-`*` prefix (`"@shop/*"`). The exact name wins over a prefix, the
|
|
163
|
+
* longest prefix over a shorter one, and a package's own declaration over all of them.
|
|
164
|
+
*/
|
|
165
|
+
readonly packages?: Readonly<Record<string, PackageEnvironment>>
|
|
166
|
+
/**
|
|
167
|
+
* App-relative folders or files holding backend code, beside `backend/` (`"migrations"`). A path
|
|
168
|
+
* listed inside another one's folder overrides it: the most specific entry wins.
|
|
169
|
+
*/
|
|
170
|
+
readonly backend?: readonly string[]
|
|
171
|
+
/** App-relative folders or files holding frontend code, beside `frontend/`. */
|
|
172
|
+
readonly frontend?: readonly string[]
|
|
173
|
+
/** App-relative folders or files holding code both sides import, beside `shared/`. */
|
|
174
|
+
readonly shared?: readonly string[]
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** A `"nifra": { "zones": ... }` setting that cannot be applied. Thrown when a classifier is created. */
|
|
178
|
+
export class ZoneConfigError extends Error {
|
|
179
|
+
override readonly name = "ZoneConfigError"
|
|
180
|
+
/** The file holding the setting, as the caller named it. */
|
|
181
|
+
readonly source: string
|
|
182
|
+
constructor(source: string, problem: string) {
|
|
183
|
+
super(`[nifra] ${source} "nifra.zones": ${problem}`)
|
|
184
|
+
this.source = source
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const FOLDER_SIDES = ["backend", "frontend", "shared"] as const
|
|
189
|
+
const FOLDER_SIDE_NAMES: ReadonlySet<string> = new Set(FOLDER_SIDES)
|
|
190
|
+
const isPackageEnvironment = (value: unknown): value is PackageEnvironment =>
|
|
191
|
+
typeof value === "string" && PACKAGE_ENVIRONMENTS.has(value)
|
|
192
|
+
const RESERVED_FOLDERS: ReadonlySet<string> = new Set([
|
|
193
|
+
"routes",
|
|
194
|
+
"frontend",
|
|
195
|
+
"backend",
|
|
196
|
+
"shared",
|
|
197
|
+
"public",
|
|
198
|
+
"node_modules",
|
|
199
|
+
])
|
|
200
|
+
|
|
201
|
+
const recordOf = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
|
|
202
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
203
|
+
? Object.fromEntries(Object.entries(value))
|
|
204
|
+
: undefined
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Validate a `zones` setting. `source` names where it came from in the error. Folders and files are
|
|
208
|
+
* plain app-relative paths (a trailing `/**` is accepted and dropped); any other glob is refused, so
|
|
209
|
+
* a pattern can never match less than its author meant without saying so. One path under two sides
|
|
210
|
+
* is refused; a path nested in another's folder is not, since the most specific one decides.
|
|
211
|
+
*/
|
|
212
|
+
export function parseZoneConfig(value: unknown, source: string): ZoneConfig {
|
|
213
|
+
const fail = (message: string): never => {
|
|
214
|
+
throw new ZoneConfigError(source, message)
|
|
215
|
+
}
|
|
216
|
+
const record = recordOf(value) ?? fail("must be an object")
|
|
217
|
+
const config: {
|
|
218
|
+
packages?: Record<string, PackageEnvironment>
|
|
219
|
+
backend?: string[]
|
|
220
|
+
frontend?: string[]
|
|
221
|
+
shared?: string[]
|
|
222
|
+
} = {}
|
|
223
|
+
for (const key of Object.keys(record)) {
|
|
224
|
+
if (key !== "packages" && !FOLDER_SIDE_NAMES.has(key))
|
|
225
|
+
fail(`unknown key "${key}" (expected packages, backend, frontend or shared)`)
|
|
226
|
+
}
|
|
227
|
+
if (record.packages !== undefined) {
|
|
228
|
+
const packages = recordOf(record.packages) ?? fail('"packages" must be an object')
|
|
229
|
+
const parsed: Record<string, PackageEnvironment> = {}
|
|
230
|
+
for (const [pattern, side] of Object.entries(packages)) {
|
|
231
|
+
if (pattern === "" || pattern.indexOf("*") !== pattern.lastIndexOf("*"))
|
|
232
|
+
fail(`package pattern "${pattern}" must be a name, or a name prefix ending in one "*"`)
|
|
233
|
+
if (pattern.includes("*") && !pattern.endsWith("*"))
|
|
234
|
+
fail(`package pattern "${pattern}" may use "*" only at its end`)
|
|
235
|
+
parsed[pattern] = isPackageEnvironment(side)
|
|
236
|
+
? side
|
|
237
|
+
: fail(`packages["${pattern}"] must be one of frontend, backend, shared, library`)
|
|
238
|
+
}
|
|
239
|
+
config.packages = parsed
|
|
240
|
+
}
|
|
241
|
+
const owner = new Map<string, string>()
|
|
242
|
+
for (const side of FOLDER_SIDES) {
|
|
243
|
+
const raw = record[side]
|
|
244
|
+
if (raw === undefined) continue
|
|
245
|
+
const entries: readonly unknown[] = Array.isArray(raw)
|
|
246
|
+
? raw
|
|
247
|
+
: fail(`"${side}" must be an array of app-relative folders or files`)
|
|
248
|
+
const folders: string[] = []
|
|
249
|
+
for (const entry of entries) {
|
|
250
|
+
const folder = (typeof entry === "string" ? entry : fail(`"${side}" must hold strings`))
|
|
251
|
+
.replaceAll("\\", "/")
|
|
252
|
+
.replace(/^\.\//, "")
|
|
253
|
+
.replace(/\/\*\*$/, "")
|
|
254
|
+
.replace(/\/+$/, "")
|
|
255
|
+
const top = folder.split("/")[0] ?? ""
|
|
256
|
+
if (folder === "" || folder.startsWith("/") || /^[a-z]:/i.test(folder))
|
|
257
|
+
fail(`"${side}" entry "${entry}" must be a folder or file relative to the app`)
|
|
258
|
+
if (folder.split("/").includes("..")) fail(`"${side}" entry "${entry}" may not contain ".."`)
|
|
259
|
+
if (/[*?[\]{}!]/.test(folder))
|
|
260
|
+
fail(`"${side}" entry "${entry}" must be a plain path (a trailing "/**" is allowed)`)
|
|
261
|
+
if (RESERVED_FOLDERS.has(top) || top.startsWith(".nifra"))
|
|
262
|
+
fail(`"${side}" entry "${entry}" is inside ${top}/, which already has its own rules`)
|
|
263
|
+
const other = owner.get(folder)
|
|
264
|
+
if (other !== undefined && other !== side)
|
|
265
|
+
fail(`"${folder}" is listed as both ${other} and ${side}; a path has one side`)
|
|
266
|
+
owner.set(folder, side)
|
|
267
|
+
folders.push(folder)
|
|
268
|
+
}
|
|
269
|
+
config[side] = folders
|
|
270
|
+
}
|
|
271
|
+
return config
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** The `nifra.zones` of the nearest package.json at or above `appRoot`, validated. */
|
|
275
|
+
function appZoneConfig(appRoot: string): ZoneConfig {
|
|
276
|
+
let dir = appRoot
|
|
277
|
+
for (;;) {
|
|
278
|
+
const manifest = join(dir, "package.json")
|
|
279
|
+
if (existsSync(manifest)) {
|
|
280
|
+
let parsed: unknown
|
|
281
|
+
try {
|
|
282
|
+
parsed = JSON.parse(readFileSync(manifest, "utf8"))
|
|
283
|
+
} catch {
|
|
284
|
+
return {}
|
|
285
|
+
}
|
|
286
|
+
const zones = recordOf(recordOf(parsed)?.nifra)?.zones
|
|
287
|
+
return zones === undefined ? {} : parseZoneConfig(zones, toPosix(manifest))
|
|
288
|
+
}
|
|
289
|
+
const parent = dirname(dir)
|
|
290
|
+
if (parent === dir) return {}
|
|
291
|
+
dir = parent
|
|
292
|
+
}
|
|
131
293
|
}
|
|
132
294
|
|
|
295
|
+
/** The side `packages` gives `name`: the exact entry, else the longest matching prefix. */
|
|
296
|
+
function mappedSide(
|
|
297
|
+
packages: Readonly<Record<string, PackageEnvironment>> | undefined,
|
|
298
|
+
name: string | undefined,
|
|
299
|
+
): PackageEnvironment | undefined {
|
|
300
|
+
if (packages === undefined || name === undefined) return undefined
|
|
301
|
+
const exact = packages[name]
|
|
302
|
+
if (exact !== undefined && Object.hasOwn(packages, name)) return exact
|
|
303
|
+
let best: string | undefined
|
|
304
|
+
for (const pattern of Object.keys(packages)) {
|
|
305
|
+
if (!pattern.endsWith("*") || !name.startsWith(pattern.slice(0, -1))) continue
|
|
306
|
+
if (best === undefined || pattern.length > best.length) best = pattern
|
|
307
|
+
}
|
|
308
|
+
return best === undefined ? undefined : packages[best]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** How the `packages` setting would name `name`: its scope wildcard when it has a scope. */
|
|
312
|
+
const suggestedPattern = (name: string): string =>
|
|
313
|
+
name.startsWith("@") && name.includes("/") ? `${name.slice(0, name.indexOf("/"))}/*` : name
|
|
314
|
+
|
|
133
315
|
export interface ZoneClassifier {
|
|
134
316
|
readonly appRoot: string
|
|
135
|
-
/**
|
|
136
|
-
|
|
317
|
+
/**
|
|
318
|
+
* Classify a file by its absolute (or appRoot-relative) path. Results are cached per path.
|
|
319
|
+
*
|
|
320
|
+
* With `specifier`, the import as written that resolved to `file`: when it names a package that
|
|
321
|
+
* gives each export entry its own side (`"nifra": { "environment": { ".": "backend", "./react":
|
|
322
|
+
* "frontend" } }`), the entry it names decides, since two entries may share one file. A specifier
|
|
323
|
+
* naming an entry the map leaves out is an error. The resolved file must belong to that entry, as
|
|
324
|
+
* the package's own imports reach it; one only other entries reach is judged by its path.
|
|
325
|
+
*/
|
|
326
|
+
classify(file: string, specifier?: string): Classification
|
|
137
327
|
}
|
|
138
328
|
|
|
139
329
|
const toPosix = (path: string): string => path.replaceAll("\\", "/")
|
|
@@ -187,7 +377,19 @@ function nodeModulesPackage(path: string): string | undefined {
|
|
|
187
377
|
interface PackageInfo {
|
|
188
378
|
readonly dir: string
|
|
189
379
|
readonly name: string | undefined
|
|
190
|
-
readonly
|
|
380
|
+
readonly declaration: EnvironmentDeclaration
|
|
381
|
+
readonly manifest: Readonly<Record<string, unknown>>
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const DECLARATION_FILE = /\.d\.[cm]?ts$/
|
|
385
|
+
|
|
386
|
+
/** The one side a file reached by several mapped entries takes: what each entry's importers may import. */
|
|
387
|
+
function ownersSide(owners: ReadonlySet<PackageEnvironment>): PackageEnvironment {
|
|
388
|
+
const [only] = owners
|
|
389
|
+
if (owners.size === 1 && only !== undefined) return only
|
|
390
|
+
return owners.has("library") || (owners.has("frontend") && owners.has("backend"))
|
|
391
|
+
? "library"
|
|
392
|
+
: "shared"
|
|
191
393
|
}
|
|
192
394
|
|
|
193
395
|
/**
|
|
@@ -198,6 +400,19 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
|
|
|
198
400
|
const appRoot = realFile(resolve(options.appRoot))
|
|
199
401
|
const routesDir = realFile(resolve(options.routesDir ?? join(options.appRoot, "routes")))
|
|
200
402
|
const generated = new Set((options.generatedFiles ?? []).map((file) => realFile(resolve(file))))
|
|
403
|
+
const zones =
|
|
404
|
+
options.zones === undefined
|
|
405
|
+
? appZoneConfig(appRoot)
|
|
406
|
+
: parseZoneConfig(options.zones, "ZoneClassifierOptions")
|
|
407
|
+
const configuredFolders: Array<readonly [string, "backend" | "frontend" | "shared"]> = []
|
|
408
|
+
for (const side of FOLDER_SIDES)
|
|
409
|
+
for (const folder of zones[side] ?? []) configuredFolders.push([folder, side])
|
|
410
|
+
// Longest first, so the most specific entry covering a file decides its side.
|
|
411
|
+
configuredFolders.sort(([a], [b]) => b.length - a.length)
|
|
412
|
+
const configuredPath = (
|
|
413
|
+
rel: string,
|
|
414
|
+
): readonly [string, "backend" | "frontend" | "shared"] | undefined =>
|
|
415
|
+
configuredFolders.find(([folder]) => rel === folder || rel.startsWith(`${folder}/`))
|
|
201
416
|
const cache = new Map<string, Classification>()
|
|
202
417
|
const packages = new Map<string, PackageInfo | null>()
|
|
203
418
|
|
|
@@ -214,17 +429,17 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
|
|
|
214
429
|
seen.push(current)
|
|
215
430
|
const manifest = join(current, "package.json")
|
|
216
431
|
if (existsSync(manifest)) {
|
|
217
|
-
let parsed:
|
|
432
|
+
let parsed: Readonly<Record<string, unknown>> = {}
|
|
218
433
|
try {
|
|
219
|
-
parsed = JSON.parse(readFileSync(manifest, "utf8"))
|
|
434
|
+
parsed = recordOf(JSON.parse(readFileSync(manifest, "utf8"))) ?? {}
|
|
220
435
|
} catch {
|
|
221
436
|
// An unreadable manifest declares nothing; the caller reports the missing declaration.
|
|
222
437
|
}
|
|
223
438
|
const info: PackageInfo = {
|
|
224
439
|
dir: current,
|
|
225
440
|
name: typeof parsed.name === "string" ? parsed.name : undefined,
|
|
226
|
-
environment
|
|
227
|
-
|
|
441
|
+
declaration: parseEnvironment(recordOf(parsed.nifra)?.environment),
|
|
442
|
+
manifest: parsed,
|
|
228
443
|
}
|
|
229
444
|
for (const visited of seen) packages.set(visited, info)
|
|
230
445
|
return info
|
|
@@ -238,15 +453,96 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
|
|
|
238
453
|
}
|
|
239
454
|
}
|
|
240
455
|
|
|
456
|
+
const ownerships = new Map<string, EntryOwnership>()
|
|
457
|
+
const ownershipOf = (
|
|
458
|
+
info: PackageInfo,
|
|
459
|
+
entries: Readonly<Record<string, PackageEnvironment>>,
|
|
460
|
+
): EntryOwnership => {
|
|
461
|
+
let found = ownerships.get(info.dir)
|
|
462
|
+
if (found === undefined) {
|
|
463
|
+
found = entryOwnership(info.dir, info.manifest, entries)
|
|
464
|
+
ownerships.set(info.dir, found)
|
|
465
|
+
}
|
|
466
|
+
return found
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** The package `file` is part of, when that package is not the app: installed, or a workspace sibling. */
|
|
470
|
+
const packageOf = (file: string): PackageInfo | undefined => {
|
|
471
|
+
const thirdParty = nodeModulesPackage(file)
|
|
472
|
+
if (thirdParty !== undefined) {
|
|
473
|
+
const at = file.lastIndexOf("/node_modules/") + "/node_modules/".length
|
|
474
|
+
return packageFor(file.slice(0, at + thirdParty.length))
|
|
475
|
+
}
|
|
476
|
+
return isInside(file, appRoot) ? undefined : packageFor(toPosix(dirname(file)))
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** What a side declared for `file` (by its package, an entry of it, or the app) makes it. */
|
|
480
|
+
const declaredAs = (
|
|
481
|
+
file: string,
|
|
482
|
+
info: PackageInfo | undefined,
|
|
483
|
+
side: PackageEnvironment,
|
|
484
|
+
entry?: string,
|
|
485
|
+
): Classification => {
|
|
486
|
+
const thirdParty = nodeModulesPackage(file)
|
|
487
|
+
const named = entry === undefined || info?.name === undefined ? {} : { packageName: info.name }
|
|
488
|
+
const decided = entry === undefined ? {} : { entry }
|
|
489
|
+
if (thirdParty !== undefined) {
|
|
490
|
+
return {
|
|
491
|
+
zone: "library",
|
|
492
|
+
packageName: thirdParty,
|
|
493
|
+
modulePath: file.slice(file.lastIndexOf("/node_modules/") + "/node_modules/".length),
|
|
494
|
+
declared: side,
|
|
495
|
+
...decided,
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (side !== "library") return { zone: side, ...named, ...decided }
|
|
499
|
+
return {
|
|
500
|
+
zone: "library",
|
|
501
|
+
...(info?.name ? { packageName: info.name } : {}),
|
|
502
|
+
declared: "library",
|
|
503
|
+
...decided,
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/** A file of a package that declares a side per entry, judged by the entries that reach it. */
|
|
508
|
+
const byOwners = (
|
|
509
|
+
file: string,
|
|
510
|
+
info: PackageInfo,
|
|
511
|
+
entries: Readonly<Record<string, PackageEnvironment>>,
|
|
512
|
+
): Classification => {
|
|
513
|
+
const ownership = ownershipOf(info, entries)
|
|
514
|
+
const owners = ownership.owners.get(file)
|
|
515
|
+
if (owners !== undefined) return declaredAs(file, info, ownersSide(owners))
|
|
516
|
+
const shown = nodeModulesPackage(file) === undefined ? file : file.slice(info.dir.length + 1)
|
|
517
|
+
const left =
|
|
518
|
+
ownership.unmapped.length === 0
|
|
519
|
+
? ""
|
|
520
|
+
: ` (the map leaves out ${ownership.unmapped.map((key) => `"${key}"`).join(", ")})`
|
|
521
|
+
return {
|
|
522
|
+
zone: "error",
|
|
523
|
+
reason: `"${shown}" is in package "${info.name ?? info.dir}", whose "nifra.environment" gives each entry its own side, and no mapped entry reaches this file${left}. Import the package through a mapped entry`,
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const invalidDeclaration = (info: PackageInfo, problem: string): Classification => {
|
|
528
|
+
const reason = `${info.dir}/package.json "nifra.environment": ${problem}`
|
|
529
|
+
return info.name === undefined
|
|
530
|
+
? { zone: "error", reason }
|
|
531
|
+
: { zone: "error", reason, package: info.name }
|
|
532
|
+
}
|
|
533
|
+
|
|
241
534
|
const classifyUncached = (file: string): Classification => {
|
|
242
535
|
if (generated.has(file)) return { zone: "generated" }
|
|
243
536
|
const thirdParty = nodeModulesPackage(file)
|
|
244
537
|
if (thirdParty !== undefined) {
|
|
245
|
-
const info =
|
|
538
|
+
const info = packageOf(file)
|
|
539
|
+
const declaration = info?.declaration ?? { kind: "none" }
|
|
540
|
+
if (info !== undefined && declaration.kind === "invalid")
|
|
541
|
+
return invalidDeclaration(info, declaration.problem)
|
|
542
|
+
if (info !== undefined && declaration.kind === "entries")
|
|
543
|
+
return byOwners(file, info, declaration.entries)
|
|
246
544
|
const declared =
|
|
247
|
-
|
|
248
|
-
? (info.environment as PackageEnvironment)
|
|
249
|
-
: undefined
|
|
545
|
+
declaration.kind === "package" ? declaration.side : mappedSide(zones.packages, thirdParty)
|
|
250
546
|
return {
|
|
251
547
|
zone: "library",
|
|
252
548
|
packageName: thirdParty,
|
|
@@ -259,6 +555,8 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
|
|
|
259
555
|
if (SERVER_MANIFEST_FILE.test(base) && isGeneratedServerManifest(file)) {
|
|
260
556
|
return { zone: "generated" }
|
|
261
557
|
}
|
|
558
|
+
if (SERVER_COMPONENT_FILE.test(base)) return { zone: "component" }
|
|
559
|
+
if (/\.island\.[cm]?[jt]sx?$/.test(base)) return { zone: "frontend" }
|
|
262
560
|
if (LEGACY_SERVER_SUFFIX.test(base)) {
|
|
263
561
|
return {
|
|
264
562
|
zone: "error",
|
|
@@ -289,13 +587,20 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
|
|
|
289
587
|
if (suffix === "shared") return { zone: "shared" }
|
|
290
588
|
return { zone: "route-frontend" }
|
|
291
589
|
}
|
|
292
|
-
const
|
|
590
|
+
const configured = ZONE_FOLDERS[top] === undefined ? configuredPath(rel) : undefined
|
|
591
|
+
const folder = ZONE_FOLDERS[top] ?? configured?.[1]
|
|
293
592
|
if (folder !== undefined) {
|
|
294
593
|
if (suffix === undefined || suffix === folder) return { zone: folder }
|
|
295
594
|
if (suffix === "fn" && folder === "backend") return { zone: "fn" }
|
|
595
|
+
const where =
|
|
596
|
+
configured === undefined
|
|
597
|
+
? `sits in ${top}/`
|
|
598
|
+
: configured[0] === rel
|
|
599
|
+
? `is listed as ${folder} in "nifra.zones"`
|
|
600
|
+
: `sits in ${configured[0]}/, which "nifra.zones" lists as ${folder},`
|
|
296
601
|
return {
|
|
297
602
|
zone: "error",
|
|
298
|
-
reason: `"${rel}"
|
|
603
|
+
reason: `"${rel}" ${where} but its ".${suffix}" suffix says otherwise. A file belongs to one side: move it, or drop the suffix`,
|
|
299
604
|
}
|
|
300
605
|
}
|
|
301
606
|
if (suffix !== undefined) return { zone: suffix === "fn" ? "fn" : suffix }
|
|
@@ -303,33 +608,80 @@ export function createZoneClassifier(options: ZoneClassifierOptions): ZoneClassi
|
|
|
303
608
|
}
|
|
304
609
|
// First-party code outside the app: a workspace package, or a relative import out of the app.
|
|
305
610
|
const info = packageFor(toPosix(dirname(file)))
|
|
306
|
-
const
|
|
307
|
-
if (
|
|
308
|
-
return
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
611
|
+
const declaration = info?.declaration ?? { kind: "none" }
|
|
612
|
+
if (info !== undefined && declaration.kind === "invalid")
|
|
613
|
+
return invalidDeclaration(info, declaration.problem)
|
|
614
|
+
if (info !== undefined && declaration.kind === "entries")
|
|
615
|
+
return byOwners(file, info, declaration.entries)
|
|
616
|
+
const environment =
|
|
617
|
+
declaration.kind === "package" ? declaration.side : mappedSide(zones.packages, info?.name)
|
|
618
|
+
if (environment !== undefined) return declaredAs(file, info, environment)
|
|
619
|
+
const declare = `"nifra": { "environment": "frontend" | "backend" | "shared" | "library" }`
|
|
620
|
+
if (info?.name === undefined) {
|
|
621
|
+
return {
|
|
622
|
+
zone: "error",
|
|
623
|
+
reason: `"${file}" is first-party code outside the app and declares no side. Add ${declare} to ${info === undefined ? "a package.json above it" : `${info.dir}/package.json`}`,
|
|
624
|
+
}
|
|
315
625
|
}
|
|
316
|
-
|
|
626
|
+
// Named per package, not per file, so a build that reaches 50 files of it reports one fact once.
|
|
317
627
|
return {
|
|
318
628
|
zone: "error",
|
|
319
|
-
|
|
629
|
+
package: info.name,
|
|
630
|
+
reason: `package "${info.name}" is first-party code outside the app and declares no side. Add ${declare} to ${info.dir}/package.json, or give it a side from the app's package.json: "nifra": { "zones": { "packages": { "${suggestedPattern(info.name)}": "backend" } } } (or "library" if the browser may load it)`,
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const byPath = (file: string): Classification => {
|
|
635
|
+
const cached = cache.get(file)
|
|
636
|
+
if (cached !== undefined) return cached
|
|
637
|
+
const result = classifyUncached(file)
|
|
638
|
+
cache.set(file, result)
|
|
639
|
+
return result
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// Keyed by file and subpath: two entries may share one file and declare different sides.
|
|
643
|
+
const imports = new Map<string, Classification>()
|
|
644
|
+
const byImport = (file: string, specifier: string): Classification => {
|
|
645
|
+
const name = specifierPackage(specifier)
|
|
646
|
+
const info = name === undefined ? undefined : packageOf(file)
|
|
647
|
+
if (name === undefined || info?.name !== name || info.declaration.kind !== "entries")
|
|
648
|
+
return byPath(file)
|
|
649
|
+
const subpath = entrySubpath(specifier, name)
|
|
650
|
+
const cacheKey = `${file}\0${subpath}`
|
|
651
|
+
const cached = imports.get(cacheKey)
|
|
652
|
+
if (cached !== undefined) return cached
|
|
653
|
+
const { entries } = info.declaration
|
|
654
|
+
const key = entryKey(entries, subpath)
|
|
655
|
+
const side = key === undefined ? undefined : entries[key]
|
|
656
|
+
let result: Classification
|
|
657
|
+
if (side === undefined) {
|
|
658
|
+
const mapped = Object.keys(entries)
|
|
659
|
+
.map((entry) => `"${entry}"`)
|
|
660
|
+
.join(", ")
|
|
661
|
+
result = {
|
|
662
|
+
zone: "error",
|
|
663
|
+
reason: `"${specifier}" names the "${subpath}" entry of package "${name}", whose "nifra.environment" map gives it no side (it maps ${mapped}). Map the entry, or import one that is mapped`,
|
|
664
|
+
entry: subpath,
|
|
665
|
+
}
|
|
666
|
+
} else {
|
|
667
|
+
// The resolver's pick must belong to the named entry; a file only other entries reach (an alias
|
|
668
|
+
// pointed at it) is judged by its path. A declaration file is types only and erased.
|
|
669
|
+
const owners = ownershipOf(info, entries).owners.get(file)
|
|
670
|
+
result =
|
|
671
|
+
owners !== undefined && !owners.has(side) && !DECLARATION_FILE.test(file)
|
|
672
|
+
? byPath(file)
|
|
673
|
+
: declaredAs(file, info, side, subpath)
|
|
320
674
|
}
|
|
675
|
+
imports.set(cacheKey, result)
|
|
676
|
+
return result
|
|
321
677
|
}
|
|
322
678
|
|
|
323
679
|
return {
|
|
324
680
|
appRoot,
|
|
325
|
-
classify(input: string): Classification {
|
|
681
|
+
classify(input: string, specifier?: string): Classification {
|
|
326
682
|
const absolute = isAbsolute(input) ? input : resolve(appRoot, input)
|
|
327
683
|
const file = realFile(absolute)
|
|
328
|
-
|
|
329
|
-
if (cached !== undefined) return cached
|
|
330
|
-
const result = classifyUncached(file)
|
|
331
|
-
cache.set(file, result)
|
|
332
|
-
return result
|
|
684
|
+
return specifier === undefined ? byPath(file) : byImport(file, specifier)
|
|
333
685
|
},
|
|
334
686
|
}
|
|
335
687
|
}
|
|
@@ -360,6 +712,27 @@ export function specifierPackage(specifier: string): string | undefined {
|
|
|
360
712
|
return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0]
|
|
361
713
|
}
|
|
362
714
|
|
|
715
|
+
/** The export subpath (`"."`, `"./react"`) a bare specifier of package `name` names. */
|
|
716
|
+
const entrySubpath = (specifier: string, name: string): string =>
|
|
717
|
+
`.${withoutQuery(specifier.slice(name.length))}`
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* The side a package's `"nifra": { "environment": ... }` value gives the bare import `specifier` of it:
|
|
721
|
+
* the package-wide string, or the side of the entry it names - the map's exact subpath, else its most
|
|
722
|
+
* specific `*` pattern. `undefined` when it gives none, as a map that leaves the entry out does.
|
|
723
|
+
*/
|
|
724
|
+
export function declaredSide(
|
|
725
|
+
environment: unknown,
|
|
726
|
+
specifier: string,
|
|
727
|
+
): PackageEnvironment | undefined {
|
|
728
|
+
const declaration = parseEnvironment(environment)
|
|
729
|
+
if (declaration.kind === "package") return declaration.side
|
|
730
|
+
const name = specifierPackage(specifier)
|
|
731
|
+
if (declaration.kind !== "entries" || name === undefined) return undefined
|
|
732
|
+
const key = entryKey(declaration.entries, entrySubpath(specifier, name))
|
|
733
|
+
return key === undefined ? undefined : declaration.entries[key]
|
|
734
|
+
}
|
|
735
|
+
|
|
363
736
|
/**
|
|
364
737
|
* Why a classified file may not ship to a browser, or `undefined` when it may. A `drizzle-orm` driver
|
|
365
738
|
* entry is recognized by the import as written (`specifier`) or by the file's path in the package.
|
|
@@ -370,13 +743,19 @@ export function browserDenial(
|
|
|
370
743
|
): string | undefined {
|
|
371
744
|
if (classification.zone === "error") return classification.reason
|
|
372
745
|
if (!BROWSER_ZONES.has(classification.zone)) {
|
|
746
|
+
if (classification.entry !== undefined)
|
|
747
|
+
return `package "${classification.packageName}" declares its "${classification.entry}" entry ${classification.zone}-only`
|
|
373
748
|
return classification.zone === "route-backend"
|
|
374
749
|
? "it is a route's backend half, which runs on the server only"
|
|
375
750
|
: "it is backend code"
|
|
376
751
|
}
|
|
377
752
|
if (classification.zone !== "library") return undefined
|
|
378
753
|
const name = classification.packageName
|
|
379
|
-
if (classification.declared === "backend")
|
|
754
|
+
if (classification.declared === "backend") {
|
|
755
|
+
return classification.entry === undefined
|
|
756
|
+
? `package "${name}" declares itself backend-only`
|
|
757
|
+
: `package "${name}" declares its "${classification.entry}" entry backend-only`
|
|
758
|
+
}
|
|
380
759
|
if (name !== undefined && SERVER_PACKAGES.includes(name)) {
|
|
381
760
|
return `package "${name}" is server code (a driver, server SDK or credential helper)`
|
|
382
761
|
}
|
|
@@ -388,8 +767,15 @@ export function browserDenial(
|
|
|
388
767
|
return undefined
|
|
389
768
|
}
|
|
390
769
|
|
|
770
|
+
/** Whether a bare specifier names one of {@link SERVER_PACKAGES} or a database driver entry. */
|
|
771
|
+
export function isServerPackageSpecifier(specifier: string): boolean {
|
|
772
|
+
const name = specifierPackage(specifier)
|
|
773
|
+
return (name !== undefined && SERVER_PACKAGES.includes(name)) || SERVER_SUBPATHS.test(specifier)
|
|
774
|
+
}
|
|
775
|
+
|
|
391
776
|
/** Whether a module in `from` may import (with a value import) a module in `to`. */
|
|
392
777
|
export function importAllowed(from: Zone, to: Zone): boolean {
|
|
778
|
+
if (from === "component") return true
|
|
393
779
|
if (from === "generated" || from === "library") return true
|
|
394
780
|
if (to === "generated" || to === "library") return true
|
|
395
781
|
if (FRONTEND_ZONES.has(from)) return FRONTEND_ZONES.has(to) || to === "shared" || to === "fn"
|