@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.141

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (460) hide show
  1. package/AGENTS.md +17 -0
  2. package/README.md +432 -7
  3. package/dist/bin/rango.js +2073 -213
  4. package/dist/testing/vitest.js +82 -0
  5. package/dist/vite/index.js +7258 -2714
  6. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  7. package/package.json +140 -67
  8. package/skills/api-client/SKILL.md +211 -0
  9. package/skills/breadcrumbs/SKILL.md +329 -0
  10. package/skills/bundle-analysis/SKILL.md +159 -0
  11. package/skills/cache-guide/SKILL.md +487 -0
  12. package/skills/caching/SKILL.md +357 -25
  13. package/skills/comparison/SKILL.md +50 -0
  14. package/skills/comparison/agents/openai.yaml +4 -0
  15. package/skills/comparison/references/framework-comparison.md +837 -0
  16. package/skills/composability/SKILL.md +246 -0
  17. package/skills/css/SKILL.md +76 -0
  18. package/skills/debug-manifest/SKILL.md +16 -10
  19. package/skills/document-cache/SKILL.md +87 -62
  20. package/skills/fonts/SKILL.md +6 -4
  21. package/skills/handler-use/SKILL.md +364 -0
  22. package/skills/hooks/SKILL.md +557 -79
  23. package/skills/host-router/SKILL.md +320 -0
  24. package/skills/i18n/SKILL.md +276 -0
  25. package/skills/intercept/SKILL.md +207 -15
  26. package/skills/layout/SKILL.md +146 -6
  27. package/skills/links/SKILL.md +304 -25
  28. package/skills/loader/SKILL.md +616 -54
  29. package/skills/middleware/SKILL.md +217 -37
  30. package/skills/migrate-nextjs/SKILL.md +611 -0
  31. package/skills/migrate-react-router/SKILL.md +927 -0
  32. package/skills/mime-routes/SKILL.md +42 -11
  33. package/skills/observability/SKILL.md +194 -0
  34. package/skills/parallel/SKILL.md +284 -3
  35. package/skills/ppr/SKILL.md +293 -0
  36. package/skills/prerender/SKILL.md +437 -52
  37. package/skills/rango/SKILL.md +369 -22
  38. package/skills/react-compiler/SKILL.md +168 -0
  39. package/skills/response-routes/SKILL.md +263 -121
  40. package/skills/route/SKILL.md +350 -21
  41. package/skills/router-setup/SKILL.md +246 -33
  42. package/skills/scripts/SKILL.md +179 -0
  43. package/skills/server-actions/SKILL.md +775 -0
  44. package/skills/shell-manifest/SKILL.md +185 -0
  45. package/skills/streams-and-websockets/SKILL.md +283 -0
  46. package/skills/tailwind/SKILL.md +27 -3
  47. package/skills/testing/SKILL.md +126 -222
  48. package/skills/testing/bindings.md +103 -0
  49. package/skills/testing/cache-prerender.md +127 -0
  50. package/skills/testing/client-components.md +124 -0
  51. package/skills/testing/e2e-parity.md +125 -0
  52. package/skills/testing/flight.md +91 -0
  53. package/skills/testing/handles.md +131 -0
  54. package/skills/testing/loader.md +128 -0
  55. package/skills/testing/middleware.md +99 -0
  56. package/skills/testing/render-handler.md +122 -0
  57. package/skills/testing/response-routes.md +95 -0
  58. package/skills/testing/reverse-and-types.md +85 -0
  59. package/skills/testing/server-actions.md +107 -0
  60. package/skills/testing/server-tree.md +128 -0
  61. package/skills/testing/setup.md +123 -0
  62. package/skills/theme/SKILL.md +9 -8
  63. package/skills/typesafety/SKILL.md +532 -103
  64. package/skills/use-cache/SKILL.md +367 -0
  65. package/skills/vercel/SKILL.md +128 -0
  66. package/skills/view-transitions/SKILL.md +337 -0
  67. package/src/__augment-tests__/augment.ts +81 -0
  68. package/src/__augment-tests__/augmented.check.ts +116 -0
  69. package/src/__internal.ts +77 -44
  70. package/src/bin/rango.ts +312 -15
  71. package/src/browser/action-coordinator.ts +114 -0
  72. package/src/browser/action-fence.ts +47 -0
  73. package/src/browser/app-shell.ts +39 -0
  74. package/src/browser/app-version.ts +14 -0
  75. package/src/browser/connection-warmup.ts +134 -0
  76. package/src/browser/cookie-name.ts +140 -0
  77. package/src/browser/event-controller.ts +293 -202
  78. package/src/browser/history-state.ts +101 -0
  79. package/src/browser/index.ts +3 -3
  80. package/src/browser/intercept-utils.ts +52 -0
  81. package/src/browser/invalidate-client-cache.ts +52 -0
  82. package/src/browser/link-interceptor.ts +24 -4
  83. package/src/browser/logging.ts +11 -0
  84. package/src/browser/merge-segment-loaders.ts +20 -12
  85. package/src/browser/navigation-bridge.ts +385 -576
  86. package/src/browser/navigation-client.ts +245 -75
  87. package/src/browser/navigation-store-handle.ts +38 -0
  88. package/src/browser/navigation-store.ts +184 -118
  89. package/src/browser/navigation-transaction.ts +247 -0
  90. package/src/browser/network-error-handler.ts +88 -0
  91. package/src/browser/partial-update.ts +412 -364
  92. package/src/browser/prefetch/cache.ts +359 -0
  93. package/src/browser/prefetch/fetch.ts +452 -0
  94. package/src/browser/prefetch/observer.ts +65 -0
  95. package/src/browser/prefetch/policy.ts +48 -0
  96. package/src/browser/prefetch/queue.ts +209 -0
  97. package/src/browser/prefetch/resource-ready.ts +77 -0
  98. package/src/browser/rango-state.ts +194 -0
  99. package/src/browser/react/Link.tsx +275 -68
  100. package/src/browser/react/NavigationProvider.tsx +265 -109
  101. package/src/browser/react/ScrollRestoration.tsx +10 -6
  102. package/src/browser/react/context.ts +11 -0
  103. package/src/browser/react/filter-segment-order.ts +70 -0
  104. package/src/browser/react/index.ts +0 -48
  105. package/src/browser/react/location-state-shared.ts +272 -60
  106. package/src/browser/react/location-state.ts +90 -20
  107. package/src/browser/react/mount-context.ts +6 -1
  108. package/src/browser/react/nonce-context.ts +23 -0
  109. package/src/browser/react/shallow-equal.ts +27 -0
  110. package/src/browser/react/use-action.ts +35 -66
  111. package/src/browser/react/use-handle.ts +39 -126
  112. package/src/browser/react/use-href.tsx +8 -1
  113. package/src/browser/react/use-link-status.ts +39 -13
  114. package/src/browser/react/use-navigation.ts +53 -69
  115. package/src/browser/react/use-params.ts +75 -0
  116. package/src/browser/react/use-pathname.ts +47 -0
  117. package/src/browser/react/use-reverse.ts +106 -0
  118. package/src/browser/react/use-router.ts +98 -0
  119. package/src/browser/react/use-search-params.ts +51 -0
  120. package/src/browser/react/use-segments.ts +72 -99
  121. package/src/browser/response-adapter.ts +164 -0
  122. package/src/browser/rsc-router.tsx +300 -72
  123. package/src/browser/scroll-restoration.ts +138 -50
  124. package/src/browser/segment-reconciler.ts +243 -0
  125. package/src/browser/segment-structure-assert.ts +17 -1
  126. package/src/browser/server-action-bridge.ts +668 -613
  127. package/src/browser/types.ts +223 -51
  128. package/src/browser/validate-redirect-origin.ts +56 -0
  129. package/src/build/collect-fallback-refs.ts +107 -0
  130. package/src/build/generate-manifest.ts +252 -161
  131. package/src/build/generate-route-types.ts +41 -1038
  132. package/src/build/index.ts +12 -7
  133. package/src/build/prefix-tree-utils.ts +123 -0
  134. package/src/build/route-trie.ts +225 -42
  135. package/src/build/route-types/ast-helpers.ts +25 -0
  136. package/src/build/route-types/ast-route-extraction.ts +105 -0
  137. package/src/build/route-types/codegen.ts +113 -0
  138. package/src/build/route-types/include-resolution.ts +812 -0
  139. package/src/build/route-types/param-extraction.ts +51 -0
  140. package/src/build/route-types/per-module-writer.ts +144 -0
  141. package/src/build/route-types/router-processing.ts +695 -0
  142. package/src/build/route-types/scan-filter.ts +85 -0
  143. package/src/build/route-types/source-scan.ts +216 -0
  144. package/src/build/runtime-discovery.ts +223 -0
  145. package/src/cache/background-task.ts +34 -0
  146. package/src/cache/cache-error.ts +104 -0
  147. package/src/cache/cache-key-utils.ts +60 -0
  148. package/src/cache/cache-policy.ts +199 -0
  149. package/src/cache/cache-runtime.ts +525 -0
  150. package/src/cache/cache-scope.ts +298 -332
  151. package/src/cache/cache-tag.ts +103 -0
  152. package/src/cache/cf/cf-base64.ts +33 -0
  153. package/src/cache/cf/cf-cache-constants.ts +127 -0
  154. package/src/cache/cf/cf-cache-store.ts +2508 -158
  155. package/src/cache/cf/cf-cache-types.ts +349 -0
  156. package/src/cache/cf/cf-kv-utils.ts +46 -0
  157. package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
  158. package/src/cache/cf/index.ts +17 -17
  159. package/src/cache/document-cache.ts +199 -92
  160. package/src/cache/handle-capture.ts +81 -0
  161. package/src/cache/handle-snapshot.ts +111 -0
  162. package/src/cache/index.ts +24 -35
  163. package/src/cache/memory-segment-store.ts +363 -30
  164. package/src/cache/profile-registry.ts +88 -0
  165. package/src/cache/read-through-swr.ts +178 -0
  166. package/src/cache/segment-codec.ts +248 -0
  167. package/src/cache/shell-snapshot.ts +368 -0
  168. package/src/cache/tag-invalidation.ts +230 -0
  169. package/src/cache/taint.ts +153 -0
  170. package/src/cache/types.ts +222 -211
  171. package/src/cache/vercel/index.ts +11 -0
  172. package/src/cache/vercel/vercel-cache-store.ts +1113 -0
  173. package/src/client.rsc.tsx +43 -21
  174. package/src/client.tsx +131 -347
  175. package/src/cloudflare/index.ts +11 -0
  176. package/src/cloudflare/tracing.ts +109 -0
  177. package/src/component-utils.ts +23 -4
  178. package/src/components/DefaultDocument.tsx +13 -3
  179. package/src/context-var.ts +168 -0
  180. package/src/debug.ts +19 -9
  181. package/src/decode-loader-results.ts +52 -0
  182. package/src/defer.ts +185 -0
  183. package/src/deps/ssr.ts +0 -1
  184. package/src/encode-kv.ts +49 -0
  185. package/src/errors.ts +106 -10
  186. package/src/escape-script.ts +52 -0
  187. package/src/handle.ts +110 -35
  188. package/src/handles/MetaTags.tsx +83 -59
  189. package/src/handles/Scripts.tsx +183 -0
  190. package/src/handles/breadcrumbs.ts +93 -0
  191. package/src/handles/deferred-resolution.ts +127 -0
  192. package/src/handles/is-thenable.ts +18 -0
  193. package/src/handles/meta.ts +44 -53
  194. package/src/handles/script.ts +244 -0
  195. package/src/host/cookie-handler.ts +20 -65
  196. package/src/host/errors.ts +21 -30
  197. package/src/host/index.ts +13 -9
  198. package/src/host/pattern-matcher.ts +50 -79
  199. package/src/host/router.ts +151 -121
  200. package/src/host/testing.ts +45 -32
  201. package/src/host/types.ts +52 -11
  202. package/src/host/utils.ts +2 -2
  203. package/src/href-client.ts +192 -57
  204. package/src/index.rsc.ts +173 -35
  205. package/src/index.ts +241 -73
  206. package/src/internal-debug.ts +9 -2
  207. package/src/loader-store.ts +500 -0
  208. package/src/loader.rsc.ts +31 -99
  209. package/src/loader.ts +30 -12
  210. package/src/missing-id-error.ts +68 -0
  211. package/src/outlet-context.ts +1 -1
  212. package/src/outlet-provider.tsx +41 -0
  213. package/src/prerender/param-hash.ts +16 -14
  214. package/src/prerender/store.ts +121 -21
  215. package/src/prerender.ts +460 -26
  216. package/src/redirect-origin.ts +100 -0
  217. package/src/regex-escape.ts +8 -0
  218. package/src/render-error-thrower.tsx +20 -0
  219. package/src/response-utils.ts +62 -0
  220. package/src/reverse.ts +198 -128
  221. package/src/root-error-boundary.tsx +42 -48
  222. package/src/route-content-wrapper.tsx +22 -77
  223. package/src/route-definition/dsl-helpers.ts +1116 -0
  224. package/src/route-definition/helper-factories.ts +88 -0
  225. package/src/route-definition/helpers-types.ts +505 -0
  226. package/src/route-definition/index.ts +54 -0
  227. package/src/route-definition/redirect.ts +134 -0
  228. package/src/route-definition/resolve-handler-use.ts +160 -0
  229. package/src/route-definition/use-item-types.ts +29 -0
  230. package/src/route-definition.ts +1 -1481
  231. package/src/route-map-builder.ts +82 -144
  232. package/src/route-name.ts +53 -0
  233. package/src/route-types.ts +71 -45
  234. package/src/router/basename.ts +14 -0
  235. package/src/router/content-negotiation.ts +263 -0
  236. package/src/router/debug-manifest.ts +72 -0
  237. package/src/router/error-handling.ts +54 -27
  238. package/src/router/find-match.ts +245 -0
  239. package/src/router/handler-context.ts +377 -125
  240. package/src/router/instrument.ts +350 -0
  241. package/src/router/intercept-resolution.ts +59 -28
  242. package/src/router/lazy-includes.ts +254 -0
  243. package/src/router/loader-resolution.ts +421 -157
  244. package/src/router/logging.ts +106 -6
  245. package/src/router/manifest.ts +131 -57
  246. package/src/router/match-api.ts +167 -246
  247. package/src/router/match-context.ts +4 -24
  248. package/src/router/match-handlers.ts +440 -0
  249. package/src/router/match-middleware/background-revalidation.ts +117 -93
  250. package/src/router/match-middleware/cache-lookup.ts +297 -150
  251. package/src/router/match-middleware/cache-store.ts +123 -51
  252. package/src/router/match-middleware/intercept-resolution.ts +44 -43
  253. package/src/router/match-middleware/segment-resolution.ts +64 -22
  254. package/src/router/match-pipelines.ts +11 -87
  255. package/src/router/match-result.ts +121 -50
  256. package/src/router/metrics.ts +219 -28
  257. package/src/router/middleware-types.ts +93 -0
  258. package/src/router/middleware.ts +505 -441
  259. package/src/router/navigation-snapshot.ts +133 -0
  260. package/src/router/params-util.ts +23 -0
  261. package/src/router/parse-pattern.ts +115 -0
  262. package/src/router/pattern-matching.ts +311 -142
  263. package/src/router/prefetch-cache-ttl.ts +51 -0
  264. package/src/router/prefetch-limits.ts +37 -0
  265. package/src/router/prerender-match.ts +547 -0
  266. package/src/router/preview-match.ts +102 -0
  267. package/src/router/request-classification.ts +278 -0
  268. package/src/router/revalidation.ts +203 -62
  269. package/src/router/route-snapshot.ts +246 -0
  270. package/src/router/router-context.ts +45 -48
  271. package/src/router/router-interfaces.ts +554 -0
  272. package/src/router/router-options.ts +779 -0
  273. package/src/router/router-registry.ts +21 -0
  274. package/src/router/segment-resolution/fresh.ts +772 -0
  275. package/src/router/segment-resolution/helpers.ts +348 -0
  276. package/src/router/segment-resolution/loader-cache.ts +250 -0
  277. package/src/router/segment-resolution/loader-mask.ts +44 -0
  278. package/src/router/segment-resolution/revalidation.ts +1331 -0
  279. package/src/router/segment-resolution/static-store.ts +81 -0
  280. package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
  281. package/src/router/segment-resolution/view-transition-default.ts +56 -0
  282. package/src/router/segment-resolution.ts +25 -1354
  283. package/src/router/segment-wrappers.ts +292 -0
  284. package/src/router/state-cookie-name.ts +33 -0
  285. package/src/router/substitute-pattern-params.ts +75 -0
  286. package/src/router/telemetry-otel.ts +261 -0
  287. package/src/router/telemetry.ts +377 -0
  288. package/src/router/timeout.ts +128 -0
  289. package/src/router/tracing.ts +206 -0
  290. package/src/router/trie-matching.ts +240 -61
  291. package/src/router/types.ts +23 -70
  292. package/src/router/url-params.ts +57 -0
  293. package/src/router.ts +781 -2378
  294. package/src/rsc/full-payload.ts +70 -0
  295. package/src/rsc/handler-context.ts +46 -0
  296. package/src/rsc/handler.ts +905 -1142
  297. package/src/rsc/helpers.ts +275 -19
  298. package/src/rsc/index.ts +2 -25
  299. package/src/rsc/json-route-result.ts +38 -0
  300. package/src/rsc/loader-fetch.ts +305 -0
  301. package/src/rsc/manifest-init.ts +77 -0
  302. package/src/rsc/nonce.ts +14 -0
  303. package/src/rsc/origin-guard.ts +155 -0
  304. package/src/rsc/progressive-enhancement.ts +502 -0
  305. package/src/rsc/redirect-guard.ts +99 -0
  306. package/src/rsc/response-cache-serve.ts +238 -0
  307. package/src/rsc/response-error.ts +104 -0
  308. package/src/rsc/response-route-handler.ts +257 -0
  309. package/src/rsc/rsc-rendering.ts +527 -0
  310. package/src/rsc/runtime-warnings.ts +55 -0
  311. package/src/rsc/server-action.ts +522 -0
  312. package/src/rsc/shell-capture.ts +897 -0
  313. package/src/rsc/shell-serve.ts +124 -0
  314. package/src/rsc/ssr-setup.ts +144 -0
  315. package/src/rsc/transition-gate.ts +89 -0
  316. package/src/rsc/types.ts +95 -12
  317. package/src/runtime-env.ts +18 -0
  318. package/src/search-params.ts +99 -82
  319. package/src/segment-content-promise.ts +67 -0
  320. package/src/segment-loader-promise.ts +149 -0
  321. package/src/segment-system.tsx +349 -134
  322. package/src/serialize.ts +243 -0
  323. package/src/server/context.ts +459 -85
  324. package/src/server/cookie-parse.ts +32 -0
  325. package/src/server/cookie-store.ts +310 -0
  326. package/src/server/fetchable-loader-store.ts +11 -6
  327. package/src/server/handle-store.ts +123 -42
  328. package/src/server/loader-registry.ts +51 -100
  329. package/src/server/request-context.ts +848 -157
  330. package/src/server.ts +15 -8
  331. package/src/ssr/index.tsx +443 -135
  332. package/src/ssr/ssr-root.tsx +228 -0
  333. package/src/static-handler.ts +45 -18
  334. package/src/testing/cache-status.ts +162 -0
  335. package/src/testing/collect-handle.ts +46 -0
  336. package/src/testing/dispatch.ts +701 -0
  337. package/src/testing/dom.entry.ts +22 -0
  338. package/src/testing/e2e/fixture.ts +188 -0
  339. package/src/testing/e2e/index.ts +128 -0
  340. package/src/testing/e2e/matchers.ts +35 -0
  341. package/src/testing/e2e/page-helpers.ts +272 -0
  342. package/src/testing/e2e/parity.ts +387 -0
  343. package/src/testing/e2e/server.ts +195 -0
  344. package/src/testing/flight-matchers.ts +97 -0
  345. package/src/testing/flight-normalize.ts +11 -0
  346. package/src/testing/flight-runtime.d.ts +57 -0
  347. package/src/testing/flight-tree.ts +682 -0
  348. package/src/testing/flight.entry.ts +52 -0
  349. package/src/testing/flight.ts +257 -0
  350. package/src/testing/generated-routes.ts +199 -0
  351. package/src/testing/index.ts +105 -0
  352. package/src/testing/internal/context.ts +371 -0
  353. package/src/testing/internal/flight-client-globals.ts +30 -0
  354. package/src/testing/internal/seed-vars.ts +54 -0
  355. package/src/testing/render-handler.ts +357 -0
  356. package/src/testing/render-route.tsx +584 -0
  357. package/src/testing/run-loader.ts +385 -0
  358. package/src/testing/run-middleware.ts +205 -0
  359. package/src/testing/run-transition-when.ts +164 -0
  360. package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
  361. package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
  362. package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
  363. package/src/testing/vitest-stubs/version.ts +5 -0
  364. package/src/testing/vitest.ts +305 -0
  365. package/src/theme/ThemeProvider.tsx +76 -98
  366. package/src/theme/ThemeScript.tsx +12 -14
  367. package/src/theme/constants.ts +57 -15
  368. package/src/theme/index.ts +3 -20
  369. package/src/theme/theme-context.ts +5 -35
  370. package/src/theme/theme-script.ts +43 -39
  371. package/src/theme/use-theme.ts +0 -3
  372. package/src/types/boundaries.ts +123 -0
  373. package/src/types/cache-types.ts +207 -0
  374. package/src/types/error-types.ts +132 -0
  375. package/src/types/global-namespace.ts +113 -0
  376. package/src/types/handler-context.ts +839 -0
  377. package/src/types/index.ts +81 -0
  378. package/src/types/loader-types.ts +212 -0
  379. package/src/types/request-scope.ts +112 -0
  380. package/src/types/route-config.ts +138 -0
  381. package/src/types/route-entry.ts +114 -0
  382. package/src/types/segments.ts +271 -0
  383. package/src/types.ts +1 -1795
  384. package/src/urls/include-helper.ts +162 -0
  385. package/src/urls/include-provider.ts +71 -0
  386. package/src/urls/index.ts +44 -0
  387. package/src/urls/path-helper-types.ts +413 -0
  388. package/src/urls/path-helper.ts +280 -0
  389. package/src/urls/pattern-types.ts +160 -0
  390. package/src/urls/response-types.ts +109 -0
  391. package/src/urls/type-extraction.ts +316 -0
  392. package/src/urls/urls-function.ts +80 -0
  393. package/src/urls.ts +1 -1341
  394. package/src/use-loader.tsx +406 -141
  395. package/src/vercel/index.ts +11 -0
  396. package/src/vercel/tracing.ts +88 -0
  397. package/src/vite/debug.ts +185 -0
  398. package/src/vite/discovery/bundle-postprocess.ts +182 -0
  399. package/src/vite/discovery/discover-routers.ts +389 -0
  400. package/src/vite/discovery/discovery-errors.ts +255 -0
  401. package/src/vite/discovery/gate-state.ts +171 -0
  402. package/src/vite/discovery/prerender-collection.ts +467 -0
  403. package/src/vite/discovery/route-types-writer.ts +214 -0
  404. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  405. package/src/vite/discovery/state.ts +161 -0
  406. package/src/vite/discovery/virtual-module-codegen.ts +183 -0
  407. package/src/vite/index.ts +23 -2255
  408. package/src/vite/inject-client-debug.ts +36 -0
  409. package/src/vite/plugin-types.ts +303 -0
  410. package/src/vite/plugins/cjs-to-esm.ts +90 -0
  411. package/src/vite/plugins/client-ref-dedup.ts +120 -0
  412. package/src/vite/plugins/client-ref-hashing.ts +118 -0
  413. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  414. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  415. package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
  416. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
  417. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
  418. package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
  419. package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
  420. package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
  421. package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
  422. package/src/vite/plugins/expose-ids/types.ts +45 -0
  423. package/src/vite/plugins/expose-internal-ids.ts +805 -0
  424. package/src/vite/plugins/performance-tracks.ts +89 -0
  425. package/src/vite/plugins/refresh-cmd.ts +127 -0
  426. package/src/vite/plugins/use-cache-transform.ts +313 -0
  427. package/src/vite/plugins/vercel-output.ts +384 -0
  428. package/src/vite/plugins/version-injector.ts +94 -0
  429. package/src/vite/plugins/version-plugin.ts +263 -0
  430. package/src/vite/plugins/virtual-entries.ts +234 -0
  431. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  432. package/src/vite/rango.ts +560 -0
  433. package/src/vite/router-discovery.ts +1638 -0
  434. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
  435. package/src/vite/utils/banner.ts +36 -0
  436. package/src/vite/utils/bundle-analysis.ts +132 -0
  437. package/src/vite/utils/client-chunks.ts +184 -0
  438. package/src/vite/utils/directive-prologue.ts +40 -0
  439. package/src/vite/utils/forward-user-plugins.ts +171 -0
  440. package/src/vite/utils/manifest-utils.ts +15 -0
  441. package/src/vite/utils/package-resolution.ts +89 -0
  442. package/src/vite/utils/prerender-utils.ts +249 -0
  443. package/src/vite/utils/shared-utils.ts +269 -0
  444. package/CLAUDE.md +0 -43
  445. package/dist/vite/index.named-routes.gen.ts +0 -103
  446. package/src/browser/lru-cache.ts +0 -69
  447. package/src/browser/react/use-client-cache.ts +0 -56
  448. package/src/browser/request-controller.ts +0 -164
  449. package/src/browser/shallow.ts +0 -35
  450. package/src/cache/memory-store.ts +0 -253
  451. package/src/handles/index.ts +0 -6
  452. package/src/href-context.ts +0 -33
  453. package/src/network-error-thrower.tsx +0 -21
  454. package/src/router.gen.ts +0 -6
  455. package/src/static-handler.gen.ts +0 -5
  456. package/src/urls.gen.ts +0 -8
  457. package/src/vite/expose-internal-ids.ts +0 -1167
  458. package/src/vite/package-resolution.ts +0 -125
  459. package/src/vite/virtual-entries.ts +0 -114
  460. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -8,30 +8,26 @@ export function normalizePath(p: string): string {
8
8
  return p.split(path.sep).join("/");
9
9
  }
