@rangojs/router 0.32.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +3 -2
  2. package/dist/bin/browser-7KIJ5HSV.js +15 -0
  3. package/dist/bin/chunk-3B7CKBRO.js +463 -0
  4. package/dist/bin/chunk-524VACCG.js +71 -0
  5. package/dist/bin/chunk-LD7UBHT4.js +196 -0
  6. package/dist/bin/chunk-TO6CRWLN.js +1429 -0
  7. package/dist/bin/chunk-TTXC2U3G.js +2477 -0
  8. package/dist/bin/client-urls-projection-DQLWPDAT.js +28 -0
  9. package/dist/bin/forward-user-plugins-URBZKJP4.js +62 -0
  10. package/dist/bin/rango.js +42 -10173
  11. package/dist/bin/runtime-EPUDEHO4.js +596 -0
  12. package/dist/bin/runtime-discovery-CZ2QHT4Q.js +352 -0
  13. package/dist/bin/state-ACD2MGKJ.js +16 -0
  14. package/dist/bin/temp-rsc-server-QRR6HD6T.js +3190 -0
  15. package/dist/types/bin/rango.d.ts +1 -0
  16. package/dist/types/browser/action-fence.d.ts +20 -0
  17. package/dist/types/browser/cookie-name.d.ts +51 -10
  18. package/dist/types/browser/invalidate-client-cache.d.ts +8 -6
  19. package/dist/types/browser/navigation-bridge.d.ts +36 -1
  20. package/dist/types/browser/navigation-client.d.ts +19 -0
  21. package/dist/types/browser/navigation-store-handle.d.ts +37 -4
  22. package/dist/types/browser/navigation-store.d.ts +2 -2
  23. package/dist/types/browser/navigation-transaction.d.ts +0 -1
  24. package/dist/types/browser/partial-update.d.ts +13 -0
  25. package/dist/types/browser/prefetch/cache.d.ts +96 -8
  26. package/dist/types/browser/prefetch/fetch.d.ts +26 -2
  27. package/dist/types/browser/prefetch/queue.d.ts +3 -2
  28. package/dist/types/browser/rango-state.d.ts +34 -5
  29. package/dist/types/browser/react/Link.d.ts +15 -6
  30. package/dist/types/browser/types.d.ts +25 -0
  31. package/dist/types/build/generate-route-types.d.ts +1 -1
  32. package/dist/types/build/route-types/include-resolution.d.ts +9 -1
  33. package/dist/types/build/route-types/router-processing.d.ts +3 -1
  34. package/dist/types/cache/cache-key-utils.d.ts +12 -8
  35. package/dist/types/cache/cache-scope.d.ts +4 -2
  36. package/dist/types/cache/cf/cf-cache-types.d.ts +9 -5
  37. package/dist/types/cache/document-cache-policy.d.ts +9 -1
  38. package/dist/types/cache/memory-segment-store.d.ts +3 -2
  39. package/dist/types/cache/segment-codec.d.ts +2 -2
  40. package/dist/types/cache/shell-snapshot.d.ts +1 -1
  41. package/dist/types/cache/types.d.ts +12 -7
  42. package/dist/types/cache/vercel/vercel-cache-store.d.ts +2 -1
  43. package/dist/types/client-urls/types.d.ts +7 -5
  44. package/dist/types/prefetch-markers.d.ts +9 -0
  45. package/dist/types/prefetch-source-scope.d.ts +66 -0
  46. package/dist/types/route-definition/helpers-types.d.ts +8 -5
  47. package/dist/types/router/intercept-resolution.d.ts +25 -11
  48. package/dist/types/router/match-context.d.ts +1 -0
  49. package/dist/types/router/match-handlers.d.ts +1 -1
  50. package/dist/types/router/prefetch-cache-ttl.d.ts +11 -3
  51. package/dist/types/router/router-context.d.ts +1 -1
  52. package/dist/types/router/router-options.d.ts +6 -3
  53. package/dist/types/router/segment-resolution/loader-cache.d.ts +8 -0
  54. package/dist/types/router/segment-resolution/prefetch-deferral.d.ts +23 -7
  55. package/dist/types/router/segment-resolution/revalidation.d.ts +19 -0
  56. package/dist/types/router/source-reads.d.ts +53 -0
  57. package/dist/types/router/types.d.ts +1 -1
  58. package/dist/types/rsc/helpers.d.ts +5 -2
  59. package/dist/types/rsc/prefetch-render-error.d.ts +7 -2
  60. package/dist/types/rsc/request-stages.d.ts +2 -0
  61. package/dist/types/rsc/shell-serve.d.ts +6 -1
  62. package/dist/types/server/context.d.ts +23 -6
  63. package/dist/types/server/cookie-store.d.ts +15 -1
  64. package/dist/types/server/document-location-state.d.ts +5 -2
  65. package/dist/types/server/handle-store.d.ts +2 -0
  66. package/dist/types/server/request-context.d.ts +43 -7
  67. package/dist/types/testing/e2e/server.d.ts +16 -1
  68. package/dist/types/testing/render-route.d.ts +66 -2
  69. package/dist/types/testing/serve-shell-request.d.ts +6 -3
  70. package/dist/types/types/handler-context.d.ts +7 -5
  71. package/dist/types/types/loader-types.d.ts +7 -4
  72. package/dist/types/types/segments.d.ts +15 -4
  73. package/dist/types/urls/path-helper-types.d.ts +7 -5
  74. package/dist/types/vite/temp-rsc-server.d.ts +2 -2
  75. package/dist/vite/index.js +449 -189
  76. package/package.json +2 -2
  77. package/skills/app-structure/SKILL.md +74 -0
  78. package/skills/app-structure/e-commerce.md +10 -9
  79. package/skills/app-structure/large-app.md +15 -2
  80. package/skills/caching/SKILL.md +10 -5
  81. package/skills/client-urls/SKILL.md +18 -11
  82. package/skills/comparison/references/framework-comparison.md +13 -8
  83. package/skills/document-cache/SKILL.md +8 -0
  84. package/skills/hooks/navigation.md +8 -8
  85. package/skills/hooks/state.md +24 -8
  86. package/skills/intercept/SKILL.md +33 -12
  87. package/skills/layout/SKILL.md +11 -0
  88. package/skills/links/SKILL.md +101 -4
  89. package/skills/loader/SKILL.md +49 -22
  90. package/skills/migrate-nextjs/SKILL.md +8 -2
  91. package/skills/migrate-react-router/data-and-actions.md +5 -0
  92. package/skills/ppr/SKILL.md +26 -5
  93. package/skills/prerender/SKILL.md +4 -1
  94. package/skills/route/SKILL.md +6 -6
  95. package/skills/router-setup/SKILL.md +2 -1
  96. package/skills/server-actions/SKILL.md +41 -8
  97. package/skills/testing/client-components.md +29 -13
  98. package/skills/testing/serve-shell-request.md +7 -7
  99. package/skills/testing/server-actions.md +8 -0
  100. package/skills/ux-architecture/SKILL.md +67 -18
  101. package/src/bin/rango.ts +39 -13
  102. package/src/browser/action-fence.ts +72 -0
  103. package/src/browser/cookie-name.ts +82 -14
  104. package/src/browser/invalidate-client-cache.ts +9 -7
  105. package/src/browser/navigation-bridge.ts +164 -32
  106. package/src/browser/navigation-client.ts +95 -20
  107. package/src/browser/navigation-store-handle.ts +67 -5
  108. package/src/browser/navigation-store.ts +86 -17
  109. package/src/browser/navigation-transaction.ts +0 -6
  110. package/src/browser/partial-update.ts +31 -12
  111. package/src/browser/prefetch/cache.ts +188 -11
  112. package/src/browser/prefetch/fetch.ts +211 -51
  113. package/src/browser/prefetch/queue.ts +3 -2
  114. package/src/browser/rango-state.ts +50 -8
  115. package/src/browser/react/Link.tsx +20 -9
  116. package/src/browser/react/use-navigation.ts +1 -1
  117. package/src/browser/rsc-router.tsx +8 -1
  118. package/src/browser/server-action-bridge.ts +3 -3
  119. package/src/browser/types.ts +25 -0
  120. package/src/build/generate-route-types.ts +1 -0
  121. package/src/build/route-types/include-resolution.ts +40 -3
  122. package/src/build/route-types/router-processing.ts +170 -25
  123. package/src/build/route-types/scan-filter.ts +1 -0
  124. package/src/cache/cache-key-utils.ts +12 -8
  125. package/src/cache/cache-runtime.ts +5 -1
  126. package/src/cache/cache-scope.ts +28 -7
  127. package/src/cache/cf/cf-cache-types.ts +9 -5
  128. package/src/cache/document-cache-policy.ts +27 -0
  129. package/src/cache/document-cache.ts +5 -0
  130. package/src/cache/memory-segment-store.ts +3 -2
  131. package/src/cache/segment-codec.ts +2 -2
  132. package/src/cache/shell-snapshot.ts +1 -1
  133. package/src/cache/types.ts +12 -7
  134. package/src/cache/vercel/vercel-cache-store.ts +2 -1
  135. package/src/client-urls/types.ts +7 -5
  136. package/src/prefetch-markers.ts +10 -0
  137. package/src/prefetch-source-scope.ts +137 -0
  138. package/src/route-definition/helpers-types.ts +8 -5
  139. package/src/router/intercept-resolution.ts +94 -50
  140. package/src/router/loader-resolution.ts +8 -6
  141. package/src/router/match-api.ts +33 -29
  142. package/src/router/match-context.ts +5 -0
  143. package/src/router/match-handlers.ts +2 -1
  144. package/src/router/match-middleware/cache-lookup.ts +21 -13
  145. package/src/router/match-result.ts +39 -87
  146. package/src/router/navigation-snapshot.ts +18 -3
  147. package/src/router/prefetch-cache-ttl.ts +15 -3
  148. package/src/router/revalidation.ts +53 -23
  149. package/src/router/router-context.ts +2 -1
  150. package/src/router/router-options.ts +6 -3
  151. package/src/router/segment-resolution/loader-cache.ts +21 -3
  152. package/src/router/segment-resolution/prefetch-deferral.ts +38 -31
  153. package/src/router/segment-resolution/revalidation.ts +40 -12
  154. package/src/router/source-reads.ts +122 -0
  155. package/src/router/types.ts +2 -1
  156. package/src/router.ts +11 -12
  157. package/src/rsc/helpers.ts +8 -3
  158. package/src/rsc/prefetch-render-error.ts +7 -2
  159. package/src/rsc/progressive-enhancement.ts +3 -2
  160. package/src/rsc/request-stages.ts +59 -0
  161. package/src/rsc/rsc-rendering.ts +49 -33
  162. package/src/rsc/server-action.ts +2 -1
  163. package/src/rsc/shell-capture.ts +4 -5
  164. package/src/rsc/shell-serve.ts +12 -1
  165. package/src/segment-system.tsx +1 -1
  166. package/src/server/context.ts +37 -6
  167. package/src/server/cookie-store.ts +22 -1
  168. package/src/server/document-location-state.ts +23 -2
  169. package/src/server/handle-store.ts +2 -0
  170. package/src/server/request-context.ts +113 -28
  171. package/src/testing/dispatch.ts +3 -0
  172. package/src/testing/e2e/fixture.ts +4 -12
  173. package/src/testing/e2e/server.ts +91 -4
  174. package/src/testing/render-route.tsx +269 -4
  175. package/src/testing/serve-shell-request.ts +6 -3
  176. package/src/types/handler-context.ts +7 -5
  177. package/src/types/loader-types.ts +7 -4
  178. package/src/types/segments.ts +15 -4
  179. package/src/urls/path-helper-types.ts +7 -5
  180. package/src/vite/temp-rsc-server.ts +2 -2
