@rangojs/router 0.0.0-experimental.19 → 0.0.0-experimental.1c0bdfad
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 +291 -61
- package/dist/bin/rango.js +544 -143
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3744 -1329
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +67 -13
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +312 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +247 -23
- package/skills/caching/SKILL.md +322 -19
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +282 -60
- package/skills/host-router/SKILL.md +278 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +50 -6
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +297 -31
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +155 -111
- package/skills/rango/SKILL.md +251 -23
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +104 -9
- package/skills/router-setup/SKILL.md +124 -11
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +125 -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 +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -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/typesafety/SKILL.md +357 -52
- package/skills/use-cache/SKILL.md +46 -14
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/bin/rango.ts +18 -0
- package/src/browser/action-coordinator.ts +53 -36
- 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 +197 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +200 -30
- package/src/browser/navigation-client.ts +217 -58
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +187 -112
- package/src/browser/prefetch/cache.ts +312 -30
- package/src/browser/prefetch/fetch.ts +344 -47
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +125 -18
- package/src/browser/react/NavigationProvider.tsx +135 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +80 -5
- package/src/browser/rsc-router.tsx +226 -75
- package/src/browser/scroll-restoration.ts +54 -42
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +619 -442
- package/src/browser/types.ts +115 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +7 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +182 -37
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +125 -24
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +392 -106
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +214 -48
- package/src/cache/cache-scope.ts +236 -89
- 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 +2224 -171
- 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 +11 -17
- package/src/cache/document-cache.ts +89 -27
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +11 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +31 -31
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +45 -21
- package/src/client.tsx +120 -336
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +56 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +95 -0
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -5
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +79 -29
- package/src/index.ts +149 -65
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +63 -26
- package/src/prerender.ts +198 -82
- 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 +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +469 -276
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -3
- package/src/route-definition/redirect.ts +53 -12
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +110 -39
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +50 -24
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +274 -56
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +33 -6
- package/src/router/match-middleware/cache-lookup.ts +214 -263
- package/src/router/match-middleware/cache-store.ts +73 -33
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -49
- package/src/router/metrics.ts +217 -26
- package/src/router/middleware-types.ts +24 -110
- package/src/router/middleware.ts +384 -197
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +148 -91
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +199 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +144 -74
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +8 -28
- package/src/router/router-interfaces.ts +129 -36
- package/src/router/router-options.ts +185 -23
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +281 -76
- package/src/router/segment-resolution/helpers.ts +116 -31
- package/src/router/segment-resolution/loader-cache.ts +63 -37
- package/src/router/segment-resolution/revalidation.ts +493 -391
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +8 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +180 -58
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +182 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +702 -460
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +127 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +98 -19
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +126 -106
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +269 -114
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/types.ts +34 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +49 -41
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +281 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +317 -63
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +26 -46
- package/src/server/request-context.ts +425 -177
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +25 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -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 +183 -0
- package/src/testing/index.ts +99 -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 +343 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -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 +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +3 -19
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +236 -88
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +10 -45
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -19
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +64 -91
- package/src/vite/discovery/discover-routers.ts +147 -88
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +247 -145
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +61 -13
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +10 -3
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +155 -65
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- 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/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +553 -317
- 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 +73 -83
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +46 -37
- package/src/vite/plugins/virtual-entries.ts +13 -18
- package/src/vite/rango.ts +241 -287
- package/src/vite/router-discovery.ts +956 -149
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- 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 +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +141 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/CLAUDE.md +0 -5
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -6
- package/src/network-error-thrower.tsx +0 -23
- package/src/route-definition/route-function.ts +0 -119
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
readFileSync,
|
|
3
|
+
writeFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
unlinkSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import {
|
|
9
|
+
join,
|
|
10
|
+
dirname,
|
|
11
|
+
resolve,
|
|
12
|
+
sep,
|
|
13
|
+
basename as pathBasename,
|
|
14
|
+
} from "node:path";
|
|
3
15
|
import ts from "typescript";
|
|
4
16
|
import { generateRouteTypesSource } from "./codegen.js";
|
|
5
17
|
import type { ScanFilter } from "./scan-filter.js";
|
|
6
|
-
import {
|
|
18
|
+
import { firstCodeMatchIndex } from "./source-scan.js";
|
|
7
19
|
import {
|
|
8
20
|
resolveImportedVariable,
|
|
9
21
|
resolveImportPath,
|
|
@@ -26,18 +38,158 @@ function countPublicRouteEntries(source: string): number {
|
|
|
26
38
|
return count;
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
const ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
|
|
42
|
+
// Global variant for the code-region scan (firstCodeMatchIndex sets lastIndex).
|
|
43
|
+
const ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
|
|
44
|
+
|
|
45
|
+
function isRoutableSourceFile(name: string): boolean {
|
|
46
|
+
return (
|
|
47
|
+
(name.endsWith(".ts") ||
|
|
48
|
+
name.endsWith(".tsx") ||
|
|
49
|
+
name.endsWith(".js") ||
|
|
50
|
+
name.endsWith(".jsx")) &&
|
|
51
|
+
!name.includes(".gen.") &&
|
|
52
|
+
!name.includes(".test.") &&
|
|
53
|
+
!name.includes(".spec.")
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findRouterFilesRecursive(
|
|
58
|
+
dir: string,
|
|
59
|
+
filter: ScanFilter | undefined,
|
|
60
|
+
results: string[],
|
|
61
|
+
): void {
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.warn(
|
|
67
|
+
`[rango] Failed to scan directory ${dir}: ${(err as Error).message}`,
|
|
68
|
+
);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const childDirs: string[] = [];
|
|
73
|
+
const routerFilesInDir: string[] = [];
|
|
74
|
+
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
const fullPath = join(dir, entry.name);
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
if (
|
|
79
|
+
entry.name === "node_modules" ||
|
|
80
|
+
entry.name === "dist" ||
|
|
81
|
+
entry.name === "coverage" ||
|
|
82
|
+
entry.name === "__tests__" ||
|
|
83
|
+
entry.name === "__mocks__" ||
|
|
84
|
+
entry.name.startsWith(".")
|
|
85
|
+
)
|
|
86
|
+
continue;
|
|
87
|
+
childDirs.push(fullPath);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!isRoutableSourceFile(entry.name)) continue;
|
|
92
|
+
if (filter && !filter(fullPath)) continue;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const source = readFileSync(fullPath, "utf-8");
|
|
96
|
+
// Fast path: most files contain no `createRouter(` at all, so the cheap
|
|
97
|
+
// raw regex short-circuits before the code-region scan. Only a file that
|
|
98
|
+
// mentions the token (real call OR a comment/string mention) is rescanned
|
|
99
|
+
// over code regions — allocation-free, never building a stripped copy —
|
|
100
|
+
// so a mention inside a comment or string is not mistaken for a real
|
|
101
|
+
// router file (which previously triggered a spurious "Multiple routers
|
|
102
|
+
// found" error).
|
|
103
|
+
if (
|
|
104
|
+
ROUTER_CALL_PATTERN.test(source) &&
|
|
105
|
+
firstCodeMatchIndex(source, ROUTER_CALL_PATTERN_G) >= 0
|
|
106
|
+
) {
|
|
107
|
+
routerFilesInDir.push(fullPath);
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// A directory that contains a router file is treated as a router root.
|
|
115
|
+
// Once found, deeper directories are skipped to avoid redundant scans.
|
|
116
|
+
if (routerFilesInDir.length > 0) {
|
|
117
|
+
results.push(...routerFilesInDir);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const childDir of childDirs) {
|
|
122
|
+
findRouterFilesRecursive(childDir, filter, results);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function findNestedRouterConflict(
|
|
127
|
+
routerFiles: string[],
|
|
128
|
+
): { ancestor: string; nested: string } | null {
|
|
129
|
+
const routerDirs = [
|
|
130
|
+
...new Set(routerFiles.map((filePath) => dirname(resolve(filePath)))),
|
|
131
|
+
].sort((a, b) => a.length - b.length);
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < routerDirs.length; i++) {
|
|
134
|
+
const ancestorDir = routerDirs[i];
|
|
135
|
+
const prefix = ancestorDir.endsWith(sep)
|
|
136
|
+
? ancestorDir
|
|
137
|
+
: `${ancestorDir}${sep}`;
|
|
138
|
+
for (let j = i + 1; j < routerDirs.length; j++) {
|
|
139
|
+
const nestedDir = routerDirs[j];
|
|
140
|
+
if (!nestedDir.startsWith(prefix)) continue;
|
|
141
|
+
const ancestorFile = routerFiles.find(
|
|
142
|
+
(filePath) => dirname(resolve(filePath)) === ancestorDir,
|
|
143
|
+
);
|
|
144
|
+
const nestedFile = routerFiles.find(
|
|
145
|
+
(filePath) => dirname(resolve(filePath)) === nestedDir,
|
|
146
|
+
);
|
|
147
|
+
if (ancestorFile && nestedFile) {
|
|
148
|
+
return { ancestor: ancestorFile, nested: nestedFile };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function formatNestedRouterConflictError(
|
|
157
|
+
conflict: { ancestor: string; nested: string },
|
|
158
|
+
prefix = "[rango]",
|
|
159
|
+
): string {
|
|
160
|
+
return (
|
|
161
|
+
`${prefix} Nested router roots are not supported.\n` +
|
|
162
|
+
`Router root: ${conflict.ancestor}\n` +
|
|
163
|
+
`Nested router: ${conflict.nested}\n` +
|
|
164
|
+
`Move the nested router into a sibling directory or configure it as a separate app root.`
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
29
168
|
// ---------------------------------------------------------------------------
|
|
30
169
|
// Router file URL extraction
|
|
31
170
|
// ---------------------------------------------------------------------------
|
|
32
171
|
|
|
33
172
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
173
|
+
* Result of extracting URL patterns from a router file.
|
|
174
|
+
* - "variable": a named variable reference (e.g., `.routes(patterns)` or `urls: patterns`)
|
|
175
|
+
* - "inline": an inline builder function (e.g., `.routes(({ path }) => [...])` or `urls: ({ path }) => [...]`)
|
|
176
|
+
*/
|
|
177
|
+
export type UrlsExtractionResult =
|
|
178
|
+
| { kind: "variable"; name: string }
|
|
179
|
+
| { kind: "inline"; block: string };
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Extract the url patterns from a router file using AST.
|
|
183
|
+
* Detects four patterns:
|
|
36
184
|
* 1. createRouter(...).routes(variableName)
|
|
37
185
|
* 2. createRouter({ urls: variableName, ... })
|
|
38
|
-
*
|
|
186
|
+
* 3. createRouter(...).routes(({ path, ... }) => [...])
|
|
187
|
+
* 4. createRouter({ urls: ({ path, ... }) => [...], ... })
|
|
188
|
+
* Returns either a variable name or an inline code block.
|
|
39
189
|
*/
|
|
40
|
-
export function
|
|
190
|
+
export function extractUrlsFromRouter(
|
|
191
|
+
code: string,
|
|
192
|
+
): UrlsExtractionResult | null {
|
|
41
193
|
const sourceFile = ts.createSourceFile(
|
|
42
194
|
"router.tsx",
|
|
43
195
|
code,
|
|
@@ -45,7 +197,7 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
45
197
|
true,
|
|
46
198
|
ts.ScriptKind.TSX,
|
|
47
199
|
);
|
|
48
|
-
let result:
|
|
200
|
+
let result: UrlsExtractionResult | null = null;
|
|
49
201
|
|
|
50
202
|
function isCreateRouterCall(node: ts.Node): boolean {
|
|
51
203
|
if (!ts.isCallExpression(node)) return false;
|
|
@@ -53,44 +205,108 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
53
205
|
return ts.isIdentifier(callee) && callee.text === "createRouter";
|
|
54
206
|
}
|
|
55
207
|
|
|
208
|
+
/** Check if a node is an arrow/function expression (inline builder). */
|
|
209
|
+
function isInlineBuilder(node: ts.Node): boolean {
|
|
210
|
+
return ts.isArrowFunction(node) || ts.isFunctionExpression(node);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Check if a .routes() call chains from createRouter(). */
|
|
214
|
+
function isRoutesOnCreateRouter(node: ts.CallExpression): boolean {
|
|
215
|
+
if (
|
|
216
|
+
!ts.isPropertyAccessExpression(node.expression) ||
|
|
217
|
+
node.expression.name.text !== "routes"
|
|
218
|
+
)
|
|
219
|
+
return false;
|
|
220
|
+
let inner: ts.Expression = node.expression.expression;
|
|
221
|
+
while (
|
|
222
|
+
ts.isCallExpression(inner) &&
|
|
223
|
+
ts.isPropertyAccessExpression(inner.expression)
|
|
224
|
+
) {
|
|
225
|
+
inner = inner.expression.expression;
|
|
226
|
+
}
|
|
227
|
+
return isCreateRouterCall(inner);
|
|
228
|
+
}
|
|
229
|
+
|
|
56
230
|
function visit(node: ts.Node) {
|
|
57
231
|
if (result) return;
|
|
58
232
|
|
|
59
|
-
// Pattern 1: createRouter(...).routes(variableName)
|
|
60
|
-
// The AST shape is CallExpression(.routes) -> PropertyAccessExpression -> CallExpression(createRouter)
|
|
233
|
+
// Pattern 1 & 3: createRouter(...).routes(variableName | builder)
|
|
61
234
|
if (
|
|
62
235
|
ts.isCallExpression(node) &&
|
|
63
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
64
|
-
node.expression.name.text === "routes" &&
|
|
65
236
|
node.arguments.length >= 1 &&
|
|
66
|
-
|
|
237
|
+
isRoutesOnCreateRouter(node)
|
|
67
238
|
) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
ts.isPropertyAccessExpression(inner.expression)
|
|
74
|
-
) {
|
|
75
|
-
inner = inner.expression.expression;
|
|
76
|
-
}
|
|
77
|
-
if (isCreateRouterCall(inner)) {
|
|
78
|
-
result = (node.arguments[0] as ts.Identifier).text;
|
|
79
|
-
return;
|
|
239
|
+
const arg = node.arguments[0];
|
|
240
|
+
if (ts.isIdentifier(arg)) {
|
|
241
|
+
result = { kind: "variable", name: arg.text };
|
|
242
|
+
} else if (isInlineBuilder(arg)) {
|
|
243
|
+
result = { kind: "inline", block: arg.getText(sourceFile) };
|
|
80
244
|
}
|
|
245
|
+
return;
|
|
81
246
|
}
|
|
82
247
|
|
|
83
|
-
// Pattern 2: createRouter({ urls: variableName, ... })
|
|
248
|
+
// Pattern 2 & 4: createRouter({ urls: variableName | builder, ... })
|
|
84
249
|
if (isCreateRouterCall(node)) {
|
|
85
250
|
const callExpr = node as ts.CallExpression;
|
|
86
|
-
for (const
|
|
251
|
+
for (const callArg of callExpr.arguments) {
|
|
252
|
+
if (ts.isObjectLiteralExpression(callArg)) {
|
|
253
|
+
for (const prop of callArg.properties) {
|
|
254
|
+
if (
|
|
255
|
+
ts.isPropertyAssignment(prop) &&
|
|
256
|
+
ts.isIdentifier(prop.name) &&
|
|
257
|
+
prop.name.text === "urls"
|
|
258
|
+
) {
|
|
259
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
260
|
+
result = { kind: "variable", name: prop.initializer.text };
|
|
261
|
+
} else if (isInlineBuilder(prop.initializer)) {
|
|
262
|
+
result = {
|
|
263
|
+
kind: "inline",
|
|
264
|
+
block: prop.initializer.getText(sourceFile),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
ts.forEachChild(node, visit);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
visit(sourceFile);
|
|
278
|
+
return result;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Extract the `basename` string literal from createRouter({ basename: "..." }).
|
|
283
|
+
* Returns the basename value or undefined if not present.
|
|
284
|
+
*/
|
|
285
|
+
export function extractBasenameFromRouter(code: string): string | undefined {
|
|
286
|
+
const sourceFile = ts.createSourceFile(
|
|
287
|
+
"router.tsx",
|
|
288
|
+
code,
|
|
289
|
+
ts.ScriptTarget.Latest,
|
|
290
|
+
true,
|
|
291
|
+
ts.ScriptKind.TSX,
|
|
292
|
+
);
|
|
293
|
+
let result: string | undefined;
|
|
294
|
+
|
|
295
|
+
function visit(node: ts.Node) {
|
|
296
|
+
if (result !== undefined) return;
|
|
297
|
+
if (
|
|
298
|
+
ts.isCallExpression(node) &&
|
|
299
|
+
ts.isIdentifier(node.expression) &&
|
|
300
|
+
node.expression.text === "createRouter"
|
|
301
|
+
) {
|
|
302
|
+
for (const arg of node.arguments) {
|
|
87
303
|
if (ts.isObjectLiteralExpression(arg)) {
|
|
88
304
|
for (const prop of arg.properties) {
|
|
89
305
|
if (
|
|
90
306
|
ts.isPropertyAssignment(prop) &&
|
|
91
307
|
ts.isIdentifier(prop.name) &&
|
|
92
|
-
prop.name.text === "
|
|
93
|
-
ts.
|
|
308
|
+
prop.name.text === "basename" &&
|
|
309
|
+
ts.isStringLiteral(prop.initializer)
|
|
94
310
|
) {
|
|
95
311
|
result = prop.initializer.text;
|
|
96
312
|
return;
|
|
@@ -99,7 +315,6 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
99
315
|
}
|
|
100
316
|
}
|
|
101
317
|
}
|
|
102
|
-
|
|
103
318
|
ts.forEachChild(node, visit);
|
|
104
319
|
}
|
|
105
320
|
|
|
@@ -107,9 +322,64 @@ export function extractUrlsVariableFromRouter(code: string): string | null {
|
|
|
107
322
|
return result;
|
|
108
323
|
}
|
|
109
324
|
|
|
325
|
+
/** Apply a basename prefix to all route patterns in a result set. */
|
|
326
|
+
function applyBasenameToRoutes(
|
|
327
|
+
result: {
|
|
328
|
+
routes: Record<string, string>;
|
|
329
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
330
|
+
},
|
|
331
|
+
basename: string,
|
|
332
|
+
): {
|
|
333
|
+
routes: Record<string, string>;
|
|
334
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
335
|
+
} {
|
|
336
|
+
const prefixed: Record<string, string> = {};
|
|
337
|
+
for (const [name, pattern] of Object.entries(result.routes)) {
|
|
338
|
+
if (pattern === "/") {
|
|
339
|
+
prefixed[name] = basename;
|
|
340
|
+
} else if (basename.endsWith("/") && pattern.startsWith("/")) {
|
|
341
|
+
prefixed[name] = basename + pattern.slice(1);
|
|
342
|
+
} else {
|
|
343
|
+
prefixed[name] = basename + pattern;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return { routes: prefixed, searchSchemas: result.searchSchemas };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Filesystem path of the generated route-types file for a router source file.
|
|
350
|
+
// Native separators — matches the self-gen-tracking Map key the watcher compares.
|
|
351
|
+
export function genFileTsPath(sourceFile: string): string {
|
|
352
|
+
const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
|
|
353
|
+
return join(dirname(sourceFile), `${base}.named-routes.gen.ts`);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Search schemas for the gen file: prefer the runtime manifest's; when it omits
|
|
357
|
+
// them (some module-runner flows) fall back to static parsing filtered to the
|
|
358
|
+
// public route-name set. Returns the runtime value unchanged otherwise.
|
|
359
|
+
export function resolveSearchSchemas(
|
|
360
|
+
publicRouteNames: string[],
|
|
361
|
+
runtimeSchemas: Record<string, Record<string, string>> | undefined,
|
|
362
|
+
sourceFile: string,
|
|
363
|
+
): Record<string, Record<string, string>> | undefined {
|
|
364
|
+
if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
|
|
365
|
+
return runtimeSchemas;
|
|
366
|
+
}
|
|
367
|
+
const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
|
|
368
|
+
if (Object.keys(staticParsed.searchSchemas).length === 0) {
|
|
369
|
+
return runtimeSchemas;
|
|
370
|
+
}
|
|
371
|
+
const filtered: Record<string, Record<string, string>> = {};
|
|
372
|
+
for (const name of publicRouteNames) {
|
|
373
|
+
const schema = staticParsed.searchSchemas[name];
|
|
374
|
+
if (schema) filtered[name] = schema;
|
|
375
|
+
}
|
|
376
|
+
return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
|
|
377
|
+
}
|
|
378
|
+
|
|
110
379
|
/**
|
|
111
380
|
* Resolve routes and search schemas from a router source file by following the
|
|
112
|
-
* variable passed to `.routes(...)` or `urls: ...` in createRouter options
|
|
381
|
+
* variable passed to `.routes(...)` or `urls: ...` in createRouter options,
|
|
382
|
+
* or by parsing an inline builder function directly.
|
|
113
383
|
*/
|
|
114
384
|
export function buildCombinedRouteMapForRouterFile(routerFilePath: string): {
|
|
115
385
|
routes: Record<string, string>;
|
|
@@ -122,21 +392,54 @@ export function buildCombinedRouteMapForRouterFile(routerFilePath: string): {
|
|
|
122
392
|
return { routes: {}, searchSchemas: {} };
|
|
123
393
|
}
|
|
124
394
|
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
395
|
+
const extraction = extractUrlsFromRouter(routerSource);
|
|
396
|
+
if (!extraction) {
|
|
127
397
|
return { routes: {}, searchSchemas: {} };
|
|
128
398
|
}
|
|
129
399
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
400
|
+
// Detect basename from createRouter({ basename: "..." })
|
|
401
|
+
const rawBasename = extractBasenameFromRouter(routerSource);
|
|
402
|
+
const basename = rawBasename
|
|
403
|
+
? ("/" + rawBasename.replace(/^\/+|\/+$/g, "")).replace(/^\/$/, "")
|
|
404
|
+
: undefined;
|
|
405
|
+
|
|
406
|
+
let result: {
|
|
407
|
+
routes: Record<string, string>;
|
|
408
|
+
searchSchemas: Record<string, Record<string, string>>;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// Inline builder: extract routes directly from the function body
|
|
412
|
+
if (extraction.kind === "inline") {
|
|
413
|
+
result = buildCombinedRouteMapWithSearch(
|
|
414
|
+
routerFilePath,
|
|
415
|
+
undefined,
|
|
416
|
+
undefined,
|
|
417
|
+
undefined,
|
|
418
|
+
extraction.block,
|
|
419
|
+
);
|
|
420
|
+
} else {
|
|
421
|
+
// Variable reference: follow imports or same-file declaration
|
|
422
|
+
const imported = resolveImportedVariable(routerSource, extraction.name);
|
|
423
|
+
if (imported) {
|
|
424
|
+
const targetFile = resolveImportPath(imported.specifier, routerFilePath);
|
|
425
|
+
if (!targetFile) {
|
|
426
|
+
return { routes: {}, searchSchemas: {} };
|
|
427
|
+
}
|
|
428
|
+
result = buildCombinedRouteMapWithSearch(
|
|
429
|
+
targetFile,
|
|
430
|
+
imported.exportedName,
|
|
431
|
+
);
|
|
432
|
+
} else {
|
|
433
|
+
result = buildCombinedRouteMapWithSearch(routerFilePath, extraction.name);
|
|
135
434
|
}
|
|
136
|
-
return buildCombinedRouteMapWithSearch(targetFile, imported.exportedName);
|
|
137
435
|
}
|
|
138
436
|
|
|
139
|
-
|
|
437
|
+
// Apply basename prefix to all extracted route patterns
|
|
438
|
+
if (basename) {
|
|
439
|
+
result = applyBasenameToRoutes(result, basename);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return result;
|
|
140
443
|
}
|
|
141
444
|
|
|
142
445
|
// ---------------------------------------------------------------------------
|
|
@@ -159,12 +462,26 @@ export function detectUnresolvableIncludes(
|
|
|
159
462
|
return [];
|
|
160
463
|
}
|
|
161
464
|
|
|
162
|
-
// Extract the urls
|
|
163
|
-
const
|
|
164
|
-
if (!
|
|
465
|
+
// Extract the urls source from the router file
|
|
466
|
+
const extraction = extractUrlsFromRouter(source);
|
|
467
|
+
if (!extraction) return [];
|
|
468
|
+
|
|
469
|
+
const diagnostics: UnresolvableInclude[] = [];
|
|
470
|
+
|
|
471
|
+
if (extraction.kind === "inline") {
|
|
472
|
+
// Inline builder: parse directly
|
|
473
|
+
buildCombinedRouteMapWithSearch(
|
|
474
|
+
realPath,
|
|
475
|
+
undefined,
|
|
476
|
+
new Set(),
|
|
477
|
+
diagnostics,
|
|
478
|
+
extraction.block,
|
|
479
|
+
);
|
|
480
|
+
return diagnostics;
|
|
481
|
+
}
|
|
165
482
|
|
|
166
|
-
//
|
|
167
|
-
const imported = resolveImportedVariable(source,
|
|
483
|
+
// Variable reference: resolve where it comes from
|
|
484
|
+
const imported = resolveImportedVariable(source, extraction.name);
|
|
168
485
|
let targetFile: string;
|
|
169
486
|
let exportedName: string | undefined;
|
|
170
487
|
|
|
@@ -186,10 +503,9 @@ export function detectUnresolvableIncludes(
|
|
|
186
503
|
} else {
|
|
187
504
|
// Same-file urls() definition
|
|
188
505
|
targetFile = realPath;
|
|
189
|
-
exportedName =
|
|
506
|
+
exportedName = extraction.name;
|
|
190
507
|
}
|
|
191
508
|
|
|
192
|
-
const diagnostics: UnresolvableInclude[] = [];
|
|
193
509
|
buildCombinedRouteMapWithSearch(
|
|
194
510
|
targetFile,
|
|
195
511
|
exportedName,
|
|
@@ -235,19 +551,8 @@ export function detectUnresolvableIncludesForUrlsFile(
|
|
|
235
551
|
* Call once at startup; the result can be reused on subsequent watcher triggers.
|
|
236
552
|
*/
|
|
237
553
|
export function findRouterFiles(root: string, filter?: ScanFilter): string[] {
|
|
238
|
-
const files = findTsFiles(root, filter);
|
|
239
554
|
const result: string[] = [];
|
|
240
|
-
|
|
241
|
-
if (filePath.includes(".gen.")) continue;
|
|
242
|
-
try {
|
|
243
|
-
const source = readFileSync(filePath, "utf-8");
|
|
244
|
-
if (/\bcreateRouter\s*[<(]/.test(source)) {
|
|
245
|
-
result.push(filePath);
|
|
246
|
-
}
|
|
247
|
-
} catch {
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
555
|
+
findRouterFilesRecursive(root, filter, result);
|
|
251
556
|
return result;
|
|
252
557
|
}
|
|
253
558
|
|
|
@@ -260,7 +565,10 @@ export function findRouterFiles(root: string, filter?: ScanFilter): string[] {
|
|
|
260
565
|
export function writeCombinedRouteTypes(
|
|
261
566
|
root: string,
|
|
262
567
|
knownRouterFiles?: string[],
|
|
263
|
-
opts?: {
|
|
568
|
+
opts?: {
|
|
569
|
+
preserveIfLarger?: boolean;
|
|
570
|
+
onWrite?: (outPath: string, content: string) => void;
|
|
571
|
+
},
|
|
264
572
|
): void {
|
|
265
573
|
// Delete old combined named-routes.gen.ts if it exists (stale from older versions)
|
|
266
574
|
try {
|
|
@@ -268,7 +576,7 @@ export function writeCombinedRouteTypes(
|
|
|
268
576
|
if (existsSync(oldCombinedPath)) {
|
|
269
577
|
unlinkSync(oldCombinedPath);
|
|
270
578
|
console.log(
|
|
271
|
-
`[
|
|
579
|
+
`[rango] Removed stale combined route types: ${oldCombinedPath}`,
|
|
272
580
|
);
|
|
273
581
|
}
|
|
274
582
|
} catch {}
|
|
@@ -276,55 +584,37 @@ export function writeCombinedRouteTypes(
|
|
|
276
584
|
const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
|
|
277
585
|
if (routerFilePaths.length === 0) return;
|
|
278
586
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
} catch {
|
|
284
|
-
continue;
|
|
285
|
-
}
|
|
286
|
-
// Extract the urls variable name from .routes(varName) or urls: varName
|
|
287
|
-
const urlsVarName = extractUrlsVariableFromRouter(routerSource);
|
|
288
|
-
if (!urlsVarName) continue;
|
|
289
|
-
|
|
290
|
-
// Resolve the variable to its source module
|
|
291
|
-
let result: {
|
|
292
|
-
routes: Record<string, string>;
|
|
293
|
-
searchSchemas: Record<string, Record<string, string>>;
|
|
294
|
-
};
|
|
587
|
+
const nestedRouterConflict = findNestedRouterConflict(routerFilePaths);
|
|
588
|
+
if (nestedRouterConflict) {
|
|
589
|
+
throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
|
|
590
|
+
}
|
|
295
591
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
592
|
+
for (const routerFilePath of routerFilePaths) {
|
|
593
|
+
const result = buildCombinedRouteMapForRouterFile(routerFilePath);
|
|
594
|
+
if (
|
|
595
|
+
Object.keys(result.routes).length === 0 &&
|
|
596
|
+
Object.keys(result.searchSchemas).length === 0
|
|
597
|
+
) {
|
|
598
|
+
// Check if the file even has a createRouter call — if not, skip entirely.
|
|
599
|
+
// If it does, fall through to write an empty placeholder below.
|
|
600
|
+
let routerSource: string;
|
|
601
|
+
try {
|
|
602
|
+
routerSource = readFileSync(routerFilePath, "utf-8");
|
|
603
|
+
} catch {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (!extractUrlsFromRouter(routerSource)) continue;
|
|
308
607
|
}
|
|
309
608
|
|
|
310
|
-
const
|
|
311
|
-
/\.(tsx?|jsx?)$/,
|
|
312
|
-
"",
|
|
313
|
-
);
|
|
314
|
-
const outPath = join(
|
|
315
|
-
dirname(routerFilePath),
|
|
316
|
-
`${routerBasename}.named-routes.gen.ts`,
|
|
317
|
-
);
|
|
609
|
+
const outPath = genFileTsPath(routerFilePath);
|
|
318
610
|
const existing = existsSync(outPath)
|
|
319
611
|
? readFileSync(outPath, "utf-8")
|
|
320
612
|
: null;
|
|
321
613
|
|
|
322
|
-
// When the static parser can't extract routes (e.g. callback-style urls()),
|
|
323
|
-
// write an empty placeholder so the build-time transform's injected import
|
|
324
|
-
// resolves. Runtime discovery will overwrite this with the real routes.
|
|
325
614
|
if (Object.keys(result.routes).length === 0) {
|
|
326
615
|
if (!existing) {
|
|
327
616
|
const emptySource = generateRouteTypesSource({});
|
|
617
|
+
opts?.onWrite?.(outPath, emptySource);
|
|
328
618
|
writeFileSync(outPath, emptySource);
|
|
329
619
|
}
|
|
330
620
|
continue;
|
|
@@ -336,11 +626,6 @@ export function writeCombinedRouteTypes(
|
|
|
336
626
|
hasSearchSchemas ? result.searchSchemas : undefined,
|
|
337
627
|
);
|
|
338
628
|
if (existing !== source) {
|
|
339
|
-
// On initial dev startup, don't overwrite a file from runtime discovery
|
|
340
|
-
// (which has all dynamic routes) with a smaller set from the static
|
|
341
|
-
// parser. The static parser can't see routes generated by Array.from()
|
|
342
|
-
// or other dynamic code. During HMR (file watcher), always write so
|
|
343
|
-
// newly added routes appear immediately.
|
|
344
629
|
if (opts?.preserveIfLarger && existing) {
|
|
345
630
|
const existingCount = countPublicRouteEntries(existing);
|
|
346
631
|
const newCount = Object.keys(result.routes).filter(
|
|
@@ -350,9 +635,10 @@ export function writeCombinedRouteTypes(
|
|
|
350
635
|
continue;
|
|
351
636
|
}
|
|
352
637
|
}
|
|
638
|
+
opts?.onWrite?.(outPath, source);
|
|
353
639
|
writeFileSync(outPath, source);
|
|
354
640
|
console.log(
|
|
355
|
-
`[
|
|
641
|
+
`[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`,
|
|
356
642
|
);
|
|
357
643
|
}
|
|
358
644
|
}
|
|
@@ -54,14 +54,21 @@ export function findTsFiles(dir: string, filter?: ScanFilter): string[] {
|
|
|
54
54
|
entries = readdirSync(dir, { withFileTypes: true });
|
|
55
55
|
} catch (err) {
|
|
56
56
|
console.warn(
|
|
57
|
-
`[
|
|
57
|
+
`[rango] Failed to scan directory ${dir}: ${(err as Error).message}`,
|
|
58
58
|
);
|
|
59
59
|
return results;
|
|
60
60
|
}
|
|
61
61
|
for (const entry of entries) {
|
|
62
62
|
const fullPath = join(dir, entry.name);
|
|
63
63
|
if (entry.isDirectory()) {
|
|
64
|
-
if (
|
|
64
|
+
if (
|
|
65
|
+
entry.name === "node_modules" ||
|
|
66
|
+
entry.name.startsWith(".") ||
|
|
67
|
+
entry.name === "dist" ||
|
|
68
|
+
entry.name === "build" ||
|
|
69
|
+
entry.name === "coverage"
|
|
70
|
+
)
|
|
71
|
+
continue;
|
|
65
72
|
results.push(...findTsFiles(fullPath, filter));
|
|
66
73
|
} else if (
|
|
67
74
|
(entry.name.endsWith(".ts") ||
|