@tanstack/router-generator 1.120.7 → 1.121.0-alpha.11

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 (51) hide show
  1. package/dist/cjs/config.cjs +14 -14
  2. package/dist/cjs/config.cjs.map +1 -1
  3. package/dist/cjs/config.d.cts +68 -31
  4. package/dist/cjs/filesystem/physical/getRouteNodes.cjs +1 -5
  5. package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -1
  6. package/dist/cjs/filesystem/physical/getRouteNodes.d.cts +2 -2
  7. package/dist/cjs/filesystem/virtual/getRouteNodes.cjs.map +1 -1
  8. package/dist/cjs/filesystem/virtual/getRouteNodes.d.cts +2 -2
  9. package/dist/cjs/generator.cjs +160 -172
  10. package/dist/cjs/generator.cjs.map +1 -1
  11. package/dist/cjs/generator.d.cts +0 -59
  12. package/dist/cjs/index.cjs +23 -2
  13. package/dist/cjs/index.cjs.map +1 -1
  14. package/dist/cjs/index.d.cts +8 -4
  15. package/dist/cjs/template.cjs +4 -12
  16. package/dist/cjs/template.cjs.map +1 -1
  17. package/dist/cjs/template.d.cts +0 -1
  18. package/dist/cjs/types.d.cts +1 -1
  19. package/dist/cjs/utils.cjs +64 -5
  20. package/dist/cjs/utils.cjs.map +1 -1
  21. package/dist/cjs/utils.d.cts +11 -2
  22. package/dist/esm/config.d.ts +68 -31
  23. package/dist/esm/config.js +14 -14
  24. package/dist/esm/config.js.map +1 -1
  25. package/dist/esm/filesystem/physical/getRouteNodes.d.ts +2 -2
  26. package/dist/esm/filesystem/physical/getRouteNodes.js +2 -6
  27. package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -1
  28. package/dist/esm/filesystem/virtual/getRouteNodes.d.ts +2 -2
  29. package/dist/esm/filesystem/virtual/getRouteNodes.js.map +1 -1
  30. package/dist/esm/generator.d.ts +0 -59
  31. package/dist/esm/generator.js +163 -175
  32. package/dist/esm/generator.js.map +1 -1
  33. package/dist/esm/index.d.ts +8 -4
  34. package/dist/esm/index.js +25 -4
  35. package/dist/esm/index.js.map +1 -1
  36. package/dist/esm/template.d.ts +0 -1
  37. package/dist/esm/template.js +4 -12
  38. package/dist/esm/template.js.map +1 -1
  39. package/dist/esm/types.d.ts +1 -1
  40. package/dist/esm/utils.d.ts +11 -2
  41. package/dist/esm/utils.js +63 -4
  42. package/dist/esm/utils.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/config.ts +14 -11
  45. package/src/filesystem/physical/getRouteNodes.ts +13 -14
  46. package/src/filesystem/virtual/getRouteNodes.ts +18 -3
  47. package/src/generator.ts +242 -221
  48. package/src/index.ts +32 -7
  49. package/src/template.ts +4 -15
  50. package/src/types.ts +0 -1
  51. package/src/utils.ts +102 -6
package/src/generator.ts CHANGED
@@ -2,12 +2,10 @@ import path from 'node:path'
2
2
  import * as fs from 'node:fs'
3
3
  import * as fsp from 'node:fs/promises'