10
10
 
11
- /**
12
- * Generate a short hash for an ID.
13
- * Uses first 8 chars of SHA-256 hash for uniqueness while keeping IDs short.
14
- * Appends export name for easier debugging in production: "abc123#ExportName"
15
- */
16
11
  export function hashId(filePath: string, exportName: string): string {
17
12
  const input = `${filePath}#${exportName}`;
18
13
  const hash = crypto.createHash("sha256").update(input).digest("hex");
19
14
  return `${hash.slice(0, 8)}#${exportName}`;
20
15
  }
21
16
 
22
- /**
23
- * Generate an 8-char hex hash for an inline static handler call site.
24
- * Uses file path and line number (plus optional index for same-line collisions).
25
- */
17
+ export function makeStubId(
18
+ filePath: string,
19
+ exportName: string,
20
+ isBuild: boolean,
21
+ ): string {
22
+ return isBuild ? hashId(filePath, exportName) : `${filePath}#${exportName}`;
23
+ }
24
+
26
25
  export function hashInlineId(
27
26
  filePath: string,
28
- lineNumber: number,
29
- index?: number,
27
+ fnName: string,
28
+ index: number,
30
29
  ): string {
31
- const input =
32
- index !== undefined && index > 0
33
- ? `${filePath}:${lineNumber}:${index}`
34
- : `${filePath}:${lineNumber}`;
30
+ const input = `${filePath}:${fnName}:${index}`;
35
31
  return crypto.createHash("sha256").update(input).digest("hex").slice(0, 8);
36
32
  }
