@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.141
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/AGENTS.md +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +293 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2508 -158
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +24 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-snapshot.ts +368 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +222 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1113 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +173 -35
- package/src/index.ts +241 -73
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +527 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +897 -0
- package/src/rsc/shell-serve.ts +124 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +459 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +848 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +443 -135
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +199 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +584 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +76 -98
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +44 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +280 -0
- package/src/urls/pattern-types.ts +160 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,812 @@
|
|
|
1
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
2
|
+
import { dirname, resolve } from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { getStringValue } from "./ast-helpers.js";
|
|
5
|
+
import { extractRoutesFromSource } from "./ast-route-extraction.js";
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Unresolvable include diagnostics
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export type UnresolvableReason =
|
|
12
|
+
| "factory-call"
|
|
13
|
+
| "dynamic-expression"
|
|
14
|
+
| "unresolvable-import"
|
|
15
|
+
| "file-not-found";
|
|
16
|
+
|
|
17
|
+
export interface UnresolvableInclude {
|
|
18
|
+
pathPrefix: string;
|
|
19
|
+
namePrefix: string | null;
|
|
20
|
+
reason: UnresolvableReason;
|
|
21
|
+
sourceFile: string;
|
|
22
|
+
detail: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Per-scan memo
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Per-scan memo shared across the include-resolution recursion. Keys are
|
|
31
|
+
* absolute file paths; values cache the readFileSync result and the
|
|
32
|
+
* ts.SourceFile parsed from the FULL file source. A separate blockSources map
|
|
33
|
+
* caches parses of extracted sub-blocks (urls() call text), keyed by the exact
|
|
34
|
+
* block string, so the two extractors (routes + includes) for the same block
|
|
35
|
+
* share a single parse.
|
|
36
|
+
*
|
|
37
|
+
* The memo is purely a performance accelerator: same input -> same parse, so
|
|
38
|
+
* generated output is identical to the un-memoized path. A fresh memo is
|
|
39
|
+
* created per top-level scan entry, so stale file edits between scans are never
|
|
40
|
+
* served.
|
|
41
|
+
*/
|
|
42
|
+
export interface ScanMemo {
|
|
43
|
+
files: Map<string, string>;
|
|
44
|
+
blockSourceFiles: Map<string, ts.SourceFile>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createScanMemo(): ScanMemo {
|
|
48
|
+
return { files: new Map(), blockSourceFiles: new Map() };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function parseBlock(memo: ScanMemo | undefined, block: string): ts.SourceFile {
|
|
52
|
+
if (memo) {
|
|
53
|
+
const cached = memo.blockSourceFiles.get(block);
|
|
54
|
+
if (cached) return cached;
|
|
55
|
+
}
|
|
56
|
+
const sf = ts.createSourceFile(
|
|
57
|
+
"input.tsx",
|
|
58
|
+
block,
|
|
59
|
+
ts.ScriptTarget.Latest,
|
|
60
|
+
true,
|
|
61
|
+
ts.ScriptKind.TSX,
|
|
62
|
+
);
|
|
63
|
+
if (memo) memo.blockSourceFiles.set(block, sf);
|
|
64
|
+
return sf;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readSourceMemoized(
|
|
68
|
+
memo: ScanMemo | undefined,
|
|
69
|
+
realPath: string,
|
|
70
|
+
): string {
|
|
71
|
+
if (memo) {
|
|
72
|
+
const cached = memo.files.get(realPath);
|
|
73
|
+
if (cached !== undefined) return cached;
|
|
74
|
+
}
|
|
75
|
+
const source = readFileSync(realPath, "utf-8");
|
|
76
|
+
if (memo) memo.files.set(realPath, source);
|
|
77
|
+
return source;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// AST-based include() parsing
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
function extractNamePrefixFromInclude(node: ts.CallExpression): string | null {
|
|
85
|
+
if (node.arguments.length >= 3) {
|
|
86
|
+
const thirdArg = node.arguments[2];
|
|
87
|
+
if (ts.isObjectLiteralExpression(thirdArg)) {
|
|
88
|
+
for (const prop of thirdArg.properties) {
|
|
89
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
90
|
+
const propName = ts.isIdentifier(prop.name) ? prop.name.text : null;
|
|
91
|
+
if (propName === "name") {
|
|
92
|
+
return getStringValue(prop.initializer);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* True when the thunk transforms its dynamic import via a `.then(cb)` whose
|
|
102
|
+
* callback selects a NAMED export other than `default` (e.g.
|
|
103
|
+
* `import("./x").then((m) => m.routes)`). The static resolver walks the module's
|
|
104
|
+
* `export default`, so such a selector would resolve the WRONG export — the
|
|
105
|
+
* caller must treat it as unresolvable instead of silently mis-resolving. Returns
|
|
106
|
+
* false for the supported shapes (no `.then`, `.then((m) => m)`,
|
|
107
|
+
* `.then((m) => m.default)`) and for any shape it cannot positively identify as a
|
|
108
|
+
* non-default member selection (those keep the existing resolve-via-default path).
|
|
109
|
+
*/
|
|
110
|
+
function thenSelectsNonDefaultMember(expr: ts.Expression): boolean {
|
|
111
|
+
const findThenCall = (e: ts.Expression): ts.CallExpression | null => {
|
|
112
|
+
if (
|
|
113
|
+
ts.isCallExpression(e) &&
|
|
114
|
+
ts.isPropertyAccessExpression(e.expression) &&
|
|
115
|
+
e.expression.name.text === "then"
|
|
116
|
+
) {
|
|
117
|
+
return e;
|
|
118
|
+
}
|
|
119
|
+
if (ts.isCallExpression(e) && ts.isPropertyAccessExpression(e.expression)) {
|
|
120
|
+
return findThenCall(e.expression.expression);
|
|
121
|
+
}
|
|
122
|
+
if (ts.isPropertyAccessExpression(e)) return findThenCall(e.expression);
|
|
123
|
+
if (ts.isParenthesizedExpression(e)) return findThenCall(e.expression);
|
|
124
|
+
if (ts.isAwaitExpression(e)) return findThenCall(e.expression);
|
|
125
|
+
return null;
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const thenCall = findThenCall(expr);
|
|
129
|
+
if (!thenCall || thenCall.arguments.length === 0) return false;
|
|
130
|
+
const cb = thenCall.arguments[0];
|
|
131
|
+
if (!ts.isArrowFunction(cb) && !ts.isFunctionExpression(cb)) return false;
|
|
132
|
+
|
|
133
|
+
let ret: ts.Expression | undefined;
|
|
134
|
+
if (ts.isBlock(cb.body)) {
|
|
135
|
+
for (const stmt of cb.body.statements) {
|
|
136
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
137
|
+
ret = stmt.expression;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
ret = cb.body;
|
|
143
|
+
}
|
|
144
|
+
if (!ret) return false;
|
|
145
|
+
|
|
146
|
+
if (ts.isPropertyAccessExpression(ret)) {
|
|
147
|
+
return ret.name.text !== "default";
|
|
148
|
+
}
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Extract the module specifier from an async include thunk
|
|
154
|
+
* (`() => import("./mod")`). Handles arrow and function-expression thunks,
|
|
155
|
+
* concise or block bodies, and `import("./mod").then(...)` chains. Returns the
|
|
156
|
+
* specifier string, or null when the second arg is not a dynamic-import thunk
|
|
157
|
+
* (so the caller can fall through to identifier / factory-call / dynamic-expr
|
|
158
|
+
* classification).
|
|
159
|
+
*/
|
|
160
|
+
function extractDynamicImportSpecifier(node: ts.Expression): string | null {
|
|
161
|
+
let body: ts.ConciseBody | undefined;
|
|
162
|
+
if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
|
|
163
|
+
body = node.body;
|
|
164
|
+
}
|
|
165
|
+
if (!body) return null;
|
|
166
|
+
|
|
167
|
+
// Concise body is the expression itself; block body: first `return <expr>`.
|
|
168
|
+
let expr: ts.Expression | undefined;
|
|
169
|
+
if (ts.isBlock(body)) {
|
|
170
|
+
for (const stmt of body.statements) {
|
|
171
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
172
|
+
expr = stmt.expression;
|
|
173
|
+
break;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
} else {
|
|
177
|
+
expr = body;
|
|
178
|
+
}
|
|
179
|
+
if (!expr) return null;
|
|
180
|
+
|
|
181
|
+
// Descend `import("./mod").then(...)` / parenthesized / await wrappers down to
|
|
182
|
+
// the `import(...)` call itself.
|
|
183
|
+
const findImportCall = (e: ts.Expression): ts.CallExpression | null => {
|
|
184
|
+
if (
|
|
185
|
+
ts.isCallExpression(e) &&
|
|
186
|
+
e.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
187
|
+
) {
|
|
188
|
+
return e;
|
|
189
|
+
}
|
|
190
|
+
if (ts.isCallExpression(e) && ts.isPropertyAccessExpression(e.expression)) {
|
|
191
|
+
return findImportCall(e.expression.expression);
|
|
192
|
+
}
|
|
193
|
+
if (ts.isPropertyAccessExpression(e)) return findImportCall(e.expression);
|
|
194
|
+
if (ts.isParenthesizedExpression(e)) return findImportCall(e.expression);
|
|
195
|
+
if (ts.isAwaitExpression(e)) return findImportCall(e.expression);
|
|
196
|
+
return null;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const importCall = findImportCall(expr);
|
|
200
|
+
if (!importCall || importCall.arguments.length === 0) return null;
|
|
201
|
+
// A `.then()` selecting a non-default named export can't be statically
|
|
202
|
+
// resolved to `export default`; return null so the caller emits a diagnostic
|
|
203
|
+
// rather than silently generating types for the wrong export.
|
|
204
|
+
if (thenSelectsNonDefaultMember(expr)) return null;
|
|
205
|
+
return getStringValue(importCall.arguments[0]);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Resolve a module's `export default` to either a same-file `urls()` variable
|
|
210
|
+
* name (`export default shopPatterns`) or the inline call block
|
|
211
|
+
* (`export default urls(...)`). Used to walk async include modules
|
|
212
|
+
* (`() => import("./mod")`), whose convention is `export default urls(...)`.
|
|
213
|
+
*/
|
|
214
|
+
function resolveDefaultExportTarget(
|
|
215
|
+
code: string,
|
|
216
|
+
sourceFile: ts.SourceFile,
|
|
217
|
+
): { variableName?: string; inlineBlock?: string } | null {
|
|
218
|
+
let result: { variableName?: string; inlineBlock?: string } | null = null;
|
|
219
|
+
|
|
220
|
+
function visit(node: ts.Node) {
|
|
221
|
+
if (result) return;
|
|
222
|
+
if (ts.isExportAssignment(node) && !node.isExportEquals) {
|
|
223
|
+
const expr = node.expression;
|
|
224
|
+
if (ts.isIdentifier(expr)) {
|
|
225
|
+
result = { variableName: expr.text };
|
|
226
|
+
} else if (ts.isCallExpression(expr)) {
|
|
227
|
+
const callee = expr.expression;
|
|
228
|
+
if (ts.isIdentifier(callee) && callee.text === "urls") {
|
|
229
|
+
result = { inlineBlock: expr.getText(sourceFile) };
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
ts.forEachChild(node, visit);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
visit(sourceFile);
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Extract include() calls with diagnostics for unresolvable ones.
|
|
243
|
+
* Returns both resolved includes (identifier second args) and unresolvable
|
|
244
|
+
* includes (factory calls, etc.) with reasons.
|
|
245
|
+
*/
|
|
246
|
+
export function extractIncludesWithDiagnostics(
|
|
247
|
+
code: string,
|
|
248
|
+
sourceFileArg?: ts.SourceFile,
|
|
249
|
+
): {
|
|
250
|
+
resolved: Array<{
|
|
251
|
+
pathPrefix: string;
|
|
252
|
+
// Exactly one of variableName / moduleSpecifier is set. variableName is the
|
|
253
|
+
// classic `include("/api", apiUrls)` identifier form; moduleSpecifier is the
|
|
254
|
+
// async `include("/api", () => import("./api"))` form, carrying the imported
|
|
255
|
+
// module path so resolution can walk its `export default`.
|
|
256
|
+
variableName?: string;
|
|
257
|
+
moduleSpecifier?: string;
|
|
258
|
+
namePrefix: string | null;
|
|
259
|
+
}>;
|
|
260
|
+
unresolvable: Array<{
|
|
261
|
+
pathPrefix: string;
|
|
262
|
+
namePrefix: string | null;
|
|
263
|
+
reason: UnresolvableReason;
|
|
264
|
+
detail: string;
|
|
265
|
+
}>;
|
|
266
|
+
} {
|
|
267
|
+
// Reuse a caller-provided SourceFile (parsed once per scan) when given.
|
|
268
|
+
const sourceFile =
|
|
269
|
+
sourceFileArg ??
|
|
270
|
+
ts.createSourceFile(
|
|
271
|
+
"input.tsx",
|
|
272
|
+
code,
|
|
273
|
+
ts.ScriptTarget.Latest,
|
|
274
|
+
true,
|
|
275
|
+
ts.ScriptKind.TSX,
|
|
276
|
+
);
|
|
277
|
+
const resolved: Array<{
|
|
278
|
+
pathPrefix: string;
|
|
279
|
+
variableName?: string;
|
|
280
|
+
moduleSpecifier?: string;
|
|
281
|
+
namePrefix: string | null;
|
|
282
|
+
}> = [];
|
|
283
|
+
const unresolvable: Array<{
|
|
284
|
+
pathPrefix: string;
|
|
285
|
+
namePrefix: string | null;
|
|
286
|
+
reason: UnresolvableReason;
|
|
287
|
+
detail: string;
|
|
288
|
+
}> = [];
|
|
289
|
+
|
|
290
|
+
function visit(node: ts.Node) {
|
|
291
|
+
if (ts.isCallExpression(node)) {
|
|
292
|
+
const callee = node.expression;
|
|
293
|
+
if (ts.isIdentifier(callee) && callee.text === "include") {
|
|
294
|
+
if (node.arguments.length < 2) {
|
|
295
|
+
ts.forEachChild(node, visit);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const pathPrefix = getStringValue(node.arguments[0]);
|
|
300
|
+
if (pathPrefix === null) {
|
|
301
|
+
ts.forEachChild(node, visit);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const secondArg = node.arguments[1];
|
|
306
|
+
const namePrefix = extractNamePrefixFromInclude(node);
|
|
307
|
+
|
|
308
|
+
const dynamicImportSpec = extractDynamicImportSpecifier(secondArg);
|
|
309
|
+
|
|
310
|
+
if (ts.isIdentifier(secondArg)) {
|
|
311
|
+
resolved.push({
|
|
312
|
+
pathPrefix,
|
|
313
|
+
variableName: secondArg.text,
|
|
314
|
+
namePrefix,
|
|
315
|
+
});
|
|
316
|
+
} else if (dynamicImportSpec !== null) {
|
|
317
|
+
// Async include: `include("/api", () => import("./api"))`. Resolvable
|
|
318
|
+
// statically by walking the imported module's `export default`.
|
|
319
|
+
resolved.push({
|
|
320
|
+
pathPrefix,
|
|
321
|
+
moduleSpecifier: dynamicImportSpec,
|
|
322
|
+
namePrefix,
|
|
323
|
+
});
|
|
324
|
+
} else if (ts.isCallExpression(secondArg)) {
|
|
325
|
+
const callText = secondArg.expression.getText(sourceFile);
|
|
326
|
+
unresolvable.push({
|
|
327
|
+
pathPrefix,
|
|
328
|
+
namePrefix,
|
|
329
|
+
reason: "factory-call",
|
|
330
|
+
detail: `${callText}()`,
|
|
331
|
+
});
|
|
332
|
+
} else {
|
|
333
|
+
unresolvable.push({
|
|
334
|
+
pathPrefix,
|
|
335
|
+
namePrefix,
|
|
336
|
+
reason: "dynamic-expression",
|
|
337
|
+
detail: secondArg.getText(sourceFile),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
ts.forEachChild(node, visit);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
visit(sourceFile);
|
|
346
|
+
return { resolved, unresolvable };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// ---------------------------------------------------------------------------
|
|
350
|
+
// Import resolution
|
|
351
|
+
// ---------------------------------------------------------------------------
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Find the import statement for a local variable name.
|
|
355
|
+
* Returns the import specifier and the exported name from the source module.
|
|
356
|
+
*/
|
|
357
|
+
export function resolveImportedVariable(
|
|
358
|
+
code: string,
|
|
359
|
+
localName: string,
|
|
360
|
+
): { specifier: string; exportedName: string } | null {
|
|
361
|
+
// Allow an optional leading default binding before the named-import brace so
|
|
362
|
+
// a combined `import Foo, { bar } from "..."` is matched (the named members
|
|
363
|
+
// are the only part we resolve; the default binding is skipped). Without the
|
|
364
|
+
// optional `(?:[\w$]+\s*,\s*)?` segment, the `Foo, ` prefix breaks the match
|
|
365
|
+
// and a legitimate static named import surfaces as `unresolvable-import`.
|
|
366
|
+
const importRegex =
|
|
367
|
+
/import\s*(?:[\w$]+\s*,\s*)?\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
|
|
368
|
+
let match;
|
|
369
|
+
|
|
370
|
+
while ((match = importRegex.exec(code)) !== null) {
|
|
371
|
+
const imports = match[1];
|
|
372
|
+
const specifier = match[2];
|
|
373
|
+
|
|
374
|
+
const parts = imports
|
|
375
|
+
.split(",")
|
|
376
|
+
.map((s) => s.trim())
|
|
377
|
+
.filter(Boolean);
|
|
378
|
+
for (const part of parts) {
|
|
379
|
+
const asMatch = part.match(/^(\w+)\s+as\s+(\w+)$/);
|
|
380
|
+
if (asMatch && asMatch[2] === localName)
|
|
381
|
+
return { specifier, exportedName: asMatch[1] };
|
|
382
|
+
if (part === localName) return { specifier, exportedName: localName };
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return null;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Resolve an import specifier relative to the importing file.
|
|
391
|
+
* Strips .js/.mjs/.jsx extensions and tries .ts/.tsx/.js/.jsx candidates.
|
|
392
|
+
*/
|
|
393
|
+
export function resolveImportPath(
|
|
394
|
+
importSpec: string,
|
|
395
|
+
fromFile: string,
|
|
396
|
+
): string | null {
|
|
397
|
+
if (!importSpec.startsWith(".")) return null;
|
|
398
|
+
|
|
399
|
+
const dir = dirname(fromFile);
|
|
400
|
+
let base = importSpec;
|
|
401
|
+
if (base.endsWith(".js")) base = base.slice(0, -3);
|
|
402
|
+
else if (base.endsWith(".mjs")) base = base.slice(0, -4);
|
|
403
|
+
else if (base.endsWith(".jsx")) base = base.slice(0, -4);
|
|
404
|
+
|
|
405
|
+
const candidates = [
|
|
406
|
+
resolve(dir, base + ".ts"),
|
|
407
|
+
resolve(dir, base + ".tsx"),
|
|
408
|
+
resolve(dir, base + ".js"),
|
|
409
|
+
resolve(dir, base + ".jsx"),
|
|
410
|
+
resolve(dir, base + "/index.ts"),
|
|
411
|
+
resolve(dir, base + "/index.tsx"),
|
|
412
|
+
resolve(dir, base + "/index.js"),
|
|
413
|
+
resolve(dir, base + "/index.jsx"),
|
|
414
|
+
];
|
|
415
|
+
|
|
416
|
+
for (const candidate of candidates) {
|
|
417
|
+
if (existsSync(candidate)) return candidate;
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ---------------------------------------------------------------------------
|
|
423
|
+
// urls() block extraction for same-file variables
|
|
424
|
+
// ---------------------------------------------------------------------------
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Extract the source of a specific `const varName = urls(...)` call using
|
|
428
|
+
* the TypeScript AST. Returns the full text of the urls() call expression.
|
|
429
|
+
*/
|
|
430
|
+
function extractUrlsBlockForVariable(
|
|
431
|
+
code: string,
|
|
432
|
+
varName: string,
|
|
433
|
+
sourceFileArg?: ts.SourceFile,
|
|
434
|
+
): string | null {
|
|
435
|
+
// Reuse a caller-provided full-source SourceFile (parsed once per scan).
|
|
436
|
+
const sourceFile =
|
|
437
|
+
sourceFileArg ??
|
|
438
|
+
ts.createSourceFile(
|
|
439
|
+
"input.tsx",
|
|
440
|
+
code,
|
|
441
|
+
ts.ScriptTarget.Latest,
|
|
442
|
+
true,
|
|
443
|
+
ts.ScriptKind.TSX,
|
|
444
|
+
);
|
|
445
|
+
let result: string | null = null;
|
|
446
|
+
|
|
447
|
+
function visit(node: ts.Node) {
|
|
448
|
+
if (result) return;
|
|
449
|
+
if (
|
|
450
|
+
ts.isVariableDeclaration(node) &&
|
|
451
|
+
ts.isIdentifier(node.name) &&
|
|
452
|
+
node.name.text === varName &&
|
|
453
|
+
node.initializer &&
|
|
454
|
+
ts.isCallExpression(node.initializer)
|
|
455
|
+
) {
|
|
456
|
+
const callee = node.initializer.expression;
|
|
457
|
+
if (ts.isIdentifier(callee) && callee.text === "urls") {
|
|
458
|
+
result = node.initializer.getText(sourceFile);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
ts.forEachChild(node, visit);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
visit(sourceFile);
|
|
466
|
+
return result;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ---------------------------------------------------------------------------
|
|
470
|
+
// Combined route map building
|
|
471
|
+
// ---------------------------------------------------------------------------
|
|
472
|
+
|
|
473
|
+
function buildRouteMapFromBlock(
|
|
474
|
+
block: string,
|
|
475
|
+
fullSource: string,
|
|
476
|
+
filePath: string,
|
|
477
|
+
visited: Set<string>,
|
|
478
|
+
searchSchemasOut?: Record<string, Record<string, string>>,
|
|
479
|
+
diagnosticsOut?: UnresolvableInclude[],
|
|
480
|
+
memo?: ScanMemo,
|
|
481
|
+
): Record<string, string> {
|
|
482
|
+
const routeMap: Record<string, string> = {};
|
|
483
|
+
|
|
484
|
+
// Parse the block once and share the SourceFile between both extractors.
|
|
485
|
+
const blockSourceFile = parseBlock(memo, block);
|
|
486
|
+
|
|
487
|
+
// Extract local path() routes
|
|
488
|
+
const localRoutes = extractRoutesFromSource(block, blockSourceFile);
|
|
489
|
+
for (const { name, pattern, search } of localRoutes) {
|
|
490
|
+
routeMap[name] = pattern;
|
|
491
|
+
if (search && searchSchemasOut) {
|
|
492
|
+
searchSchemasOut[name] = search;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Extract include() calls with diagnostics for unresolvable ones
|
|
497
|
+
const { resolved: includes, unresolvable } = extractIncludesWithDiagnostics(
|
|
498
|
+
block,
|
|
499
|
+
blockSourceFile,
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
if (diagnosticsOut) {
|
|
503
|
+
for (const entry of unresolvable) {
|
|
504
|
+
diagnosticsOut.push({ ...entry, sourceFile: filePath });
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
for (const inc of includes) {
|
|
509
|
+
const { pathPrefix, namePrefix } = inc;
|
|
510
|
+
let childResult: {
|
|
511
|
+
routes: Record<string, string>;
|
|
512
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
if (inc.moduleSpecifier) {
|
|
516
|
+
// Async include `() => import("./mod")`: resolve the imported module's
|
|
517
|
+
// `export default` (convention: `export default urls(...)`), then recurse
|
|
518
|
+
// into it exactly like an identifier include. Nested include()s inside the
|
|
519
|
+
// async module — eager or async — resolve through the same recursion.
|
|
520
|
+
const targetFile = resolveImportPath(inc.moduleSpecifier, filePath);
|
|
521
|
+
if (!targetFile) {
|
|
522
|
+
diagnosticsOut?.push({
|
|
523
|
+
pathPrefix,
|
|
524
|
+
namePrefix,
|
|
525
|
+
reason: "file-not-found",
|
|
526
|
+
sourceFile: filePath,
|
|
527
|
+
detail: `import("${inc.moduleSpecifier}") resolved to no file`,
|
|
528
|
+
});
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
let targetSource: string;
|
|
532
|
+
try {
|
|
533
|
+
targetSource = readSourceMemoized(memo, resolve(targetFile));
|
|
534
|
+
} catch (err) {
|
|
535
|
+
// Every other failure path in this function emits a diagnostic; a bare
|
|
536
|
+
// `continue` here would drop the group from generated types with no
|
|
537
|
+
// signal at all. Emit one like the siblings do.
|
|
538
|
+
diagnosticsOut?.push({
|
|
539
|
+
pathPrefix,
|
|
540
|
+
namePrefix,
|
|
541
|
+
reason: "file-not-found",
|
|
542
|
+
sourceFile: filePath,
|
|
543
|
+
detail: `import("${inc.moduleSpecifier}") resolved to "${targetFile}" but reading it failed: ${(err as Error)?.message ?? String(err)}`,
|
|
544
|
+
});
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
const def = resolveDefaultExportTarget(
|
|
548
|
+
targetSource,
|
|
549
|
+
parseBlock(memo, targetSource),
|
|
550
|
+
);
|
|
551
|
+
if (!def) {
|
|
552
|
+
diagnosticsOut?.push({
|
|
553
|
+
pathPrefix,
|
|
554
|
+
namePrefix,
|
|
555
|
+
reason: "unresolvable-import",
|
|
556
|
+
sourceFile: filePath,
|
|
557
|
+
detail: `import("${inc.moduleSpecifier}") has no resolvable \`export default urls(...)\``,
|
|
558
|
+
});
|
|
559
|
+
continue;
|
|
560
|
+
}
|
|
561
|
+
if (def.inlineBlock) {
|
|
562
|
+
// `export default urls(...)` — recurse into the inline call block.
|
|
563
|
+
childResult = buildCombinedRouteMapWithSearch(
|
|
564
|
+
targetFile,
|
|
565
|
+
undefined,
|
|
566
|
+
visited,
|
|
567
|
+
diagnosticsOut,
|
|
568
|
+
def.inlineBlock,
|
|
569
|
+
memo,
|
|
570
|
+
);
|
|
571
|
+
} else {
|
|
572
|
+
// `export default <name>`. That name may be a same-file
|
|
573
|
+
// `const name = urls(...)` OR itself re-exported from an import
|
|
574
|
+
// (`import { name } from "./x"; export default name`). The shared helper
|
|
575
|
+
// resolves the import chain first, then falls back to same-file
|
|
576
|
+
// extraction — without this a re-exported default silently yields empty
|
|
577
|
+
// routes. Resolution scope is the imported module (targetFile); the
|
|
578
|
+
// diagnostic is still attributed to the file with the async include.
|
|
579
|
+
const variableName = def.variableName!;
|
|
580
|
+
const resolved = resolveIncludedVariable({
|
|
581
|
+
variableName,
|
|
582
|
+
resolutionFile: targetFile,
|
|
583
|
+
resolutionSource: targetSource,
|
|
584
|
+
reportFile: filePath,
|
|
585
|
+
memo,
|
|
586
|
+
visited,
|
|
587
|
+
diagnosticsOut,
|
|
588
|
+
pathPrefix,
|
|
589
|
+
namePrefix,
|
|
590
|
+
fileNotFoundDetail: (specifier) =>
|
|
591
|
+
`import("${inc.moduleSpecifier}") default re-exports "${variableName}" from "${specifier}", which resolved to no file`,
|
|
592
|
+
notFoundDetail: () =>
|
|
593
|
+
`import("${inc.moduleSpecifier}") default "${variableName}" not found in its imports or same-file scope`,
|
|
594
|
+
});
|
|
595
|
+
if (!resolved) continue;
|
|
596
|
+
childResult = resolved;
|
|
597
|
+
}
|
|
598
|
+
} else if (inc.variableName) {
|
|
599
|
+
// Identifier include `include(prefix, patterns)`: resolve `patterns`
|
|
600
|
+
// (imported or same-file) through the same shared helper as the async
|
|
601
|
+
// `export default <name>` form above.
|
|
602
|
+
const variableName = inc.variableName;
|
|
603
|
+
const resolved = resolveIncludedVariable({
|
|
604
|
+
variableName,
|
|
605
|
+
resolutionFile: filePath,
|
|
606
|
+
resolutionSource: fullSource,
|
|
607
|
+
reportFile: filePath,
|
|
608
|
+
memo,
|
|
609
|
+
visited,
|
|
610
|
+
diagnosticsOut,
|
|
611
|
+
pathPrefix,
|
|
612
|
+
namePrefix,
|
|
613
|
+
fileNotFoundDetail: (specifier) =>
|
|
614
|
+
`import "${specifier}" resolved to no file`,
|
|
615
|
+
notFoundDetail: () =>
|
|
616
|
+
`variable "${variableName}" not found in imports or same-file scope`,
|
|
617
|
+
});
|
|
618
|
+
if (!resolved) continue;
|
|
619
|
+
childResult = resolved;
|
|
620
|
+
} else {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Includes without a name keep their child names private to the mounted
|
|
625
|
+
// module. They remain active at runtime via an internal scope prefix, but
|
|
626
|
+
// they are intentionally omitted from generated public route maps.
|
|
627
|
+
if (namePrefix === null) {
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Apply prefixes
|
|
632
|
+
for (const [name, pattern] of Object.entries(childResult.routes)) {
|
|
633
|
+
const prefixedName = namePrefix ? `${namePrefix}.${name}` : name;
|
|
634
|
+
let prefixedPattern: string;
|
|
635
|
+
if (pattern === "/") {
|
|
636
|
+
prefixedPattern = pathPrefix || "/";
|
|
637
|
+
} else if (pathPrefix.endsWith("/") && pattern.startsWith("/")) {
|
|
638
|
+
prefixedPattern = pathPrefix + pattern.slice(1);
|
|
639
|
+
} else {
|
|
640
|
+
prefixedPattern = pathPrefix + pattern;
|
|
641
|
+
}
|
|
642
|
+
routeMap[prefixedName] = prefixedPattern;
|
|
643
|
+
// Propagate search schemas with prefix
|
|
644
|
+
if (childResult.searchSchemas[name] && searchSchemasOut) {
|
|
645
|
+
searchSchemasOut[prefixedName] = childResult.searchSchemas[name];
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return routeMap;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Resolve an include's target VARIABLE to its child route map. The variable may
|
|
655
|
+
* be imported (`import { name } from "./x"`) or defined in the same file
|
|
656
|
+
* (`const name = urls(...)`). Shared by the identifier include branch and the
|
|
657
|
+
* async-include `export default <name>` branch so a future import-resolution fix
|
|
658
|
+
* cannot silently miss one form. Returns null (with a diagnostic pushed) when
|
|
659
|
+
* the variable is unresolvable — the caller should skip the include.
|
|
660
|
+
*
|
|
661
|
+
* `resolutionFile`/`resolutionSource` is the module whose import + same-file
|
|
662
|
+
* scope is searched; `reportFile` is the file the diagnostic is attributed to
|
|
663
|
+
* (the one containing the include() call), which differs for async includes.
|
|
664
|
+
*/
|
|
665
|
+
function resolveIncludedVariable(opts: {
|
|
666
|
+
variableName: string;
|
|
667
|
+
resolutionFile: string;
|
|
668
|
+
resolutionSource: string;
|
|
669
|
+
reportFile: string;
|
|
670
|
+
memo: ScanMemo | undefined;
|
|
671
|
+
visited: Set<string> | undefined;
|
|
672
|
+
diagnosticsOut: UnresolvableInclude[] | undefined;
|
|
673
|
+
pathPrefix: string;
|
|
674
|
+
namePrefix: string | null;
|
|
675
|
+
fileNotFoundDetail: (specifier: string) => string;
|
|
676
|
+
notFoundDetail: () => string;
|
|
677
|
+
}): {
|
|
678
|
+
routes: Record<string, string>;
|
|
679
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
680
|
+
} | null {
|
|
681
|
+
const {
|
|
682
|
+
variableName,
|
|
683
|
+
resolutionFile,
|
|
684
|
+
resolutionSource,
|
|
685
|
+
reportFile,
|
|
686
|
+
memo,
|
|
687
|
+
visited,
|
|
688
|
+
diagnosticsOut,
|
|
689
|
+
pathPrefix,
|
|
690
|
+
namePrefix,
|
|
691
|
+
} = opts;
|
|
692
|
+
const imported = resolveImportedVariable(resolutionSource, variableName);
|
|
693
|
+
if (imported) {
|
|
694
|
+
const targetFile = resolveImportPath(imported.specifier, resolutionFile);
|
|
695
|
+
if (!targetFile) {
|
|
696
|
+
diagnosticsOut?.push({
|
|
697
|
+
pathPrefix,
|
|
698
|
+
namePrefix,
|
|
699
|
+
reason: "file-not-found",
|
|
700
|
+
sourceFile: reportFile,
|
|
701
|
+
detail: opts.fileNotFoundDetail(imported.specifier),
|
|
702
|
+
});
|
|
703
|
+
return null;
|
|
704
|
+
}
|
|
705
|
+
return buildCombinedRouteMapWithSearch(
|
|
706
|
+
targetFile,
|
|
707
|
+
imported.exportedName,
|
|
708
|
+
visited,
|
|
709
|
+
diagnosticsOut,
|
|
710
|
+
undefined,
|
|
711
|
+
memo,
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
// Not imported: it must be a same-file `const name = urls(...)`. Confirm it
|
|
715
|
+
// exists so an undefined name emits a diagnostic instead of empty routes.
|
|
716
|
+
const sameFileBlock = extractUrlsBlockForVariable(
|
|
717
|
+
resolutionSource,
|
|
718
|
+
variableName,
|
|
719
|
+
parseBlock(memo, resolutionSource),
|
|
720
|
+
);
|
|
721
|
+
if (!sameFileBlock) {
|
|
722
|
+
diagnosticsOut?.push({
|
|
723
|
+
pathPrefix,
|
|
724
|
+
namePrefix,
|
|
725
|
+
reason: "unresolvable-import",
|
|
726
|
+
sourceFile: reportFile,
|
|
727
|
+
detail: opts.notFoundDetail(),
|
|
728
|
+
});
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
return buildCombinedRouteMapWithSearch(
|
|
732
|
+
resolutionFile,
|
|
733
|
+
variableName,
|
|
734
|
+
visited,
|
|
735
|
+
diagnosticsOut,
|
|
736
|
+
undefined,
|
|
737
|
+
memo,
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Build route map and search schemas together.
|
|
743
|
+
* Internal helper used by the include resolution path.
|
|
744
|
+
*
|
|
745
|
+
* @param inlineBlock - Optional pre-extracted code block (e.g. from an inline
|
|
746
|
+
* builder function). When provided, variableName is ignored and the block
|
|
747
|
+
* is parsed directly for path()/include() calls.
|
|
748
|
+
* @param memo - Per-scan readFileSync/parse memo. A fresh one is created at the
|
|
749
|
+
* top-level entry and threaded through the recursion so each file is read and
|
|
750
|
+
* parsed once per scan. Behavior-preserving (same input -> same output).
|
|
751
|
+
*/
|
|
752
|
+
export function buildCombinedRouteMapWithSearch(
|
|
753
|
+
filePath: string,
|
|
754
|
+
variableName?: string,
|
|
755
|
+
visited?: Set<string>,
|
|
756
|
+
diagnosticsOut?: UnresolvableInclude[],
|
|
757
|
+
inlineBlock?: string,
|
|
758
|
+
memo?: ScanMemo,
|
|
759
|
+
): {
|
|
760
|
+
routes: Record<string, string>;
|
|
761
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
762
|
+
} {
|
|
763
|
+
visited = visited ?? new Set();
|
|
764
|
+
memo = memo ?? createScanMemo();
|
|
765
|
+
const realPath = resolve(filePath);
|
|
766
|
+
const key = variableName ? `${realPath}:${variableName}` : realPath;
|
|
767
|
+
if (visited.has(key)) {
|
|
768
|
+
console.warn(`[rango] Circular include detected, skipping: ${key}`);
|
|
769
|
+
return { routes: {}, searchSchemas: {} };
|
|
770
|
+
}
|
|
771
|
+
visited.add(key);
|
|
772
|
+
|
|
773
|
+
let source: string;
|
|
774
|
+
try {
|
|
775
|
+
source = readSourceMemoized(memo, realPath);
|
|
776
|
+
} catch {
|
|
777
|
+
return { routes: {}, searchSchemas: {} };
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
let block: string;
|
|
781
|
+
if (inlineBlock) {
|
|
782
|
+
block = inlineBlock;
|
|
783
|
+
} else if (variableName) {
|
|
784
|
+
const extracted = extractUrlsBlockForVariable(
|
|
785
|
+
source,
|
|
786
|
+
variableName,
|
|
787
|
+
parseBlock(memo, source),
|
|
788
|
+
);
|
|
789
|
+
if (!extracted) return { routes: {}, searchSchemas: {} };
|
|
790
|
+
block = extracted;
|
|
791
|
+
} else {
|
|
792
|
+
block = source;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const searchSchemas: Record<string, Record<string, string>> = {};
|
|
796
|
+
const routes = buildRouteMapFromBlock(
|
|
797
|
+
block,
|
|
798
|
+
source,
|
|
799
|
+
realPath,
|
|
800
|
+
visited,
|
|
801
|
+
searchSchemas,
|
|
802
|
+
diagnosticsOut,
|
|
803
|
+
memo,
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
// Remove from visited so sibling branches can include the same variable
|
|
807
|
+
// without false circular-include detection. Only ancestors in the current
|
|
808
|
+
// recursion path should trigger the cycle guard.
|
|
809
|
+
visited.delete(key);
|
|
810
|
+
|
|
811
|
+
return { routes, searchSchemas };
|
|
812
|
+
}
|