Skip to content

Commit 328d7e5

Browse files
fix(router-generator): normalize virtual physical subtree paths (#7169)
Co-authored-by: schiller-manuel <6340397+schiller-manuel@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent c857087 commit 328d7e5

14 files changed

Lines changed: 241 additions & 46 deletions

File tree

.changeset/five-badgers-listen.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@tanstack/router-generator': patch
3+
---
4+
5+
Fix malformed generated paths when a `physical()` mount points at a subtree rooted by `__virtual.ts`, including nested virtual layouts that mount additional physical routes.

packages/router-generator/src/filesystem/physical/getRouteNodes.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from 'node:path'
22
import * as fsp from 'node:fs/promises'
33
import {
4+
cleanPath,
45
determineInitialRoutePath,
56
escapeRegExp,
67
hasEscapedLeadingUnderscore,
@@ -121,16 +122,21 @@ export async function getRouteNodes(
121122
)
122123
allPhysicalDirectories.push(...physicalDirectories)
123124
virtualRouteNodes.forEach((node) => {
124-
const filePath = replaceBackslash(path.join(dir, node.filePath))
125-
const routePath = `/${dir}${node.routePath}`
125+
const normalizedDir = dir === './' ? '' : dir
126+
const filePath = replaceBackslash(
127+
path.join(normalizedDir, node.filePath),
128+
)
129+
const routePath = cleanPath(`/${normalizedDir}${node.routePath}`)
126130

127131
node.variableName = routePathToVariable(
128-
`${dir}/${removeExt(node.filePath)}`,
132+
cleanPath(`/${normalizedDir}/${removeExt(node.filePath)}`),
129133
)
130134
node.routePath = routePath
131135
// Keep originalRoutePath aligned with routePath for escape detection
132136
if (node.originalRoutePath) {
133-
node.originalRoutePath = `/${dir}${node.originalRoutePath}`
137+
node.originalRoutePath = cleanPath(
138+
`/${normalizedDir}${node.originalRoutePath}`,
139+
)
134140
}
135141
node.filePath = filePath
136142
// Virtual subtree nodes (from __virtual.ts) are embedded in a

packages/router-generator/src/filesystem/virtual/getRouteNodes.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path, { join, resolve } from 'node:path'
22
import {
3+
cleanPath,
34
determineInitialRoutePath,
45
removeExt,
56
removeLeadingSlash,
@@ -164,10 +165,14 @@ export async function getRouteNodesRecursive(
164165
subtreeNode.variableName = routePathToVariable(
165166
`${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`,
166167
)
167-
subtreeNode.routePath = `${parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.routePath}`
168+
subtreeNode.routePath = cleanPath(
169+
`${parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.routePath}`,
170+
)
168171
// Keep originalRoutePath aligned with routePath for escape detection
169172
if (subtreeNode.originalRoutePath) {
170-
subtreeNode.originalRoutePath = `${parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.originalRoutePath}`
173+
subtreeNode.originalRoutePath = cleanPath(
174+
`${parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.originalRoutePath}`,
175+
)
171176
}
172177
subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`
173178
})

packages/router-generator/tests/generator/virtual-inside-nested/routeTree.snapshot.ts

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
import { Route as rootRouteImport } from './routes/__root'
1212
import { Route as IndexRouteImport } from './routes/index'
1313
import { Route as FooBarRouteImport } from './routes/foo/bar'
14-
import { Route as fooBarDetailsRouteImport } from './routes/foo/bar/details'
15-
import { Route as fooBarHomeRouteImport } from './routes/foo/bar/home'
14+
import { Route as FooBarDetailsRouteImport } from './routes/foo/bar/details'
15+
import { Route as FooBarHomeRouteImport } from './routes/foo/bar/home'
1616

1717
const IndexRoute = IndexRouteImport.update({
1818
id: '/',
@@ -24,12 +24,12 @@ const FooBarRoute = FooBarRouteImport.update({
2424
path: '/foo/bar',
2525
getParentRoute: () => rootRouteImport,
2626
} as any)
27-
const fooBarDetailsRoute = fooBarDetailsRouteImport.update({
27+
const FooBarDetailsRoute = FooBarDetailsRouteImport.update({
2828
id: '/$id',
2929
path: '/$id',
3030
getParentRoute: () => FooBarRoute,
3131
} as any)
32-
const fooBarHomeRoute = fooBarHomeRouteImport.update({
32+
const FooBarHomeRoute = FooBarHomeRouteImport.update({
3333
id: '/',
3434
path: '/',
3535
getParentRoute: () => FooBarRoute,
@@ -38,20 +38,20 @@ const fooBarHomeRoute = fooBarHomeRouteImport.update({
3838
export interface FileRoutesByFullPath {
3939
'/': typeof IndexRoute
4040
'/foo/bar': typeof FooBarRouteWithChildren
41-
'/foo/bar/': typeof fooBarHomeRoute
42-
'/foo/bar/$id': typeof fooBarDetailsRoute
41+
'/foo/bar/': typeof FooBarHomeRoute
42+
'/foo/bar/$id': typeof FooBarDetailsRoute
4343
}
4444
export interface FileRoutesByTo {
4545
'/': typeof IndexRoute
46-
'/foo/bar': typeof fooBarHomeRoute
47-
'/foo/bar/$id': typeof fooBarDetailsRoute
46+
'/foo/bar': typeof FooBarHomeRoute
47+
'/foo/bar/$id': typeof FooBarDetailsRoute
4848
}
4949
export interface FileRoutesById {
5050
__root__: typeof rootRouteImport
5151
'/': typeof IndexRoute
5252
'/foo/bar': typeof FooBarRouteWithChildren
53-
'/foo/bar/': typeof fooBarHomeRoute
54-
'/foo/bar/$id': typeof fooBarDetailsRoute
53+
'/foo/bar/': typeof FooBarHomeRoute
54+
'/foo/bar/$id': typeof FooBarDetailsRoute
5555
}
5656
export interface FileRouteTypes {
5757
fileRoutesByFullPath: FileRoutesByFullPath
@@ -86,27 +86,27 @@ declare module '@tanstack/react-router' {
8686
id: '/foo/bar/$id'
8787
path: '/$id'
8888
fullPath: '/foo/bar/$id'
89-
preLoaderRoute: typeof fooBarDetailsRouteImport
89+
preLoaderRoute: typeof FooBarDetailsRouteImport
9090
parentRoute: typeof FooBarRoute
9191
}
9292
'/foo/bar/': {
9393
id: '/foo/bar/'
9494
path: '/'
9595
fullPath: '/foo/bar/'
96-
preLoaderRoute: typeof fooBarHomeRouteImport
96+
preLoaderRoute: typeof FooBarHomeRouteImport
9797
parentRoute: typeof FooBarRoute
9898
}
9999
}
100100
}
101101

102102
interface FooBarRouteChildren {
103-
fooBarHomeRoute: typeof fooBarHomeRoute
104-
fooBarDetailsRoute: typeof fooBarDetailsRoute
103+
FooBarHomeRoute: typeof FooBarHomeRoute
104+
FooBarDetailsRoute: typeof FooBarDetailsRoute
105105
}
106106

107107
const FooBarRouteChildren: FooBarRouteChildren = {
108-
fooBarHomeRoute: fooBarHomeRoute,
109-
fooBarDetailsRoute: fooBarDetailsRoute,
108+
FooBarHomeRoute: FooBarHomeRoute,
109+
FooBarDetailsRoute: FooBarDetailsRoute,
110110
}
111111

112112
const FooBarRouteWithChildren =

packages/router-generator/tests/generator/virtual-inside-with-escaped-underscore/routeTree.snapshot.ts

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,49 +10,49 @@
1010

1111
import { Route as rootRouteImport } from './routes/__root'
1212
import { Route as IndexRouteImport } from './routes/index'
13-
import { Route as nestedCallbackRouteImport } from './routes/nested/callback'
14-
import { Route as nestedAuthRouteImport } from './routes/nested/auth'
15-
import { Route as nestedHomeRouteImport } from './routes/nested/home'
13+
import { Route as NestedCallbackRouteImport } from './routes/nested/callback'
14+
import { Route as NestedAuthRouteImport } from './routes/nested/auth'
15+
import { Route as NestedHomeRouteImport } from './routes/nested/home'
1616

1717
const IndexRoute = IndexRouteImport.update({
1818
id: '/',
1919
path: '/',
2020
getParentRoute: () => rootRouteImport,
2121
} as any)
22-
const nestedCallbackRoute = nestedCallbackRouteImport.update({
22+
const NestedCallbackRoute = NestedCallbackRouteImport.update({
2323
id: '/nested/_callback',
2424
path: '/nested/_callback',
2525
getParentRoute: () => rootRouteImport,
2626
} as any)
27-
const nestedAuthRoute = nestedAuthRouteImport.update({
27+
const NestedAuthRoute = NestedAuthRouteImport.update({
2828
id: '/nested/_auth',
2929
path: '/nested/_auth',
3030
getParentRoute: () => rootRouteImport,
3131
} as any)
32-
const nestedHomeRoute = nestedHomeRouteImport.update({
32+
const NestedHomeRoute = NestedHomeRouteImport.update({
3333
id: '/nested/',
3434
path: '/nested/',
3535
getParentRoute: () => rootRouteImport,
3636
} as any)
3737

3838
export interface FileRoutesByFullPath {
3939
'/': typeof IndexRoute
40-
'/nested/': typeof nestedHomeRoute
41-
'/nested/_auth': typeof nestedAuthRoute
42-
'/nested/_callback': typeof nestedCallbackRoute
40+
'/nested/': typeof NestedHomeRoute
41+
'/nested/_auth': typeof NestedAuthRoute
42+
'/nested/_callback': typeof NestedCallbackRoute
4343
}
4444
export interface FileRoutesByTo {
4545
'/': typeof IndexRoute
46-
'/nested': typeof nestedHomeRoute
47-
'/nested/_auth': typeof nestedAuthRoute
48-
'/nested/_callback': typeof nestedCallbackRoute
46+
'/nested': typeof NestedHomeRoute
47+
'/nested/_auth': typeof NestedAuthRoute
48+
'/nested/_callback': typeof NestedCallbackRoute
4949
}
5050
export interface FileRoutesById {
5151
__root__: typeof rootRouteImport
5252
'/': typeof IndexRoute
53-
'/nested/': typeof nestedHomeRoute
54-
'/nested/_auth': typeof nestedAuthRoute
55-
'/nested/_callback': typeof nestedCallbackRoute
53+
'/nested/': typeof NestedHomeRoute
54+
'/nested/_auth': typeof NestedAuthRoute
55+
'/nested/_callback': typeof NestedCallbackRoute
5656
}
5757
export interface FileRouteTypes {
5858
fileRoutesByFullPath: FileRoutesByFullPath
@@ -64,9 +64,9 @@ export interface FileRouteTypes {
6464
}
6565
export interface RootRouteChildren {
6666
IndexRoute: typeof IndexRoute
67-
nestedHomeRoute: typeof nestedHomeRoute
68-
nestedAuthRoute: typeof nestedAuthRoute
69-
nestedCallbackRoute: typeof nestedCallbackRoute
67+
NestedHomeRoute: typeof NestedHomeRoute
68+
NestedAuthRoute: typeof NestedAuthRoute
69+
NestedCallbackRoute: typeof NestedCallbackRoute
7070
}
7171

7272
declare module '@tanstack/react-router' {
@@ -82,31 +82,31 @@ declare module '@tanstack/react-router' {
8282
id: '/nested/_callback'
8383
path: '/nested/_callback'
8484
fullPath: '/nested/_callback'
85-
preLoaderRoute: typeof nestedCallbackRouteImport
85+
preLoaderRoute: typeof NestedCallbackRouteImport
8686
parentRoute: typeof rootRouteImport
8787
}
8888
'/nested/_auth': {
8989
id: '/nested/_auth'
9090
path: '/nested/_auth'
9191
fullPath: '/nested/_auth'
92-
preLoaderRoute: typeof nestedAuthRouteImport
92+
preLoaderRoute: typeof NestedAuthRouteImport
9393
parentRoute: typeof rootRouteImport
9494
}
9595
'/nested/': {
9696
id: '/nested/'
9797
path: '/nested'
9898
fullPath: '/nested/'
99-
preLoaderRoute: typeof nestedHomeRouteImport
99+
preLoaderRoute: typeof NestedHomeRouteImport
100100
parentRoute: typeof rootRouteImport
101101
}
102102
}
103103
}
104104

105105
const rootRouteChildren: RootRouteChildren = {
106106
IndexRoute: IndexRoute,
107-
nestedHomeRoute: nestedHomeRoute,
108-
nestedAuthRoute: nestedAuthRoute,
109-
nestedCallbackRoute: nestedCallbackRoute,
107+
NestedHomeRoute: NestedHomeRoute,
108+
NestedAuthRoute: NestedAuthRoute,
109+
NestedCallbackRoute: NestedCallbackRoute,
110110
}
111111
export const routeTree = rootRouteImport
112112
._addFileChildren(rootRouteChildren)

0 commit comments

Comments
 (0)