@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.10 → 3.5.0-ultramodern.101

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 (268) hide show
  1. package/dist/cjs/cli/artifacts.js +157 -0
  2. package/dist/cjs/cli/index.js +8 -79
  3. package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +113 -0
  4. package/dist/cjs/cli/tanstackTypes/shared.js +123 -0
  5. package/dist/cjs/cli/tanstackTypes/source.js +254 -0
  6. package/dist/cjs/cli/tanstackTypes.js +4 -350
  7. package/dist/cjs/runtime/blockingSubscribe.js +57 -0
  8. package/dist/cjs/runtime/{pluginCore.js → clientBasepath.js} +12 -19
  9. package/dist/cjs/runtime/clientHydration.js +118 -0
  10. package/dist/cjs/runtime/dataMutation.js +11 -176
  11. package/dist/cjs/runtime/formData.js +116 -0
  12. package/dist/cjs/runtime/loaderBridge.js +7 -11
  13. package/dist/cjs/runtime/plugin.js +21 -125
  14. package/dist/cjs/runtime/plugin.node.js +45 -125
  15. package/dist/cjs/runtime/pluginShared.js +87 -0
  16. package/dist/cjs/runtime/prefetchLink.js +15 -2
  17. package/dist/cjs/runtime/register.js +3 -16
  18. package/dist/cjs/runtime/routeTree/components.js +102 -0
  19. package/dist/cjs/runtime/routeTree/construction.js +152 -0
  20. package/dist/cjs/runtime/routeTree/index.js +47 -0
  21. package/dist/cjs/runtime/routeTree/loaders.js +158 -0
  22. package/dist/cjs/runtime/routeTree/paths.js +57 -0
  23. package/dist/cjs/runtime/routeTree/staticData.js +63 -0
  24. package/dist/cjs/runtime/routeTree/tanstackRoutes.js +57 -0
  25. package/dist/cjs/runtime/routeTree/types.js +18 -0
  26. package/dist/cjs/runtime/routeTree.js +7 -320
  27. package/dist/cjs/runtime/router.js +15 -2
  28. package/dist/cjs/runtime/rsc/CompositeComponent.js +18 -13
  29. package/dist/cjs/runtime/rsc/ReplayableStream.js +0 -4
  30. package/dist/cjs/runtime/rsc/RscNodeRenderer.js +5 -16
  31. package/dist/cjs/runtime/rsc/client.js +8 -25
  32. package/dist/cjs/runtime/rsc/flightSerialization.js +132 -0
  33. package/dist/cjs/runtime/rsc/payloadErrors.js +99 -0
  34. package/dist/cjs/runtime/rsc/payloadFetch.js +105 -0
  35. package/dist/cjs/runtime/rsc/payloadRouter.js +21 -142
  36. package/dist/cjs/runtime/rsc/payloadRoutes.js +94 -0
  37. package/dist/cjs/runtime/rsc/payloadStorage.js +58 -0
  38. package/dist/cjs/runtime/rsc/server.js +14 -18
  39. package/dist/cjs/runtime/rsc/shared.js +68 -0
  40. package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +1 -0
  41. package/dist/cjs/runtime/rsc/treeGetter.js +72 -0
  42. package/dist/cjs/runtime/shared/isAbsoluteUrl.js +47 -0
  43. package/dist/cjs/runtime/ssrManagedTags.js +103 -0
  44. package/dist/cjs/runtime/ssrPreload.js +89 -0
  45. package/dist/cjs/runtime/ssrTypes.js +18 -0
  46. package/dist/cjs/runtime/state.js +1 -1
  47. package/dist/cjs/runtime/submitAction.js +161 -0
  48. package/dist/cjs/runtime/utils.js +49 -86
  49. package/dist/esm/cli/artifacts.mjs +103 -0
  50. package/dist/esm/cli/index.mjs +8 -82
  51. package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +75 -0
  52. package/dist/esm/cli/tanstackTypes/shared.mjs +61 -0
  53. package/dist/esm/cli/tanstackTypes/source.mjs +206 -0
  54. package/dist/esm/cli/tanstackTypes.mjs +2 -339
  55. package/dist/esm/runtime/blockingSubscribe.mjs +19 -0
  56. package/dist/esm/runtime/clientBasepath.mjs +10 -0
  57. package/dist/esm/runtime/clientHydration.mjs +71 -0
  58. package/dist/esm/runtime/dataMutation.mjs +2 -167
  59. package/dist/esm/runtime/formData.mjs +63 -0
  60. package/dist/esm/runtime/loaderBridge.mjs +5 -9
  61. package/dist/esm/runtime/plugin.mjs +14 -118
  62. package/dist/esm/runtime/plugin.node.mjs +41 -121
  63. package/dist/esm/runtime/pluginShared.mjs +40 -0
  64. package/dist/esm/runtime/prefetchLink.mjs +18 -5
  65. package/dist/esm/runtime/register.mjs +1 -14
  66. package/dist/esm/runtime/routeTree/components.mjs +55 -0
  67. package/dist/esm/runtime/routeTree/construction.mjs +111 -0
  68. package/dist/esm/runtime/routeTree/index.mjs +2 -0
  69. package/dist/esm/runtime/routeTree/loaders.mjs +117 -0
  70. package/dist/esm/runtime/routeTree/paths.mjs +16 -0
  71. package/dist/esm/runtime/routeTree/staticData.mjs +22 -0
  72. package/dist/esm/runtime/routeTree/tanstackRoutes.mjs +13 -0
  73. package/dist/esm/runtime/routeTree/types.mjs +0 -0
  74. package/dist/esm/runtime/routeTree.mjs +1 -318
  75. package/dist/esm/runtime/router.mjs +16 -2
  76. package/dist/esm/runtime/rsc/CompositeComponent.mjs +18 -13
  77. package/dist/esm/runtime/rsc/ReplayableStream.mjs +1 -1
  78. package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +4 -15
  79. package/dist/esm/runtime/rsc/client.mjs +6 -26
  80. package/dist/esm/runtime/rsc/flightSerialization.mjs +88 -0
  81. package/dist/esm/runtime/rsc/payloadErrors.mjs +49 -0
  82. package/dist/esm/runtime/rsc/payloadFetch.mjs +55 -0
  83. package/dist/esm/runtime/rsc/payloadRouter.mjs +12 -133
  84. package/dist/esm/runtime/rsc/payloadRoutes.mjs +50 -0
  85. package/dist/esm/runtime/rsc/payloadStorage.mjs +14 -0
  86. package/dist/esm/runtime/rsc/server.mjs +17 -21
  87. package/dist/esm/runtime/rsc/shared.mjs +21 -0
  88. package/dist/esm/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  89. package/dist/esm/runtime/rsc/treeGetter.mjs +34 -0
  90. package/dist/esm/runtime/shared/isAbsoluteUrl.mjs +9 -0
  91. package/dist/esm/runtime/ssrManagedTags.mjs +53 -0
  92. package/dist/esm/runtime/ssrPreload.mjs +51 -0
  93. package/dist/esm/runtime/ssrTypes.mjs +0 -0
  94. package/dist/esm/runtime/state.mjs +1 -1
  95. package/dist/esm/runtime/submitAction.mjs +120 -0
  96. package/dist/esm/runtime/utils.mjs +47 -75
  97. package/dist/esm-node/cli/artifacts.mjs +104 -0
  98. package/dist/esm-node/cli/index.mjs +8 -82
  99. package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +76 -0
  100. package/dist/esm-node/cli/tanstackTypes/shared.mjs +62 -0
  101. package/dist/esm-node/cli/tanstackTypes/source.mjs +207 -0
  102. package/dist/esm-node/cli/tanstackTypes.mjs +2 -339
  103. package/dist/esm-node/runtime/blockingSubscribe.mjs +20 -0
  104. package/dist/esm-node/runtime/clientBasepath.mjs +11 -0
  105. package/dist/esm-node/runtime/clientHydration.mjs +72 -0
  106. package/dist/esm-node/runtime/dataMutation.mjs +2 -167
  107. package/dist/esm-node/runtime/formData.mjs +64 -0
  108. package/dist/esm-node/runtime/loaderBridge.mjs +5 -9
  109. package/dist/esm-node/runtime/plugin.mjs +14 -118
  110. package/dist/esm-node/runtime/plugin.node.mjs +41 -121
  111. package/dist/esm-node/runtime/pluginShared.mjs +41 -0
  112. package/dist/esm-node/runtime/prefetchLink.mjs +18 -5
  113. package/dist/esm-node/runtime/register.mjs +1 -14
  114. package/dist/esm-node/runtime/routeTree/components.mjs +56 -0
  115. package/dist/esm-node/runtime/routeTree/construction.mjs +112 -0
  116. package/dist/esm-node/runtime/routeTree/index.mjs +3 -0
  117. package/dist/esm-node/runtime/routeTree/loaders.mjs +118 -0
  118. package/dist/esm-node/runtime/routeTree/paths.mjs +17 -0
  119. package/dist/esm-node/runtime/routeTree/staticData.mjs +23 -0
  120. package/dist/esm-node/runtime/routeTree/tanstackRoutes.mjs +14 -0
  121. package/dist/esm-node/runtime/routeTree/types.mjs +1 -0
  122. package/dist/esm-node/runtime/routeTree.mjs +1 -318
  123. package/dist/esm-node/runtime/router.mjs +16 -2
  124. package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +18 -13
  125. package/dist/esm-node/runtime/rsc/ReplayableStream.mjs +1 -1
  126. package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +4 -15
  127. package/dist/esm-node/runtime/rsc/client.mjs +6 -26
  128. package/dist/esm-node/runtime/rsc/flightSerialization.mjs +89 -0
  129. package/dist/esm-node/runtime/rsc/payloadErrors.mjs +50 -0
  130. package/dist/esm-node/runtime/rsc/payloadFetch.mjs +56 -0
  131. package/dist/esm-node/runtime/rsc/payloadRouter.mjs +12 -133
  132. package/dist/esm-node/runtime/rsc/payloadRoutes.mjs +51 -0
  133. package/dist/esm-node/runtime/rsc/payloadStorage.mjs +15 -0
  134. package/dist/esm-node/runtime/rsc/server.mjs +17 -21
  135. package/dist/esm-node/runtime/rsc/shared.mjs +22 -0
  136. package/dist/esm-node/runtime/rsc/slotUsageSanitizer.mjs +1 -0
  137. package/dist/esm-node/runtime/rsc/treeGetter.mjs +35 -0
  138. package/dist/esm-node/runtime/shared/isAbsoluteUrl.mjs +10 -0
  139. package/dist/esm-node/runtime/ssrManagedTags.mjs +54 -0
  140. package/dist/esm-node/runtime/ssrPreload.mjs +52 -0
  141. package/dist/esm-node/runtime/ssrTypes.mjs +1 -0
  142. package/dist/esm-node/runtime/state.mjs +1 -1
  143. package/dist/esm-node/runtime/submitAction.mjs +121 -0
  144. package/dist/esm-node/runtime/utils.mjs +47 -75
  145. package/dist/types/cli/artifacts.d.ts +33 -0
  146. package/dist/types/cli/index.d.ts +2 -22
  147. package/dist/types/cli/tanstackTypes/canonicalRoutes.d.ts +22 -0
  148. package/dist/types/cli/tanstackTypes/shared.d.ts +20 -0
  149. package/dist/types/cli/tanstackTypes/source.d.ts +12 -0
  150. package/dist/types/cli/tanstackTypes.d.ts +3 -31
  151. package/dist/types/runtime/blockingSubscribe.d.ts +1 -0
  152. package/dist/types/runtime/clientBasepath.d.ts +1 -0
  153. package/dist/types/runtime/clientHydration.d.ts +10 -0
  154. package/dist/types/runtime/dataMutation.d.ts +3 -12
  155. package/dist/types/runtime/formData.d.ts +24 -0
  156. package/dist/types/runtime/loaderBridge.d.ts +2 -1
  157. package/dist/types/runtime/plugin.d.ts +1 -1
  158. package/dist/types/runtime/plugin.node.d.ts +1 -1
  159. package/dist/types/runtime/pluginShared.d.ts +38 -0
  160. package/dist/types/runtime/register.d.ts +10 -9
  161. package/dist/types/runtime/routeTree/components.d.ts +7 -0
  162. package/dist/types/runtime/routeTree/construction.d.ts +5 -0
  163. package/dist/types/runtime/routeTree/index.d.ts +2 -0
  164. package/dist/types/runtime/routeTree/loaders.d.ts +4 -0
  165. package/dist/types/runtime/routeTree/paths.d.ts +3 -0
  166. package/dist/types/runtime/routeTree/staticData.d.ts +19 -0
  167. package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +4 -0
  168. package/dist/types/runtime/routeTree/types.d.ts +76 -0
  169. package/dist/types/runtime/routeTree.d.ts +1 -9
  170. package/dist/types/runtime/router.d.ts +6 -7
  171. package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -2
  172. package/dist/types/runtime/rsc/ReplayableStream.d.ts +3 -2
  173. package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +2 -3
  174. package/dist/types/runtime/rsc/SlotContext.d.ts +2 -1
  175. package/dist/types/runtime/rsc/client.d.ts +2 -0
  176. package/dist/types/runtime/rsc/flightSerialization.d.ts +3 -0
  177. package/dist/types/runtime/rsc/payloadErrors.d.ts +6 -0
  178. package/dist/types/runtime/rsc/payloadFetch.d.ts +8 -0
  179. package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -10
  180. package/dist/types/runtime/rsc/payloadRoutes.d.ts +40 -0
  181. package/dist/types/runtime/rsc/payloadStorage.d.ts +5 -0
  182. package/dist/types/runtime/rsc/shared.d.ts +18 -0
  183. package/dist/types/runtime/rsc/treeGetter.d.ts +1 -0
  184. package/dist/types/runtime/shared/isAbsoluteUrl.d.ts +1 -0
  185. package/dist/types/runtime/ssrManagedTags.d.ts +7 -0
  186. package/dist/types/runtime/ssrPreload.d.ts +2 -0
  187. package/dist/types/runtime/ssrTypes.d.ts +44 -0
  188. package/dist/types/runtime/state.d.ts +1 -1
  189. package/dist/types/runtime/submitAction.d.ts +14 -0
  190. package/dist/types/runtime/types.d.ts +4 -10
  191. package/dist/types/runtime/utils.d.ts +4 -24
  192. package/package.json +38 -17
  193. package/rstest.config.mts +23 -0
  194. package/src/cli/artifacts.ts +255 -0
  195. package/src/cli/index.ts +7 -185
  196. package/src/cli/tanstackTypes/canonicalRoutes.ts +157 -0
  197. package/src/cli/tanstackTypes/shared.ts +113 -0
  198. package/src/cli/tanstackTypes/source.ts +413 -0
  199. package/src/cli/tanstackTypes.ts +4 -647
  200. package/src/runtime/blockingSubscribe.ts +48 -0
  201. package/src/runtime/clientBasepath.ts +12 -0
  202. package/src/runtime/clientHydration.ts +149 -0
  203. package/src/runtime/dataMutation.tsx +9 -306
  204. package/src/runtime/formData.ts +153 -0
  205. package/src/runtime/loaderBridge.ts +11 -12
  206. package/src/runtime/plugin.node.tsx +76 -291
  207. package/src/runtime/plugin.tsx +21 -257
  208. package/src/runtime/pluginShared.ts +112 -0
  209. package/src/runtime/prefetchLink.tsx +78 -15
  210. package/src/runtime/register.ts +10 -42
  211. package/src/runtime/routeTree/components.ts +130 -0
  212. package/src/runtime/routeTree/construction.ts +238 -0
  213. package/src/runtime/routeTree/index.ts +7 -0
  214. package/src/runtime/routeTree/loaders.ts +232 -0
  215. package/src/runtime/routeTree/paths.ts +36 -0
  216. package/src/runtime/routeTree/staticData.ts +66 -0
  217. package/src/runtime/routeTree/tanstackRoutes.ts +36 -0
  218. package/src/runtime/routeTree/types.ts +87 -0
  219. package/src/runtime/routeTree.ts +5 -750
  220. package/src/runtime/router.ts +35 -7
  221. package/src/runtime/rsc/CompositeComponent.tsx +29 -20
  222. package/src/runtime/rsc/ReplayableStream.ts +2 -2
  223. package/src/runtime/rsc/RscNodeRenderer.tsx +9 -27
  224. package/src/runtime/rsc/SlotContext.tsx +1 -1
  225. package/src/runtime/rsc/client.tsx +12 -40
  226. package/src/runtime/rsc/flightSerialization.ts +158 -0
  227. package/src/runtime/rsc/payloadErrors.ts +73 -0
  228. package/src/runtime/rsc/payloadFetch.ts +92 -0
  229. package/src/runtime/rsc/payloadRouter.ts +29 -234
  230. package/src/runtime/rsc/payloadRoutes.ts +122 -0
  231. package/src/runtime/rsc/payloadStorage.ts +35 -0
  232. package/src/runtime/rsc/server.tsx +20 -29
  233. package/src/runtime/rsc/shared.ts +70 -0
  234. package/src/runtime/rsc/slotUsageSanitizer.ts +4 -0
  235. package/src/runtime/rsc/treeGetter.tsx +52 -0
  236. package/src/runtime/shared/isAbsoluteUrl.ts +8 -0
  237. package/src/runtime/ssrManagedTags.ts +107 -0
  238. package/src/runtime/ssrPreload.ts +125 -0
  239. package/src/runtime/ssrTypes.ts +51 -0
  240. package/src/runtime/state.ts +9 -3
  241. package/src/runtime/submitAction.ts +201 -0
  242. package/src/runtime/types.ts +4 -18
  243. package/src/runtime/utils.tsx +76 -110
  244. package/src/types/react-server-dom-rspack.d.ts +56 -0
  245. package/tests/realm-isolation.test.ts +114 -0
  246. package/tests/router/cli.test.ts +207 -96
  247. package/tests/router/clientHydration.test.tsx +61 -0
  248. package/tests/router/dataMutation.test.tsx +59 -0
  249. package/tests/router/fixtures/legacyRuntimeRouter.ts +10 -0
  250. package/tests/router/fixtures/router-gen.oxlint.json +5 -0
  251. package/tests/router/flightSerialization.roundtrip.test.tsx +137 -0
  252. package/tests/router/generateRouteArtifacts.test.ts +106 -0
  253. package/tests/router/hooks.test.ts +14 -0
  254. package/tests/router/loaderBridge.test.ts +21 -0
  255. package/tests/router/prefetchLink.test.tsx +106 -59
  256. package/tests/router/prefetchLinkPreload.test.tsx +85 -0
  257. package/tests/router/register.test.ts +1 -39
  258. package/tests/router/routeTree.test.ts +51 -11
  259. package/tests/router/routerCompatibility.test.ts +239 -0
  260. package/tests/router/rsc.test.tsx +189 -0
  261. package/tests/router/rscPayloadRouterMatrix.test.ts +197 -0
  262. package/tests/router/slotUsageSanitizer.test.ts +23 -0
  263. package/tests/router/ssrPreload.test.ts +30 -0
  264. package/tests/router/tanstackTypes.test.ts +480 -196
  265. package/dist/esm/runtime/pluginCore.mjs +0 -14
  266. package/dist/esm-node/runtime/pluginCore.mjs +0 -15
  267. package/dist/types/runtime/pluginCore.d.ts +0 -21
  268. package/src/runtime/pluginCore.ts +0 -48