@@ -0,0 +1,1429 @@
1
+ import {
2
+ firstCodeMatchIndex,
3
+ isAutoGeneratedRouteName,
4
+ isUniversallySkippedScanDir,
5
+ normalizeBasename
6
+ } from "./chunk-LD7UBHT4.js";
7
+
8
+ // src/build/route-types/param-extraction.ts
9
+ function extractParamsFromPattern(pattern) {
10
+ const params = {};
11
+ const regex = /:([a-zA-Z_$][\w$]*)(?:\([^)]+\))?(\?)?/g;
12
+ let match;
13
+ while ((match = regex.exec(pattern)) !== null) {
14
+ params[match[1]] = match[2] ? "string?" : "string";
15
+ }
16
+ return Object.keys(params).length > 0 ? params : void 0;
17
+ }
18
+ function formatRouteEntry(key, pattern, _params, search) {
19
+ const hasSearch = search && Object.keys(search).length > 0;
20
+ if (!hasSearch) {
21
+ return ` ${key}: ${JSON.stringify(pattern)},`;
22
+ }
23
+ const searchBody = Object.entries(search).map(([k, v]) => `${k}: ${JSON.stringify(v)}`).join(", ");
24
+ return ` ${key}: { path: ${JSON.stringify(pattern)}, search: { ${searchBody} } },`;
25
+ }
26
+
27
+ // src/build/route-types/codegen.ts
28
+ function generatePerModuleTypesSource(routes) {
29
+ const valid = routes.filter(({ name }) => {
30
+ if (!name || /["'\\`\n\r]/.test(name)) {
31
+ console.warn(
32
+ `[rango] Skipping route with invalid name: ${JSON.stringify(name)}`
33
+ );
34
+ return false;
35
+ }
36
+ return true;
37
+ });
38
+ const deduped = /* @__PURE__ */ new Map();
39
+ for (const { name, pattern, params, search } of valid) {
40
+ if (deduped.has(name)) {
41
+ console.warn(
42
+ `[rango] Duplicate route name "${name}" \u2014 keeping first definition`
43
+ );
44
+ continue;
45
+ }
46
+ deduped.set(name, { pattern, params, search });
47
+ }
48
+ const sorted = [...deduped.entries()].sort(
49
+ ([a], [b]) => a.localeCompare(b, "en")
50
+ );
51
+ const body = sorted.map(([name, { pattern, params, search }]) => {
52
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
53
+ return formatRouteEntry(key, pattern, params, search);
54
+ }).join("\n");
55
+ return `// Auto-generated by @rangojs/router - do not edit
56
+ export const routes = {
57
+ ${body}
58
+ } as const;
59
+ export type routes = typeof routes;
60
+ `;
61
+ }
62
+ function generateRouteTypesSource(routeManifest, searchSchemas) {
63
+ const entries = Object.entries(routeManifest).filter(([name]) => !isAutoGeneratedRouteName(name)).sort(([a], [b]) => a.localeCompare(b, "en"));
64
+ const filteredSearchSchemas = searchSchemas ? Object.fromEntries(
65
+ Object.entries(searchSchemas).filter(
66
+ ([name]) => !isAutoGeneratedRouteName(name)
67
+ )
68
+ ) : void 0;
69
+ const objectBody = entries.map(([name, pattern]) => {
70
+ const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `"${name}"`;
71
+ const params = extractParamsFromPattern(pattern);
72
+ const search = filteredSearchSchemas?.[name];
73
+ return formatRouteEntry(key, pattern, params, search);
74
+ }).join("\n");
75
+ return `// Auto-generated by @rangojs/router - do not edit
76
+ export const NamedRoutes = {
77
+ ${objectBody}
78
+ } as const;
79
+
80
+ // Aliased so the augmentation below does not pay a homomorphic mapped-type
81
+ // instantiation per route; \`as const\` already makes the members readonly.
82
+ type NamedRoutesShape = typeof NamedRoutes;
83
+
84
+ declare global {
85
+ namespace Rango {
86
+ interface GeneratedRouteMap extends NamedRoutesShape {}
87
+ }
88
+ }
89
+ `;
90
+ }
91
+
92
+ // src/build/route-types/per-module-writer.ts
93
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2 } from "node:fs";
94
+ import ts4 from "typescript";
95
+
96
+ // src/build/route-types/ast-route-extraction.ts
97
+ import ts2 from "typescript";
98
+
99
+ // src/build/route-types/ast-helpers.ts
100
+ import ts from "typescript";
101
+ function getStringValue(node) {
102
+ if (ts.isStringLiteral(node)) return node.text;
103
+ if (ts.isNoSubstitutionTemplateLiteral(node)) return node.text;
104
+ return null;
105
+ }
106
+ function extractObjectStringProperties(node) {
107
+ const result = {};
108
+ for (const prop of node.properties) {
109
+ if (!ts.isPropertyAssignment(prop)) continue;
110
+ const key = ts.isIdentifier(prop.name) ? prop.name.text : ts.isStringLiteral(prop.name) ? prop.name.text : null;
111
+ if (!key) continue;
112
+ const val = getStringValue(prop.initializer);
113
+ if (val !== null) result[key] = val;
114
+ }
115
+ return result;
116
+ }
117
+
118
+ // src/build/route-types/ast-route-extraction.ts
119
+ function extractRoutesFromSource(code, sourceFileArg) {
120
+ const sourceFile = sourceFileArg ?? ts2.createSourceFile(
121
+ "input.tsx",
122
+ code,
123
+ ts2.ScriptTarget.Latest,
124
+ true,
125
+ ts2.ScriptKind.TSX
126
+ );
127
+ const routes = [];
128
+ function visit(node) {
129
+ if (ts2.isCallExpression(node)) {
130
+ const callee = node.expression;
131
+ const isPath = ts2.isIdentifier(callee) && callee.text === "path" || ts2.isPropertyAccessExpression(callee) && ts2.isIdentifier(callee.expression) && callee.expression.text === "path";
132
+ if (isPath && node.arguments.length >= 1) {
133
+ const route = extractRouteFromCallExpression(node);
134
+ if (route) routes.push(route);
135
+ }
136
+ }
137
+ ts2.forEachChild(node, visit);
138
+ }
139
+ visit(sourceFile);
140
+ return routes;
141
+ }
142
+ function extractRouteFromCallExpression(node) {
143
+ const patternNode = node.arguments[0];
144
+ const pattern = getStringValue(patternNode);
145
+ if (pattern === null) return null;
146
+ let name = null;
147
+ let search;
148
+ for (let i = 1; i < node.arguments.length; i++) {
149
+ const arg = node.arguments[i];
150
+ if (ts2.isObjectLiteralExpression(arg)) {
151
+ for (const prop of arg.properties) {
152
+ if (!ts2.isPropertyAssignment(prop)) continue;
153
+ const propName = ts2.isIdentifier(prop.name) ? prop.name.text : null;
154
+ if (propName === "name") {
155
+ name = getStringValue(prop.initializer);
156
+ } else if (propName === "search" && ts2.isObjectLiteralExpression(prop.initializer)) {
157
+ search = extractObjectStringProperties(prop.initializer);
158
+ }
159
+ }
160
+ }
161
+ }
162
+ if (!name) return null;
163
+ const params = extractParamsFromPattern(pattern);
164
+ return {
165
+ name,
166
+ pattern,
167
+ ...params ? { params } : {},
168
+ ...search && Object.keys(search).length > 0 ? { search } : {}
169
+ };
170
+ }
171
+
172
+ // src/build/route-types/include-resolution.ts
173
+ import { readFileSync, existsSync } from "node:fs";
174
+ import { dirname, resolve } from "node:path";
175
+ import ts3 from "typescript";
176
+ function createScanMemo() {
177
+ return { files: /* @__PURE__ */ new Map(), blockSourceFiles: /* @__PURE__ */ new Map() };
178
+ }
179
+ function parseBlock(memo, block) {
180
+ if (memo) {
181
+ const cached = memo.blockSourceFiles.get(block);
182
+ if (cached) return cached;
183
+ }
184
+ const sf = ts3.createSourceFile(
185
+ "input.tsx",
186
+ block,
187
+ ts3.ScriptTarget.Latest,
188
+ true,
189
+ ts3.ScriptKind.TSX
190
+ );
191
+ if (memo) memo.blockSourceFiles.set(block, sf);
192
+ return sf;
193
+ }
194
+ function readSourceMemoized(memo, realPath) {
195
+ if (memo) {
196
+ const cached = memo.files.get(realPath);
197
+ if (cached !== void 0) return cached;
198
+ }
199
+ const source = readFileSync(realPath, "utf-8");
200
+ if (memo) memo.files.set(realPath, source);
201
+ return source;
202
+ }
203
+ function isUrlsDefinitionCall(node, aliases) {
204
+ const callee = node.expression;
205
+ return ts3.isIdentifier(callee) && (callee.text === "urls" || callee.text === "clientUrls" || aliases?.has(callee.text) === true);
206
+ }
207
+ function collectUrlsImportAliases(sourceFile) {
208
+ const aliases = /* @__PURE__ */ new Set();
209
+ for (const stmt of sourceFile.statements) {
210
+ if (!ts3.isImportDeclaration(stmt) || !ts3.isStringLiteral(stmt.moduleSpecifier) || stmt.moduleSpecifier.text !== "@rangojs/router" && stmt.moduleSpecifier.text !== "@rangojs/router/client") {
211
+ continue;
212
+ }
213
+ const bindings = stmt.importClause?.namedBindings;
214
+ if (!bindings || !ts3.isNamedImports(bindings)) continue;
215
+ for (const el of bindings.elements) {
216
+ const imported = (el.propertyName ?? el.name).text;
217
+ if (imported === "urls" || imported === "clientUrls") {
218
+ aliases.add(el.name.text);
219
+ }
220
+ }
221
+ }
222
+ return aliases;
223
+ }
224
+ function extractNamePrefixFromInclude(node) {
225
+ if (node.arguments.length >= 3) {
226
+ const thirdArg = node.arguments[2];
227
+ if (ts3.isObjectLiteralExpression(thirdArg)) {
228
+ for (const prop of thirdArg.properties) {
229
+ if (!ts3.isPropertyAssignment(prop)) continue;
230
+ const propName = ts3.isIdentifier(prop.name) ? prop.name.text : null;
231
+ if (propName === "name") {
232
+ return getStringValue(prop.initializer);
233
+ }
234
+ }
235
+ }
236
+ }
237
+ return null;
238
+ }
239
+ function thenSelectsNonDefaultMember(expr) {
240
+ const findThenCall = (e) => {
241
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression) && e.expression.name.text === "then") {
242
+ return e;
243
+ }
244
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression)) {
245
+ return findThenCall(e.expression.expression);
246
+ }
247
+ if (ts3.isPropertyAccessExpression(e)) return findThenCall(e.expression);
248
+ if (ts3.isParenthesizedExpression(e)) return findThenCall(e.expression);
249
+ if (ts3.isAwaitExpression(e)) return findThenCall(e.expression);
250
+ return null;
251
+ };
252
+ const thenCall = findThenCall(expr);
253
+ if (!thenCall || thenCall.arguments.length === 0) return false;
254
+ const cb = thenCall.arguments[0];
255
+ if (!ts3.isArrowFunction(cb) && !ts3.isFunctionExpression(cb)) return false;
256
+ let ret;
257
+ if (ts3.isBlock(cb.body)) {
258
+ for (const stmt of cb.body.statements) {
259
+ if (ts3.isReturnStatement(stmt) && stmt.expression) {
260
+ ret = stmt.expression;
261
+ break;
262
+ }
263
+ }
264
+ } else {
265
+ ret = cb.body;
266
+ }
267
+ if (!ret) return false;
268
+ if (ts3.isPropertyAccessExpression(ret)) {
269
+ return ret.name.text !== "default";
270
+ }
271
+ return false;
272
+ }
273
+ function extractDynamicImportSpecifier(node) {
274
+ let body;
275
+ if (ts3.isArrowFunction(node) || ts3.isFunctionExpression(node)) {
276
+ body = node.body;
277
+ }
278
+ if (!body) return null;
279
+ let expr;
280
+ if (ts3.isBlock(body)) {
281
+ for (const stmt of body.statements) {
282
+ if (ts3.isReturnStatement(stmt) && stmt.expression) {
283
+ expr = stmt.expression;
284
+ break;
285
+ }
286
+ }
287
+ } else {
288
+ expr = body;
289
+ }
290
+ if (!expr) return null;
291
+ const findImportCall = (e) => {
292
+ if (ts3.isCallExpression(e) && e.expression.kind === ts3.SyntaxKind.ImportKeyword) {
293
+ return e;
294
+ }
295
+ if (ts3.isCallExpression(e) && ts3.isPropertyAccessExpression(e.expression)) {
296
+ return findImportCall(e.expression.expression);
297
+ }
298
+ if (ts3.isPropertyAccessExpression(e)) return findImportCall(e.expression);
299
+ if (ts3.isParenthesizedExpression(e)) return findImportCall(e.expression);
300
+ if (ts3.isAwaitExpression(e)) return findImportCall(e.expression);
301
+ return null;
302
+ };
303
+ const importCall = findImportCall(expr);
304
+ if (!importCall || importCall.arguments.length === 0) return null;
305
+ if (thenSelectsNonDefaultMember(expr)) return null;
306
+ return getStringValue(importCall.arguments[0]);
307
+ }
308
+ function resolveDefaultExportTarget(code, sourceFile) {
309
+ let result = null;
310
+ function visit(node) {
311
+ if (result) return;
312
+ if (ts3.isExportAssignment(node) && !node.isExportEquals) {
313
+ const expr = node.expression;
314
+ if (ts3.isIdentifier(expr)) {
315
+ result = { variableName: expr.text };
316
+ } else if (ts3.isCallExpression(expr) && isUrlsDefinitionCall(expr)) {
317
+ result = { inlineBlock: expr.getText(sourceFile) };
318
+ }
319
+ return;
320
+ }
321
+ ts3.forEachChild(node, visit);
322
+ }
323
+ visit(sourceFile);
324
+ return result;
325
+ }
326
+ function extractIncludesWithDiagnostics(code, sourceFileArg) {
327
+ const sourceFile = sourceFileArg ?? ts3.createSourceFile(
328
+ "input.tsx",
329
+ code,
330
+ ts3.ScriptTarget.Latest,
331
+ true,
332
+ ts3.ScriptKind.TSX
333
+ );
334
+ const resolved = [];
335
+ const unresolvable = [];
336
+ function visit(node) {
337
+ if (ts3.isCallExpression(node)) {
338
+ const callee = node.expression;
339
+ if (ts3.isIdentifier(callee) && callee.text === "include") {
340
+ if (node.arguments.length < 2) {
341
+ ts3.forEachChild(node, visit);
342
+ return;
343
+ }
344
+ const pathPrefix = getStringValue(node.arguments[0]);
345
+ if (pathPrefix === null) {
346
+ ts3.forEachChild(node, visit);
347
+ return;
348
+ }
349
+ const secondArg = node.arguments[1];
350
+ const namePrefix = extractNamePrefixFromInclude(node);
351
+ const dynamicImportSpec = extractDynamicImportSpecifier(secondArg);
352
+ if (ts3.isIdentifier(secondArg)) {
353
+ resolved.push({
354
+ pathPrefix,
355
+ variableName: secondArg.text,
356
+ namePrefix
357
+ });
358
+ } else if (dynamicImportSpec !== null) {
359
+ resolved.push({
360
+ pathPrefix,
361
+ moduleSpecifier: dynamicImportSpec,
362
+ namePrefix
363
+ });
364
+ } else if (ts3.isCallExpression(secondArg)) {
365
+ const callText = secondArg.expression.getText(sourceFile);
366
+ unresolvable.push({
367
+ pathPrefix,
368
+ namePrefix,
369
+ reason: "factory-call",
370
+ detail: `${callText}()`
371
+ });
372
+ } else {
373
+ unresolvable.push({
374
+ pathPrefix,
375
+ namePrefix,
376
+ reason: "dynamic-expression",
377
+ detail: secondArg.getText(sourceFile)
378
+ });
379
+ }
380
+ }
381
+ }
382
+ ts3.forEachChild(node, visit);
383
+ }
384
+ visit(sourceFile);
385
+ return { resolved, unresolvable };
386
+ }
387
+ function resolveImportedVariable(code, localName) {
388
+ const defaultImportRegex = /import\s+([\w$]+)\s*(?:,\s*\{[^}]*\})?\s+from\s*["']([^"']+)["']/g;
389
+ let defaultMatch;
390
+ while ((defaultMatch = defaultImportRegex.exec(code)) !== null) {
391
+ if (defaultMatch[1] === localName) {
392
+ return { specifier: defaultMatch[2], exportedName: "default" };
393
+ }
394
+ }
395
+ const importRegex = /import\s*(?:[\w$]+\s*,\s*)?\{([^}]+)\}\s*from\s*["']([^"']+)["']/g;
396
+ let match;
397
+ while ((match = importRegex.exec(code)) !== null) {
398
+ const imports = match[1];
399
+ const specifier = match[2];
400
+ const parts = imports.split(",").map((s) => s.trim()).filter(Boolean);
401
+ for (const part of parts) {
402
+ const asMatch = part.match(/^(\w+)\s+as\s+(\w+)$/);
403
+ if (asMatch && asMatch[2] === localName)
404
+ return { specifier, exportedName: asMatch[1] };
405
+ if (part === localName) return { specifier, exportedName: localName };
406
+ }
407
+ }
408
+ return null;
409
+ }
410
+ function resolveImportPath(importSpec, fromFile) {
411
+ if (!importSpec.startsWith(".")) return null;
412
+ const dir = dirname(fromFile);
413
+ let base = importSpec;
414
+ if (base.endsWith(".js")) base = base.slice(0, -3);
415
+ else if (base.endsWith(".mjs")) base = base.slice(0, -4);
416
+ else if (base.endsWith(".jsx")) base = base.slice(0, -4);
417
+ const candidates = [
418
+ resolve(dir, base + ".ts"),
419
+ resolve(dir, base + ".tsx"),
420
+ resolve(dir, base + ".js"),
421
+ resolve(dir, base + ".jsx"),
422
+ resolve(dir, base + "/index.ts"),
423
+ resolve(dir, base + "/index.tsx"),
424
+ resolve(dir, base + "/index.js"),
425
+ resolve(dir, base + "/index.jsx")
426
+ ];
427
+ for (const candidate of candidates) {
428
+ if (existsSync(candidate)) return candidate;
429
+ }
430
+ return null;
431
+ }
432
+ function extractUrlsBlockForVariable(code, varName, sourceFileArg) {
433
+ const sourceFile = sourceFileArg ?? ts3.createSourceFile(
434
+ "input.tsx",
435
+ code,
436
+ ts3.ScriptTarget.Latest,
437
+ true,
438
+ ts3.ScriptKind.TSX
439
+ );
440
+ let result = null;
441
+ function visit(node) {
442
+ if (result) return;
443
+ if (ts3.isVariableDeclaration(node) && ts3.isIdentifier(node.name) && node.name.text === varName && node.initializer && ts3.isCallExpression(node.initializer)) {
444
+ if (isUrlsDefinitionCall(node.initializer)) {
445
+ result = node.initializer.getText(sourceFile);
446
+ return;
447
+ }
448
+ }
449
+ ts3.forEachChild(node, visit);
450
+ }
451
+ visit(sourceFile);
452
+ return result;
453
+ }
454
+ function buildRouteMapFromBlock(block, fullSource, filePath, visited, searchSchemasOut, diagnosticsOut, memo) {
455
+ const routeMap = {};
456
+ const blockSourceFile = parseBlock(memo, block);
457
+ const localRoutes = extractRoutesFromSource(block, blockSourceFile);
458
+ for (const { name, pattern, search } of localRoutes) {
459
+ routeMap[name] = pattern;
460
+ if (search && searchSchemasOut) {
461
+ searchSchemasOut[name] = search;
462
+ }
463
+ }
464
+ const { resolved: includes, unresolvable } = extractIncludesWithDiagnostics(
465
+ block,
466
+ blockSourceFile
467
+ );
468
+ if (diagnosticsOut) {
469
+ for (const entry of unresolvable) {
470
+ diagnosticsOut.push({ ...entry, sourceFile: filePath });
471
+ }
472
+ }
473
+ for (const inc of includes) {
474
+ const { pathPrefix, namePrefix } = inc;
475
+ let childResult;
476
+ if (inc.moduleSpecifier) {
477
+ const targetFile = resolveImportPath(inc.moduleSpecifier, filePath);
478
+ if (!targetFile) {
479
+ diagnosticsOut?.push({
480
+ pathPrefix,
481
+ namePrefix,
482
+ reason: "file-not-found",
483
+ sourceFile: filePath,
484
+ detail: `import("${inc.moduleSpecifier}") resolved to no file`
485
+ });
486
+ continue;
487
+ }
488
+ let targetSource;
489
+ try {
490
+ targetSource = readSourceMemoized(memo, resolve(targetFile));
491
+ } catch (err) {
492
+ diagnosticsOut?.push({
493
+ pathPrefix,
494
+ namePrefix,
495
+ reason: "file-not-found",
496
+ sourceFile: filePath,
497
+ detail: `import("${inc.moduleSpecifier}") resolved to "${targetFile}" but reading it failed: ${err?.message ?? String(err)}`
498
+ });
499
+ continue;
500
+ }
501
+ const def = resolveDefaultExportTarget(
502
+ targetSource,
503
+ parseBlock(memo, targetSource)
504
+ );
505
+ if (!def) {
506
+ diagnosticsOut?.push({
507
+ pathPrefix,
508
+ namePrefix,
509
+ reason: "unresolvable-import",
510
+ sourceFile: filePath,
511
+ detail: `import("${inc.moduleSpecifier}") has no resolvable \`export default urls(...)\``
512
+ });
513
+ continue;
514
+ }
515
+ if (def.inlineBlock) {
516
+ childResult = buildCombinedRouteMapWithSearch(
517
+ targetFile,
518
+ void 0,
519
+ visited,
520
+ diagnosticsOut,
521
+ def.inlineBlock,
522
+ memo
523
+ );
524
+ } else {
525
+ const variableName = def.variableName;
526
+ const resolved = resolveIncludedVariable({
527
+ variableName,
528
+ resolutionFile: targetFile,
529
+ resolutionSource: targetSource,
530
+ reportFile: filePath,
531
+ memo,
532
+ visited,
533
+ diagnosticsOut,
534
+ pathPrefix,
535
+ namePrefix,
536
+ fileNotFoundDetail: (specifier) => `import("${inc.moduleSpecifier}") default re-exports "${variableName}" from "${specifier}", which resolved to no file`,
537
+ notFoundDetail: () => `import("${inc.moduleSpecifier}") default "${variableName}" not found in its imports or same-file scope`
538
+ });
539
+ if (!resolved) continue;
540
+ childResult = resolved;
541
+ }
542
+ } else if (inc.variableName) {
543
+ const variableName = inc.variableName;
544
+ const resolved = resolveIncludedVariable({
545
+ variableName,
546
+ resolutionFile: filePath,
547
+ resolutionSource: fullSource,
548
+ reportFile: filePath,
549
+ memo,
550
+ visited,
551
+ diagnosticsOut,
552
+ pathPrefix,
553
+ namePrefix,
554
+ fileNotFoundDetail: (specifier) => `import "${specifier}" resolved to no file`,
555
+ notFoundDetail: () => `variable "${variableName}" not found in imports or same-file scope`
556
+ });
557
+ if (!resolved) continue;
558
+ childResult = resolved;
559
+ } else {
560
+ continue;
561
+ }
562
+ if (namePrefix === null) {
563
+ continue;
564
+ }
565
+ for (const [name, pattern] of Object.entries(childResult.routes)) {
566
+ const prefixedName = namePrefix ? `${namePrefix}.${name}` : name;
567
+ let prefixedPattern;
568
+ if (pattern === "/") {
569
+ prefixedPattern = pathPrefix || "/";
570
+ } else if (pathPrefix.endsWith("/") && pattern.startsWith("/")) {
571
+ prefixedPattern = pathPrefix + pattern.slice(1);
572
+ } else {
573
+ prefixedPattern = pathPrefix + pattern;
574
+ }
575
+ routeMap[prefixedName] = prefixedPattern;
576
+ if (childResult.searchSchemas[name] && searchSchemasOut) {
577
+ searchSchemasOut[prefixedName] = childResult.searchSchemas[name];
578
+ }
579
+ }
580
+ }
581
+ return routeMap;
582
+ }
583
+ function resolveIncludedVariable(opts) {
584
+ const {
585
+ variableName,
586
+ resolutionFile,
587
+ resolutionSource,
588
+ reportFile,
589
+ memo,
590
+ visited,
591
+ diagnosticsOut,
592
+ pathPrefix,
593
+ namePrefix
594
+ } = opts;
595
+ const imported = resolveImportedVariable(resolutionSource, variableName);
596
+ if (imported) {
597
+ const targetFile = resolveImportPath(imported.specifier, resolutionFile);
598
+ if (!targetFile) {
599
+ diagnosticsOut?.push({
600
+ pathPrefix,
601
+ namePrefix,
602
+ reason: "file-not-found",
603
+ sourceFile: reportFile,
604
+ detail: opts.fileNotFoundDetail(imported.specifier)
605
+ });
606
+ return null;
607
+ }
608
+ return buildCombinedRouteMapWithSearch(
609
+ targetFile,
610
+ imported.exportedName,
611
+ visited,
612
+ diagnosticsOut,
613
+ void 0,
614
+ memo
615
+ );
616
+ }
617
+ const sameFileBlock = extractUrlsBlockForVariable(
618
+ resolutionSource,
619
+ variableName,
620
+ parseBlock(memo, resolutionSource)
621
+ );
622
+ if (!sameFileBlock) {
623
+ diagnosticsOut?.push({
624
+ pathPrefix,
625
+ namePrefix,
626
+ reason: "unresolvable-import",
627
+ sourceFile: reportFile,
628
+ detail: opts.notFoundDetail()
629
+ });
630
+ return null;
631
+ }
632
+ return buildCombinedRouteMapWithSearch(
633
+ resolutionFile,
634
+ variableName,
635
+ visited,
636
+ diagnosticsOut,
637
+ void 0,
638
+ memo
639
+ );
640
+ }
641
+ function buildCombinedRouteMapWithSearch(filePath, variableName, visited, diagnosticsOut, inlineBlock, memo) {
642
+ visited = visited ?? /* @__PURE__ */ new Set();
643
+ memo = memo ?? createScanMemo();
644
+ const realPath = resolve(filePath);
645
+ const key = variableName ? `${realPath}:${variableName}` : realPath;
646
+ if (visited.has(key)) {
647
+ console.warn(`[rango] Circular include detected, skipping: ${key}`);
648
+ return { routes: {}, searchSchemas: {} };
649
+ }
650
+ visited.add(key);
651
+ let source;
652
+ try {
653
+ source = readSourceMemoized(memo, realPath);
654
+ } catch {
655
+ return { routes: {}, searchSchemas: {} };
656
+ }
657
+ let block;
658
+ if (inlineBlock) {
659
+ block = inlineBlock;
660
+ } else if (variableName === "default") {
661
+ const defaultTarget = resolveDefaultExportTarget(
662
+ source,
663
+ parseBlock(memo, source)
664
+ );
665
+ if (!defaultTarget) {
666
+ visited.delete(key);
667
+ return { routes: {}, searchSchemas: {} };
668
+ }
669
+ if (defaultTarget.inlineBlock) {
670
+ block = defaultTarget.inlineBlock;
671
+ } else {
672
+ const targetName = defaultTarget.variableName;
673
+ const imported = resolveImportedVariable(source, targetName);
674
+ if (imported) {
675
+ const targetFile = resolveImportPath(imported.specifier, realPath);
676
+ if (!targetFile) {
677
+ visited.delete(key);
678
+ return { routes: {}, searchSchemas: {} };
679
+ }
680
+ const result = buildCombinedRouteMapWithSearch(
681
+ targetFile,
682
+ imported.exportedName,
683
+ visited,
684
+ diagnosticsOut,
685
+ void 0,
686
+ memo
687
+ );
688
+ visited.delete(key);
689
+ return result;
690
+ }
691
+ const extracted = extractUrlsBlockForVariable(
692
+ source,
693
+ targetName,
694
+ parseBlock(memo, source)
695
+ );
696
+ if (!extracted) {
697
+ visited.delete(key);
698
+ return { routes: {}, searchSchemas: {} };
699
+ }
700
+ block = extracted;
701
+ }
702
+ } else if (variableName) {
703
+ const extracted = extractUrlsBlockForVariable(
704
+ source,
705
+ variableName,
706
+ parseBlock(memo, source)
707
+ );
708
+ if (!extracted) return { routes: {}, searchSchemas: {} };
709
+ block = extracted;
710
+ } else {
711
+ block = source;
712
+ }
713
+ const searchSchemas = {};
714
+ const routes = buildRouteMapFromBlock(
715
+ block,
716
+ source,
717
+ realPath,
718
+ visited,
719
+ searchSchemas,
720
+ diagnosticsOut,
721
+ memo
722
+ );
723
+ visited.delete(key);
724
+ return { routes, searchSchemas };
725
+ }
726
+
727
+ // src/build/route-types/per-module-writer.ts
728
+ function findUrlsVariableNames(code) {
729
+ const sourceFile = ts4.createSourceFile(
730
+ "input.tsx",
731
+ code,
732
+ ts4.ScriptTarget.Latest,
733
+ true,
734
+ ts4.ScriptKind.TSX
735
+ );
736
+ const names = [];
737
+ function visit(node) {
738
+ if (ts4.isVariableDeclaration(node) && ts4.isIdentifier(node.name) && node.initializer && ts4.isCallExpression(node.initializer)) {
739
+ const callee = node.initializer.expression;
740
+ if (ts4.isIdentifier(callee) && (callee.text === "urls" || callee.text === "clientUrls")) {
741
+ names.push(node.name.text);
742
+ }
743
+ }
744
+ ts4.forEachChild(node, visit);
745
+ }
746
+ visit(sourceFile);
747
+ return names;
748
+ }
749
+ function planPerModuleRouteTypesForFile(filePath) {
750
+ const source = readFileSync2(filePath, "utf-8");
751
+ if (!source.includes("urls(") && !source.includes("clientUrls(")) return null;
752
+ const varNames = findUrlsVariableNames(source);
753
+ let routes;
754
+ if (varNames.length > 0) {
755
+ routes = [];
756
+ const memo = createScanMemo();
757
+ for (const varName of varNames) {
758
+ const { routes: routeMap, searchSchemas } = buildCombinedRouteMapWithSearch(
759
+ filePath,
760
+ varName,
761
+ void 0,
762
+ void 0,
763
+ void 0,
764
+ memo
765
+ );
766
+ for (const [name, pattern] of Object.entries(routeMap)) {
767
+ const params = extractParamsFromPattern(pattern);
768
+ routes.push({
769
+ name,
770
+ pattern,
771
+ ...params ? { params } : {},
772
+ ...searchSchemas[name] ? { search: searchSchemas[name] } : {}
773
+ });
774
+ }
775
+ }
776
+ } else {
777
+ routes = extractRoutesFromSource(source);
778
+ }
779
+ const genPath = filePath.replace(/\.(tsx?|jsx?)$/, ".gen.ts");
780
+ if (routes.length === 0) {
781
+ if (varNames.length === 0) return null;
782
+ return {
783
+ genPath,
784
+ isPlaceholder: true,
785
+ content: generatePerModuleTypesSource([])
786
+ };
787
+ }
788
+ return {
789
+ genPath,
790
+ isPlaceholder: false,
791
+ content: generatePerModuleTypesSource(routes)
792
+ };
793
+ }
794
+ function writePerModuleRouteTypesForFile(filePath) {
795
+ try {
796
+ const plan = planPerModuleRouteTypesForFile(filePath);
797
+ if (!plan) return;
798
+ const { genPath, isPlaceholder, content } = plan;
799
+ const exists = existsSync2(genPath);
800
+ if (isPlaceholder) {
801
+ if (!exists) {
802
+ writeFileSync(genPath, content);
803
+ console.log(
804
+ `[rango] Generated route types (placeholder) -> ${genPath}`
805
+ );
806
+ }
807
+ return;
808
+ }
809
+ if (!exists || readFileSync2(genPath, "utf-8") !== content) {
810
+ writeFileSync(genPath, content);
811
+ console.log(`[rango] Generated route types -> ${genPath}`);
812
+ }
813
+ } catch (err) {
814
+ console.warn(
815
+ `[rango] Failed to generate route types for ${filePath}: ${err.message}`
816
+ );
817
+ }
818
+ }
819
+
820
+ // src/build/route-types/router-processing.ts
821
+ import {
822
+ readFileSync as readFileSync3,
823
+ writeFileSync as writeFileSync2,
824
+ existsSync as existsSync3,
825
+ unlinkSync,
826
+ readdirSync
827
+ } from "node:fs";
828
+ import {
829
+ join,
830
+ dirname as dirname2,
831
+ resolve as resolve2,
832
+ sep,
833
+ basename as pathBasename
834
+ } from "node:path";
835
+ import ts5 from "typescript";
836
+ function countPublicRouteEntries(source) {
837
+ const matches = source.matchAll(/^\s+(?:"([^"]+)"|([a-zA-Z_$][^:]*)):\s*["{]/gm) ?? [];
838
+ let count = 0;
839
+ for (const match of matches) {
840
+ const routeName = match[1] || match[2];
841
+ if (routeName && !isAutoGeneratedRouteName(routeName.trim())) {
842
+ count++;
843
+ }
844
+ }
845
+ return count;
846
+ }
847
+ var ROUTER_CALL_PATTERN = /\bcreateRouter\s*[<(]/;
848
+ var ROUTER_CALL_PATTERN_G = /\bcreateRouter\s*[<(]/g;
849
+ function isRoutableSourceFile(name) {
850
+ return (name.endsWith(".ts") || name.endsWith(".tsx") || name.endsWith(".js") || name.endsWith(".jsx")) && !name.includes(".gen.") && !name.includes(".test.") && !name.includes(".rsc-test.") && !name.includes(".spec.");
851
+ }
852
+ function isExcludedScanDir(name) {
853
+ return isUniversallySkippedScanDir(name) || name === "__tests__" || name === "__mocks__";
854
+ }
855
+ function findCallSiteFilesRecursive(dir, pattern, patternG, stopAtMatchDir, filter, results) {
856
+ let entries;
857
+ try {
858
+ entries = readdirSync(dir, { withFileTypes: true });
859
+ } catch (err) {
860
+ console.warn(
861
+ `[rango] Failed to scan directory ${dir}: ${err.message}`
862
+ );
863
+ return;
864
+ }
865
+ const childDirs = [];
866
+ const matchesInDir = [];
867
+ for (const entry of entries) {
868
+ const fullPath = join(dir, entry.name);
869
+ if (entry.isDirectory()) {
870
+ if (!isExcludedScanDir(entry.name)) childDirs.push(fullPath);
871
+ continue;
872
+ }
873
+ if (!isRoutableSourceFile(entry.name)) continue;
874
+ if (filter && !filter(fullPath)) continue;
875
+ try {
876
+ const source = readFileSync3(fullPath, "utf-8");
877
+ if (pattern.test(source) && firstCodeMatchIndex(source, patternG) >= 0) {
878
+ matchesInDir.push(fullPath);
879
+ }
880
+ } catch {
881
+ continue;
882
+ }
883
+ }
884
+ results.push(...matchesInDir);
885
+ if (!stopAtMatchDir || matchesInDir.length === 0) {
886
+ for (const childDir of childDirs) {
887
+ findCallSiteFilesRecursive(
888
+ childDir,
889
+ pattern,
890
+ patternG,
891
+ stopAtMatchDir,
892
+ filter,
893
+ results
894
+ );
895
+ }
896
+ }
897
+ }
898
+ function findNestedRouterConflict(routerFiles) {
899
+ const routerDirs = [
900
+ ...new Set(routerFiles.map((filePath) => dirname2(resolve2(filePath))))
901
+ ].sort((a, b) => a.length - b.length);
902
+ for (let i = 0; i < routerDirs.length; i++) {
903
+ const ancestorDir = routerDirs[i];
904
+ const prefix = ancestorDir.endsWith(sep) ? ancestorDir : `${ancestorDir}${sep}`;
905
+ for (let j = i + 1; j < routerDirs.length; j++) {
906
+ const nestedDir = routerDirs[j];
907
+ if (!nestedDir.startsWith(prefix)) continue;
908
+ const ancestorFile = routerFiles.find(
909
+ (filePath) => dirname2(resolve2(filePath)) === ancestorDir
910
+ );
911
+ const nestedFile = routerFiles.find(
912
+ (filePath) => dirname2(resolve2(filePath)) === nestedDir
913
+ );
914
+ if (ancestorFile && nestedFile) {
915
+ return { ancestor: ancestorFile, nested: nestedFile };
916
+ }
917
+ }
918
+ }
919
+ return null;
920
+ }
921
+ function formatNestedRouterConflictError(conflict, prefix = "[rango]") {
922
+ return `${prefix} Nested router roots are not supported.
923
+ Router root: ${conflict.ancestor}
924
+ Nested router: ${conflict.nested}
925
+ Move the nested router into a sibling directory or configure it as a separate app root.`;
926
+ }
927
+ function isCreateRouterCall(node) {
928
+ if (!ts5.isCallExpression(node)) return false;
929
+ const callee = node.expression;
930
+ return ts5.isIdentifier(callee) && callee.text === "createRouter";
931
+ }
932
+ function extractUrlsArgument(arg, sourceFile, urlsAliases) {
933
+ const node = unwrapTypeOperators(arg);
934
+ if (ts5.isIdentifier(node)) {
935
+ return { kind: "variable", name: node.text };
936
+ }
937
+ if (ts5.isArrowFunction(node) || ts5.isFunctionExpression(node) || ts5.isCallExpression(node) && isUrlsDefinitionCall(node, urlsAliases)) {
938
+ return { kind: "inline", block: node.getText(sourceFile) };
939
+ }
940
+ return null;
941
+ }
942
+ function collectCreateRouterCallChain(endpoint) {
943
+ const calls = [];
944
+ let current = endpoint;
945
+ while (true) {
946
+ calls.push(current);
947
+ if (isCreateRouterCall(current)) return calls.reverse();
948
+ if (!ts5.isPropertyAccessExpression(current.expression)) return null;
949
+ const receiver = current.expression.expression;
950
+ if (!ts5.isCallExpression(receiver)) return null;
951
+ current = receiver;
952
+ }
953
+ }
954
+ function collectExportedLocalNames(sourceFile) {
955
+ const names = /* @__PURE__ */ new Set();
956
+ const addIdentifier = (expr) => {
957
+ const node = expr && unwrapTypeOperators(expr);
958
+ if (node && ts5.isIdentifier(node)) names.add(node.text);
959
+ };
960
+ for (const statement of sourceFile.statements) {
961
+ if (ts5.isExportAssignment(statement)) {
962
+ addIdentifier(statement.expression);
963
+ } else if (ts5.isExportDeclaration(statement) && !statement.moduleSpecifier && !statement.isTypeOnly && statement.exportClause && ts5.isNamedExports(statement.exportClause)) {
964
+ for (const element of statement.exportClause.elements) {
965
+ if (!element.isTypeOnly) {
966
+ names.add((element.propertyName ?? element.name).text);
967
+ }
968
+ }
969
+ } else if (ts5.isVariableStatement(statement) && hasModifier(statement, ts5.SyntaxKind.ExportKeyword)) {
970
+ for (const decl of statement.declarationList.declarations) {
971
+ addIdentifier(decl.initializer);
972
+ }
973
+ }
974
+ }
975
+ return names;
976
+ }
977
+ function isExportedChain(chainEnd, exportedNames) {
978
+ let current = chainEnd;
979
+ while (current.parent && (ts5.isAsExpression(current.parent) || ts5.isSatisfiesExpression(current.parent) || ts5.isParenthesizedExpression(current.parent))) {
980
+ current = current.parent;
981
+ }
982
+ const parent = current.parent;
983
+ if (!parent) return false;
984
+ if (ts5.isExportAssignment(parent))
985
+ return parent.parent === current.getSourceFile();
986
+ if (!ts5.isVariableDeclaration(parent) || parent.initializer !== current || !ts5.isIdentifier(parent.name)) {
987
+ return false;
988
+ }
989
+ const statement = parent.parent.parent;
990
+ if (!ts5.isVariableStatement(statement) || !ts5.isSourceFile(statement.parent)) {
991
+ return false;
992
+ }
993
+ return hasModifier(statement, ts5.SyntaxKind.ExportKeyword) || exportedNames.has(parent.name.text);
994
+ }
995
+ function findRouterUrls(sourceFile) {
996
+ const exported = [];
997
+ const local = [];
998
+ let anyExported = false;
999
+ const seen = /* @__PURE__ */ new Set();
1000
+ const urlsAliases = collectUrlsImportAliases(sourceFile);
1001
+ const exportedNames = collectExportedLocalNames(sourceFile);
1002
+ function visit(node) {
1003
+ if (ts5.isCallExpression(node)) {
1004
+ const chain = collectCreateRouterCallChain(node);
1005
+ if (chain && !seen.has(chain[0])) {
1006
+ const extracted = [];
1007
+ const unreadable = [];
1008
+ const createRouterCall = chain[0];
1009
+ seen.add(createRouterCall);
1010
+ const addMount = (arg, mount) => {
1011
+ const urls = extractUrlsArgument(arg, sourceFile, urlsAliases);
1012
+ if (urls) extracted.push(urls);
1013
+ else
1014
+ unreadable.push(
1015
+ mount(arg.getText(sourceFile).replace(/\s+/g, " "))
1016
+ );
1017
+ };
1018
+ for (const callArg of createRouterCall.arguments) {
1019
+ if (!ts5.isObjectLiteralExpression(callArg)) continue;
1020
+ for (const prop of callArg.properties) {
1021
+ if (!ts5.isPropertyAssignment(prop) || !ts5.isIdentifier(prop.name) || prop.name.text !== "urls") {
1022
+ continue;
1023
+ }
1024
+ addMount(prop.initializer, (text) => `urls: ${text}`);
1025
+ break;
1026
+ }
1027
+ }
1028
+ for (const call of chain.slice(1)) {
1029
+ if (!ts5.isPropertyAccessExpression(call.expression) || call.expression.name.text !== "routes" || call.arguments.length === 0) {
1030
+ continue;
1031
+ }
1032
+ addMount(call.arguments[0], (text) => `.routes(${text})`);
1033
+ }
1034
+ const isExported = isExportedChain(node, exportedNames);
1035
+ if (isExported) anyExported = true;
1036
+ if (extracted.length > 0 || unreadable.length > 0) {
1037
+ (isExported ? exported : local).push({
1038
+ createRouterCall,
1039
+ extractions: extracted,
1040
+ unreadable
1041
+ });
1042
+ }
1043
+ }
1044
+ }
1045
+ ts5.forEachChild(node, visit);
1046
+ }
1047
+ visit(sourceFile);
1048
+ const candidates = anyExported ? exported : local;
1049
+ return candidates.find((c) => c.extractions.length > 0) ?? candidates[0] ?? null;
1050
+ }
1051
+ function parseRouterSource(code) {
1052
+ return ts5.createSourceFile(
1053
+ "router.tsx",
1054
+ code,
1055
+ ts5.ScriptTarget.Latest,
1056
+ true,
1057
+ ts5.ScriptKind.TSX
1058
+ );
1059
+ }
1060
+ function extractAllUrlsFromRouter(code) {
1061
+ return findRouterUrls(parseRouterSource(code))?.extractions ?? [];
1062
+ }
1063
+ function extractUrlsFromRouter(code) {
1064
+ const extractions = extractAllUrlsFromRouter(code);
1065
+ return extractions[extractions.length - 1] ?? null;
1066
+ }
1067
+ function unwrapTypeOperators(expr) {
1068
+ while (ts5.isAsExpression(expr) || ts5.isSatisfiesExpression(expr) || ts5.isParenthesizedExpression(expr)) {
1069
+ expr = expr.expression;
1070
+ }
1071
+ return expr;
1072
+ }
1073
+ function hasModifier(statement, kind) {
1074
+ return ts5.canHaveModifiers(statement) && (ts5.getModifiers(statement)?.some((m) => m.kind === kind) ?? false);
1075
+ }
1076
+ function findTopLevelConst(sourceFile, name, exported) {
1077
+ for (const statement of sourceFile.statements) {
1078
+ if (ts5.isVariableStatement(statement) && statement.declarationList.flags & ts5.NodeFlags.Const && !hasModifier(statement, ts5.SyntaxKind.DeclareKeyword) && (!exported || hasModifier(statement, ts5.SyntaxKind.ExportKeyword))) {
1079
+ for (const decl of statement.declarationList.declarations) {
1080
+ if (ts5.isIdentifier(decl.name) && decl.name.text === name) {
1081
+ return decl.initializer && unwrapTypeOperators(decl.initializer);
1082
+ }
1083
+ }
1084
+ }
1085
+ }
1086
+ return void 0;
1087
+ }
1088
+ function findNamedImport(sourceFile, name) {
1089
+ for (const statement of sourceFile.statements) {
1090
+ if (!ts5.isImportDeclaration(statement) || !ts5.isStringLiteral(statement.moduleSpecifier) || statement.importClause?.isTypeOnly) {
1091
+ continue;
1092
+ }
1093
+ const bindings = statement.importClause?.namedBindings;
1094
+ if (!bindings || !ts5.isNamedImports(bindings)) continue;
1095
+ for (const el of bindings.elements) {
1096
+ if (!el.isTypeOnly && el.name.text === name) {
1097
+ return {
1098
+ specifier: statement.moduleSpecifier.text,
1099
+ exportedName: el.propertyName?.text ?? name
1100
+ };
1101
+ }
1102
+ }
1103
+ }
1104
+ return void 0;
1105
+ }
1106
+ function resolveTopLevelConst(name, sourceFile, routerFilePath) {
1107
+ const local = findTopLevelConst(sourceFile, name, false);
1108
+ if (local) return local;
1109
+ const imported = findNamedImport(sourceFile, name);
1110
+ if (!imported) return void 0;
1111
+ const target = resolveImportPath(imported.specifier, routerFilePath);
1112
+ if (!target) return void 0;
1113
+ const targetSource = readFileSync3(target, "utf-8");
1114
+ return findTopLevelConst(
1115
+ ts5.createSourceFile(target, targetSource, ts5.ScriptTarget.Latest, true),
1116
+ imported.exportedName,
1117
+ true
1118
+ );
1119
+ }
1120
+ function isTopLevelScope(node) {
1121
+ for (let p = node.parent; !ts5.isSourceFile(p); p = p.parent) {
1122
+ if (ts5.isFunctionLike(p) || ts5.isClassLike(p) || ts5.isBlock(p) || ts5.isCaseBlock(p) || ts5.isModuleBlock(p) || ts5.isIterationStatement(p, false)) {
1123
+ return false;
1124
+ }
1125
+ }
1126
+ return true;
1127
+ }
1128
+ function resolveRouterBasename(routerSource, routerFilePath) {
1129
+ const sourceFile = ts5.createSourceFile(
1130
+ routerFilePath,
1131
+ routerSource,
1132
+ ts5.ScriptTarget.Latest,
1133
+ true,
1134
+ ts5.ScriptKind.TSX
1135
+ );
1136
+ const options = findRouterUrls(sourceFile)?.createRouterCall.arguments[0];
1137
+ const prop = options && ts5.isObjectLiteralExpression(options) ? options.properties.find(
1138
+ (p) => (ts5.isPropertyAssignment(p) || ts5.isShorthandPropertyAssignment(p)) && ts5.isIdentifier(p.name) && p.name.text === "basename"
1139
+ ) : void 0;
1140
+ if (!prop) return {};
1141
+ const valueExpr = ts5.isPropertyAssignment(prop) ? prop.initializer : prop.name;
1142
+ const expr = unwrapTypeOperators(valueExpr);
1143
+ let value = getStringValue(expr);
1144
+ if (value === null && isTopLevelScope(prop)) {
1145
+ if (ts5.isIdentifier(expr)) {
1146
+ const init = resolveTopLevelConst(expr.text, sourceFile, routerFilePath);
1147
+ value = init ? getStringValue(init) : null;
1148
+ } else if (ts5.isPropertyAccessExpression(expr) && ts5.isIdentifier(expr.expression)) {
1149
+ const owner = resolveTopLevelConst(
1150
+ expr.expression.text,
1151
+ sourceFile,
1152
+ routerFilePath
1153
+ );
1154
+ value = owner ? resolveConstMember(owner, expr.name.text) : null;
1155
+ }
1156
+ }
1157
+ return value === null ? { unresolved: valueExpr.getText(sourceFile).replace(/\s+/g, " ") } : { value };
1158
+ }
1159
+ function resolveConstMember(owner, name) {
1160
+ if (!ts5.isObjectLiteralExpression(owner) || owner.properties.some(ts5.isSpreadAssignment)) {
1161
+ return null;
1162
+ }
1163
+ const member = owner.properties.find(
1164
+ (p) => ts5.isPropertyAssignment(p) && ts5.isIdentifier(p.name) && p.name.text === name
1165
+ );
1166
+ return member ? getStringValue(unwrapTypeOperators(member.initializer)) : null;
1167
+ }
1168
+ function applyBasenameToRoutes(result, basename) {
1169
+ const prefixed = {};
1170
+ for (const [name, pattern] of Object.entries(result.routes)) {
1171
+ if (pattern === "/") {
1172
+ prefixed[name] = basename;
1173
+ } else if (basename.endsWith("/") && pattern.startsWith("/")) {
1174
+ prefixed[name] = basename + pattern.slice(1);
1175
+ } else {
1176
+ prefixed[name] = basename + pattern;
1177
+ }
1178
+ }
1179
+ return { routes: prefixed, searchSchemas: result.searchSchemas };
1180
+ }
1181
+ function resolveRouterUrlsExtraction(routerFilePath, routerSource, extraction, diagnosticsOut, memo) {
1182
+ if (extraction.kind === "inline") {
1183
+ return buildCombinedRouteMapWithSearch(
1184
+ routerFilePath,
1185
+ void 0,
1186
+ /* @__PURE__ */ new Set(),
1187
+ diagnosticsOut,
1188
+ extraction.block,
1189
+ memo
1190
+ );
1191
+ }
1192
+ const imported = resolveImportedVariable(routerSource, extraction.name);
1193
+ if (imported) {
1194
+ const targetFile = resolveImportPath(imported.specifier, routerFilePath);
1195
+ if (!targetFile) {
1196
+ diagnosticsOut?.push({
1197
+ pathPrefix: "/",
1198
+ namePrefix: null,
1199
+ reason: "file-not-found",
1200
+ sourceFile: routerFilePath,
1201
+ detail: `import "${imported.specifier}" resolved to no file`
1202
+ });
1203
+ return { routes: {}, searchSchemas: {} };
1204
+ }
1205
+ return buildCombinedRouteMapWithSearch(
1206
+ targetFile,
1207
+ imported.exportedName,
1208
+ /* @__PURE__ */ new Set(),
1209
+ diagnosticsOut,
1210
+ void 0,
1211
+ memo
1212
+ );
1213
+ }
1214
+ return buildCombinedRouteMapWithSearch(
1215
+ routerFilePath,
1216
+ extraction.name,
1217
+ /* @__PURE__ */ new Set(),
1218
+ diagnosticsOut,
1219
+ void 0,
1220
+ memo
1221
+ );
1222
+ }
1223
+ function mergeRouteMaps(target, source) {
1224
+ for (const [name, pattern] of Object.entries(source.routes)) {
1225
+ target.routes[name] = pattern;
1226
+ const search = source.searchSchemas[name];
1227
+ if (search) {
1228
+ target.searchSchemas[name] = search;
1229
+ } else {
1230
+ delete target.searchSchemas[name];
1231
+ }
1232
+ }
1233
+ }
1234
+ function genFileTsPath(sourceFile) {
1235
+ const base = pathBasename(sourceFile).replace(/\.(tsx?|jsx?)$/, "");
1236
+ return join(dirname2(sourceFile), `${base}.named-routes.gen.ts`);
1237
+ }
1238
+ function resolveSearchSchemas(publicRouteNames, runtimeSchemas, sourceFile) {
1239
+ if (runtimeSchemas && Object.keys(runtimeSchemas).length > 0) {
1240
+ return runtimeSchemas;
1241
+ }
1242
+ const staticParsed = buildCombinedRouteMapForRouterFile(sourceFile);
1243
+ if (Object.keys(staticParsed.searchSchemas).length === 0) {
1244
+ return runtimeSchemas;
1245
+ }
1246
+ const filtered = {};
1247
+ for (const name of publicRouteNames) {
1248
+ const schema = staticParsed.searchSchemas[name];
1249
+ if (schema) filtered[name] = schema;
1250
+ }
1251
+ return Object.keys(filtered).length > 0 ? filtered : runtimeSchemas;
1252
+ }
1253
+ function buildCombinedRouteMapForRouterFile(routerFilePath) {
1254
+ let routerSource;
1255
+ try {
1256
+ routerSource = readFileSync3(routerFilePath, "utf-8");
1257
+ } catch {
1258
+ return { routes: {}, searchSchemas: {} };
1259
+ }
1260
+ const extractions = extractAllUrlsFromRouter(routerSource);
1261
+ if (extractions.length === 0) {
1262
+ return { routes: {}, searchSchemas: {} };
1263
+ }
1264
+ const basename = normalizeBasename(
1265
+ resolveRouterBasename(routerSource, routerFilePath).value
1266
+ );
1267
+ const result = { routes: {}, searchSchemas: {} };
1268
+ const memo = createScanMemo();
1269
+ for (const extraction of extractions) {
1270
+ mergeRouteMaps(
1271
+ result,
1272
+ resolveRouterUrlsExtraction(
1273
+ routerFilePath,
1274
+ routerSource,
1275
+ extraction,
1276
+ void 0,
1277
+ memo
1278
+ )
1279
+ );
1280
+ }
1281
+ if (basename) {
1282
+ return applyBasenameToRoutes(result, basename);
1283
+ }
1284
+ return result;
1285
+ }
1286
+ function detectUnresolvableIncludes(routerFilePath) {
1287
+ const realPath = resolve2(routerFilePath);
1288
+ let source;
1289
+ try {
1290
+ source = readFileSync3(realPath, "utf-8");
1291
+ } catch {
1292
+ return [];
1293
+ }
1294
+ const routerUrls = findRouterUrls(parseRouterSource(source));
1295
+ if (!routerUrls) return [];
1296
+ const diagnostics = [];
1297
+ for (const mount of routerUrls.unreadable) {
1298
+ diagnostics.push({
1299
+ pathPrefix: "/",
1300
+ namePrefix: null,
1301
+ reason: "unresolvable-routes",
1302
+ sourceFile: realPath,
1303
+ detail: `\`${mount}\` is not a urls() call (as imported from @rangojs/router), a builder function or a variable; its routes would be missing`
1304
+ });
1305
+ }
1306
+ const memo = createScanMemo();
1307
+ for (const extraction of routerUrls.extractions) {
1308
+ resolveRouterUrlsExtraction(
1309
+ realPath,
1310
+ source,
1311
+ extraction,
1312
+ diagnostics,
1313
+ memo
1314
+ );
1315
+ }
1316
+ const { unresolved } = resolveRouterBasename(source, realPath);
1317
+ if (unresolved !== void 0) {
1318
+ diagnostics.push({
1319
+ pathPrefix: "/",
1320
+ namePrefix: null,
1321
+ reason: "unresolvable-basename",
1322
+ sourceFile: realPath,
1323
+ detail: `basename \`${unresolved}\` is not a string literal or top-level const; every pattern would lack the prefix`
1324
+ });
1325
+ }
1326
+ return diagnostics;
1327
+ }
1328
+ function detectUnresolvableIncludesForUrlsFile(filePath) {
1329
+ const realPath = resolve2(filePath);
1330
+ let source;
1331
+ try {
1332
+ source = readFileSync3(realPath, "utf-8");
1333
+ } catch {
1334
+ return [];
1335
+ }
1336
+ const varNames = findUrlsVariableNames(source);
1337
+ if (varNames.length === 0) return [];
1338
+ const diagnostics = [];
1339
+ for (const varName of varNames) {
1340
+ buildCombinedRouteMapWithSearch(realPath, varName, /* @__PURE__ */ new Set(), diagnostics);
1341
+ }
1342
+ return diagnostics;
1343
+ }
1344
+ function findRouterFiles(root, filter) {
1345
+ const result = [];
1346
+ findCallSiteFilesRecursive(
1347
+ root,
1348
+ ROUTER_CALL_PATTERN,
1349
+ ROUTER_CALL_PATTERN_G,
1350
+ true,
1351
+ filter,
1352
+ result
1353
+ );
1354
+ return result;
1355
+ }
1356
+ function writeCombinedRouteTypes(root, knownRouterFiles, opts) {
1357
+ try {
1358
+ const oldCombinedPath = join(root, "src", "named-routes.gen.ts");
1359
+ if (existsSync3(oldCombinedPath)) {
1360
+ unlinkSync(oldCombinedPath);
1361
+ console.log(
1362
+ `[rango] Removed stale combined route types: ${oldCombinedPath}`
1363
+ );
1364
+ }
1365
+ } catch {
1366
+ }
1367
+ const routerFilePaths = knownRouterFiles ?? findRouterFiles(root);
1368
+ if (routerFilePaths.length === 0) return;
1369
+ const nestedRouterConflict = findNestedRouterConflict(routerFilePaths);
1370
+ if (nestedRouterConflict) {
1371
+ throw new Error(formatNestedRouterConflictError(nestedRouterConflict));
1372
+ }
1373
+ for (const routerFilePath of routerFilePaths) {
1374
+ const result = buildCombinedRouteMapForRouterFile(routerFilePath);
1375
+ if (Object.keys(result.routes).length === 0 && Object.keys(result.searchSchemas).length === 0) {
1376
+ let routerSource;
1377
+ try {
1378
+ routerSource = readFileSync3(routerFilePath, "utf-8");
1379
+ } catch {
1380
+ continue;
1381
+ }
1382
+ if (!extractUrlsFromRouter(routerSource)) continue;
1383
+ }
1384
+ const outPath = genFileTsPath(routerFilePath);
1385
+ const existing = existsSync3(outPath) ? readFileSync3(outPath, "utf-8") : null;
1386
+ if (Object.keys(result.routes).length === 0) {
1387
+ if (!existing) {
1388
+ const emptySource = generateRouteTypesSource({});
1389
+ opts?.onWrite?.(outPath, emptySource);
1390
+ writeFileSync2(outPath, emptySource);
1391
+ }
1392
+ continue;
1393
+ }
1394
+ const hasSearchSchemas = Object.keys(result.searchSchemas).length > 0;
1395
+ const source = generateRouteTypesSource(
1396
+ result.routes,
1397
+ hasSearchSchemas ? result.searchSchemas : void 0
1398
+ );
1399
+ if (existing !== source) {
1400
+ if (opts?.preserveIfLarger && existing) {
1401
+ const existingCount = countPublicRouteEntries(existing);
1402
+ const newCount = Object.keys(result.routes).filter(
1403
+ (name) => !isAutoGeneratedRouteName(name)
1404
+ ).length;
1405
+ if (existingCount > newCount) {
1406
+ continue;
1407
+ }
1408
+ }
1409
+ opts?.onWrite?.(outPath, source);
1410
+ writeFileSync2(outPath, source);
1411
+ console.log(
1412
+ `[rango] Generated route types (${Object.keys(result.routes).length} routes) -> ${outPath}`
1413
+ );
1414
+ }
1415
+ }
1416
+ }
1417
+
1418
+ export {
1419
+ generateRouteTypesSource,
1420
+ writePerModuleRouteTypesForFile,
1421
+ isRoutableSourceFile,
1422
+ findNestedRouterConflict,
1423
+ formatNestedRouterConflictError,
1424
+ genFileTsPath,
1425
+ resolveSearchSchemas,
1426
+ detectUnresolvableIncludes,
1427
+ detectUnresolvableIncludesForUrlsFile,
1428
+ writeCombinedRouteTypes
1429
+ };