4
4
  import {
5
- determineInitialRoutePath,
6
5
  format,
7
6
  logging,
8
7
  multiSortBy,
9
8
  removeExt,
10
- removeTrailingSlash,
11
9
  removeUnderscores,
12
10
  replaceBackslash,
13
11
  resetRegex,
@@ -18,18 +16,12 @@ import {
18
16
  import { getRouteNodes as physicalGetRouteNodes } from './filesystem/physical/getRouteNodes'
19
17
  import { getRouteNodes as virtualGetRouteNodes } from './filesystem/virtual/getRouteNodes'
20
18
  import { rootPathId } from './filesystem/physical/rootPathId'
21
- import {
22
- defaultAPIRouteTemplate,
23
- fillTemplate,
24
- getTargetTemplate,
25
- } from './template'
19
+ import { fillTemplate, getTargetTemplate } from './template'
26
20
  import type { FsRouteType, GetRouteNodesResult, RouteNode } from './types'
27
21
  import type { Config } from './config'
28
22
 
29
- export const CONSTANTS = {
30
- // When changing this, you'll want to update the import in `start-api-routes/src/index.ts#defaultAPIFileRouteHandler`
31
- APIRouteExportVariable: 'APIRoute',
32
- }
23
+ // Maybe import this from `@tanstack/router-core` in the future???
24
+ const rootRouteId = '__root__'
33
25
 
34
26
  let latestTask = 0
35
27
  const routeGroupPatternRegex = /\(.+\)/g
@@ -76,10 +68,6 @@ export async function generator(config: Config, root: string) {
76
68
 
77
69
  const TYPES_DISABLED = config.disableTypes
78
70
 
79
- // Controls whether API Routes are generated for TanStack Start
80
- const ENABLED_API_ROUTES_GENERATION =
81
- config.__enableAPIRoutesGeneration ?? false
82
-
83
71
  let getRouteNodesResult: GetRouteNodesResult
84
72
 
85
73
  if (config.virtualRouteConfig) {
@@ -117,30 +105,6 @@ export async function generator(config: Config, root: string) {
117
105
  const routeTree: Array<RouteNode> = []
118
106
  const routePiecesByPath: Record<string, RouteSubNode> = {}
119
107
 
120
- // Filtered API Route nodes
121
- const onlyAPIRouteNodes = preRouteNodes.filter((d) => {
122
- if (!ENABLED_API_ROUTES_GENERATION) {
123
- return false
124
- }
125
-
126
- if (d._fsRouteType !== 'api') {
127
- return false
128
- }
129
-
130
- return true
131
- })
132
-
133
- // Filtered Generator Route nodes
134
- const onlyGeneratorRouteNodes = preRouteNodes.filter((d) => {
135
- if (ENABLED_API_ROUTES_GENERATION) {
136
- if (d._fsRouteType === 'api') {
137
- return false
138
- }
139
- }
140
-
141
- return true
142
- })
143
-
144
108
  // Loop over the flat list of routeNodes and
145
109
  // build up a tree based on the routeNodes' routePath
146
110
  const routeNodes: Array<RouteNode> = []
@@ -243,7 +207,7 @@ export async function generator(config: Config, root: string) {
243
207
  tLazyRouteTemplate.template(),
244
208
  {
245
209
  tsrImports: tLazyRouteTemplate.imports.tsrImports(),
246
- tsrPath: escapedRoutePath,
210
+ tsrPath: escapedRoutePath.replaceAll(/\{(.+)\}/gm, '$1'),
247
211
  tsrExportStart:
248
212
  tLazyRouteTemplate.imports.tsrExportStart(escapedRoutePath),
249
213
  tsrExportEnd: tLazyRouteTemplate.imports.tsrExportEnd(),
@@ -269,20 +233,104 @@ export async function generator(config: Config, root: string) {
269
233
  tRouteTemplate.template(),
270
234
  {
271
235
  tsrImports: tRouteTemplate.imports.tsrImports(),
272
- tsrPath: escapedRoutePath,
236
+ tsrPath: escapedRoutePath.replaceAll(/\{(.+)\}/gm, '$1'),
273
237
  tsrExportStart:
274
238
  tRouteTemplate.imports.tsrExportStart(escapedRoutePath),
275
239
  tsrExportEnd: tRouteTemplate.imports.tsrExportEnd(),
276
240
  },
277
241
  )
278
242
  }
243
+ } else if (config.verboseFileRoutes === false) {
244
+ // Check if the route file has a Route export
245
+ if (
246
+ !routeCode
247
+ .split('\n')
248
+ .some((line) => line.trim().startsWith('export const Route'))
249
+ ) {
250
+ return
251
+ }
252
+
253
+ // Update the existing route file
254
+ replaced = routeCode
255
+ .replace(
256
+ /(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
257
+ (_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
258
+ )
259
+ .replace(
260
+ new RegExp(
261
+ `(import\\s*\\{)(.*)(create(Lazy)?FileRoute)(.*)(\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
262
+ 'gs',
263
+ ),
264
+ (_, p1, p2, ___, ____, p5, p6) => {
265
+ const beforeCreateFileRoute = () => {
266
+ if (!p2) return ''
267
+
268
+ let trimmed = p2.trim()
269
+
270
+ if (trimmed.endsWith(',')) {
271
+ trimmed = trimmed.slice(0, -1)
272
+ }
273
+
274
+ return trimmed
275
+ }
276
+
277
+ const afterCreateFileRoute = () => {
278
+ if (!p5) return ''
279
+
280
+ let trimmed = p5.trim()
281
+
282
+ if (trimmed.startsWith(',')) {
283
+ trimmed = trimmed.slice(1)
284
+ }
285
+
286
+ return trimmed
287
+ }
288
+
289
+ const newImport = () => {
290
+ const before = beforeCreateFileRoute()
291
+ const after = afterCreateFileRoute()
292
+
293
+ if (!before) return after
294
+
295
+ if (!after) return before
296
+
297
+ return `${before},${after}`
298
+ }
299
+
300
+ const middle = newImport()
301
+
302
+ if (middle === '') return ''
303
+
304
+ return `${p1} ${newImport()} ${p6}`
305
+ },
306
+ )
307
+ .replace(
308
+ /create(Lazy)?FileRoute(\(\s*['"])([^\s]*)(['"],?\s*\))/g,
309
+ (_, __, p2, ___, p4) =>
310
+ `${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}`,
311
+ )
279
312
  } else {
313
+ // Check if the route file has a Route export
314
+ if (
315
+ !routeCode
316
+ .split('\n')
317
+ .some((line) => line.trim().startsWith('export const Route'))
318
+ ) {
319
+ return
320
+ }
321
+
280
322
  // Update the existing route file
281
323
  replaced = routeCode
324
+ // fix wrong ids
282
325
  .replace(
283
326
  /(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
284
327
  (_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
285
328
  )
329
+ // fix missing ids
330
+ .replace(
331
+ /((FileRoute)(\s*)(\({))/g,
332
+ (_, __, p2, p3, p4) => `${p2}('${escapedRoutePath}')${p3}${p4}`,
333
+ )
286
334
  .replace(
287
335
  new RegExp(
288
336
  `(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
@@ -296,6 +344,18 @@ export async function generator(config: Config, root: string) {
296
344
  (_, __, p2, ___, p4) =>
297
345
  `${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}${p2}${escapedRoutePath}${p4}`,
298
346
  )
347
+
348
+ // check whether the import statement is already present
349
+ const regex = new RegExp(
350
+ `(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
351
+ 'gm',
352
+ )
353
+ if (!replaced.match(regex)) {
354
+ replaced = [
355
+ `import { ${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'} } from '@tanstack/${ROUTE_TEMPLATE.subPkg}'`,
356
+ ...replaced.split('\n'),
357
+ ].join('\n')
358
+ }
299
359
  }
300
360
 
301
361
  await writeIfDifferent(node.fullPath, routeCode, replaced, {
@@ -406,77 +466,22 @@ export async function generator(config: Config, root: string) {
406
466
  routeNodes.push(node)
407
467
  }
408
468
 
409
- for (const node of onlyGeneratorRouteNodes) {
469
+ for (const node of preRouteNodes) {
410
470
  await handleNode(node)
411
471
  }
472
+
473
+ // This is run against the `preRouteNodes` array since it
474
+ // has the flattened Route nodes and not the full tree
475
+ // Since TSR allows multiple way of defining a route,
476
+ // we need to ensure that a user hasn't defined the
477
+ // same route in multiple ways (i.e. `flat`, `nested`, `virtual`)
412
478
  checkRouteFullPathUniqueness(
413
479
  preRouteNodes.filter(
414
- (d) =>
415
- d.children === undefined &&
416
- (['api', 'lazy'] satisfies Array<FsRouteType>).every(
417
- (type) => type !== d._fsRouteType,
418
- ),
480
+ (d) => d.children === undefined && 'lazy' !== d._fsRouteType,
419
481
  ),
420
482
  config,
421
483
  )
422
484
 
423
- const startAPIRouteNodes: Array<RouteNode> = checkStartAPIRoutes(
424
- onlyAPIRouteNodes,
425
- config,
426
- )
427
-
428
- const handleAPINode = async (node: RouteNode) => {
429
- const routeCode = fs.readFileSync(node.fullPath, 'utf-8')
430
-
431
- const escapedRoutePath = node.routePath?.replaceAll('$', '$$') ?? ''
432
-
433
- if (!routeCode) {
434
- const replaced = await fillTemplate(
435
- config,
436
- config.customScaffolding?.apiTemplate ?? defaultAPIRouteTemplate,
437
- {
438
- tsrImports:
439
- "import { createAPIFileRoute } from '@tanstack/react-start/api';",
440
- tsrPath: escapedRoutePath,
441
- tsrExportStart: `export const ${CONSTANTS.APIRouteExportVariable} = createAPIFileRoute('${escapedRoutePath}')(`,
442
- tsrExportEnd: ');',
443
- },
444
- )
445
-
446
- await writeIfDifferent(
447
- node.fullPath,
448
- '', // Empty string because the file doesn't exist yet
449
- replaced,
450
- {
451
- beforeWrite: () => {
452
- logger.log(`🟡 Creating ${node.fullPath}`)
453
- },
454
- },
455
- )
456
- } else {
457
- await writeIfDifferent(
458
- node.fullPath,
459
- routeCode,
460
- routeCode.replace(
461
- /(createAPIFileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
462
- (_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
463
- ),
464
- {
465
- beforeWrite: () => {
466
- logger.log(`🟡 Updating ${node.fullPath}`)
467
- },
468
- },
469
- )
470
- }
471
- }
472
-
473
- // Handle the API routes for TanStack Start
474
- if (ENABLED_API_ROUTES_GENERATION) {
475
- for (const node of startAPIRouteNodes) {
476
- await handleAPINode(node)
477
- }
478
- }
479
-
480
485
  function buildRouteTreeConfig(nodes: Array<RouteNode>, depth = 1): string {
481
486
  const children = nodes.map((node) => {
482
487
  if (node._fsRouteType === '__root') {
@@ -527,6 +532,30 @@ export async function generator(config: Config, root: string) {
527
532
  (d) => d,
528
533
  ])
529
534
 
535
+ const typeImports = Object.entries({
536
+ // Used for augmentation of regular routes
537
+ CreateFileRoute:
538
+ config.verboseFileRoutes === false &&
539
+ sortedRouteNodes.some(
540
+ (d) => isRouteNodeValidForAugmentation(d) && d._fsRouteType !== 'lazy',
541
+ ),
542
+ // Used for augmentation of lazy (`.lazy`) routes
543
+ CreateLazyFileRoute:
544
+ config.verboseFileRoutes === false &&
545
+ sortedRouteNodes.some(
546
+ (node) =>
547
+ routePiecesByPath[node.routePath!]?.lazy &&
548
+ isRouteNodeValidForAugmentation(node),
549
+ ),
550
+ // Used in the process of augmenting the routes
551
+ FileRoutesByPath:
552
+ config.verboseFileRoutes === false &&
553
+ sortedRouteNodes.some((d) => isRouteNodeValidForAugmentation(d)),
554
+ })
555
+ .filter((d) => d[1])
556
+ .map((d) => d[0])
557
+ .sort((a, b) => a.localeCompare(b))
558
+
530
559
  const imports = Object.entries({
531
560
  createFileRoute: sortedRouteNodes.some((d) => d.isVirtual),
532
561
  lazyFn: sortedRouteNodes.some(
@@ -555,14 +584,22 @@ export async function generator(config: Config, root: string) {
555
584
  ),
556
585
  )
557
586
  }
587
+
558
588
  const routeImports = [
559
589
  ...config.routeTreeFileHeader,
560
590
  `// This file was automatically generated by TanStack Router.
561
591
  // You should NOT make any changes in this file as it will be overwritten.
562
592
  // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`,
563
- imports.length
564
- ? `import { ${imports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'\n`
565
- : '',
593
+ [
594
+ imports.length
595
+ ? `import { ${imports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
596
+ : '',
597
+ !TYPES_DISABLED && typeImports.length
598
+ ? `import type { ${typeImports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
599
+ : '',
600
+ ]
601
+ .filter(Boolean)
602
+ .join('\n'),
566
603
  '// Import Routes',
567
604
  [
568
605
  `import { Route as rootRoute } from './${getImportPath(rootRouteNode)}'`,
@@ -571,7 +608,7 @@ export async function generator(config: Config, root: string) {
571
608
  .map((node) => {
572
609
  return `import { Route as ${
573
610
  node.variableName
574
- }Import } from './${getImportPath(node)}'`
611
+ }RouteImport } from './${getImportPath(node)}'`
575
612
  }),
576
613
  ].join('\n'),
577
614
  virtualRouteNodes.length ? '// Create Virtual Routes' : '',
@@ -579,7 +616,7 @@ export async function generator(config: Config, root: string) {
579
616
  .map((node) => {
580
617
  return `const ${
581
618
  node.variableName
582
- }Import = createFileRoute('${node.routePath}')()`
619
+ }RouteImport = createFileRoute('${node.routePath}')()`
583
620
  })
584
621
  .join('\n'),
585
622
  '// Create/Update Routes',
@@ -594,7 +631,8 @@ export async function generator(config: Config, root: string) {
594
631
  const lazyComponentNode = routePiecesByPath[node.routePath!]?.lazy
595
632
 
596
633
  return [
597
- `const ${node.variableName}Route = ${node.variableName}Import.update({
634
+ [
635
+ `const ${node.variableName}Route = ${node.variableName}RouteImport.update({
598
636
  ${[
599
637
  `id: '${node.path}'`,
600
638
  !node.isNonPath ? `path: '${node.cleanedPath}'` : undefined,
@@ -603,19 +641,19 @@ export async function generator(config: Config, root: string) {
603
641
  .filter(Boolean)
604
642
  .join(',')}
605
643
  }${TYPES_DISABLED ? '' : 'as any'})`,
606
- loaderNode
607
- ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
608
- removeExt(
609
- path.relative(
610
- path.dirname(config.generatedRouteTree),
611
- path.resolve(config.routesDirectory, loaderNode.filePath),
644
+ loaderNode
645
+ ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
646
+ removeExt(
647
+ path.relative(
648
+ path.dirname(config.generatedRouteTree),
649
+ path.resolve(config.routesDirectory, loaderNode.filePath),
650
+ ),
651
+ config.addExtensions,
612
652
  ),
613
- config.addExtensions,
614
- ),
615
- )}'), 'loader') })`
616
- : '',
617
- componentNode || errorComponentNode || pendingComponentNode
618
- ? `.update({
653
+ )}'), 'loader') })`
654
+ : '',
655
+ componentNode || errorComponentNode || pendingComponentNode
656
+ ? `.update({
619
657
  ${(
620
658
  [
621
659
  ['component', componentNode],
@@ -639,22 +677,23 @@ export async function generator(config: Config, root: string) {
639
677
  })
640
678
  .join('\n,')}
641
679
  })`
642
- : '',
643
- lazyComponentNode
644
- ? `.lazy(() => import('./${replaceBackslash(
645
- removeExt(
646
- path.relative(
647
- path.dirname(config.generatedRouteTree),
648
- path.resolve(
649
- config.routesDirectory,
650
- lazyComponentNode.filePath,
680
+ : '',
681
+ lazyComponentNode
682
+ ? `.lazy(() => import('./${replaceBackslash(
683
+ removeExt(
684
+ path.relative(
685
+ path.dirname(config.generatedRouteTree),
686
+ path.resolve(
687
+ config.routesDirectory,
688
+ lazyComponentNode.filePath,
689
+ ),
651
690
  ),
691
+ config.addExtensions,
652
692
  ),
653
- config.addExtensions,
654
- ),
655
- )}').then((d) => d.Route))`
656
- : '',
657
- ].join('')
693
+ )}').then((d) => d.Route))`
694
+ : '',
695
+ ].join(''),
696
+ ].join('\n\n')
658
697
  })
659
698
  .join('\n\n'),
660
699
  ...(TYPES_DISABLED
@@ -671,12 +710,12 @@ export async function generator(config: Config, root: string) {
671
710
  id: '${filePathId}'
672
711
  path: '${inferPath(routeNode)}'
673
712
  fullPath: '${inferFullPath(routeNode)}'
674
- preLoaderRoute: typeof ${routeNode.variableName}Import
713
+ preLoaderRoute: typeof ${routeNode.variableName}RouteImport
675
714
  parentRoute: typeof ${
676
715
  routeNode.isVirtualParentRequired
677
716
  ? `${routeNode.parent?.variableName}Route`
678
717
  : routeNode.parent?.variableName
679
- ? `${routeNode.parent.variableName}Import`
718
+ ? `${routeNode.parent.variableName}RouteImport`
680
719
  : 'rootRoute'
681
720
  }
682
721
  }`
@@ -685,6 +724,41 @@ export async function generator(config: Config, root: string) {
685
724
  }
686
725
  }`,
687
726
  ]),
727
+ ...(TYPES_DISABLED
728
+ ? []
729
+ : config.verboseFileRoutes !== false
730
+ ? []
731
+ : [
732
+ `// Add type-safety to the createFileRoute function across the route tree`,
733
+ routeNodes
734
+ .map((routeNode) => {
735
+ function getModuleDeclaration(routeNode?: RouteNode) {
736
+ if (!isRouteNodeValidForAugmentation(routeNode)) {
737
+ return ''
738
+ }
739
+ return `declare module './${getImportPath(routeNode)}' {
740
+ const ${routeNode._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}: ${
741
+ routeNode._fsRouteType === 'lazy'
742
+ ? `CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>}`
743
+ : `CreateFileRoute<
744
+ '${routeNode.routePath}',
745
+ FileRoutesByPath['${routeNode.routePath}']['parentRoute'],
746
+ FileRoutesByPath['${routeNode.routePath}']['id'],
747
+ FileRoutesByPath['${routeNode.routePath}']['path'],
748
+ FileRoutesByPath['${routeNode.routePath}']['fullPath']
749
+ >
750
+ }`
751
+ }`
752
+ }
753
+ return (
754
+ getModuleDeclaration(routeNode) +
755
+ getModuleDeclaration(
756
+ routePiecesByPath[routeNode.routePath!]?.lazy,
757
+ )
758
+ )
759
+ })
760
+ .join('\n'),
761
+ ]),
688
762
  '// Create and export the route tree',
689
763
  routeConfigChildrenText,
690
764
  ...(TYPES_DISABLED
@@ -703,7 +777,7 @@ export async function generator(config: Config, root: string) {
703
777
  })}
704
778
  }`,
705
779
  `export interface FileRoutesById {
706
- '__root__': typeof rootRoute,
780
+ '${rootRouteId}': typeof rootRoute,
707
781
  ${[...createRouteNodesById(routeNodes).entries()].map(([id, routeNode]) => {
708
782
  return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`
709
783
  })}
@@ -713,7 +787,7 @@ export async function generator(config: Config, root: string) {
713
787
  fullPaths: ${routeNodes.length > 0 ? [...createRouteNodesByFullPath(routeNodes).keys()].map((fullPath) => `'${fullPath}'`).join('|') : 'never'}
714
788
  fileRoutesByTo: FileRoutesByTo
715
789
  to: ${routeNodes.length > 0 ? [...createRouteNodesByTo(routeNodes).keys()].map((to) => `'${to}'`).join('|') : 'never'}
716
- id: ${[`'__root__'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
790
+ id: ${[`'${rootRouteId}'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
717
791
  fileRoutesById: FileRoutesById
718
792
  }`,
719
793
  `export interface RootRouteChildren {
@@ -731,7 +805,7 @@ export async function generator(config: Config, root: string) {
731
805
 
732
806
  const createRouteManifest = () => {
733
807
  const routesManifest = {
734
- __root__: {
808
+ [rootRouteId]: {
735
809
  filePath: rootRouteNode.filePath,
736
810
  children: routeTree.map((d) => d.routePath),
737
811
  },
@@ -827,6 +901,22 @@ function removeGroups(s: string) {
827
901
  return s.replace(possiblyNestedRouteGroupPatternRegex, '')
828
902
  }
829
903
 
904
+ /**
905
+ * Checks if a given RouteNode is valid for augmenting it with typing based on conditions.
906
+ * Also asserts that the RouteNode is defined.
907
+ *
908
+ * @param routeNode - The RouteNode to check.
909
+ * @returns A boolean indicating whether the RouteNode is defined.
910
+ */
911
+ function isRouteNodeValidForAugmentation(
912
+ routeNode?: RouteNode,
913
+ ): routeNode is RouteNode {
914
+ if (!routeNode || routeNode.isVirtual) {
915
+ return false
916
+ }
917
+ return true
918
+ }
919
+
830
920
  /**
831
921
  * The `node.path` is used as the `id` in the route definition.
832
922
  * This function checks if the given node has a parent and if so, it determines the correct path for the given node.
@@ -847,7 +937,7 @@ function determineNodePath(node: RouteNode) {
847
937
  * @example
848
938
  * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'
849
939
  */
850
- export function removeLastSegmentFromPath(routePath: string = '/'): string {
940
+ function removeLastSegmentFromPath(routePath: string = '/'): string {
851
941
  const segments = routePath.split('/')
852
942
  segments.pop() // Remove the last segment
853
943
  return segments.join('/')
@@ -867,7 +957,7 @@ function removeLayoutSegments(routePath: string = '/'): string {
867
957
  return newSegments.join('/')
868
958
  }
869
959
 
870
- export function hasParentRoute(
960
+ function hasParentRoute(
871
961
  routes: Array<RouteNode>,
872
962
  node: RouteNode,
873
963
  routePathToCheck: string | undefined,
@@ -902,9 +992,7 @@ export function hasParentRoute(
902
992
  /**
903
993
  * Gets the final variable name for a route
904
994
  */
905
- export const getResolvedRouteNodeVariableName = (
906
- routeNode: RouteNode,
907
- ): string => {
995
+ const getResolvedRouteNodeVariableName = (routeNode: RouteNode): string => {
908
996
  return routeNode.children?.length
909
997
  ? `${routeNode.variableName}RouteWithChildren`
910
998
  : `${routeNode.variableName}Route`
@@ -913,7 +1001,7 @@ export const getResolvedRouteNodeVariableName = (
913
1001
  /**
914
1002
  * Creates a map from fullPath to routeNode
915
1003
  */
916
- export const createRouteNodesByFullPath = (
1004
+ const createRouteNodesByFullPath = (
917
1005
  routeNodes: Array<RouteNode>,
918
1006
  ): Map<string, RouteNode> => {
919
1007
  return new Map(
@@ -924,7 +1012,7 @@ export const createRouteNodesByFullPath = (
924
1012
  /**
925
1013
  * Create a map from 'to' to a routeNode
926
1014
  */
927
- export const createRouteNodesByTo = (
1015
+ const createRouteNodesByTo = (
928
1016
  routeNodes: Array<RouteNode>,
929
1017
  ): Map<string, RouteNode> => {
930
1018
  return new Map(
@@ -938,7 +1026,7 @@ export const createRouteNodesByTo = (
938
1026
  /**
939
1027
  * Create a map from 'id' to a routeNode
940
1028
  */
941
- export const createRouteNodesById = (
1029
+ const createRouteNodesById = (
942
1030
  routeNodes: Array<RouteNode>,
943
1031
  ): Map<string, RouteNode> => {
944
1032
  return new Map(
@@ -952,7 +1040,7 @@ export const createRouteNodesById = (
952
1040
  /**
953
1041
  * Infers the full path for use by TS
954
1042
  */
955
- export const inferFullPath = (routeNode: RouteNode): string => {
1043
+ const inferFullPath = (routeNode: RouteNode): string => {
956
1044
  const fullPath = removeGroups(
957
1045
  removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
958
1046
  )
@@ -963,7 +1051,7 @@ export const inferFullPath = (routeNode: RouteNode): string => {
963
1051
  /**
964
1052
  * Infers the path for use by TS
965
1053
  */
966
- export const inferPath = (routeNode: RouteNode): string => {
1054
+ const inferPath = (routeNode: RouteNode): string => {
967
1055
  return routeNode.cleanedPath === '/'
968
1056
  ? routeNode.cleanedPath
969
1057
  : (routeNode.cleanedPath?.replace(/\/$/, '') ?? '')
@@ -972,7 +1060,7 @@ export const inferPath = (routeNode: RouteNode): string => {
972
1060
  /**
973
1061
  * Infers to path
974
1062
  */
975
- export const inferTo = (routeNode: RouteNode): string => {
1063
+ const inferTo = (routeNode: RouteNode): string => {
976
1064
  const fullPath = inferFullPath(routeNode)
977
1065
 
978
1066
  if (fullPath === '/') return fullPath
@@ -983,7 +1071,7 @@ export const inferTo = (routeNode: RouteNode): string => {
983
1071
  /**
984
1072
  * Dedupes branches and index routes
985
1073
  */
986
- export const dedupeBranchesAndIndexRoutes = (
1074
+ const dedupeBranchesAndIndexRoutes = (
987
1075
  routes: Array<RouteNode>,
988
1076
  ): Array<RouteNode> => {
989
1077
  return routes.filter((route) => {
@@ -1022,75 +1110,8 @@ function checkRouteFullPathUniqueness(
1022
1110
  const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
1023
1111
  .map((p) => `"${p.inferredFullPath}"`)
1024
1112
  .join(', ')}.
1025
- Please ensure each route has a unique full path.
1026
- Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
1027
- throw new Error(errorMessage)
1028
- }
1029
- }
1030
-
1031
- function checkStartAPIRoutes(_routes: Array<RouteNode>, config: Config) {
1032
- if (_routes.length === 0) {
1033
- return []
1034
- }
1035
-
1036
- // Make sure these are valid URLs
1037
- // Route Groups and Layout Routes aren't being removed since
1038
- // you may want to have an API route that starts with an underscore
1039
- // or be wrapped in parentheses
1040
- const routes = _routes.map((d) => {
1041
- const routePath = removeTrailingSlash(d.routePath ?? '')
1042
- return { ...d, routePath }
1043
- })
1044
-
1045
- const conflictingFiles = checkUnique(routes, 'routePath')
1046
-
1047
- if (conflictingFiles !== undefined) {
1048
- const errorMessage = `Conflicting configuration paths were found for the following API route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
1049
- .map((p) => `"${p}"`)
1050
- .join(', ')}.
1051
- Please ensure each API route has a unique route path.
1113
+ Please ensure each Route has a unique full path.
1052
1114
  Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
1053
1115
  throw new Error(errorMessage)
1054
1116
  }
1055
-
1056
- return routes
1057
- }
1058
-
1059
- export type StartAPIRoutePathSegment = {
1060
- value: string
1061
- type: 'path' | 'param' | 'splat'
1062
- }
1063
-
1064
- /**
1065
- * This function takes in a path in the format accepted by TanStack Router
1066
- * and returns an array of path segments that can be used to generate
1067
- * the pathname of the TanStack Start API route.
1068
- *
1069
- * @param src
1070
- * @returns
1071
- */
1072
- export function startAPIRouteSegmentsFromTSRFilePath(
1073
- src: string,
1074
- config: Config,
1075
- ): Array<StartAPIRoutePathSegment> {
1076
- const routePath = determineInitialRoutePath(src)
1077
-
1078
- const parts = routePath
1079
- .replaceAll('.', '/')
1080
- .split('/')
1081
- .filter((p) => !!p && p !== config.indexToken)
1082
- const segments: Array<StartAPIRoutePathSegment> = parts.map((part) => {
1083
- if (part.startsWith('$')) {
1084
- if (part === '$') {
1085
- return { value: part, type: 'splat' }
1086
- }
1087
-
1088
- part.replaceAll('$', '')
1089
- return { value: part, type: 'param' }
1090
- }
1091
-
1092
- return { value: part, type: 'path' }
1093
- })
1094
-
1095
- return segments
1096
1117
  }