@@ -3,24 +3,368 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
3
  import { tmpdir } from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { promisify } from 'node:util';
6
+
7
+ const resolverDelays = new Map<string, number>();
8
+
9
+ rstest.mock('../../src/cli/tanstackTypes/shared', () => {
10
+ const actual = rstest.requireActual(
11
+ '../../src/cli/tanstackTypes/shared',
12
+ ) as typeof import('../../src/cli/tanstackTypes/shared');
13
+
14
+ return {
15
+ ...actual,
16
+ resolveFileNoExt: async (inputNoExtPath: string) => {
17
+ const normalized = inputNoExtPath.replaceAll('\\', '/');
18
+ const delay =
19
+ [...resolverDelays.entries()].find(([suffix]) =>
20
+ normalized.endsWith(suffix),
21
+ )?.[1] ?? 0;
22
+
23
+ if (delay > 0) {
24
+ await new Promise(resolve => setTimeout(resolve, delay));
25
+ }
26
+
27
+ return actual.resolveFileNoExt(inputNoExtPath);
28
+ },
29
+ };
30
+ });
31
+
6
32
  import {
7
33
  collectCanonicalRoutesForEntry,
8
34
  generateTanstackRouterTypesSourceForEntry,
9
35
  } from '../../src/cli/tanstackTypes';
