@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.
Files changed (406) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +291 -61
  3. package/dist/bin/rango.js +544 -143
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +3744 -1329
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +67 -13
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +312 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +247 -23
  12. package/skills/caching/SKILL.md +322 -19
  13. package/skills/composability/SKILL.md +27 -2
  14. package/skills/css/SKILL.md +76 -0
  15. package/skills/debug-manifest/SKILL.md +4 -2
  16. package/skills/document-cache/SKILL.md +78 -55
  17. package/skills/handler-use/SKILL.md +364 -0
  18. package/skills/hooks/SKILL.md +282 -60
  19. package/skills/host-router/SKILL.md +278 -0
  20. package/skills/i18n/SKILL.md +276 -0
  21. package/skills/intercept/SKILL.md +50 -6
  22. package/skills/layout/SKILL.md +35 -9
  23. package/skills/links/SKILL.md +249 -17
  24. package/skills/loader/SKILL.md +297 -31
  25. package/skills/middleware/SKILL.md +52 -13
  26. package/skills/migrate-nextjs/SKILL.md +584 -0
  27. package/skills/migrate-react-router/SKILL.md +771 -0
  28. package/skills/mime-routes/SKILL.md +28 -1
  29. package/skills/observability/SKILL.md +172 -0
  30. package/skills/parallel/SKILL.md +203 -7
  31. package/skills/prerender/SKILL.md +155 -111
  32. package/skills/rango/SKILL.md +251 -23
  33. package/skills/react-compiler/SKILL.md +168 -0
  34. package/skills/response-routes/SKILL.md +123 -48
  35. package/skills/route/SKILL.md +104 -9
  36. package/skills/router-setup/SKILL.md +124 -11
  37. package/skills/scripts/SKILL.md +179 -0
  38. package/skills/server-actions/SKILL.md +775 -0
  39. package/skills/streams-and-websockets/SKILL.md +283 -0
  40. package/skills/tailwind/SKILL.md +27 -3
  41. package/skills/testing/SKILL.md +125 -222
  42. package/skills/testing/bindings.md +103 -0
  43. package/skills/testing/cache-prerender.md +127 -0
  44. package/skills/testing/client-components.md +124 -0
  45. package/skills/testing/e2e-parity.md +125 -0
  46. package/skills/testing/flight.md +91 -0
  47. package/skills/testing/handles.md +129 -0
  48. package/skills/testing/loader.md +128 -0
  49. package/skills/testing/middleware.md +99 -0
  50. package/skills/testing/render-handler.md +121 -0
  51. package/skills/testing/response-routes.md +95 -0
  52. package/skills/testing/reverse-and-types.md +84 -0
  53. package/skills/testing/server-actions.md +107 -0
  54. package/skills/testing/server-tree.md +128 -0
  55. package/skills/testing/setup.md +123 -0
  56. package/skills/typesafety/SKILL.md +357 -52
  57. package/skills/use-cache/SKILL.md +46 -14
  58. package/skills/view-transitions/SKILL.md +294 -0
  59. package/src/__augment-tests__/augment.ts +81 -0
  60. package/src/__augment-tests__/augmented.check.ts +116 -0
  61. package/src/__internal.ts +67 -40
  62. package/src/bin/rango.ts +18 -0
  63. package/src/browser/action-coordinator.ts +53 -36
  64. package/src/browser/action-fence.ts +47 -0
  65. package/src/browser/app-shell.ts +39 -0
  66. package/src/browser/app-version.ts +14 -0
  67. package/src/browser/connection-warmup.ts +134 -0
  68. package/src/browser/cookie-name.ts +140 -0
  69. package/src/browser/event-controller.ts +197 -150
  70. package/src/browser/history-state.ts +21 -0
  71. package/src/browser/index.ts +3 -3
  72. package/src/browser/invalidate-client-cache.ts +52 -0
  73. package/src/browser/link-interceptor.ts +4 -0
  74. package/src/browser/navigation-bridge.ts +200 -30
  75. package/src/browser/navigation-client.ts +217 -58
  76. package/src/browser/navigation-store-handle.ts +38 -0
  77. package/src/browser/navigation-store.ts +76 -67
  78. package/src/browser/navigation-transaction.ts +18 -66
  79. package/src/browser/network-error-handler.ts +34 -7
  80. package/src/browser/partial-update.ts +187 -112
  81. package/src/browser/prefetch/cache.ts +312 -30
  82. package/src/browser/prefetch/fetch.ts +344 -47
  83. package/src/browser/prefetch/policy.ts +6 -0
  84. package/src/browser/prefetch/queue.ts +126 -20
  85. package/src/browser/prefetch/resource-ready.ts +77 -0
  86. package/src/browser/rango-state.ts +158 -76
  87. package/src/browser/react/Link.tsx +125 -18
  88. package/src/browser/react/NavigationProvider.tsx +135 -120
  89. package/src/browser/react/ScrollRestoration.tsx +10 -6
  90. package/src/browser/react/context.ts +7 -2
  91. package/src/browser/react/filter-segment-order.ts +66 -7
  92. package/src/browser/react/index.ts +0 -48
  93. package/src/browser/react/location-state-shared.ts +178 -8
  94. package/src/browser/react/location-state.ts +39 -14
  95. package/src/browser/react/use-action.ts +6 -15
  96. package/src/browser/react/use-handle.ts +23 -69
  97. package/src/browser/react/use-href.tsx +8 -1
  98. package/src/browser/react/use-link-status.ts +33 -8
  99. package/src/browser/react/use-navigation.ts +32 -7
  100. package/src/browser/react/use-params.ts +20 -10
  101. package/src/browser/react/use-reverse.ts +106 -0
  102. package/src/browser/react/use-router.ts +46 -11
  103. package/src/browser/react/use-search-params.ts +0 -5
  104. package/src/browser/react/use-segments.ts +11 -21
  105. package/src/browser/response-adapter.ts +80 -5
  106. package/src/browser/rsc-router.tsx +226 -75
  107. package/src/browser/scroll-restoration.ts +54 -42
  108. package/src/browser/segment-reconciler.ts +36 -9
  109. package/src/browser/segment-structure-assert.ts +2 -2
  110. package/src/browser/server-action-bridge.ts +619 -442
  111. package/src/browser/types.ts +115 -11
  112. package/src/browser/validate-redirect-origin.ts +43 -16
  113. package/src/build/collect-fallback-refs.ts +107 -0
  114. package/src/build/generate-manifest.ts +65 -40
  115. package/src/build/generate-route-types.ts +7 -1
  116. package/src/build/index.ts +8 -2
  117. package/src/build/prefix-tree-utils.ts +123 -0
  118. package/src/build/route-trie.ts +182 -37
  119. package/src/build/route-types/ast-route-extraction.ts +15 -8
  120. package/src/build/route-types/codegen.ts +16 -5
  121. package/src/build/route-types/include-resolution.ts +125 -24
  122. package/src/build/route-types/param-extraction.ts +6 -3
  123. package/src/build/route-types/per-module-writer.ts +22 -6
  124. package/src/build/route-types/router-processing.ts +392 -106
  125. package/src/build/route-types/scan-filter.ts +9 -2
  126. package/src/build/route-types/source-scan.ts +216 -0
  127. package/src/build/runtime-discovery.ts +9 -20
  128. package/src/cache/cache-error.ts +104 -0
  129. package/src/cache/cache-key-utils.ts +29 -13
  130. package/src/cache/cache-policy.ts +108 -34
  131. package/src/cache/cache-runtime.ts +214 -48
  132. package/src/cache/cache-scope.ts +236 -89
  133. package/src/cache/cache-tag.ts +103 -0
  134. package/src/cache/cf/cf-base64.ts +33 -0
  135. package/src/cache/cf/cf-cache-constants.ts +127 -0
  136. package/src/cache/cf/cf-cache-store.ts +2224 -171
  137. package/src/cache/cf/cf-cache-types.ts +349 -0
  138. package/src/cache/cf/cf-kv-utils.ts +46 -0
  139. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  140. package/src/cache/cf/index.ts +11 -17
  141. package/src/cache/document-cache.ts +89 -27
  142. package/src/cache/handle-snapshot.ts +70 -0
  143. package/src/cache/index.ts +11 -20
  144. package/src/cache/memory-segment-store.ts +136 -37
  145. package/src/cache/profile-registry.ts +31 -31
  146. package/src/cache/read-through-swr.ts +41 -11
  147. package/src/cache/segment-codec.ts +9 -17
  148. package/src/cache/tag-invalidation.ts +230 -0
  149. package/src/cache/taint.ts +55 -0
  150. package/src/cache/types.ts +37 -100
  151. package/src/client.rsc.tsx +45 -21
  152. package/src/client.tsx +120 -336
  153. package/src/cloudflare/index.ts +11 -0
  154. package/src/cloudflare/tracing.ts +109 -0
  155. package/src/component-utils.ts +19 -0
  156. package/src/components/DefaultDocument.tsx +8 -2
  157. package/src/context-var.ts +84 -2
  158. package/src/debug.ts +2 -2
  159. package/src/decode-loader-results.ts +52 -0
  160. package/src/defer.ts +196 -0
  161. package/src/deps/ssr.ts +0 -1
  162. package/src/encode-kv.ts +49 -0
  163. package/src/errors.ts +30 -4
  164. package/src/escape-script.ts +52 -0
  165. package/src/handle.ts +70 -22
  166. package/src/handles/MetaTags.tsx +56 -19
  167. package/src/handles/Scripts.tsx +183 -0
  168. package/src/handles/breadcrumbs.ts +95 -0
  169. package/src/handles/is-thenable.ts +19 -0
  170. package/src/handles/meta.ts +51 -40
  171. package/src/handles/script.ts +244 -0
  172. package/src/host/cookie-handler.ts +9 -60
  173. package/src/host/errors.ts +0 -24
  174. package/src/host/index.ts +8 -5
  175. package/src/host/pattern-matcher.ts +23 -52
  176. package/src/host/router.ts +107 -99
  177. package/src/host/testing.ts +40 -27
  178. package/src/host/types.ts +37 -4
  179. package/src/host/utils.ts +1 -1
  180. package/src/href-client.ts +137 -22
  181. package/src/index.rsc.ts +79 -29
  182. package/src/index.ts +149 -65
  183. package/src/internal-debug.ts +11 -10
  184. package/src/loader-store.ts +500 -0
  185. package/src/loader.rsc.ts +20 -13
  186. package/src/loader.ts +12 -11
  187. package/src/missing-id-error.ts +68 -0
  188. package/src/outlet-context.ts +1 -1
  189. package/src/outlet-provider.tsx +1 -5
  190. package/src/prerender/param-hash.ts +16 -16
  191. package/src/prerender/store.ts +63 -26
  192. package/src/prerender.ts +198 -82
  193. package/src/redirect-origin.ts +100 -0
  194. package/src/regex-escape.ts +8 -0
  195. package/src/render-error-thrower.tsx +20 -0
  196. package/src/response-utils.ts +62 -0
  197. package/src/reverse.ts +65 -15
  198. package/src/root-error-boundary.tsx +1 -19
  199. package/src/route-content-wrapper.tsx +7 -72
  200. package/src/route-definition/dsl-helpers.ts +469 -276
  201. package/src/route-definition/helper-factories.ts +29 -139
  202. package/src/route-definition/helpers-types.ts +113 -37
  203. package/src/route-definition/index.ts +3 -3
  204. package/src/route-definition/redirect.ts +53 -12
  205. package/src/route-definition/resolve-handler-use.ts +161 -0
  206. package/src/route-definition/use-item-types.ts +32 -0
  207. package/src/route-map-builder.ts +7 -17
  208. package/src/route-types.ts +37 -41
  209. package/src/router/basename.ts +14 -0
  210. package/src/router/content-negotiation.ts +164 -17
  211. package/src/router/error-handling.ts +45 -18
  212. package/src/router/find-match.ts +45 -22
  213. package/src/router/handler-context.ts +110 -39
  214. package/src/router/instrument.ts +350 -0
  215. package/src/router/intercept-resolution.ts +50 -24
  216. package/src/router/lazy-includes.ts +19 -53
  217. package/src/router/loader-resolution.ts +274 -56
  218. package/src/router/logging.ts +5 -8
  219. package/src/router/manifest.ts +49 -45
  220. package/src/router/match-api.ts +121 -205
  221. package/src/router/match-context.ts +0 -22
  222. package/src/router/match-handlers.ts +58 -58
  223. package/src/router/match-middleware/background-revalidation.ts +33 -6
  224. package/src/router/match-middleware/cache-lookup.ts +214 -263
  225. package/src/router/match-middleware/cache-store.ts +73 -33
  226. package/src/router/match-middleware/intercept-resolution.ts +8 -28
  227. package/src/router/match-middleware/segment-resolution.ts +52 -18
  228. package/src/router/match-pipelines.ts +1 -42
  229. package/src/router/match-result.ts +104 -49
  230. package/src/router/metrics.ts +217 -26
  231. package/src/router/middleware-types.ts +24 -110
  232. package/src/router/middleware.ts +384 -197
  233. package/src/router/navigation-snapshot.ts +131 -0
  234. package/src/router/params-util.ts +23 -0
  235. package/src/router/pattern-matching.ts +148 -91
  236. package/src/router/prefetch-cache-ttl.ts +51 -0
  237. package/src/router/prerender-match.ts +199 -56
  238. package/src/router/preview-match.ts +32 -102
  239. package/src/router/request-classification.ts +276 -0
  240. package/src/router/revalidation.ts +144 -74
  241. package/src/router/route-snapshot.ts +244 -0
  242. package/src/router/router-context.ts +8 -28
  243. package/src/router/router-interfaces.ts +129 -36
  244. package/src/router/router-options.ts +185 -23
  245. package/src/router/router-registry.ts +2 -5
  246. package/src/router/segment-resolution/fresh.ts +281 -76
  247. package/src/router/segment-resolution/helpers.ts +116 -31
  248. package/src/router/segment-resolution/loader-cache.ts +63 -37
  249. package/src/router/segment-resolution/revalidation.ts +493 -391
  250. package/src/router/segment-resolution/static-store.ts +19 -5
  251. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  252. package/src/router/segment-resolution/view-transition-default.ts +36 -0
  253. package/src/router/segment-resolution.ts +5 -1
  254. package/src/router/segment-wrappers.ts +8 -5
  255. package/src/router/state-cookie-name.ts +33 -0
  256. package/src/router/substitute-pattern-params.ts +56 -0
  257. package/src/router/telemetry-otel.ts +161 -199
  258. package/src/router/telemetry.ts +96 -19
  259. package/src/router/timeout.ts +0 -20
  260. package/src/router/tracing.ts +206 -0
  261. package/src/router/trie-matching.ts +180 -58
  262. package/src/router/types.ts +10 -63
  263. package/src/router/url-params.ts +44 -0
  264. package/src/router.ts +182 -54
  265. package/src/rsc/handler-context.ts +3 -2
  266. package/src/rsc/handler.ts +702 -460
  267. package/src/rsc/helpers.ts +168 -46
  268. package/src/rsc/index.ts +2 -25
  269. package/src/rsc/json-route-result.ts +38 -0
  270. package/src/rsc/loader-fetch.ts +127 -31
  271. package/src/rsc/manifest-init.ts +33 -42
  272. package/src/rsc/origin-guard.ts +39 -25
  273. package/src/rsc/progressive-enhancement.ts +98 -19
  274. package/src/rsc/redirect-guard.ts +99 -0
  275. package/src/rsc/response-cache-serve.ts +238 -0
  276. package/src/rsc/response-error.ts +79 -12
  277. package/src/rsc/response-route-handler.ts +99 -189
  278. package/src/rsc/rsc-rendering.ts +126 -106
  279. package/src/rsc/runtime-warnings.ts +23 -10
  280. package/src/rsc/server-action.ts +269 -114
  281. package/src/rsc/ssr-setup.ts +144 -0
  282. package/src/rsc/types.ts +34 -6
  283. package/src/runtime-env.ts +18 -0
  284. package/src/search-params.ts +49 -41
  285. package/src/segment-content-promise.ts +67 -0
  286. package/src/segment-loader-promise.ts +149 -0
  287. package/src/segment-system.tsx +281 -129
  288. package/src/serialize.ts +243 -0
  289. package/src/server/context.ts +317 -63
  290. package/src/server/cookie-parse.ts +32 -0
  291. package/src/server/cookie-store.ts +80 -5
  292. package/src/server/handle-store.ts +40 -38
  293. package/src/server/loader-registry.ts +26 -46
  294. package/src/server/request-context.ts +425 -177
  295. package/src/server.ts +6 -0
  296. package/src/ssr/index.tsx +25 -16
  297. package/src/static-handler.ts +27 -18
  298. package/src/testing/cache-status.ts +162 -0
  299. package/src/testing/collect-handle.ts +40 -0
  300. package/src/testing/dispatch.ts +701 -0
  301. package/src/testing/dom.entry.ts +22 -0
  302. package/src/testing/e2e/fixture.ts +188 -0
  303. package/src/testing/e2e/index.ts +128 -0
  304. package/src/testing/e2e/matchers.ts +35 -0
  305. package/src/testing/e2e/page-helpers.ts +272 -0
  306. package/src/testing/e2e/parity.ts +387 -0
  307. package/src/testing/e2e/server.ts +195 -0
  308. package/src/testing/flight-matchers.ts +97 -0
  309. package/src/testing/flight-normalize.ts +11 -0
  310. package/src/testing/flight-runtime.d.ts +57 -0
  311. package/src/testing/flight-tree.ts +682 -0
  312. package/src/testing/flight.entry.ts +52 -0
  313. package/src/testing/flight.ts +257 -0
  314. package/src/testing/generated-routes.ts +183 -0
  315. package/src/testing/index.ts +99 -0
  316. package/src/testing/internal/context.ts +371 -0
  317. package/src/testing/internal/flight-client-globals.ts +30 -0
  318. package/src/testing/internal/seed-vars.ts +54 -0
  319. package/src/testing/render-handler.ts +343 -0
  320. package/src/testing/render-route.tsx +581 -0
  321. package/src/testing/run-loader.ts +385 -0
  322. package/src/testing/run-middleware.ts +205 -0
  323. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  324. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  325. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  326. package/src/testing/vitest-stubs/version.ts +5 -0
  327. package/src/testing/vitest.ts +305 -0
  328. package/src/theme/ThemeProvider.tsx +20 -58
  329. package/src/theme/ThemeScript.tsx +7 -9
  330. package/src/theme/constants.ts +52 -13
  331. package/src/theme/index.ts +3 -19
  332. package/src/theme/theme-context.ts +1 -5
  333. package/src/theme/theme-script.ts +22 -21
  334. package/src/theme/use-theme.ts +0 -3
  335. package/src/types/boundaries.ts +0 -35
  336. package/src/types/cache-types.ts +17 -8
  337. package/src/types/error-types.ts +30 -90
  338. package/src/types/global-namespace.ts +54 -41
  339. package/src/types/handler-context.ts +236 -88
  340. package/src/types/index.ts +1 -10
  341. package/src/types/loader-types.ts +44 -15
  342. package/src/types/request-scope.ts +112 -0
  343. package/src/types/route-config.ts +10 -45
  344. package/src/types/route-entry.ts +19 -7
  345. package/src/types/segments.ts +37 -19
  346. package/src/urls/include-helper.ts +33 -70
  347. package/src/urls/index.ts +1 -11
  348. package/src/urls/path-helper-types.ts +58 -11
  349. package/src/urls/path-helper.ts +57 -111
  350. package/src/urls/pattern-types.ts +48 -19
  351. package/src/urls/response-types.ts +25 -22
  352. package/src/urls/type-extraction.ts +58 -139
  353. package/src/urls/urls-function.ts +1 -18
  354. package/src/use-loader.tsx +346 -89
  355. package/src/vite/debug.ts +185 -0
  356. package/src/vite/discovery/bundle-postprocess.ts +64 -91
  357. package/src/vite/discovery/discover-routers.ts +147 -88
  358. package/src/vite/discovery/discovery-errors.ts +194 -0
  359. package/src/vite/discovery/gate-state.ts +171 -0
  360. package/src/vite/discovery/prerender-collection.ts +247 -145
  361. package/src/vite/discovery/route-types-writer.ts +40 -84
  362. package/src/vite/discovery/self-gen-tracking.ts +27 -1
  363. package/src/vite/discovery/state.ts +61 -13
  364. package/src/vite/discovery/virtual-module-codegen.ts +14 -34
  365. package/src/vite/index.ts +10 -3
  366. package/src/vite/inject-client-debug.ts +36 -0
  367. package/src/vite/plugin-types.ts +155 -65
  368. package/src/vite/plugins/cjs-to-esm.ts +16 -19
  369. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  370. package/src/vite/plugins/client-ref-hashing.ts +28 -15
  371. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  372. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  373. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  374. package/src/vite/plugins/expose-action-id.ts +49 -98
  375. package/src/vite/plugins/expose-id-utils.ts +96 -51
  376. package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
  377. package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
  378. package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
  379. package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
  380. package/src/vite/plugins/expose-internal-ids.ts +553 -317
  381. package/src/vite/plugins/performance-tracks.ts +89 -0
  382. package/src/vite/plugins/refresh-cmd.ts +127 -0
  383. package/src/vite/plugins/use-cache-transform.ts +73 -83
  384. package/src/vite/plugins/version-injector.ts +21 -25
  385. package/src/vite/plugins/version-plugin.ts +46 -37
  386. package/src/vite/plugins/virtual-entries.ts +13 -18
  387. package/src/vite/rango.ts +241 -287
  388. package/src/vite/router-discovery.ts +956 -149
  389. package/src/vite/utils/ast-handler-extract.ts +26 -35
  390. package/src/vite/utils/banner.ts +4 -4
  391. package/src/vite/utils/bundle-analysis.ts +10 -15
  392. package/src/vite/utils/client-chunks.ts +184 -0
  393. package/src/vite/utils/directive-prologue.ts +40 -0
  394. package/src/vite/utils/forward-user-plugins.ts +171 -0
  395. package/src/vite/utils/manifest-utils.ts +4 -59
  396. package/src/vite/utils/package-resolution.ts +20 -52
  397. package/src/vite/utils/prerender-utils.ts +141 -34
  398. package/src/vite/utils/shared-utils.ts +92 -42
  399. package/CLAUDE.md +0 -5
  400. package/src/browser/action-response-classifier.ts +0 -99
  401. package/src/browser/react/use-client-cache.ts +0 -58
  402. package/src/browser/shallow.ts +0 -40
  403. package/src/handles/index.ts +0 -6
  404. package/src/network-error-thrower.tsx +0 -23
  405. package/src/route-definition/route-function.ts +0 -119
  406. package/src/router/middleware-cookies.ts +0 -55
