@tanstack/router-generator 1.120.3 → 1.120.4-alpha.1

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 +157 -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 +61 -2
  20. package/dist/cjs/utils.cjs.map +1 -1
  21. package/dist/cjs/utils.d.cts +4 -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 +160 -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 +4 -2
  41. package/dist/esm/utils.js +61 -2
  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 +233 -222
  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 +85 -4
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,94 @@ 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
  }
279
- } else {
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
+
280
253
  // Update the existing route file
281
254
  replaced = routeCode
282
255
  .replace(
283
256
  /(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
284
257
  (_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
285
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
+ )
312
+ } else {
313
+ replaced = routeCode
314
+ // fix wrong ids
315
+ .replace(
316
+ /(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
317
+ (_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
318
+ )
319
+ // fix missing ids
320
+ .replace(
321
+ /((FileRoute)(\s*)(\({))/g,
322
+ (_, __, p2, p3, p4) => `${p2}('${escapedRoutePath}')${p3}${p4}`,
323
+ )
286
324
  .replace(
287
325
  new RegExp(
288
326
  `(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
@@ -296,6 +334,18 @@ export async function generator(config: Config, root: string) {
296
334
  (_, __, p2, ___, p4) =>
297
335
  `${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}${p2}${escapedRoutePath}${p4}`,
298
336
  )
337
+
338
+ // check whether the import statement is already present
339
+ const regex = new RegExp(
340
+ `(import\\s*\\{.*)(create(Lazy)?FileRoute)(.*\\}\\s*from\\s*['"]@tanstack\\/${ROUTE_TEMPLATE.subPkg}['"])`,
341
+ 'gm',
342
+ )
343
+ if (!replaced.match(regex)) {
344
+ replaced = [
345
+ `import { ${node._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'} } from '@tanstack/${ROUTE_TEMPLATE.subPkg}'`,
346
+ ...replaced.split('\n'),
347
+ ].join('\n')
348
+ }
299
349
  }
300
350
 
301
351
  await writeIfDifferent(node.fullPath, routeCode, replaced, {
@@ -406,77 +456,22 @@ export async function generator(config: Config, root: string) {
406
456
  routeNodes.push(node)
407
457
  }
408
458
 
409
- for (const node of onlyGeneratorRouteNodes) {
459
+ for (const node of preRouteNodes) {
410
460
  await handleNode(node)
411
461
  }
462
+
463
+ // This is run against the `preRouteNodes` array since it
464
+ // has the flattened Route nodes and not the full tree
465
+ // Since TSR allows multiple way of defining a route,
466
+ // we need to ensure that a user hasn't defined the
467
+ // same route in multiple ways (i.e. `flat`, `nested`, `virtual`)
412
468
  checkRouteFullPathUniqueness(
413
469
  preRouteNodes.filter(
414
- (d) =>
415
- d.children === undefined &&
416
- (['api', 'lazy'] satisfies Array<FsRouteType>).every(
417
- (type) => type !== d._fsRouteType,
418
- ),
470
+ (d) => d.children === undefined && 'lazy' !== d._fsRouteType,
419
471
  ),
420
472
  config,
421
473
  )
422
474
 
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
475
  function buildRouteTreeConfig(nodes: Array<RouteNode>, depth = 1): string {
481
476
  const children = nodes.map((node) => {
482
477
  if (node._fsRouteType === '__root') {
@@ -527,6 +522,30 @@ export async function generator(config: Config, root: string) {
527
522
  (d) => d,
528
523
  ])
529
524
 
525
+ const typeImports = Object.entries({
526
+ // Used for augmentation of regular routes
527
+ CreateFileRoute:
528
+ config.verboseFileRoutes === false &&
529
+ sortedRouteNodes.some(
530
+ (d) => isRouteNodeValidForAugmentation(d) && d._fsRouteType !== 'lazy',
531
+ ),
532
+ // Used for augmentation of lazy (`.lazy`) routes
533
+ CreateLazyFileRoute:
534
+ config.verboseFileRoutes === false &&
535
+ sortedRouteNodes.some(
536
+ (node) =>
537
+ routePiecesByPath[node.routePath!]?.lazy &&
538
+ isRouteNodeValidForAugmentation(node),
539
+ ),
540
+ // Used in the process of augmenting the routes
541
+ FileRoutesByPath:
542
+ config.verboseFileRoutes === false &&
543
+ sortedRouteNodes.some((d) => isRouteNodeValidForAugmentation(d)),
544
+ })
545
+ .filter((d) => d[1])
546
+ .map((d) => d[0])
547
+ .sort((a, b) => a.localeCompare(b))
548
+
530
549
  const imports = Object.entries({
531
550
  createFileRoute: sortedRouteNodes.some((d) => d.isVirtual),
532
551
  lazyFn: sortedRouteNodes.some(
@@ -555,14 +574,22 @@ export async function generator(config: Config, root: string) {
555
574
  ),
556
575
  )
557
576
  }
577
+
558
578
  const routeImports = [
559
579
  ...config.routeTreeFileHeader,
560
580
  `// This file was automatically generated by TanStack Router.
561
581
  // You should NOT make any changes in this file as it will be overwritten.
562
582
  // 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
- : '',
583
+ [
584
+ imports.length
585
+ ? `import { ${imports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
586
+ : '',
587
+ !TYPES_DISABLED && typeImports.length
588
+ ? `import type { ${typeImports.join(', ')} } from '${ROUTE_TEMPLATE.fullPkg}'`
589
+ : '',
590
+ ]
591
+ .filter(Boolean)
592
+ .join('\n'),
566
593
  '// Import Routes',
567
594
  [
568
595
  `import { Route as rootRoute } from './${getImportPath(rootRouteNode)}'`,
@@ -571,7 +598,7 @@ export async function generator(config: Config, root: string) {
571
598
  .map((node) => {
572
599
  return `import { Route as ${
573
600
  node.variableName
574
- }Import } from './${getImportPath(node)}'`
601
+ }RouteImport } from './${getImportPath(node)}'`
575
602
  }),
576
603
  ].join('\n'),
577
604
  virtualRouteNodes.length ? '// Create Virtual Routes' : '',
@@ -579,7 +606,7 @@ export async function generator(config: Config, root: string) {
579
606
  .map((node) => {
580
607
  return `const ${
581
608
  node.variableName
582
- }Import = createFileRoute('${node.routePath}')()`
609
+ }RouteImport = createFileRoute('${node.routePath}')()`
583
610
  })
584
611
  .join('\n'),
585
612
  '// Create/Update Routes',
@@ -594,7 +621,8 @@ export async function generator(config: Config, root: string) {
594
621
  const lazyComponentNode = routePiecesByPath[node.routePath!]?.lazy
595
622
 
596
623
  return [
597
- `const ${node.variableName}Route = ${node.variableName}Import.update({
624
+ [
625
+ `const ${node.variableName}Route = ${node.variableName}RouteImport.update({
598
626
  ${[
599
627
  `id: '${node.path}'`,
600
628
  !node.isNonPath ? `path: '${node.cleanedPath}'` : undefined,
@@ -603,19 +631,19 @@ export async function generator(config: Config, root: string) {
603
631
  .filter(Boolean)
604
632
  .join(',')}
605
633
  }${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),
634
+ loaderNode
635
+ ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash(
636
+ removeExt(
637
+ path.relative(
638
+ path.dirname(config.generatedRouteTree),
639
+ path.resolve(config.routesDirectory, loaderNode.filePath),
640
+ ),
641
+ config.addExtensions,
612
642
  ),
613
- config.addExtensions,
614
- ),
615
- )}'), 'loader') })`
616
- : '',
617
- componentNode || errorComponentNode || pendingComponentNode
618
- ? `.update({
643
+ )}'), 'loader') })`
644
+ : '',
645
+ componentNode || errorComponentNode || pendingComponentNode
646
+ ? `.update({
619
647
  ${(
620
648
  [
621
649
  ['component', componentNode],
@@ -639,22 +667,23 @@ export async function generator(config: Config, root: string) {
639
667
  })
640
668
  .join('\n,')}
641
669
  })`
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,
670
+ : '',
671
+ lazyComponentNode
672
+ ? `.lazy(() => import('./${replaceBackslash(
673
+ removeExt(
674
+ path.relative(
675
+ path.dirname(config.generatedRouteTree),
676
+ path.resolve(
677
+ config.routesDirectory,
678
+ lazyComponentNode.filePath,
679
+ ),
651
680
  ),
681
+ config.addExtensions,
652
682
  ),
653
- config.addExtensions,
654
- ),
655
- )}').then((d) => d.Route))`
656
- : '',
657
- ].join('')
683
+ )}').then((d) => d.Route))`
684
+ : '',
685
+ ].join(''),
686
+ ].join('\n\n')
658
687
  })
659
688
  .join('\n\n'),
660
689
  ...(TYPES_DISABLED
@@ -671,12 +700,12 @@ export async function generator(config: Config, root: string) {
671
700
  id: '${filePathId}'
672
701
  path: '${inferPath(routeNode)}'
673
702
  fullPath: '${inferFullPath(routeNode)}'
674
- preLoaderRoute: typeof ${routeNode.variableName}Import
703
+ preLoaderRoute: typeof ${routeNode.variableName}RouteImport
675
704
  parentRoute: typeof ${
676
705
  routeNode.isVirtualParentRequired
677
706
  ? `${routeNode.parent?.variableName}Route`
678
707
  : routeNode.parent?.variableName
679
- ? `${routeNode.parent.variableName}Import`
708
+ ? `${routeNode.parent.variableName}RouteImport`
680
709
  : 'rootRoute'
681
710
  }
682
711
  }`
@@ -685,6 +714,41 @@ export async function generator(config: Config, root: string) {
685
714
  }
686
715
  }`,
687
716
  ]),
717
+ ...(TYPES_DISABLED
718
+ ? []
719
+ : config.verboseFileRoutes !== false
720
+ ? []
721
+ : [
722
+ `// Add type-safety to the createFileRoute function across the route tree`,
723
+ routeNodes
724
+ .map((routeNode) => {
725
+ function getModuleDeclaration(routeNode?: RouteNode) {
726
+ if (!isRouteNodeValidForAugmentation(routeNode)) {
727
+ return ''
728
+ }
729
+ return `declare module './${getImportPath(routeNode)}' {
730
+ const ${routeNode._fsRouteType === 'lazy' ? 'createLazyFileRoute' : 'createFileRoute'}: ${
731
+ routeNode._fsRouteType === 'lazy'
732
+ ? `CreateLazyFileRoute<FileRoutesByPath['${routeNode.routePath}']['preLoaderRoute']>}`
733
+ : `CreateFileRoute<
734
+ '${routeNode.routePath}',
735
+ FileRoutesByPath['${routeNode.routePath}']['parentRoute'],
736
+ FileRoutesByPath['${routeNode.routePath}']['id'],
737
+ FileRoutesByPath['${routeNode.routePath}']['path'],
738
+ FileRoutesByPath['${routeNode.routePath}']['fullPath']
739
+ >
740
+ }`
741
+ }`
742
+ }
743
+ return (
744
+ getModuleDeclaration(routeNode) +
745
+ getModuleDeclaration(
746
+ routePiecesByPath[routeNode.routePath!]?.lazy,
747
+ )
748
+ )
749
+ })
750
+ .join('\n'),
751
+ ]),
688
752
  '// Create and export the route tree',
689
753
  routeConfigChildrenText,
690
754
  ...(TYPES_DISABLED
@@ -703,7 +767,7 @@ export async function generator(config: Config, root: string) {
703
767
  })}
704
768
  }`,
705
769
  `export interface FileRoutesById {
706
- '__root__': typeof rootRoute,
770
+ '${rootRouteId}': typeof rootRoute,
707
771
  ${[...createRouteNodesById(routeNodes).entries()].map(([id, routeNode]) => {
708
772
  return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}`
709
773
  })}
@@ -713,7 +777,7 @@ export async function generator(config: Config, root: string) {
713
777
  fullPaths: ${routeNodes.length > 0 ? [...createRouteNodesByFullPath(routeNodes).keys()].map((fullPath) => `'${fullPath}'`).join('|') : 'never'}
714
778
  fileRoutesByTo: FileRoutesByTo
715
779
  to: ${routeNodes.length > 0 ? [...createRouteNodesByTo(routeNodes).keys()].map((to) => `'${to}'`).join('|') : 'never'}
716
- id: ${[`'__root__'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
780
+ id: ${[`'${rootRouteId}'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')}
717
781
  fileRoutesById: FileRoutesById
718
782
  }`,
719
783
  `export interface RootRouteChildren {
@@ -731,7 +795,7 @@ export async function generator(config: Config, root: string) {
731
795
 
732
796
  const createRouteManifest = () => {
733
797
  const routesManifest = {
734
- __root__: {
798
+ [rootRouteId]: {
735
799
  filePath: rootRouteNode.filePath,
736
800
  children: routeTree.map((d) => d.routePath),
737
801
  },
@@ -827,6 +891,22 @@ function removeGroups(s: string) {
827
891
  return s.replace(possiblyNestedRouteGroupPatternRegex, '')
828
892
  }
829
893
 
894
+ /**
895
+ * Checks if a given RouteNode is valid for augmenting it with typing based on conditions.
896
+ * Also asserts that the RouteNode is defined.
897
+ *
898
+ * @param routeNode - The RouteNode to check.
899
+ * @returns A boolean indicating whether the RouteNode is defined.
900
+ */
901
+ function isRouteNodeValidForAugmentation(
902
+ routeNode?: RouteNode,
903
+ ): routeNode is RouteNode {
904
+ if (!routeNode || routeNode.isVirtual) {
905
+ return false
906
+ }
907
+ return true
908
+ }
909
+
830
910
  /**
831
911
  * The `node.path` is used as the `id` in the route definition.
832
912
  * This function checks if the given node has a parent and if so, it determines the correct path for the given node.
@@ -847,7 +927,7 @@ function determineNodePath(node: RouteNode) {
847
927
  * @example
848
928
  * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'
849
929
  */
850
- export function removeLastSegmentFromPath(routePath: string = '/'): string {
930
+ function removeLastSegmentFromPath(routePath: string = '/'): string {
851
931
  const segments = routePath.split('/')
852
932
  segments.pop() // Remove the last segment
853
933
  return segments.join('/')
@@ -867,7 +947,7 @@ function removeLayoutSegments(routePath: string = '/'): string {
867
947
  return newSegments.join('/')
868
948
  }
869
949
 
870
- export function hasParentRoute(
950
+ function hasParentRoute(
871
951
  routes: Array<RouteNode>,
872
952
  node: RouteNode,
873
953
  routePathToCheck: string | undefined,
@@ -902,9 +982,7 @@ export function hasParentRoute(
902
982
  /**
903
983
  * Gets the final variable name for a route
904
984
  */
905
- export const getResolvedRouteNodeVariableName = (
906
- routeNode: RouteNode,
907
- ): string => {
985
+ const getResolvedRouteNodeVariableName = (routeNode: RouteNode): string => {
908
986
  return routeNode.children?.length
909
987
  ? `${routeNode.variableName}RouteWithChildren`
910
988
  : `${routeNode.variableName}Route`
@@ -913,7 +991,7 @@ export const getResolvedRouteNodeVariableName = (
913
991
  /**
914
992
  * Creates a map from fullPath to routeNode
915
993
  */
916
- export const createRouteNodesByFullPath = (
994
+ const createRouteNodesByFullPath = (
917
995
  routeNodes: Array<RouteNode>,
918
996
  ): Map<string, RouteNode> => {
919
997
  return new Map(
@@ -924,7 +1002,7 @@ export const createRouteNodesByFullPath = (
924
1002
  /**
925
1003
  * Create a map from 'to' to a routeNode
926
1004
  */
927
- export const createRouteNodesByTo = (
1005
+ const createRouteNodesByTo = (
928
1006
  routeNodes: Array<RouteNode>,
929
1007
  ): Map<string, RouteNode> => {
930
1008
  return new Map(
@@ -938,7 +1016,7 @@ export const createRouteNodesByTo = (
938
1016
  /**
939
1017
  * Create a map from 'id' to a routeNode
940
1018
  */
941
- export const createRouteNodesById = (
1019
+ const createRouteNodesById = (
942
1020
  routeNodes: Array<RouteNode>,
943
1021
  ): Map<string, RouteNode> => {
944
1022
  return new Map(
@@ -952,7 +1030,7 @@ export const createRouteNodesById = (
952
1030
  /**
953
1031
  * Infers the full path for use by TS
954
1032
  */
955
- export const inferFullPath = (routeNode: RouteNode): string => {
1033
+ const inferFullPath = (routeNode: RouteNode): string => {
956
1034
  const fullPath = removeGroups(
957
1035
  removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',
958
1036
  )
@@ -963,7 +1041,7 @@ export const inferFullPath = (routeNode: RouteNode): string => {
963
1041
  /**
964
1042
  * Infers the path for use by TS
965
1043
  */
966
- export const inferPath = (routeNode: RouteNode): string => {
1044
+ const inferPath = (routeNode: RouteNode): string => {
967
1045
  return routeNode.cleanedPath === '/'
968
1046
  ? routeNode.cleanedPath
969
1047
  : (routeNode.cleanedPath?.replace(/\/$/, '') ?? '')
@@ -972,7 +1050,7 @@ export const inferPath = (routeNode: RouteNode): string => {
972
1050
  /**
973
1051
  * Infers to path
974
1052
  */
975
- export const inferTo = (routeNode: RouteNode): string => {
1053
+ const inferTo = (routeNode: RouteNode): string => {
976
1054
  const fullPath = inferFullPath(routeNode)
977
1055
 
978
1056
  if (fullPath === '/') return fullPath
@@ -983,7 +1061,7 @@ export const inferTo = (routeNode: RouteNode): string => {
983
1061
  /**
984
1062
  * Dedupes branches and index routes
985
1063
  */
986
- export const dedupeBranchesAndIndexRoutes = (
1064
+ const dedupeBranchesAndIndexRoutes = (
987
1065
  routes: Array<RouteNode>,
988
1066
  ): Array<RouteNode> => {
989
1067
  return routes.filter((route) => {
@@ -1022,75 +1100,8 @@ function checkRouteFullPathUniqueness(
1022
1100
  const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles
1023
1101
  .map((p) => `"${p.inferredFullPath}"`)
1024
1102
  .join(', ')}.
1025
- Please ensure each route has a unique full path.
1103
+ Please ensure each Route has a unique full path.
1026
1104
  Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
1027
1105
  throw new Error(errorMessage)
1028
1106
  }
1029
1107
  }
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.
1052
- Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n`
1053
- throw new Error(errorMessage)
1054
- }
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
- }