10
36
 
11
37
  const execFileAsync = promisify(execFile);
38
+ const routerGenOxlintConfigPath = path.join(
39
+ __dirname,
40
+ 'fixtures',
41
+ 'router-gen.oxlint.json',
42
+ );
43
+ const oxlintCliPath = path.resolve(
44
+ __dirname,
45
+ '../../../../toolkit/code-tools/node_modules/oxlint/bin/oxlint',
46
+ );
47
+ const strictestTsconfigPath = path.resolve(
48
+ __dirname,
49
+ '../../node_modules/@tsconfig/strictest/tsconfig.json',
50
+ );
51
+
52
+ async function writeRuntimeTestPackage(projectDirectory: string) {
53
+ const runtimePackageDirectory = path.join(
54
+ projectDirectory,
55
+ 'node_modules',
56
+ '@modern-js',
57
+ 'plugin-tanstack',
58
+ );
59
+ await mkdir(runtimePackageDirectory, { recursive: true });
60
+ await writeFile(
61
+ path.join(runtimePackageDirectory, 'package.json'),
62
+ JSON.stringify({
63
+ name: '@modern-js/plugin-tanstack',
64
+ type: 'commonjs',
65
+ exports: {
66
+ './runtime': {
67
+ types: './runtime.d.ts',
68
+ default: './runtime.js',
69
+ },
70
+ },
71
+ }),
72
+ );
73
+ await writeFile(
74
+ path.join(runtimePackageDirectory, 'runtime.d.ts'),
75
+ [
76
+ 'export type ModernRouterContext = { request?: Request; requestContext?: unknown };',
77
+ 'export type ModernRouteStaticData = { modernRouteId?: string; modernRouteAction?: unknown; modernRouteLoader?: unknown };',
78
+ 'export type RouteOptions = {',
79
+ ' component?: unknown;',
80
+ ' getParentRoute?: (...args: never[]) => unknown;',
81
+ ' id?: string;',
82
+ ' loader?: (...args: never[]) => unknown;',
83
+ ' loaderDeps?: (...args: never[]) => unknown;',
84
+ ' path?: string;',
85
+ ' staticData?: ModernRouteStaticData;',
86
+ ' validateSearch?: (...args: never[]) => unknown;',
87
+ '};',
88
+ 'export type Route<TOptions extends RouteOptions = RouteOptions> = {',
89
+ ' options: TOptions;',
90
+ ' addChildren<const TChildren extends readonly unknown[]>(children: TChildren): Route<TOptions> & { children: TChildren };',
91
+ '};',
92
+ 'export const runtimeState: { adapterCalls: Array<{ hasSplat: boolean; modernLoader: unknown }> };',
93
+ 'export function createMemoryHistory<TOptions>(options: TOptions): TOptions;',
94
+ 'export const modernTanstackRouterFastDefaults: { defaultPreload: "intent" };',
95
+ 'export function createRootRouteWithContext<TContext extends ModernRouterContext>(): <const TOptions extends RouteOptions>(options: TOptions) => Route<TOptions>;',
96
+ 'export function createRoute<const TOptions extends RouteOptions>(options: TOptions): Route<TOptions>;',
97
+ 'export function createRouter<const TOptions extends { context: ModernRouterContext; history: unknown; routeTree: unknown }>(options: TOptions): TOptions;',
98
+ 'export function createRouteStaticData<const TData extends ModernRouteStaticData>(data: TData): TData;',
99
+ 'export function modernLoaderToTanstack<TLoader extends (...args: never[]) => unknown>(options: { hasSplat: boolean }, modernLoader: TLoader): (context: unknown) => Promise<Awaited<ReturnType<TLoader>>>;',
100
+ ].join('\n'),
101
+ );
102
+ await writeFile(
103
+ path.join(runtimePackageDirectory, 'runtime.js'),
104
+ [
105
+ 'const runtimeState = { adapterCalls: [] };',
106
+ 'const createRouteRecord = options => ({',
107
+ ' options,',
108
+ ' addChildren(children) {',
109
+ ' this.children = children;',
110
+ ' return this;',
111
+ ' },',
112
+ '});',
113
+ 'const createMemoryHistory = options => options;',
114
+ 'const modernTanstackRouterFastDefaults = { defaultPreload: "intent" };',
115
+ 'const createRootRouteWithContext = () => options => createRouteRecord(options);',
116
+ 'const createRoute = options => createRouteRecord(options);',
117
+ 'const createRouter = options => options;',
118
+ 'const createRouteStaticData = data => data;',
119
+ 'const modernLoaderToTanstack = (options, modernLoader) => {',
120
+ ' runtimeState.adapterCalls.push({ ...options, modernLoader });',
121
+ ' return async context => modernLoader(context);',
122
+ '};',
123
+ 'module.exports = { createMemoryHistory, createRootRouteWithContext, createRoute, createRouter, createRouteStaticData, modernLoaderToTanstack, modernTanstackRouterFastDefaults, runtimeState };',
124
+ ].join('\n'),
125
+ );
126
+ }
127
+
128
+ async function compileAndRunGeneratedRouter(options: {
129
+ projectDirectory: string;
130
+ routerGenTs: string;
131
+ runtimeCheck: string;
132
+ }) {
133
+ const { projectDirectory, routerGenTs, runtimeCheck } = options;
134
+ const generatedDirectory = path.join(
135
+ projectDirectory,
136
+ 'src',
137
+ 'modern-tanstack',
138
+ 'index',
139
+ );
140
+ await mkdir(generatedDirectory, { recursive: true });
141
+ await writeFile(path.join(generatedDirectory, 'router.gen.ts'), routerGenTs);
142
+ await writeFile(
143
+ path.join(projectDirectory, 'src', 'runtime-check.ts'),
144
+ runtimeCheck,
145
+ );
146
+ await writeRuntimeTestPackage(projectDirectory);
147
+ await writeFile(
148
+ path.join(projectDirectory, 'package.json'),
149
+ JSON.stringify({ private: true, type: 'commonjs' }),
150
+ );
151
+ await writeFile(
152
+ path.join(projectDirectory, 'tsconfig.json'),
153
+ JSON.stringify(
154
+ {
155
+ extends: strictestTsconfigPath,
156
+ compilerOptions: {
157
+ lib: ['ESNext', 'DOM'],
158
+ jsx: 'react-jsx',
159
+ module: 'Node16',
160
+ moduleResolution: 'Node16',
161
+ outDir: 'dist',
162
+ rootDir: 'src',
163
+ target: 'ES2022',
164
+ types: [],
165
+ verbatimModuleSyntax: false,
166
+ },
167
+ include: ['src/**/*.ts', 'src/**/*.tsx'],
168
+ },
169
+ null,
170
+ 2,
171
+ ),
172
+ );
173
+
174
+ try {
175
+ await execFileAsync(
176
+ process.platform === 'win32' ? 'tsgo.cmd' : 'tsgo',
177
+ ['-p', 'tsconfig.json'],
178
+ {
179
+ cwd: projectDirectory,
180
+ shell: process.platform === 'win32',
181
+ },
182
+ );
183
+ } catch (error) {
184
+ if (
185
+ error &&
186
+ typeof error === 'object' &&
187
+ 'stdout' in error &&
188
+ typeof error.stdout === 'string'
189
+ ) {
190
+ throw new Error(error.stdout, { cause: error });
191
+ }
192
+ throw error;
193
+ }
194
+ await execFileAsync(
195
+ process.execPath,
196
+ [path.join(projectDirectory, 'dist', 'runtime-check.js')],
197
+ { cwd: projectDirectory },
198
+ );
199
+ }
200
+
201
+ async function writeComprehensiveRouterFixture(srcDirectory: string) {
202
+ const files = new Map([
203
+ [
204
+ 'routes/(app)/layout.tsx',
205
+ 'export default function AppLayout() { return null; }',
206
+ ],
207
+ [
208
+ 'routes/(app)/users/(userId)/page.tsx',
209
+ 'export default function UserPage() { return null; }',
210
+ ],
211
+ [
212
+ 'routes/(app)/users/(userId)/page.data.ts',
213
+ [
214
+ 'export const loader = () => ({ userId: "42" });',
215
+ 'export const action = () => Response.json({ ok: true });',
216
+ ].join('\n'),
217
+ ],
218
+ [
219
+ 'routes/(app)/docs/splat.tsx',
220
+ 'export default function DocsSplatPage() { return null; }',
221
+ ],
222
+ [
223
+ 'routes/search.contract.ts',
224
+ [
225
+ 'export const validateSearch = (search: { tab?: string }) => ({ tab: search.tab ?? "overview" });',
226
+ 'export const loaderDeps = ({ search }: { search: { tab: string } }) => ({ tab: search.tab });',
227
+ ].join('\n'),
228
+ ],
229
+ ]);
230
+
231
+ for (const [relativePath, contents] of files) {
232
+ const filePath = path.join(srcDirectory, relativePath);
233
+ await mkdir(path.dirname(filePath), { recursive: true });
234
+ await writeFile(filePath, contents);
235
+ }
236
+ }
237
+
238
+ async function generateComprehensiveRouterGen(srcDirectory: string) {
239
+ await writeComprehensiveRouterFixture(srcDirectory);
240
+
241
+ const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
242
+ appContext: {
243
+ srcDirectory,
244
+ internalSrcAlias: '@/_',
245
+ } as any,
246
+ entryName: 'golden',
247
+ routes: [
248
+ {
249
+ type: 'nested',
250
+ id: 'layout',
251
+ isRoot: true,
252
+ children: [
253
+ {
254
+ type: 'nested',
255
+ id: '(app)/layout',
256
+ _component: '@/_/routes/(app)/layout',
257
+ children: [
258
+ {
259
+ type: 'nested',
260
+ id: '(app)/users/(userId)/page',
261
+ path: 'users/:userId',
262
+ _component: '@/_/routes/(app)/users/(userId)/page',
263
+ data: '@/_/routes/(app)/users/(userId)/page.data',
264
+ action: '@/_/routes/(app)/users/(userId)/page.data',
265
+ validateSearch: '@/_/routes/search.contract',
266
+ loaderDeps: '@/_/routes/search.contract',
267
+ },
268
+ {
269
+ type: 'nested',
270
+ id: '(app)/docs/splat/page',
271
+ path: 'docs/*',
272
+ _component: '@/_/routes/(app)/docs/splat',
273
+ },
274
+ ],
275
+ },
276
+ ],
277
+ },
278
+ ] as any,
279
+ });
280
+
281
+ return routerGenTs;
282
+ }
12
283
 