@@ -1,9 +1,21 @@
1
- import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
2
- import { join, dirname, resolve, basename as pathBasename } from "node:path";
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 { findTsFiles } from "./scan-filter.js";
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
- * Extract the url patterns variable from a router file using AST.
35
- * Detects two patterns:
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
- * Returns the local variable name.
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 extractUrlsVariableFromRouter(code: string): string | null {
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: string | null = null;
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
- ts.isIdentifier(node.arguments[0])
237
+ isRoutesOnCreateRouter(node)
67
238
  ) {
68
- // Walk up the chain: createRouter().middleware(...).routes(x) etc.
69
- // The innermost call should be createRouter(...)
70
- let inner: ts.Expression = node.expression.expression;
71
- while (
72
- ts.isCallExpression(inner) &&
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 arg of callExpr.arguments) {
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 === "urls" &&
93
- ts.isIdentifier(prop.initializer)
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 urlsVarName = extractUrlsVariableFromRouter(routerSource);
126
- if (!urlsVarName) {
395
+ const extraction = extractUrlsFromRouter(routerSource);
396
+ if (!extraction) {
127
397
  return { routes: {}, searchSchemas: {} };
128
398
  }
129
399
 
130
- const imported = resolveImportedVariable(routerSource, urlsVarName);
131
- if (imported) {
132
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
133
- if (!targetFile) {
134
- return { routes: {}, searchSchemas: {} };
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
- return buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
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 variable from the router file
163
- const urlsVarName = extractUrlsVariableFromRouter(source);
164
- if (!urlsVarName) return [];
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
- // Resolve where the urls variable comes from
167
- const imported = resolveImportedVariable(source, urlsVarName);
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 = urlsVarName;
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
- for (const filePath of files) {
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?: { preserveIfLarger?: boolean },
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
- `[rsc-router] Removed stale combined route types: ${oldCombinedPath}`,
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
- for (const routerFilePath of routerFilePaths) {
280
- let routerSource: string;
281
- try {
282
- routerSource = readFileSync(routerFilePath, "utf-8");
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
- const imported = resolveImportedVariable(routerSource, urlsVarName);
297
- if (imported) {
298
- // Variable is imported from another module
299
- const targetFile = resolveImportPath(imported.specifier, routerFilePath);
300
- if (!targetFile) continue;
301
- result = buildCombinedRouteMapWithSearch(
302
- targetFile,
303
- imported.exportedName,
304
- );
305
- } else {
306
- // Variable is defined in the same file
307
- result = buildCombinedRouteMapWithSearch(routerFilePath, urlsVarName);
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 routerBasename = pathBasename(routerFilePath).replace(
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
- `[rsc-router] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`,
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
- `[rsc-router] Failed to scan directory ${dir}: ${(err as Error).message}`,
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 (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
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") ||