37
33
 
@@ -45,11 +41,6 @@ export interface DetectedImports {
45
41
  any: boolean;
46
42
  }
47
43
 
48
- /**
49
- * Build a map from local binding name to exported names by walking
50
- * ExportNamedDeclaration nodes. Handles `export const X`, `export { X }`,
51
- * and `export { X as Y }`. Skips re-exports (`export { X } from "..."`).
52
- */
53
44
  export function buildExportMap(program: any): Map<string, string[]> {
54
45
  const exportMap = new Map<string, string[]>();
55
46
 
@@ -89,10 +80,6 @@ export function buildExportMap(program: any): Map<string, string[]> {
89
80
  return exportMap;
90
81
  }
91
82
 
92
- /**
93
- * Single-pass detection of all create* imports from @rangojs/router.
94
- * Returns which create functions are imported so we can skip unnecessary transforms.
95
- */
96
83
  export function detectImports(code: string): DetectedImports {
97
84
  // Extract all import declarations from @rangojs/router in one scan
98
85
  const importPattern =
@@ -119,7 +106,6 @@ export function detectImports(code: string): DetectedImports {
119
106
  if (/\bcreateRouter\b/.test(imports)) result.router = true;
120
107
  }
121
108
 
122
- // createRouter has a stricter check: only from "@rangojs/router" (not sub-paths)
123
109
  if (result.router) {
124
110
  result.router =
125
111
  /import\s*\{[^}]*\bcreateRouter\b[^}]*\}\s*from\s*["']@rangojs\/router["']/.test(
@@ -138,11 +124,6 @@ export function detectImports(code: string): DetectedImports {
138
124
  return result;
139
125
  }
140
126
 
141
- /**
142
- * Skip past a string literal, template literal, or comment starting at pos.
143
- * Returns the index after the closing delimiter, or pos if not at a
144
- * string/comment start. Handles escape sequences and nested ${} in templates.
145
- */
146
127
  export function skipStringOrComment(code: string, pos: number): number {
147
128
  const ch = code[pos];
148
129
 
@@ -200,11 +181,6 @@ export function skipStringOrComment(code: string, pos: number): number {
200
181
  return pos;
201
182
  }
202
183
 
203
- /**
204
- * Find the matching closing paren starting after an already-opened paren.
205
- * Skips strings, template literals, and comments so parens inside them
206
- * don't affect depth tracking. Returns the index after the closing paren.
207
- */
208
184
  export function findMatchingParen(code: string, startPos: number): number {
209
185
  let depth = 1;
210
186
  let i = startPos;
@@ -221,10 +197,6 @@ export function findMatchingParen(code: string, startPos: number): number {
221
197
  return i;
222
198
  }
223
199
 
224
- /**
225
- * Count the number of top-level arguments in a function call.
226
- * Skips nested parens, brackets, braces, strings, and comments.
227
- */
228
200
  export function countArgs(
229
201
  code: string,
230
202
  startPos: number,
@@ -238,7 +210,16 @@ export function countArgs(
238
210
  while (i < endPos) {
239
211
  const skipped = skipStringOrComment(code, i);
240
212
  if (skipped > i) {
241
- hasContent = true;
213
+ // A comment is transparent: `createHandle(/* meta */)` has ZERO real
214
+ // arguments, so a comment-only region must NOT set hasContent (else the
215
+ // fallback path would count it as 1 arg and inject `, "id"` over an elided
216
+ // first slot — a syntax error). A string/template literal IS content.
217
+ const ch = code[i];
218
+ const isComment =
219
+ ch === "/" &&
220
+ i + 1 < code.length &&
221
+ (code[i + 1] === "/" || code[i + 1] === "*");
222
+ if (!isComment) hasContent = true;
242
223
  i = skipped;
243
224
  continue;
244
225
  }
@@ -260,10 +241,6 @@ export function countArgs(
260
241
  return hasContent ? argCount + 1 : 0;
261
242
  }
262
243
 
263
- /**
264
- * Find the end of a statement: skip whitespace and optional semicolon after
265
- * a closing paren position.
266
- */
267
244
  export function findStatementEnd(code: string, pos: number): number {
268
245
  let i = pos;
269
246
  while (i < code.length && /\s/.test(code[i])) {
@@ -275,48 +252,81 @@ export function findStatementEnd(code: string, pos: number): number {
275
252
  return i;
276
253
  }
277
254
 
278
- /**
279
- * Simple depth-counter paren matching. Does NOT skip strings/comments.
280
- * Used by loader and handle transforms where the simpler approach suffices.
281
- * Returns the index after the closing paren.
282
- */
283
- export function findClosingParen(code: string, startPos: number): number {
284
- let depth = 1;
285
- let i = startPos;
286
- while (i < code.length && depth > 0) {
287
- if (code[i] === "(") depth++;
288
- if (code[i] === ")") depth--;
289
- i++;
290
- }
291
- return i;
292
- }
255
+ export { escapeRegExp } from "../../regex-escape.js";
293
256
 
294
257
  /**
295
- * Count arguments using simple depth counting (no string/comment awareness).
296
- * Used by the loader transform where the original code used this simpler approach.
258
+ * Given an index pointing just past a `create*` callee identifier, return the
259
+ * index of the call's opening `(`, accounting for an optional generic type
260
+ * argument list `<...>` with arbitrarily nested `<>`. Returns -1 when the next
261
+ * non-trivia token is neither `<` nor `(` (i.e. not a call site).
262
+ *
263
+ * Replaces the single-level `<[^>]*>` regex assumption, which stopped at the
264
+ * first `>` and so never reached `(` for `createRouter<Config<Env>>(`.
265
+ *
266
+ * Whitespace, comments, and strings between the callee and `(` are skipped via
267
+ * skipStringOrComment. The `<...>` scan only balances angle brackets; it does
268
+ * not attempt to parse full TS type syntax (sufficient for locating the paren).
297
269
  */
298
- export function countArgsSimple(
270
+ export function findCallParenAfterGenerics(
299
271
  code: string,
300
- startPos: number,
301
- endPos: number,
272
+ afterCalleeIndex: number,
302
273
  ): number {
303
- let depth = 0;
304
- let argCount = 0;
305
- let hasContent = false;
274
+ let i = afterCalleeIndex;
275
+ // Skip leading whitespace/comments/strings before `<` or `(`.
276
+ while (i < code.length) {
277
+ const skipped = skipStringOrComment(code, i);
278
+ if (skipped > i) {
279
+ i = skipped;
280
+ continue;
281
+ }
282
+ if (/\s/.test(code[i])) {
283
+ i++;
284
+ continue;
285
+ }
286
+ break;
287
+ }
306
288
 
307
- for (let i = startPos; i < endPos; i++) {
308
- const char = code[i];
309
- if (char === "(" || char === "[" || char === "{") {
310
- depth++;
311
- hasContent = true;
312
- } else if (char === ")" || char === "]" || char === "}") {
313
- depth--;
314
- } else if (char === "," && depth === 0) {
315
- argCount++;
316
- } else if (!/\s/.test(char)) {
317
- hasContent = true;
289
+ if (i >= code.length) return -1;
290
+
291
+ if (code[i] === "(") return i;
292
+
293
+ if (code[i] === "<") {
294
+ let depth = 0;
295
+ while (i < code.length) {
296
+ const skipped = skipStringOrComment(code, i);
297
+ if (skipped > i) {
298
+ i = skipped;
299
+ continue;
300
+ }
301
+ const ch = code[i];
302
+ if (ch === "<") {
303
+ depth++;
304
+ } else if (ch === ">") {
305
+ depth--;
306
+ if (depth === 0) {
307
+ i++;
308
+ break;
309
+ }
310
+ }
311
+ i++;
312
+ }
313
+ if (depth !== 0) return -1;
314
+ // After the balanced generic list, skip trivia and expect `(`.
315
+ while (i < code.length) {
316
+ const skipped = skipStringOrComment(code, i);
317
+ if (skipped > i) {
318
+ i = skipped;
319
+ continue;
320
+ }
321
+ if (/\s/.test(code[i])) {
322
+ i++;
323
+ continue;
324
+ }
325
+ break;
318
326
  }
327
+ if (i < code.length && code[i] === "(") return i;
328
+ return -1;
319
329
  }
320
330
 
321
- return hasContent ? argCount + 1 : 0;
331
+ return -1;
322
332
  }
@@ -0,0 +1,363 @@
1
+ import { parseAst } from "vite";
2
+ import {
3
+ findMatchingParen,
4
+ countArgs,
5
+ findStatementEnd,
6
+ buildExportMap,
7
+ escapeRegExp,
8
+ findCallParenAfterGenerics,
9
+ } from "../expose-id-utils.js";
10
+ import { codeMatchIndices } from "../../../build/route-types/source-scan.js";
11
+ import type { CreateExportBinding } from "./types.js";
12
+
13
+ export function isExportOnlyFile(
14
+ code: string,
15
+ bindings: CreateExportBinding[],
16
+ ): boolean {
17
+ if (bindings.length === 0) return false;
18
+
19
+ const knownLocals = new Set<string>();
20
+ const knownExports = new Set<string>();
21
+ for (const b of bindings) {
22
+ knownLocals.add(b.localName);
23
+ for (const e of b.exportNames) knownExports.add(e);
24
+ }
25
+
26
+ if (/export\s*\*/.test(code)) return false;
27
+
28
+ const declExportPattern =
29
+ /export\s+(const|let|var|function|class|default)\s+(\w+)/g;
30
+ let match: RegExpExecArray | null;
31
+ while ((match = declExportPattern.exec(code)) !== null) {
32
+ if (!knownExports.has(match[2])) return false;
33
+ }
34
+
35
+ const specExportPattern = /export\s*\{([^}]+)\}/g;
36
+ while ((match = specExportPattern.exec(code)) !== null) {
37
+ const specifiers = match[1]
38
+ .split(",")
39
+ .map((s) => s.trim())
40
+ .filter(Boolean);
41
+ for (const spec of specifiers) {
42
+ const m = spec.match(
43
+ /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/,
44
+ );
45
+ if (!m) continue;
46
+ const local = m[1];
47
+ if (!knownLocals.has(local)) return false;
48
+ }
49
+ }
50
+
51
+ return true;
52
+ }
53
+
54
+ // Matches the callee identifier only. The optional generic argument list
55
+ // (which may be nested, e.g. createLoader<A<B>>(...)) and the call paren are
56
+ // resolved per match via findCallParenAfterGenerics, so a nested `>` no longer
57
+ // defeats the scan the way `<[^>]*>` did (it stopped at the first `>`).
58
+ function createCallPattern(fnNames: string[]): RegExp {
59
+ return new RegExp(`\\b(?:${fnNames.map(escapeRegExp).join("|")})\\b`, "g");
60
+ }
61
+
62
+ /**
63
+ * Byte offsets of every create*-call site in real code: a callee-identifier
64
+ * match that is actually followed by a call `(` (after an optional nested
65
+ * generic list). Non-call references (type positions, the import specifier
66
+ * itself) yield -1 from findCallParenAfterGenerics and are dropped.
67
+ */
68
+ function createCallStartIndices(code: string, fnNames: string[]): number[] {
69
+ return codeMatchIndices(code, createCallPattern(fnNames)).filter(
70
+ (index) =>
71
+ findCallParenAfterGenerics(
72
+ code,
73
+ index + matchedNameLength(code, index),
74
+ ) !== -1,
75
+ );
76
+ }
77
+
78
+ // Length of the identifier match at `index` (run of identifier chars).
79
+ function matchedNameLength(code: string, index: number): number {
80
+ let i = index;
81
+ while (i < code.length && /[A-Za-z0-9_$]/.test(code[i])) i++;
82
+ return i - index;
83
+ }
84
+
85
+ export function countCreateCallsForNames(
86
+ code: string,
87
+ fnNames: string[],
88
+ ): number {
89
+ return createCallStartIndices(code, fnNames).length;
90
+ }
91
+
92
+ export function offsetToLineColumn(
93
+ code: string,
94
+ index: number,
95
+ ): { line: number; column: number } {
96
+ let line = 1;
97
+ let lineStart = 0;
98
+ const end = Math.min(index, code.length);
99
+ for (let i = 0; i < end; i++) {
100
+ if (code[i] === "\n") {
101
+ line++;
102
+ lineStart = i + 1;
103
+ }
104
+ }
105
+ return { line, column: index - lineStart + 1 };
106
+ }
107
+
108
+ export function findUnsupportedCreateCallSites(
109
+ code: string,
110
+ fnNames: string[],
111
+ supportedBindings: CreateExportBinding[],
112
+ ): Array<{ line: number; column: number }> {
113
+ const supported = new Set(supportedBindings.map((b) => b.callExprStart));
114
+ return createCallStartIndices(code, fnNames)
115
+ .filter((index) => !supported.has(index))
116
+ .map((index) => offsetToLineColumn(code, index));
117
+ }
118
+
119
+ export function getImportedFnNames(
120
+ code: string,
121
+ importedName: string,
122
+ ): string[] {
123
+ const importPattern =
124
+ /import\s*\{([^}]*)\}\s*from\s*["']@rangojs\/router(?:\/[^"']*)?["']/g;
125
+
126
+ const localNames = new Set<string>();
127
+ let match: RegExpExecArray | null;
128
+
129
+ while ((match = importPattern.exec(code)) !== null) {
130
+ const specList = match[1]
131
+ .split(",")
132
+ .map((s) => s.trim())
133
+ .filter(Boolean);
134
+
135
+ for (const spec of specList) {
136
+ const m = spec.match(
137
+ /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/,
138
+ );
139
+ if (!m) continue;
140
+ const imported = m[1];
141
+ const local = m[2] || imported;
142
+ if (imported === importedName) {
143
+ localNames.add(local);
144
+ }
145
+ }
146
+ }
147
+
148
+ const names = Array.from(localNames);
149
+ return names.length > 0 ? names : [importedName];
150
+ }
151
+
152
+ export function getCalledIdentifierFromCall(callExpr: any): string | null {
153
+ const callee = callExpr?.callee;
154
+ if (callee?.type === "Identifier") return callee.name;
155
+ if (
156
+ callee?.type === "TSInstantiationExpression" &&
157
+ callee.expression?.type === "Identifier"
158
+ ) {
159
+ return callee.expression.name;
160
+ }
161
+ return null;
162
+ }
163
+
164
+ function unwrapSignatureWrappedCall(init: any, fnNameSet: Set<string>): any {
165
+ if (init?.type !== "CallExpression") return init;
166
+ const directId = getCalledIdentifierFromCall(init);
167
+ if (directId && fnNameSet.has(directId)) return init;
168
+ const firstArg = init.arguments?.[0];
169
+ if (firstArg?.type === "CallExpression") {
170
+ const innerId = getCalledIdentifierFromCall(firstArg);
171
+ if (innerId && fnNameSet.has(innerId)) return firstArg;
172
+ }
173
+ return init;
174
+ }
175
+
176
+ export function collectCreateExportBindingsFallback(
177
+ code: string,
178
+ fnNames: string[],
179
+ ): CreateExportBinding[] {
180
+ const alternation = fnNames.map(escapeRegExp).join("|");
181
+ const exportConstPattern = new RegExp(
182
+ `export\\s+const\\s+(\\w+)\\s*=\\s*(?:${alternation})\\s*(?:<[^>]*>)?\\s*\\(`,
183
+ "g",
184
+ );
185
+ const localDeclPattern = new RegExp(
186
+ `\\bconst\\s+(\\w+)\\s*=\\s*((?:${alternation})\\s*(?:<[^>]*>)?\\s*\\()`,
187
+ "g",
188
+ );
189
+ const exportSpecPattern = /export\s*\{([^}]+)\}/g;
190
+
191
+ const exportMap = new Map<string, string[]>();
192
+ const pushExport = (local: string, exported: string) => {
193
+ const list = exportMap.get(local);
194
+ if (list) {
195
+ if (!list.includes(exported)) list.push(exported);
196
+ return;
197
+ }
198
+ exportMap.set(local, [exported]);
199
+ };
200
+
201
+ let match: RegExpExecArray | null;
202
+ while ((match = exportConstPattern.exec(code)) !== null) {
203
+ pushExport(match[1], match[1]);
204
+ }
205
+
206
+ while ((match = exportSpecPattern.exec(code)) !== null) {
207
+ const specifiers = match[1]
208
+ .split(",")
209
+ .map((s) => s.trim())
210
+ .filter(Boolean);
211
+ for (const specifier of specifiers) {
212
+ const specMatch = specifier.match(
213
+ /^([A-Za-z_$][\w$]*)(?:\s+as\s+([A-Za-z_$][\w$]*))?$/,
214
+ );
215
+ if (!specMatch) continue;
216
+ const local = specMatch[1];
217
+ const exported = specMatch[2] || local;
218
+ pushExport(local, exported);
219
+ }
220
+ }
221
+
222
+ const bindings: CreateExportBinding[] = [];
223
+ while ((match = localDeclPattern.exec(code)) !== null) {
224
+ const localName = match[1];
225
+ const exportNames = exportMap.get(localName) ?? [];
226
+ if (exportNames.length === 0) continue;
227
+
228
+ const openParenPos = match.index + match[0].length - 1;
229
+ const closeParenPos = findMatchingParen(code, openParenPos + 1) - 1;
230
+ if (closeParenPos <= openParenPos) continue;
231
+
232
+ bindings.push({
233
+ localName,
234
+ exportNames,
235
+ callExprStart: match.index + match[0].length - match[2].length,
236
+ callOpenParenPos: openParenPos,
237
+ callCloseParenPos: closeParenPos,
238
+ argCount: countArgs(code, openParenPos + 1, closeParenPos),
239
+ statementEnd: findStatementEnd(code, closeParenPos + 1),
240
+ });
241
+ }
242
+
243
+ return bindings;
244
+ }
245
+
246
+ export function collectCreateExportBindings(
247
+ code: string,
248
+ fnNames: string[],
249
+ program?: any,
250
+ ): CreateExportBinding[] {
251
+ if (!program) {
252
+ try {
253
+ program = parseAst(code, { lang: "tsx" });
254
+ } catch {
255
+ return collectCreateExportBindingsFallback(code, fnNames);
256
+ }
257
+ }
258
+
259
+ const exportMap = buildExportMap(program);
260
+ const fnNameSet = new Set(fnNames);
261
+ const bindings: CreateExportBinding[] = [];
262
+
263
+ const collectFromVarDecl = (varDecl: any, statementEnd: number) => {
264
+ if (varDecl?.type !== "VariableDeclaration" || varDecl.kind !== "const") {
265
+ return;
266
+ }
267
+
268
+ for (const decl of varDecl.declarations ?? []) {
269
+ // Unwrap a Fast Refresh signature wrapper (`_s(createLoader(...), ...)`)
270
+ // so injection targets the inner create* call. Falls back to decl.init.
271
+ const callExpr = unwrapSignatureWrappedCall(decl?.init, fnNameSet);
272
+ const calledIdentifier = getCalledIdentifierFromCall(callExpr);
273
+ if (
274
+ decl?.id?.type !== "Identifier" ||
275
+ callExpr?.type !== "CallExpression" ||
276
+ !calledIdentifier ||
277
+ !fnNameSet.has(calledIdentifier)
278
+ ) {
279
+ continue;
280
+ }
281
+
282
+ const localName = decl.id.name;
283
+ const exportNames = exportMap.get(localName) ?? [];
284
+ if (exportNames.length === 0) continue;
285
+
286
+ const callEnd = callExpr.end as number;
287
+ const calleeEnd = callExpr.callee.end as number;
288
+
289
+ let openParenPos = -1;
290
+ for (let i = calleeEnd; i < callEnd; i++) {
291
+ if (code[i] === "(") {
292
+ openParenPos = i;
293
+ break;
294
+ }
295
+ }
296
+ if (openParenPos === -1) continue;
297
+
298
+ const closeParenPos = findMatchingParen(code, openParenPos + 1) - 1;
299
+ if (closeParenPos <= openParenPos) continue;
300
+
301
+ bindings.push({
302
+ localName,
303
+ exportNames,
304
+ callExprStart: callExpr.start as number,
305
+ callOpenParenPos: openParenPos,
306
+ callCloseParenPos: closeParenPos,
307
+ argCount: callExpr.arguments?.length ?? 0,
308
+ statementEnd,
309
+ });
310
+ }
311
+ };
312
+
313
+ for (const node of program.body ?? []) {
314
+ if (node?.type === "VariableDeclaration") {
315
+ collectFromVarDecl(node, node.end as number);
316
+ continue;
317
+ }
318
+
319
+ if (
320
+ node?.type === "ExportNamedDeclaration" &&
321
+ node.declaration?.type === "VariableDeclaration"
322
+ ) {
323
+ collectFromVarDecl(node.declaration, node.end as number);
324
+ }
325
+ }
326
+
327
+ if (bindings.length === 0) {
328
+ return collectCreateExportBindingsFallback(code, fnNames);
329
+ }
330
+
331
+ return bindings;
332
+ }
333
+
334
+ export function buildUnsupportedShapeWarning(
335
+ filePath: string,
336
+ fnName: string,
337
+ sites: Array<{ line: number; column: number }> = [],
338
+ ): string {
339
+ const lines = [`[rango] Unsupported ${fnName} shape in "${filePath}".`];
340
+
341
+ if (sites.length === 1) {
342
+ const s = sites[0];
343
+ lines.push(
344
+ `The ${fnName}(...) call at ${filePath}:${s.line}:${s.column} has no stable $$id injected — it is not in a supported shape.`,
345
+ );
346
+ } else if (sites.length > 1) {
347
+ lines.push(
348
+ `These ${fnName}(...) calls have no stable $$id injected — they are not in a supported shape:`,
349
+ );
350
+ for (const s of sites) lines.push(` - ${filePath}:${s.line}:${s.column}`);
351
+ }
352
+
353
+ lines.push(
354
+ `Supported shapes are:`,
355
+ ` - export const X = ${fnName}(...)`,
356
+ ` - const X = ${fnName}(...); export { X }`,
357
+ ` - const X = ${fnName}(...); export { X as Y }`,
358
+ `Potentially unsupported forms include:`,
359
+ ` - export let/var X = ${fnName}(...)`,
360
+ ` - inline ${fnName}(...) calls`,
361
+ );
362
+ return lines.join("\n");
363
+ }