13
284
  describe('tanstack router type generation', () => {
14
285
  let tempDir: string | undefined;
15
286
 
16
287
  afterEach(async () => {
288
+ resolverDelays.clear();
17
289
  if (tempDir) {
18
290
  await rm(tempDir, { recursive: true, force: true });
19
291
  tempDir = undefined;
20
292
  }
21
293
  });
22
294
 
23
- test('emits inline data actions into route static data', async () => {
295
+ test('strictly compiles and executes a loader-free conventional route tree', async () => {
296
+ tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
297
+ const srcDirectory = path.join(tempDir, 'src');
298
+ const files = new Map([
299
+ [
300
+ 'routes/layout.tsx',
301
+ 'export default function Layout() { return null; }',
302
+ ],
303
+ ['routes/page.tsx', 'export default function Home() { return null; }'],
304
+ [
305
+ 'routes/reviews/[scanId]/page.tsx',
306
+ 'export default function Review() { return null; }',
307
+ ],
308
+ ]);
309
+
310
+ for (const [relativePath, contents] of files) {
311
+ const filePath = path.join(srcDirectory, relativePath);
312
+ await mkdir(path.dirname(filePath), { recursive: true });
313
+ await writeFile(filePath, contents);
314
+ }
315
+
316
+ const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
317
+ appContext: {
318
+ srcDirectory,
319
+ internalSrcAlias: '@/_',
320
+ } as any,
321
+ entryName: 'index',
322
+ routes: [
323
+ {
324
+ type: 'nested',
325
+ id: 'layout',
326
+ isRoot: true,
327
+ _component: '@/_/routes/layout',
328
+ children: [
329
+ {
330
+ type: 'nested',
331
+ id: 'page',
332
+ index: true,
333
+ _component: '@/_/routes/page',
334
+ },
335
+ {
336
+ type: 'nested',
337
+ id: 'reviews/(scanId)/page',
338
+ path: 'reviews/:scanId',
339
+ _component: '@/_/routes/reviews/[scanId]/page',
340
+ },
341
+ ],
342
+ },
343
+ ] as any,
344
+ });
345
+
346
+ await compileAndRunGeneratedRouter({
347
+ projectDirectory: tempDir,
348
+ routerGenTs,
349
+ runtimeCheck: [
350
+ "import { runtimeState } from '@modern-js/plugin-tanstack/runtime';",
351
+ "import Layout from './routes/layout';",
352
+ "import Home from './routes/page';",
353
+ "import Review from './routes/reviews/[scanId]/page';",
354
+ "import { rootRoute, routeTree, router } from './modern-tanstack/index/router.gen';",
355
+ '',
356
+ 'if (runtimeState.adapterCalls.length !== 0) throw new Error("loader adapter registered without a loader");',
357
+ 'if (Object.keys(router.context).length !== 0) throw new Error("router context is not empty");',
358
+ 'if (rootRoute.options.component !== Layout) throw new Error("root component was not wired");',
359
+ 'const [homeRoute, reviewRoute] = routeTree.children;',
360
+ 'if (homeRoute.options.component !== Home) throw new Error("index component was not wired");',
361
+ 'if (reviewRoute.options.component !== Review) throw new Error("review component was not wired");',
362
+ 'if (reviewRoute.options.path !== "reviews/$scanId") throw new Error("route params were not translated");',
363
+ ].join('\n'),
364
+ });
365
+ });
366
+
367
+ test('executes generated loader and action wiring through route static data', async () => {
24
368
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
25
369
  const srcDirectory = path.join(tempDir, 'src');
26
370
  const routeDir = path.join(srcDirectory, 'routes', 'mf');
@@ -57,41 +401,46 @@ describe('tanstack router type generation', () => {
57
401
  ] as any,
58
402
  });
59
403
 
60
- expect(routerGenTs).toContain(
61
- 'import { loader as loader_0, action as action_0 } from "../../routes/mf/page.data";',
62
- );
63
- expect(routerGenTs).toContain('modernRouteLoader: loader_0');
64
- expect(routerGenTs).toContain('modernRouteAction: action_0');
65
- expect(routerGenTs).toContain(
66
- "} from '@modern-js/plugin-tanstack/runtime';",
67
- );
68
- expect(routerGenTs).toContain('modernTanstackRouterFastDefaults,');
69
- expect(routerGenTs).toContain('...modernTanstackRouterFastDefaults,');
70
-
71
- // The loader-bridge helpers are imported from the package runtime instead
72
- // of being inlined into every generated file (bugfixes ship via package
73
- // update, and the broken inline absolute-redirect handler is gone).
74
- expect(routerGenTs).toContain('createRouteStaticData,');
75
- expect(routerGenTs).toContain('modernLoaderToTanstack,');
76
- expect(routerGenTs).toContain('type ModernRouterContext,');
77
- expect(routerGenTs).not.toContain('function modernLoaderToTanstack');
78
- expect(routerGenTs).not.toContain('function createRouteStaticData');
79
- expect(routerGenTs).not.toContain('function throwTanstackRedirect');
404
+ await compileAndRunGeneratedRouter({
405
+ projectDirectory: tempDir,
406
+ routerGenTs,
407
+ runtimeCheck: [
408
+ "import { runtimeState } from '@modern-js/plugin-tanstack/runtime';",
409
+ "import { action, loader } from './routes/mf/page.data';",
410
+ "import { routeTree } from './modern-tanstack/index/router.gen';",
411
+ '',
412
+ 'async function main() {',
413
+ 'const [mfRoute] = routeTree.children;',
414
+ 'if (runtimeState.adapterCalls.length !== 1) throw new Error("loader adapter was not registered exactly once");',
415
+ 'if (runtimeState.adapterCalls[0]?.modernLoader !== loader) throw new Error("wrong loader was adapted");',
416
+ 'const result = await mfRoute.options.loader({});',
417
+ 'if (result.count !== 0) throw new Error("adapted loader returned the wrong value");',
418
+ 'if (mfRoute.options.staticData.modernRouteLoader !== loader) throw new Error("loader static data was not wired");',
419
+ 'if (mfRoute.options.staticData.modernRouteAction !== action) throw new Error("action static data was not wired");',
420
+ '}',
421
+ 'void main();',
422
+ ].join('\n'),
423
+ });
80
424
  });
81
425
 
82
- test('emits resolvable relative component imports for routes carrying _component', async () => {
426
+ test('executes component sharing correctly when module resolution races', async () => {
83
427
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
84
428
  const srcDirectory = path.join(tempDir, 'src');
429
+
85
430
  for (const componentFile of [
86
- 'routes/layout.tsx',
87
- 'routes/page.tsx',
88
- 'routes/about/page.tsx',
431
+ 'routes/slow-page.tsx',
432
+ 'routes/fast-page.tsx',
433
+ 'routes/shared-page.tsx',
89
434
  ]) {
90
435
  const componentPath = path.join(srcDirectory, componentFile);
91
436
  await mkdir(path.dirname(componentPath), { recursive: true });
92
- await writeFile(componentPath, 'export default () => null;');
437
+ await writeFile(
438
+ componentPath,
439
+ 'export default function Page() { return null; }',
440
+ );
93
441
  }
94
442
 
443
+ resolverDelays.set('/routes/slow-page', 25);
95
444
  const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
96
445
  appContext: {
97
446
  srcDirectory,
@@ -103,29 +452,32 @@ describe('tanstack router type generation', () => {
103
452
  type: 'nested',
104
453
  id: 'layout',
105
454
  isRoot: true,
106
- _component: '@/_/routes/layout',
107
455
  children: [
108
456
  {
109
457
  type: 'nested',
110
- id: 'page',
111
- index: true,
112
- _component: '@/_/routes/page',
458
+ id: 'slow/page',
459
+ path: 'slow',
460
+ _component: '@/_/routes/slow-page',
113
461
  },
114
462
  {
115
463
  type: 'nested',
116
- id: 'about/page',
117
- path: 'about',
118
- _component: '@/_/routes/about/page',
464
+ id: 'fast/page',
465
+ path: 'fast',
466
+ _component: '@/_/routes/fast-page',
119
467
  },
120
468
  {
121
- // Shares the page component module: the import must be reused.
122
469
  type: 'nested',
123
- id: 'about-alias/page',
124
- path: 'about-alias',
125
- _component: '@/_/routes/about/page',
470
+ id: 'shared-a/page',
471
+ path: 'shared-a',
472
+ _component: '@/_/routes/shared-page',
473
+ },
474
+ {
475
+ type: 'nested',
476
+ id: 'shared-b/page',
477
+ path: 'shared-b',
478
+ _component: '@/_/routes/shared-page',
126
479
  },
127
480
  {
128
- // No _component: no component option may be emitted.
129
481
  type: 'nested',
130
482
  id: 'data-only/page',
131
483
  path: 'data-only',
@@ -135,162 +487,92 @@ describe('tanstack router type generation', () => {
135
487
  ] as any,
136
488
  });
137
489
 
138
- // Children are emitted first, the root route component import last.
139
- // Imports are relative (resolved like loader modules) — the raw
140
- // `@/_` internal alias is not mapped by app tsconfigs.
141
- expect(routerGenTs).toContain(
142
- 'import component_0 from "../../routes/page";',
143
- );
144
- expect(routerGenTs).toContain(
145
- 'import component_1 from "../../routes/about/page";',
146
- );
147
- expect(routerGenTs).toContain(
148
- 'import component_2 from "../../routes/layout";',
149
- );
150
- expect(routerGenTs).not.toContain('@/_/routes');
151
- // The shared module is imported exactly once.
152
- expect(routerGenTs).not.toContain('component_3');
153
- expect(
154
- routerGenTs.match(/from "\.\.\/\.\.\/routes\/about\/page";/g),
155
- ).toHaveLength(1);
156
-
157
- expect(routerGenTs).toContain('component: component_0,');
158
- // The shared component import is referenced by both aliased routes.
159
- expect(routerGenTs.match(/component: component_1,/g)).toHaveLength(2);
160
- // The root route gets its component option.
161
- expect(routerGenTs).toContain('component: component_2,');
162
-
163
- const dataOnlyRoute = routerGenTs
164
- .split('const ')
165
- .find(block => block.includes('path: "data-only",'));
166
- expect(dataOnlyRoute).toBeDefined();
167
- expect(dataOnlyRoute).not.toContain('component:');
490
+ await compileAndRunGeneratedRouter({
491
+ projectDirectory: tempDir,
492
+ routerGenTs,
493
+ runtimeCheck: [
494
+ "import SlowPage from './routes/slow-page';",
495
+ "import FastPage from './routes/fast-page';",
496
+ "import SharedPage from './routes/shared-page';",
497
+ "import { routeTree } from './modern-tanstack/index/router.gen';",
498
+ '',
499
+ 'const [slowRoute, fastRoute, sharedA, sharedB, dataOnly] = routeTree.children;',
500
+ 'if (slowRoute.options.component !== SlowPage) throw new Error("slow component was crossed during resolution");',
501
+ 'if (fastRoute.options.component !== FastPage) throw new Error("fast component was crossed during resolution");',
502
+ 'if (sharedA.options.component !== SharedPage || sharedB.options.component !== SharedPage) throw new Error("shared component identity was not preserved");',
503
+ 'if (Reflect.has(dataOnly.options, "component")) throw new Error("data-only route gained a component");',
504
+ ].join('\n'),
505
+ });
168
506
  });
169
507
 
170
- test('typechecks generated TanStack search contracts', async () => {
508
+ test('strictly compiles and executes nested loader and search contracts', async () => {
171
509
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
172
510
  const srcDirectory = path.join(tempDir, 'src');
173
- const routeDir = path.join(srcDirectory, 'routes');
174
- const generatedDir = path.join(srcDirectory, 'modern-tanstack', 'index');
175
- await mkdir(routeDir, { recursive: true });
176
- await mkdir(generatedDir, { recursive: true });
177
- await writeFile(
178
- path.join(routeDir, 'search.contract.ts'),
179
- [
180
- 'export const validateSearch = (search: { q?: string }) => ({ q: search.q ?? "" });',
181
- 'export const loaderDeps = ({ search }: { search: { q: string } }) => ({ q: search.q });',
511
+ const routerGenTs = await generateComprehensiveRouterGen(srcDirectory);
512
+
513
+ await compileAndRunGeneratedRouter({
514
+ projectDirectory: tempDir,
515
+ routerGenTs,
516
+ runtimeCheck: [
517
+ "import { runtimeState } from '@modern-js/plugin-tanstack/runtime';",
518
+ "import { action, loader } from './routes/(app)/users/(userId)/page.data';",
519
+ "import { loaderDeps, validateSearch } from './routes/search.contract';",
520
+ "import { routeTree } from './modern-tanstack/index/router.gen';",
521
+ '',
522
+ 'async function main() {',
523
+ 'const [appRoute] = routeTree.children;',
524
+ 'const [userRoute, docsRoute] = appRoute.children;',
525
+ 'if (runtimeState.adapterCalls.length !== 1) throw new Error("nested loader was not adapted");',
526
+ 'if (userRoute.options.validateSearch !== validateSearch) throw new Error("search validator was not wired");',
527
+ 'if (userRoute.options.loaderDeps !== loaderDeps) throw new Error("loader dependencies were not wired");',
528
+ 'if (userRoute.options.staticData.modernRouteLoader !== loader) throw new Error("nested static loader was not wired");',
529
+ 'if (userRoute.options.staticData.modernRouteAction !== action) throw new Error("nested static action was not wired");',
530
+ 'const user = await userRoute.options.loader({});',
531
+ 'if (user.userId !== "42") throw new Error("nested loader result was lost");',
532
+ 'if (userRoute.options.path !== "users/$userId") throw new Error("required route param was not translated");',
533
+ 'if (docsRoute.options.path !== "docs/$") throw new Error("splat route was not translated");',
534
+ 'const search = userRoute.options.validateSearch({});',
535
+ 'if (search.tab !== "overview") throw new Error("search contract returned the wrong value");',
536
+ '}',
537
+ 'void main();',
182
538
  ].join('\n'),
183
- );
184
-
185
- const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
186
- appContext: {
187
- srcDirectory,
188
- internalSrcAlias: '@/_',
189
- } as any,
190
- entryName: 'index',
191
- routes: [
192
- {
193
- type: 'nested',
194
- id: 'layout',
195
- isRoot: true,
196
- validateSearch: '@/_/routes/search.contract',
197
- loaderDeps: '@/_/routes/search.contract',
198
- children: [
199
- {
200
- type: 'nested',
201
- id: 'search/page',
202
- path: 'search',
203
- validateSearch: '@/_/routes/search.contract',
204
- loaderDeps: '@/_/routes/search.contract',
205
- },
206
- ],
207
- },
208
- ] as any,
209
539
  });
540
+ });
210
541
 
211
- await writeFile(path.join(generatedDir, 'router.gen.ts'), routerGenTs);
212
- await writeFile(
213
- path.join(srcDirectory, 'runtime-shim.d.ts'),
214
- [
215
- "declare module '@modern-js/plugin-tanstack/runtime' {",
216
- ' type RouteOptions = {',
217
- ' getParentRoute?: () => unknown;',
218
- ' id?: string;',
219
- ' loader?: unknown;',
220
- ' loaderDeps?: unknown;',
221
- ' path?: string;',
222
- ' staticData?: unknown;',
223
- ' validateSearch?: unknown;',
224
- ' };',
225
- ' type Route<TOptions extends RouteOptions> = {',
226
- ' options: TOptions;',
227
- ' addChildren<TChildren extends readonly unknown[]>(children: TChildren): Route<TOptions> & { children: TChildren };',
228
- ' };',
229
- ' export type ModernRouterContext = {',
230
- ' request?: Request;',
231
- ' requestContext?: unknown;',
232
- ' };',
233
- ' export function createMemoryHistory(options: unknown): unknown;',
234
- ' export const modernTanstackRouterFastDefaults: Record<string, unknown>;',
235
- ' export function createRootRouteWithContext<TContext>(): <TOptions extends RouteOptions>(options: TOptions) => Route<TOptions>;',
236
- ' export function createRoute<TOptions extends RouteOptions>(options: TOptions): Route<TOptions>;',
237
- ' export function createRouter<TOptions extends Record<string, unknown>>(options: TOptions): TOptions;',
238
- ' export function createRouteStaticData(opts: { modernRouteId?: string; modernRouteAction?: unknown; modernRouteLoader?: unknown }): Record<string, unknown>;',
239
- ' export function modernLoaderToTanstack<TLoader extends (args: never) => unknown>(opts: { hasSplat: boolean }, modernLoader: TLoader): (ctx: unknown) => Promise<Awaited<ReturnType<TLoader>>>;',
240
- '}',
241
- ].join('\n'),
542
+ test('passes the generated-artifact lint contract', async () => {
543
+ tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
544
+ const srcDirectory = path.join(tempDir, 'src');
545
+ const routerGenTs = await generateComprehensiveRouterGen(srcDirectory);
546
+ const routerGenPath = path.join(
547
+ srcDirectory,
548
+ 'modern-tanstack',
549
+ 'golden',
550
+ 'router.gen.ts',
242
551
  );
243
- await writeFile(
244
- path.join(srcDirectory, 'assert-search-contracts.ts'),
552
+
553
+ await mkdir(path.dirname(routerGenPath), { recursive: true });
554
+ await writeFile(routerGenPath, routerGenTs);
555
+
556
+ const { stderr, stdout } = await execFileAsync(
557
+ process.execPath,
245
558
  [
246
- "import { rootRoute, routeTree } from './modern-tanstack/index/router.gen';",
247
- "import { loaderDeps, validateSearch } from './routes/search.contract';",
248
- '',
249
- 'const rootValidateSearch: typeof validateSearch = rootRoute.options.validateSearch;',
250
- 'const rootLoaderDeps: typeof loaderDeps = rootRoute.options.loaderDeps;',
251
- 'const childValidateSearch: typeof validateSearch = routeTree.children[0].options.validateSearch;',
252
- 'const childLoaderDeps: typeof loaderDeps = routeTree.children[0].options.loaderDeps;',
253
- '',
254
- "rootValidateSearch({ q: 'root' });",
255
- "rootLoaderDeps({ search: { q: 'root' } });",
256
- "childValidateSearch({ q: 'child' });",
257
- "childLoaderDeps({ search: { q: 'child' } });",
258
- ].join('\n'),
259
- );
260
- await writeFile(
261
- path.join(tempDir, 'tsconfig.json'),
262
- JSON.stringify(
263
- {
264
- compilerOptions: {
265
- lib: ['ESNext', 'DOM'],
266
- module: 'Preserve',
267
- moduleResolution: 'Bundler',
268
- noEmit: true,
269
- skipLibCheck: true,
270
- strict: true,
271
- target: 'ESNext',
272
- types: [],
273
- },
274
- include: ['src/**/*.ts', 'src/**/*.d.ts'],
275
- },
276
- null,
277
- 2,
278
- ),
559
+ oxlintCliPath,
560
+ routerGenPath,
561
+ '--config',
562
+ routerGenOxlintConfigPath,
563
+ '--no-ignore',
564
+ '--report-unused-disable-directives-severity',
565
+ 'error',
566
+ '--format',
567
+ 'unix',
568
+ ],
569
+ { cwd: tempDir },
279
570
  );
280
571
 
281
- await expect(
282
- execFileAsync(
283
- process.platform === 'win32' ? 'tsgo.cmd' : 'tsgo',
284
- ['--noEmit', '-p', 'tsconfig.json'],
285
- {
286
- cwd: tempDir,
287
- shell: process.platform === 'win32',
288
- },
289
- ),
290
- ).resolves.toBeDefined();
572
+ expect(`${stdout}${stderr}`).toBe('');
291
573
  });
292
574
 
293
- test('preserves typed child trees for localized nested route aliases', async () => {
575
+ test('executes localized aliases with typed children and correct parents', async () => {
294
576
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
295
577
  const srcDirectory = path.join(tempDir, 'src');
296
578
 
@@ -333,21 +615,23 @@ describe('tanstack router type generation', () => {
333
615
  ] as any,
334
616
  });
335
617
 
336
- expect(routerGenTs).toContain(
337
- 'const route__lang__layout__base = createRoute({',
338
- );
339
- expect(routerGenTs).toContain(
340
- 'getParentRoute: () => route__lang__layout__base,',
341
- );
342
- expect(routerGenTs).toContain('path: "produkty/$slug",');
343
- expect(routerGenTs).toContain('path: "volitelne/{-$slug}",');
344
- expect(routerGenTs).toContain(
345
- 'const route__lang__layout = route__lang__layout__base.addChildren([route__lang__products__slug__page, route__lang__products__slug__page__localised_produkty_slug, route__lang__optional__slug$__page__localised_volitelne_slug]);',
346
- );
347
- expect(routerGenTs).toContain(
348
- 'export const routeTree = rootRoute.addChildren([route__lang__layout]);',
349
- );
350
- expect(routerGenTs).not.toContain('route__lang__layout.addChildren([');
618
+ await compileAndRunGeneratedRouter({
619
+ projectDirectory: tempDir,
620
+ routerGenTs,
621
+ runtimeCheck: [
622
+ "import { routeTree } from './modern-tanstack/index/router.gen';",
623
+ '',
624
+ 'const [localeRoute] = routeTree.children;',
625
+ 'const [productRoute, localizedProductRoute, optionalRoute] = localeRoute.children;',
626
+ 'if (localeRoute.options.path !== "$lang") throw new Error("locale param was not translated");',
627
+ 'if (productRoute.options.path !== "products/$slug") throw new Error("product param was not translated");',
628
+ 'if (localizedProductRoute.options.path !== "produkty/$slug") throw new Error("localized param was not translated");',
629
+ 'if (optionalRoute.options.path !== "volitelne/{-$slug}") throw new Error("optional param was not translated");',
630
+ 'for (const child of localeRoute.children) {',
631
+ ' if (child.options.getParentRoute() !== localeRoute) throw new Error("localized child points at the wrong parent");',
632
+ '}',
633
+ ].join('\n'),
634
+ });
351
635
  });
352
636
  });
353
637