@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.100 → 3.5.0-ultramodern.102

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.
@@ -169,7 +169,9 @@ var __webpack_exports__ = {};
169
169
  if (0 === tanstackEntrypoints.length) return;
170
170
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
171
171
  const routesByEntry = await handleGeneratorEntryCode(api, tanstackEntrypoints, {
172
- entrypointsKey: ENTRYPOINTS_KEY
172
+ entrypointsKey: ENTRYPOINTS_KEY,
173
+ hydrateRscClientRoutes: true,
174
+ serverRoutesFileName: 'tanstack-routes.server.js'
173
175
  });
174
176
  await (0, external_artifacts_js_namespaceObject.writeTanstackRouterTypesForEntries)({
175
177
  appContext: api.getAppContext(),
@@ -189,7 +191,9 @@ var __webpack_exports__ = {};
189
191
  regenerate: async ({ api, entrypoints })=>{
190
192
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
191
193
  const routesByEntry = await handleGeneratorEntryCode(api, entrypoints, {
192
- entrypointsKey: ENTRYPOINTS_KEY
194
+ entrypointsKey: ENTRYPOINTS_KEY,
195
+ hydrateRscClientRoutes: true,
196
+ serverRoutesFileName: 'tanstack-routes.server.js'
193
197
  });
194
198
  await (0, external_artifacts_js_namespaceObject.writeTanstackRouterTypesForEntries)({
195
199
  appContext: api.getAppContext(),
@@ -214,8 +214,7 @@ import {
214
214
  createRouter,
215
215
  createRouteStaticData,
216
216
  type ModernRouterContext,
217
- modernLoaderToTanstack,
218
- modernTanstackRouterFastDefaults,
217
+ ${loaderImportMap.size > 0 ? ' modernLoaderToTanstack,\n' : ''} modernTanstackRouterFastDefaults,
219
218
  } from '@modern-js/plugin-tanstack/runtime';
220
219
 
221
220
  ${imports.join('\n')}
@@ -239,7 +238,7 @@ export const router = createRouter({
239
238
  history: createMemoryHistory({
240
239
  initialEntries: ['/'],
241
240
  }),
242
- context: {} as ModernRouterContext,
241
+ context: {},
243
242
  });
244
243
  `;
245
244
  return {
@@ -110,7 +110,9 @@ function tanstackRouterPlugin(options = {}) {
110
110
  if (0 === tanstackEntrypoints.length) return;
111
111
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
112
112
  const routesByEntry = await handleGeneratorEntryCode(api, tanstackEntrypoints, {
113
- entrypointsKey: ENTRYPOINTS_KEY
113
+ entrypointsKey: ENTRYPOINTS_KEY,
114
+ hydrateRscClientRoutes: true,
115
+ serverRoutesFileName: 'tanstack-routes.server.js'
114
116
  });
115
117
  await writeTanstackRouterTypesForEntries({
116
118
  appContext: api.getAppContext(),
@@ -130,7 +132,9 @@ function tanstackRouterPlugin(options = {}) {
130
132
  regenerate: async ({ api, entrypoints })=>{
131
133
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
132
134
  const routesByEntry = await handleGeneratorEntryCode(api, entrypoints, {
133
- entrypointsKey: ENTRYPOINTS_KEY
135
+ entrypointsKey: ENTRYPOINTS_KEY,
136
+ hydrateRscClientRoutes: true,
137
+ serverRoutesFileName: 'tanstack-routes.server.js'
134
138
  });
135
139
  await writeTanstackRouterTypesForEntries({
136
140
  appContext: api.getAppContext(),
@@ -172,8 +172,7 @@ import {
172
172
  createRouter,
173
173
  createRouteStaticData,
174
174
  type ModernRouterContext,
175
- modernLoaderToTanstack,
176
- modernTanstackRouterFastDefaults,
175
+ ${loaderImportMap.size > 0 ? ' modernLoaderToTanstack,\n' : ''} modernTanstackRouterFastDefaults,
177
176
  } from '@modern-js/plugin-tanstack/runtime';
178
177
 
179
178
  ${imports.join('\n')}
@@ -197,7 +196,7 @@ export const router = createRouter({
197
196
  history: createMemoryHistory({
198
197
  initialEntries: ['/'],
199
198
  }),
200
- context: {} as ModernRouterContext,
199
+ context: {},
201
200
  });
202
201
  `;
203
202
  return {
@@ -114,7 +114,9 @@ function tanstackRouterPlugin(options = {}) {
114
114
  if (0 === tanstackEntrypoints.length) return;
115
115
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
116
116
  const routesByEntry = await handleGeneratorEntryCode(api, tanstackEntrypoints, {
117
- entrypointsKey: ENTRYPOINTS_KEY
117
+ entrypointsKey: ENTRYPOINTS_KEY,
118
+ hydrateRscClientRoutes: true,
119
+ serverRoutesFileName: 'tanstack-routes.server.js'
118
120
  });
119
121
  await writeTanstackRouterTypesForEntries({
120
122
  appContext: api.getAppContext(),
@@ -134,7 +136,9 @@ function tanstackRouterPlugin(options = {}) {
134
136
  regenerate: async ({ api, entrypoints })=>{
135
137
  const { handleGeneratorEntryCode } = getRuntimeRouterCli();
136
138
  const routesByEntry = await handleGeneratorEntryCode(api, entrypoints, {
137
- entrypointsKey: ENTRYPOINTS_KEY
139
+ entrypointsKey: ENTRYPOINTS_KEY,
140
+ hydrateRscClientRoutes: true,
141
+ serverRoutesFileName: 'tanstack-routes.server.js'
138
142
  });
139
143
  await writeTanstackRouterTypesForEntries({
140
144
  appContext: api.getAppContext(),
@@ -173,8 +173,7 @@ import {
173
173
  createRouter,
174
174
  createRouteStaticData,
175
175
  type ModernRouterContext,
176
- modernLoaderToTanstack,
177
- modernTanstackRouterFastDefaults,
176
+ ${loaderImportMap.size > 0 ? ' modernLoaderToTanstack,\n' : ''} modernTanstackRouterFastDefaults,
178
177
  } from '@modern-js/plugin-tanstack/runtime';
179
178
 
180
179
  ${imports.join('\n')}
@@ -198,7 +197,7 @@ export const router = createRouter({
198
197
  history: createMemoryHistory({
199
198
  initialEntries: ['/'],
200
199
  }),
201
- context: {} as ModernRouterContext,
200
+ context: {},
202
201
  });
203
202
  `;
204
203
  return {
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  "modern.js",
19
19
  "tanstack-router"
20
20
  ],
21
- "version": "3.5.0-ultramodern.100",
21
+ "version": "3.5.0-ultramodern.102",
22
22
  "engines": {
23
23
  "node": ">=20"
24
24
  },
@@ -94,28 +94,29 @@
94
94
  }
95
95
  },
96
96
  "dependencies": {
97
- "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.100",
98
- "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.100",
99
- "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.100",
100
- "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.100",
97
+ "@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.5.0-ultramodern.102",
98
+ "@modern-js/runtime-utils": "npm:@bleedingdev/modern-js-runtime-utils@3.5.0-ultramodern.102",
99
+ "@modern-js/types": "npm:@bleedingdev/modern-js-types@3.5.0-ultramodern.102",
100
+ "@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.5.0-ultramodern.102",
101
101
  "@swc/helpers": "^0.5.23",
102
102
  "@tanstack/react-router": "1.170.17",
103
103
  "@tanstack/router-core": "1.171.14",
104
104
  "react-server-dom-rspack": "0.0.2"
105
105
  },
106
106
  "peerDependencies": {
107
- "@modern-js/app-tools": "3.5.0-ultramodern.100",
108
- "@modern-js/runtime": "3.5.0-ultramodern.100",
107
+ "@modern-js/app-tools": "3.5.0-ultramodern.102",
108
+ "@modern-js/runtime": "3.5.0-ultramodern.102",
109
109
  "react": "^19.2.7",
110
110
  "react-dom": "^19.2.7"
111
111
  },
112
112
  "devDependencies": {
113
- "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.100",
114
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.100",
113
+ "@modern-js/app-tools": "npm:@bleedingdev/modern-js-app-tools@3.5.0-ultramodern.102",
114
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.5.0-ultramodern.102",
115
115
  "@rslib/core": "0.23.2",
116
116
  "@scripts/rstest-config": "2.66.0",
117
117
  "@tanstack/history": "1.162.0",
118
118
  "@testing-library/react": "^16.3.2",
119
+ "@tsconfig/strictest": "2.0.8",
119
120
  "@types/node": "^26.1.1",
120
121
  "@types/react": "^19.2.17",
121
122
  "@types/react-dom": "^19.2.3",
package/src/cli/index.ts CHANGED
@@ -75,6 +75,8 @@ type RuntimeRouterCliHelpers = {
75
75
  entrypoints: Entrypoint[],
76
76
  options?: {
77
77
  entrypointsKey?: string;
78
+ hydrateRscClientRoutes?: boolean;
79
+ serverRoutesFileName?: string;
78
80
  },
79
81
  ) => Promise<Record<string, (NestedRouteForCli | PageRoute)[]>>;
80
82
  handleModifyEntrypoints: (
@@ -265,6 +267,8 @@ export function tanstackRouterPlugin(
265
267
  tanstackEntrypoints,
266
268
  {
267
269
  entrypointsKey: ENTRYPOINTS_KEY,
270
+ hydrateRscClientRoutes: true,
271
+ serverRoutesFileName: 'tanstack-routes.server.js',
268
272
  },
269
273
  );
270
274
 
@@ -291,6 +295,8 @@ export function tanstackRouterPlugin(
291
295
  entrypoints,
292
296
  {
293
297
  entrypointsKey: ENTRYPOINTS_KEY,
298
+ hydrateRscClientRoutes: true,
299
+ serverRoutesFileName: 'tanstack-routes.server.js',
294
300
  },
295
301
  );
296
302
 
@@ -379,8 +379,7 @@ import {
379
379
  createRouter,
380
380
  createRouteStaticData,
381
381
  type ModernRouterContext,
382
- modernLoaderToTanstack,
383
- modernTanstackRouterFastDefaults,
382
+ ${loaderImportMap.size > 0 ? ' modernLoaderToTanstack,\n' : ''} modernTanstackRouterFastDefaults,
384
383
  } from '@modern-js/plugin-tanstack/runtime';
385
384
 
386
385
  ${imports.join('\n')}
@@ -406,7 +405,7 @@ export const router = createRouter({
406
405
  history: createMemoryHistory({
407
406
  initialEntries: ['/'],
408
407
  }),
409
- context: {} as ModernRouterContext,
408
+ context: {},
410
409
  });
411
410
  `;
412
411
 
@@ -1,6 +1,8 @@
1
- import { mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdir, mkdtemp, rm } from 'node:fs/promises';
2
3
  import { tmpdir } from 'node:os';
3
4
  import path from 'node:path';
5
+ import { promisify } from 'node:util';
4
6
  import { mergeConfig } from '@modern-js/plugin/cli';
5
7
  import type { Entrypoint } from '@modern-js/types';
6
8
  import { fs, NESTED_ROUTE_SPEC_FILE } from '@modern-js/utils';
@@ -12,6 +14,119 @@ import {
12
14
  writeTanstackRouterTypesForEntries,
13
15
  } from '../../src/cli';
14
16
 
17
+ const execFileAsync = promisify(execFile);
18
+ const strictestTsconfigPath = path.resolve(
19
+ __dirname,
20
+ '../../node_modules/@tsconfig/strictest/tsconfig.json',
21
+ );
22
+
23
+ async function typecheckGeneratedRegistration(options: {
24
+ entries: string[];
25
+ generatedDirName: string;
26
+ srcDirectory: string;
27
+ runtimeModule?: string;
28
+ i18nRuntimeModule?: string;
29
+ canonicalTypeChecks?: string[];
30
+ }) {
31
+ const {
32
+ entries,
33
+ generatedDirName,
34
+ srcDirectory,
35
+ runtimeModule = '@modern-js/plugin-tanstack/runtime',
36
+ i18nRuntimeModule,
37
+ canonicalTypeChecks = [],
38
+ } = options;
39
+ const projectDirectory = path.dirname(srcDirectory);
40
+
41
+ for (const entry of entries) {
42
+ const routerPath = path.join(
43
+ srcDirectory,
44
+ generatedDirName,
45
+ entry,
46
+ 'router.gen.ts',
47
+ );
48
+ if (!(await fs.pathExists(routerPath))) {
49
+ await fs.outputFile(routerPath, 'export const router = { context: {} };');
50
+ }
51
+ }
52
+
53
+ const runtimeDeclaration = [
54
+ `declare module '${runtimeModule}' {`,
55
+ ' export interface Register {}',
56
+ ' export type ModernRouterContext = { request?: Request; requestContext?: unknown };',
57
+ ' type RouteOptions = { getParentRoute?: (...args: never[]) => unknown; id?: string; loader?: (...args: never[]) => unknown; path?: string; staticData?: unknown };',
58
+ ' type Route<TOptions extends RouteOptions = RouteOptions> = { options: TOptions; addChildren<const TChildren extends readonly unknown[]>(children: TChildren): Route<TOptions> & { children: TChildren } };',
59
+ ' export function createMemoryHistory<TOptions>(options: TOptions): TOptions;',
60
+ ' export function createRootRouteWithContext<TContext extends ModernRouterContext>(): <const TOptions extends RouteOptions>(options: TOptions) => Route<TOptions>;',
61
+ ' export function createRoute<const TOptions extends RouteOptions>(options: TOptions): Route<TOptions>;',
62
+ ' export function createRouter<const TOptions extends { context: ModernRouterContext; routeTree: unknown }>(options: TOptions): TOptions;',
63
+ ' export function createRouteStaticData<const TData extends Record<string, unknown>>(data: TData): TData;',
64
+ ' export function modernLoaderToTanstack<TLoader extends (...args: never[]) => unknown>(options: { hasSplat: boolean }, loader: TLoader): (context: unknown) => Promise<Awaited<ReturnType<TLoader>>>;',
65
+ ' export const modernTanstackRouterFastDefaults: Record<string, unknown>;',
66
+ '}',
67
+ ];
68
+ if (i18nRuntimeModule) {
69
+ runtimeDeclaration.push(
70
+ `declare module '${i18nRuntimeModule}' { export interface UltramodernCanonicalRoutes {} }`,
71
+ );
72
+ }
73
+ await fs.outputFile(
74
+ path.join(srcDirectory, 'generated-runtime-shim.d.ts'),
75
+ runtimeDeclaration.join('\n'),
76
+ );
77
+
78
+ const contractLines = [
79
+ `import type { Register } from '${runtimeModule}';`,
80
+ ...entries.map(
81
+ (entry, index) =>
82
+ `import { router as router${index} } from './${generatedDirName}/${entry}/router.gen';`,
83
+ ),
84
+ ...entries.map(
85
+ (_, index) =>
86
+ `const registeredRouter${index}: Register['router'] = router${index};`,
87
+ ),
88
+ ...entries.map((_, index) => `void registeredRouter${index};`),
89
+ ...canonicalTypeChecks,
90
+ ];
91
+ await fs.outputFile(
92
+ path.join(srcDirectory, 'registration-contract.ts'),
93
+ contractLines.join('\n'),
94
+ );
95
+ await fs.outputJSON(path.join(projectDirectory, 'tsconfig.json'), {
96
+ extends: strictestTsconfigPath,
97
+ compilerOptions: {
98
+ lib: ['ESNext', 'DOM'],
99
+ module: 'Preserve',
100
+ moduleResolution: 'Bundler',
101
+ noEmit: true,
102
+ target: 'ESNext',
103
+ types: [],
104
+ },
105
+ include: ['src/**/*.ts', 'src/**/*.d.ts'],
106
+ });
107
+
108
+ try {
109
+ await execFileAsync(
110
+ process.platform === 'win32' ? 'tsgo.cmd' : 'tsgo',
111
+ ['-p', 'tsconfig.json'],
112
+ {
113
+ cwd: projectDirectory,
114
+ shell: process.platform === 'win32',
115
+ },
116
+ );
117
+ } catch (error) {
118
+ if (
119
+ error &&
120
+ typeof error === 'object' &&
121
+ 'stdout' in error &&
122
+ typeof error.stdout === 'string'
123
+ ) {
124
+ throw new Error(error.stdout, { cause: error });
125
+ }
126
+ throw error;
127
+ }
128
+ }
129
+
15
130
  const runtimeCliMocks = {
16
131
  handleFileChange: rstest.fn(),
17
132
  handleGeneratorEntryCode: rstest.fn(),
@@ -86,7 +201,7 @@ describe('tanstack router cli plugin', () => {
86
201
  }
87
202
  });
88
203
 
89
- test('writes plugin-owned router types and register metadata', async () => {
204
+ test('typechecks plugin-owned routers and register metadata under strictest', async () => {
90
205
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
91
206
  const srcDirectory = path.join(tempDir, 'src');
92
207
  await mkdir(srcDirectory, { recursive: true });
@@ -102,33 +217,33 @@ describe('tanstack router cli plugin', () => {
102
217
  } as any,
103
218
  generatedDirName: 'generated-router',
104
219
  routesByEntry: {
105
- dashboard: [],
106
- main: [],
220
+ dashboard: [
221
+ {
222
+ type: 'nested',
223
+ id: 'layout',
224
+ isRoot: true,
225
+ children: [{ type: 'nested', id: 'page', index: true }],
226
+ },
227
+ ],
228
+ main: [
229
+ {
230
+ type: 'nested',
231
+ id: 'layout',
232
+ isRoot: true,
233
+ children: [{ type: 'nested', id: 'page', index: true }],
234
+ },
235
+ ],
107
236
  },
108
237
  });
109
238
 
110
- const mainRouter = await readFile(
111
- path.join(srcDirectory, 'generated-router', 'main', 'router.gen.ts'),
112
- 'utf-8',
113
- );
114
- expect(mainRouter).toContain(
115
- "} from '@modern-js/plugin-tanstack/runtime';",
116
- );
117
-
118
- const register = await readFile(
119
- path.join(srcDirectory, 'generated-router', 'register.gen.d.ts'),
120
- 'utf-8',
121
- );
122
- expect(register).toContain("from './main/router.gen'");
123
- expect(register.indexOf("from './main/router.gen'")).toBeLessThan(
124
- register.indexOf("from './dashboard/router.gen'"),
125
- );
126
- expect(register).toContain(
127
- "declare module '@modern-js/plugin-tanstack/runtime'",
128
- );
239
+ await typecheckGeneratedRegistration({
240
+ entries: ['main', 'dashboard'],
241
+ generatedDirName: 'generated-router',
242
+ srcDirectory,
243
+ });
129
244
  });
130
245
 
131
- test('can write register metadata without routes for custom entry lists', async () => {
246
+ test('typechecks register metadata for custom entry lists without routes', async () => {
132
247
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
133
248
  const srcDirectory = path.join(tempDir, 'src');
134
249
 
@@ -138,17 +253,14 @@ describe('tanstack router cli plugin', () => {
138
253
  srcDirectory,
139
254
  });
140
255
 
141
- const register = await readFile(
142
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
143
- 'utf-8',
144
- );
145
- expect(register).toContain('router: typeof router0');
146
- expect(register).toContain(
147
- "declare module '@modern-js/plugin-tanstack/runtime'",
148
- );
256
+ await typecheckGeneratedRegistration({
257
+ entries: ['main'],
258
+ generatedDirName: 'tanstack',
259
+ srcDirectory,
260
+ });
149
261
  });
150
262
 
151
- test('writes plugin-i18n module augmentation when canonicalRoutes are provided', async () => {
263
+ test('exposes canonical route params through plugin-i18n types', async () => {
152
264
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
153
265
  const srcDirectory = path.join(tempDir, 'src');
154
266
 
@@ -162,20 +274,22 @@ describe('tanstack router cli plugin', () => {
162
274
  },
163
275
  });
164
276
 
165
- const register = await readFile(
166
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
167
- 'utf-8',
168
- );
169
-
170
- expect(register).toContain(
171
- "declare module '@modern-js/plugin-i18n/runtime'",
172
- );
173
- expect(register).toContain('interface UltramodernCanonicalRoutes');
174
- expect(register).toContain("'/': Record<string, never>;");
175
- expect(register).toContain('\'/products/$slug\': { "slug": string };');
277
+ await typecheckGeneratedRegistration({
278
+ entries: ['main'],
279
+ generatedDirName: 'tanstack',
280
+ srcDirectory,
281
+ i18nRuntimeModule: '@modern-js/plugin-i18n/runtime',
282
+ canonicalTypeChecks: [
283
+ "import type { UltramodernCanonicalRoutes } from '@modern-js/plugin-i18n/runtime';",
284
+ "const rootParams: UltramodernCanonicalRoutes['/'] = {};",
285
+ "const productParams: UltramodernCanonicalRoutes['/products/$slug'] = { slug: 'tractor' };",
286
+ 'void rootParams;',
287
+ 'void productParams;',
288
+ ],
289
+ });
176
290
  });
177
291
 
178
- test('does not emit plugin-i18n augmentation when canonicalRoutes is absent (back-compat)', async () => {
292
+ test('typechecks a plain TanStack register without plugin-i18n installed', async () => {
179
293
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
180
294
  const srcDirectory = path.join(tempDir, 'src');
181
295
 
@@ -183,23 +297,16 @@ describe('tanstack router cli plugin', () => {
183
297
  entries: ['main'],
184
298
  generatedDirName: 'tanstack',
185
299
  srcDirectory,
186
- // No canonicalRoutes provided at all — plain TanStack app
187
300
  });
188
301
 
189
- const register = await readFile(
190
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
191
- 'utf-8',
192
- );
193
-
194
- expect(register).not.toContain('plugin-i18n');
195
- expect(register).not.toContain('UltramodernCanonicalRoutes');
196
- // But the standard TanStack runtime augmentation must still be present.
197
- expect(register).toContain(
198
- "declare module '@modern-js/plugin-tanstack/runtime'",
199
- );
302
+ await typecheckGeneratedRegistration({
303
+ entries: ['main'],
304
+ generatedDirName: 'tanstack',
305
+ srcDirectory,
306
+ });
200
307
  });
201
308
 
202
- test('uses a custom i18nRuntimeModule when specified', async () => {
309
+ test('exposes canonical route params through a custom i18n runtime module', async () => {
203
310
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-cli-'));
204
311
  const srcDirectory = path.join(tempDir, 'src');
205
312
 
@@ -213,15 +320,17 @@ describe('tanstack router cli plugin', () => {
213
320
  i18nRuntimeModule: '@my-org/i18n/runtime',
214
321
  });
215
322
 
216
- const register = await readFile(
217
- path.join(srcDirectory, 'tanstack', 'register.gen.d.ts'),
218
- 'utf-8',
219
- );
220
-
221
- expect(register).toContain("declare module '@my-org/i18n/runtime'");
222
- expect(register).not.toContain(
223
- "declare module '@modern-js/plugin-i18n/runtime'",
224
- );
323
+ await typecheckGeneratedRegistration({
324
+ entries: ['main'],
325
+ generatedDirName: 'tanstack',
326
+ srcDirectory,
327
+ i18nRuntimeModule: '@my-org/i18n/runtime',
328
+ canonicalTypeChecks: [
329
+ "import type { UltramodernCanonicalRoutes } from '@my-org/i18n/runtime';",
330
+ "const talksParams: UltramodernCanonicalRoutes['/talks'] = {};",
331
+ 'void talksParams;',
332
+ ],
333
+ });
225
334
  });
226
335
 
227
336
  test('claims custom routes, injects runtime plugin, and merges route specs', async () => {
@@ -576,32 +685,36 @@ describe('tanstack router cli plugin', () => {
576
685
  tanstackRouterPlugin().setup!(api as any);
577
686
  await taps.generateEntryCode({ entrypoints: [entrypoint] });
578
687
 
579
- const register = await readFile(
580
- path.join(srcDirectory, 'modern-tanstack', 'register.gen.d.ts'),
581
- 'utf-8',
688
+ const hasI18n = registeredPlugins.some(
689
+ plugin => plugin.name === '@modern-js/plugin-i18n',
582
690
  );
691
+ await typecheckGeneratedRegistration({
692
+ entries: ['main'],
693
+ generatedDirName: 'modern-tanstack',
694
+ srcDirectory,
695
+ ...(hasI18n && {
696
+ i18nRuntimeModule: '@modern-js/plugin-i18n/runtime',
697
+ }),
698
+ canonicalTypeChecks: hasI18n
699
+ ? [
700
+ "import type { UltramodernCanonicalRoutes } from '@modern-js/plugin-i18n/runtime';",
701
+ "const aboutParams: UltramodernCanonicalRoutes['/about'] = {};",
702
+ 'void aboutParams;',
703
+ ]
704
+ : [],
705
+ });
583
706
  await rm(dir, { recursive: true, force: true });
584
- return register;
585
707
  };
586
708
 
587
709
  // A hand-rolled `/:lang/` app WITHOUT plugin-i18n must not get the
588
710
  // augmentation — it would reference an unresolvable module (TS2664).
589
- const withoutI18n = await runGenerate([{ name: '@modern-js/app-tools' }]);
590
- expect(withoutI18n).not.toContain('plugin-i18n');
591
- expect(withoutI18n).not.toContain('UltramodernCanonicalRoutes');
592
- expect(withoutI18n).toContain(
593
- "declare module '@modern-js/plugin-tanstack/runtime'",
594
- );
711
+ await runGenerate([{ name: '@modern-js/app-tools' }]);
595
712
 
596
713
  // With plugin-i18n registered the canonical route map is emitted.
597
- const withI18n = await runGenerate([
714
+ await runGenerate([
598
715
  { name: '@modern-js/app-tools' },
599
716
  { name: '@modern-js/plugin-i18n' },
600
717
  ]);
601
- expect(withI18n).toContain(
602
- "declare module '@modern-js/plugin-i18n/runtime'",
603
- );
604
- expect(withI18n).toContain("'/about': Record<string, never>;");
605
718
  });
606
719
 
607
720
  test('generates plugin-owned TanStack route files through core route generation', async () => {
@@ -689,23 +802,16 @@ describe('tanstack router cli plugin', () => {
689
802
  [entrypoint],
690
803
  {
691
804
  entrypointsKey: '@modern-js/plugin-tanstack',
805
+ hydrateRscClientRoutes: true,
806
+ serverRoutesFileName: 'tanstack-routes.server.js',
692
807
  },
693
808
  );
694
809
 
695
- const routerGen = await readFile(
696
- path.join(srcDirectory, 'tanstack-generated', 'main', 'router.gen.ts'),
697
- 'utf-8',
698
- );
699
- expect(routerGen).toContain("} from '@modern-js/plugin-tanstack/runtime';");
700
- expect(routerGen).toContain('modernRouteAction: action_0');
701
-
702
- const register = await readFile(
703
- path.join(srcDirectory, 'tanstack-generated', 'register.gen.d.ts'),
704
- 'utf-8',
705
- );
706
- expect(register).toContain(
707
- "declare module '@modern-js/plugin-tanstack/runtime'",
708
- );
810
+ await typecheckGeneratedRegistration({
811
+ entries: ['main'],
812
+ generatedDirName: 'tanstack-generated',
813
+ srcDirectory,
814
+ });
709
815
  });
710
816
 
711
817
  test('regenerates plugin-owned TanStack files for scoped file changes', async () => {
@@ -199,12 +199,6 @@ describe('runtime router compatibility', () => {
199
199
  runTsgo(tsgoBin, path.join(tempRoot, 'emit.json'));
200
200
 
201
201
  const declarationPath = path.join(emittedRoot, 'router.d.ts');
202
- const declaration = readFileSync(declarationPath, 'utf8');
203
- expect(declaration).not.toMatch(
204
- /\b(?:createRouterPlugin|RouterPluginFactory)\b/u,
205
- );
206
- expect(declaration).toContain("['routerPlugin']");
207
-
208
202
  writeFileSync(
209
203
  path.join(tempRoot, 'legacyRuntimeRouter.ts'),
210
204
  readFileSync(legacyFixturePath, 'utf8'),