@tanstack/router-generator 1.167.7 → 1.167.8

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.
@@ -54,12 +54,15 @@ async function getRouteNodes(config, root, tokenRegexes) {
54
54
  virtualRouteNodes.forEach((node) => {
55
55
  const normalizedDir = dir === "./" ? "" : dir;
56
56
  const filePath = require_utils.replaceBackslash(node_path.default.join(normalizedDir, node.filePath));
57
- const prefixPath = require_utils.cleanPath(`/${normalizedDir}`);
58
- const routePath = require_utils.cleanPath(`/${normalizedDir}${node.routePath}`);
59
- node.variableName = require_utils.routePathToVariable(require_utils.cleanPath(`/${normalizedDir}/${require_utils.removeExt(node.filePath)}`));
60
- node._routePathSegmentMetadata = require_utils.joinRoutePathSegmentMetadata(routePath, prefixPath, void 0, node._routePathSegmentMetadata);
57
+ const { routePath: prefixPath, originalRoutePath: originalPrefixPath } = normalizedDir ? require_utils.determineInitialRoutePath(normalizedDir) : {
58
+ routePath: "",
59
+ originalRoutePath: ""
60
+ };
61
+ const routePath = require_utils.cleanPath(`${prefixPath}${node.routePath}`);
62
+ node.variableName = require_utils.routePathToVariable(require_utils.cleanPath(`${prefixPath}/${require_utils.removeExt(node.filePath)}`));
63
+ node._routePathSegmentMetadata = require_utils.joinRoutePathSegmentMetadata(routePath, prefixPath, require_utils.createRoutePathSegmentMetadata(prefixPath, originalPrefixPath), node._routePathSegmentMetadata);
61
64
  node.routePath = routePath;
62
- if (node.originalRoutePath) node.originalRoutePath = require_utils.cleanPath(`/${normalizedDir}${node.originalRoutePath}`);
65
+ if (node.originalRoutePath) node.originalRoutePath = require_utils.cleanPath(`${originalPrefixPath}${node.originalRoutePath}`);
63
66
  node.filePath = filePath;
64
67
  delete node._virtualParentRoutePath;
65
68
  });
@@ -134,7 +137,6 @@ async function getRouteNodes(config, root, tokenRegexes) {
134
137
  const updatedLastRouteSegment = updatedRouteSegments[updatedRouteSegments.length - 1] || "";
135
138
  if (indexTokenSegmentRegex.test(updatedLastRouteSegment)) {
136
139
  if (routePathSegments.length === 1) routePath = "/";
137
- if (lastOriginalSegment === updatedLastRouteSegment) originalRoutePath = "/";
138
140
  const isLayoutRoute = routeType === "layout";
139
141
  routePath = routePath.replace(new RegExp(`/${require_utils.escapeRegExp(updatedLastRouteSegment)}$`), "/") || (isLayoutRoute ? "" : "/");
140
142
  originalRoutePath = originalRoutePath.replace(new RegExp(`/${require_utils.escapeRegExp(indexTokenCandidate)}$`), "/") || (isLayoutRoute ? "" : "/");
@@ -1 +1 @@
1
- {"version":3,"file":"getRouteNodes.cjs","names":[],"sources":["../../../../src/filesystem/physical/getRouteNodes.ts"],"sourcesContent":["import path from 'node:path'\nimport * as fsp from 'node:fs/promises'\nimport {\n cleanPath,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n escapeRegExp,\n hasEscapedLeadingUnderscore,\n joinRoutePathSegmentMetadata,\n removeExt,\n replaceBackslash,\n routePathToVariable,\n unwrapBracketWrappedSegment,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesVirtual } from '../virtual/getRouteNodes'\nimport { loadConfigFile } from '../virtual/loadConfigFile'\nimport { logging } from '../../logger'\nimport { rootPathId } from './rootPathId'\nimport type {\n VirtualRootRoute,\n VirtualRouteSubtreeConfig,\n} from '@tanstack/virtual-file-routes'\nimport type { FsRouteType, GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\n\n/**\n * Pre-compiled segment regexes for matching token patterns against route segments.\n * These are created once (in Generator constructor) and passed through to avoid\n * repeated regex compilation during route crawling.\n */\nexport interface TokenRegexBundle {\n indexTokenSegmentRegex: RegExp\n routeTokenSegmentRegex: RegExp\n}\n\nconst disallowedRouteGroupConfiguration = /\\(([^)]+)\\).(ts|js|tsx|jsx|vue)/\n\nconst virtualConfigFileRegExp = /__virtual\\.[mc]?[jt]s$/\nexport function isVirtualConfigFile(fileName: string): boolean {\n return virtualConfigFileRegExp.test(fileName)\n}\n\nexport async function getRouteNodes(\n config: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFilePrefix'\n | 'routeFileIgnorePrefix'\n | 'routeFileIgnorePattern'\n | 'disableLogging'\n | 'routeToken'\n | 'indexToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const { routeFilePrefix, routeFileIgnorePrefix, routeFileIgnorePattern } =\n config\n\n const logger = logging({ disabled: config.disableLogging })\n const routeFileIgnoreRegExp = new RegExp(routeFileIgnorePattern ?? '', 'g')\n\n const routeNodes: Array<RouteNode> = []\n const allPhysicalDirectories: Array<string> = []\n\n async function recurse(dir: string) {\n const fullDir = path.resolve(config.routesDirectory, dir)\n let dirList = await fsp.readdir(fullDir, { withFileTypes: true })\n\n dirList = dirList.filter((d) => {\n if (\n d.name.startsWith('.') ||\n (routeFileIgnorePrefix && d.name.startsWith(routeFileIgnorePrefix))\n ) {\n return false\n }\n\n if (routeFilePrefix) {\n if (routeFileIgnorePattern) {\n return (\n d.name.startsWith(routeFilePrefix) &&\n !d.name.match(routeFileIgnoreRegExp)\n )\n }\n\n return d.name.startsWith(routeFilePrefix)\n }\n\n if (routeFileIgnorePattern) {\n return !d.name.match(routeFileIgnoreRegExp)\n }\n\n return true\n })\n\n const virtualConfigFile = dirList.find((dirent) => {\n return dirent.isFile() && isVirtualConfigFile(dirent.name)\n })\n\n if (virtualConfigFile !== undefined) {\n const virtualRouteConfigExport = await loadConfigFile(\n path.resolve(fullDir, virtualConfigFile.name),\n )\n let virtualRouteSubtreeConfig: VirtualRouteSubtreeConfig\n if (typeof virtualRouteConfigExport.default === 'function') {\n virtualRouteSubtreeConfig = await virtualRouteConfigExport.default()\n } else {\n virtualRouteSubtreeConfig = virtualRouteConfigExport.default\n }\n const dummyRoot: VirtualRootRoute = {\n type: 'root',\n file: '',\n children: virtualRouteSubtreeConfig,\n }\n const { routeNodes: virtualRouteNodes, physicalDirectories } =\n await getRouteNodesVirtual(\n {\n ...config,\n routesDirectory: fullDir,\n virtualRouteConfig: dummyRoot,\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(...physicalDirectories)\n virtualRouteNodes.forEach((node) => {\n const normalizedDir = dir === './' ? '' : dir\n const filePath = replaceBackslash(\n path.join(normalizedDir, node.filePath),\n )\n const prefixPath = cleanPath(`/${normalizedDir}`)\n const routePath = cleanPath(`/${normalizedDir}${node.routePath}`)\n\n node.variableName = routePathToVariable(\n cleanPath(`/${normalizedDir}/${removeExt(node.filePath)}`),\n )\n node._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n prefixPath,\n undefined,\n node._routePathSegmentMetadata,\n )\n node.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (node.originalRoutePath) {\n node.originalRoutePath = cleanPath(\n `/${normalizedDir}${node.originalRoutePath}`,\n )\n }\n node.filePath = filePath\n // Virtual subtree nodes (from __virtual.ts) are embedded in a\n // physical directory tree. They should use path-based parent\n // inference, not the explicit virtual parent tracking. Clear any\n // _virtualParentRoutePath that was set at construction time.\n delete node._virtualParentRoutePath\n })\n\n routeNodes.push(...virtualRouteNodes)\n\n return\n }\n\n await Promise.all(\n dirList.map(async (dirent) => {\n const fullPath = replaceBackslash(path.join(fullDir, dirent.name))\n const relativePath = path.posix.join(dir, dirent.name)\n\n if (dirent.isDirectory()) {\n await recurse(relativePath)\n } else if (fullPath.match(/\\.(tsx|ts|jsx|js|vue)$/)) {\n const filePath = replaceBackslash(path.join(dir, dirent.name))\n const filePathNoExt = removeExt(filePath)\n const {\n routePath: initialRoutePath,\n originalRoutePath: initialOriginalRoutePath,\n } = determineInitialRoutePath(filePathNoExt)\n\n let routePath = initialRoutePath\n let originalRoutePath = initialOriginalRoutePath\n\n if (routeFilePrefix) {\n routePath = routePath.replaceAll(routeFilePrefix, '')\n originalRoutePath = originalRoutePath.replaceAll(\n routeFilePrefix,\n '',\n )\n }\n\n if (disallowedRouteGroupConfiguration.test(dirent.name)) {\n const errorMessage = `A route configuration for a route group was found at \\`${filePath}\\`. This is not supported. Did you mean to use a layout/pathless route instead?`\n logger.error(`ERROR: ${errorMessage}`)\n throw new Error(errorMessage)\n }\n\n const meta = getRouteMeta(routePath, originalRoutePath, tokenRegexes)\n const variableName = meta.variableName\n let routeType: FsRouteType = meta.fsRouteType\n\n if (routeType === 'lazy') {\n routePath = routePath.replace(/\\/lazy$/, '')\n originalRoutePath = originalRoutePath.replace(/\\/lazy$/, '')\n }\n\n // this check needs to happen after the lazy route has been cleaned up\n // since the routePath is used to determine if a route is pathless\n if (\n isValidPathlessLayoutRoute(\n routePath,\n originalRoutePath,\n routeType,\n tokenRegexes,\n )\n ) {\n routeType = 'pathless_layout'\n }\n\n // Only show deprecation warning for .tsx/.ts files, not .vue files\n // Vue files using .component.vue is the Vue-native way\n const isVueFile = filePath.endsWith('.vue')\n if (!isVueFile) {\n ;(\n [\n ['component', 'component'],\n ['errorComponent', 'errorComponent'],\n ['notFoundComponent', 'notFoundComponent'],\n ['pendingComponent', 'pendingComponent'],\n ['loader', 'loader'],\n ] satisfies Array<[FsRouteType, string]>\n ).forEach(([matcher, type]) => {\n if (routeType === matcher) {\n logger.warn(\n `WARNING: The \\`.${type}.tsx\\` suffix used for the ${filePath} file is deprecated. Use the new \\`.lazy.tsx\\` suffix instead.`,\n )\n }\n })\n }\n\n // Get the last segment of originalRoutePath to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegmentForSuffix =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } =\n tokenRegexes\n\n // List of special suffixes that can be escaped\n const specialSuffixes = [\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ]\n\n const routePathSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment =\n routePathSegments[routePathSegments.length - 1] || ''\n\n const suffixToStrip = specialSuffixes.find((suffix) => {\n const endsWithSuffix = routePath.endsWith(`/${suffix}`)\n // A suffix is escaped if wrapped in brackets in the original: [lazy] means literal \"lazy\"\n const isEscaped =\n lastOriginalSegmentForSuffix.startsWith('[') &&\n lastOriginalSegmentForSuffix.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegmentForSuffix) ===\n suffix\n return endsWithSuffix && !isEscaped\n })\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(\n lastOriginalSegmentForSuffix,\n )\n const isRouteTokenEscaped =\n lastOriginalSegmentForSuffix !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n const shouldStripRouteToken =\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n !isRouteTokenEscaped\n\n if (suffixToStrip || shouldStripRouteToken) {\n const stripSegment = suffixToStrip ?? lastRouteSegment\n routePath = routePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n originalRoutePath = originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n }\n\n // Check if the index token should be treated specially or as a literal path\n // Escaping stays literal-only: if the last original segment is bracket-wrapped,\n // treat it as literal even if it matches the token regex.\n const lastOriginalSegment =\n originalRoutePath.split('/').filter(Boolean).pop() || ''\n\n const indexTokenCandidate =\n unwrapBracketWrappedSegment(lastOriginalSegment)\n const isIndexEscaped =\n lastOriginalSegment !== indexTokenCandidate &&\n indexTokenSegmentRegex.test(indexTokenCandidate)\n\n if (!isIndexEscaped) {\n const updatedRouteSegments = routePath.split('/').filter(Boolean)\n const updatedLastRouteSegment =\n updatedRouteSegments[updatedRouteSegments.length - 1] || ''\n\n if (indexTokenSegmentRegex.test(updatedLastRouteSegment)) {\n if (routePathSegments.length === 1) {\n routePath = '/'\n }\n\n if (lastOriginalSegment === updatedLastRouteSegment) {\n originalRoutePath = '/'\n }\n\n // For layout routes, don't use '/' fallback - an empty path means\n // \"layout for the parent path\" which is important for physical() mounts\n // where route.tsx at root should have empty path, not '/'\n const isLayoutRoute = routeType === 'layout'\n\n routePath =\n routePath.replace(\n new RegExp(`/${escapeRegExp(updatedLastRouteSegment)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n\n originalRoutePath =\n originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(indexTokenCandidate)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n }\n }\n\n routeNodes.push({\n filePath,\n fullPath,\n routePath,\n variableName,\n _fsRouteType: routeType,\n originalRoutePath,\n _routePathSegmentMetadata: createRoutePathSegmentMetadata(\n routePath,\n originalRoutePath,\n ),\n })\n }\n }),\n )\n\n return routeNodes\n }\n\n await recurse('./')\n\n // Find the root route node - prefer the actual route file over component/loader files\n const rootRouteNode =\n routeNodes.find(\n (d) =>\n d.routePath === `/${rootPathId}` &&\n ![\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ].includes(d._fsRouteType),\n ) ?? routeNodes.find((d) => d.routePath === `/${rootPathId}`)\n if (rootRouteNode) {\n rootRouteNode._fsRouteType = '__root'\n rootRouteNode.variableName = 'root'\n }\n\n return {\n rootRouteNode,\n routeNodes,\n physicalDirectories: allPhysicalDirectories,\n }\n}\n\n/**\n * Determines the metadata for a given route path based on the provided configuration.\n *\n * @param routePath - The determined initial routePath (with brackets removed).\n * @param originalRoutePath - The original route path (may contain brackets for escaped content).\n * @param tokenRegexes - Pre-compiled token regexes for matching.\n * @returns An object containing the type of the route and the variable name derived from the route path.\n */\nexport function getRouteMeta(\n routePath: string,\n originalRoutePath: string,\n tokenRegexes: TokenRegexBundle,\n): {\n // `__root` is can be more easily determined by filtering down to routePath === /${rootPathId}\n // `pathless` is needs to determined after `lazy` has been cleaned up from the routePath\n fsRouteType: Extract<\n FsRouteType,\n | 'static'\n | 'layout'\n | 'api'\n | 'lazy'\n | 'loader'\n | 'component'\n | 'pendingComponent'\n | 'errorComponent'\n | 'notFoundComponent'\n >\n variableName: string\n} {\n let fsRouteType: FsRouteType = 'static'\n\n // Get the last segment from the original path to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex } = tokenRegexes\n\n // Helper to check if a specific suffix is escaped (literal-only)\n // A suffix is escaped if the original segment is wrapped in brackets: [lazy] means literal \"lazy\"\n const isSuffixEscaped = (suffix: string): boolean => {\n return (\n lastOriginalSegment.startsWith('[') &&\n lastOriginalSegment.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegment) === suffix\n )\n }\n\n const routeSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment = routeSegments[routeSegments.length - 1] || ''\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(lastOriginalSegment)\n const isRouteTokenEscaped =\n lastOriginalSegment !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n if (routeTokenSegmentRegex.test(lastRouteSegment) && !isRouteTokenEscaped) {\n // layout routes, i.e `/foo/route.tsx` or `/foo/_layout/route.tsx`\n fsRouteType = 'layout'\n } else if (routePath.endsWith('/lazy') && !isSuffixEscaped('lazy')) {\n // lazy routes, i.e. `/foo.lazy.tsx`\n fsRouteType = 'lazy'\n } else if (routePath.endsWith('/loader') && !isSuffixEscaped('loader')) {\n // loader routes, i.e. `/foo.loader.tsx`\n fsRouteType = 'loader'\n } else if (\n routePath.endsWith('/component') &&\n !isSuffixEscaped('component')\n ) {\n // component routes, i.e. `/foo.component.tsx`\n fsRouteType = 'component'\n } else if (\n routePath.endsWith('/pendingComponent') &&\n !isSuffixEscaped('pendingComponent')\n ) {\n // pending component routes, i.e. `/foo.pendingComponent.tsx`\n fsRouteType = 'pendingComponent'\n } else if (\n routePath.endsWith('/errorComponent') &&\n !isSuffixEscaped('errorComponent')\n ) {\n // error component routes, i.e. `/foo.errorComponent.tsx`\n fsRouteType = 'errorComponent'\n } else if (\n routePath.endsWith('/notFoundComponent') &&\n !isSuffixEscaped('notFoundComponent')\n ) {\n // not found component routes, i.e. `/foo.notFoundComponent.tsx`\n fsRouteType = 'notFoundComponent'\n }\n\n // Use originalRoutePath for variable name when any segment is fully\n // bracket-wrapped (e.g. [index], [route], [_]auth) to avoid collisions\n // with their non-escaped counterparts that get special token treatment\n const hasFullyEscapedSegment = originalSegments.some(\n (seg) =>\n seg.startsWith('[') &&\n seg.endsWith(']') &&\n !seg.slice(1, -1).includes('[') &&\n !seg.slice(1, -1).includes(']'),\n )\n const variableName = routePathToVariable(\n hasFullyEscapedSegment ? originalRoutePath : routePath,\n )\n\n return { fsRouteType, variableName }\n}\n\n/**\n * Used to validate if a route is a pathless layout route\n * @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route`\n * @param originalRoutePath Original route path with brackets for escaped content\n * @param routeType The route type determined from file extension\n * @param tokenRegexes Pre-compiled token regexes for matching\n * @returns Boolean indicating if the route is a pathless layout route\n */\nfunction isValidPathlessLayoutRoute(\n normalizedRoutePath: string,\n originalRoutePath: string,\n routeType: FsRouteType,\n tokenRegexes: TokenRegexBundle,\n): boolean {\n if (routeType === 'lazy') {\n return false\n }\n\n const segments = normalizedRoutePath.split('/').filter(Boolean)\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n\n if (segments.length === 0) {\n return false\n }\n\n const lastRouteSegment = segments[segments.length - 1]!\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n const secondToLastRouteSegment = segments[segments.length - 2]\n const secondToLastOriginalSegment =\n originalSegments[originalSegments.length - 2]\n\n // If segment === __root, then exit as false\n if (lastRouteSegment === rootPathId) {\n return false\n }\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } = tokenRegexes\n\n // If segment matches routeToken and secondToLastSegment is a string that starts with _, then exit as true\n // Since the route is actually a configuration route for a layout/pathless route\n // i.e. /foo/_layout/route.tsx === /foo/_layout.tsx\n // But if the underscore is escaped, it's not a pathless layout\n if (\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n typeof secondToLastRouteSegment === 'string' &&\n typeof secondToLastOriginalSegment === 'string'\n ) {\n // Check if the underscore is escaped\n if (hasEscapedLeadingUnderscore(secondToLastOriginalSegment)) {\n return false\n }\n return secondToLastRouteSegment.startsWith('_')\n }\n\n // Segment starts with _ but check if it's escaped\n // If the original segment has [_] at the start, the underscore is escaped and it's not a pathless layout\n if (hasEscapedLeadingUnderscore(lastOriginalSegment)) {\n return false\n }\n\n return (\n !indexTokenSegmentRegex.test(lastRouteSegment) &&\n !routeTokenSegmentRegex.test(lastRouteSegment) &&\n lastRouteSegment.startsWith('_')\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"getRouteNodes.cjs","names":[],"sources":["../../../../src/filesystem/physical/getRouteNodes.ts"],"sourcesContent":["import path from 'node:path'\nimport * as fsp from 'node:fs/promises'\nimport {\n cleanPath,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n escapeRegExp,\n hasEscapedLeadingUnderscore,\n joinRoutePathSegmentMetadata,\n removeExt,\n replaceBackslash,\n routePathToVariable,\n unwrapBracketWrappedSegment,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesVirtual } from '../virtual/getRouteNodes'\nimport { loadConfigFile } from '../virtual/loadConfigFile'\nimport { logging } from '../../logger'\nimport { rootPathId } from './rootPathId'\nimport type {\n VirtualRootRoute,\n VirtualRouteSubtreeConfig,\n} from '@tanstack/virtual-file-routes'\nimport type { FsRouteType, GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\n\n/**\n * Pre-compiled segment regexes for matching token patterns against route segments.\n * These are created once (in Generator constructor) and passed through to avoid\n * repeated regex compilation during route crawling.\n */\nexport interface TokenRegexBundle {\n indexTokenSegmentRegex: RegExp\n routeTokenSegmentRegex: RegExp\n}\n\nconst disallowedRouteGroupConfiguration = /\\(([^)]+)\\).(ts|js|tsx|jsx|vue)/\n\nconst virtualConfigFileRegExp = /__virtual\\.[mc]?[jt]s$/\nexport function isVirtualConfigFile(fileName: string): boolean {\n return virtualConfigFileRegExp.test(fileName)\n}\n\nexport async function getRouteNodes(\n config: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFilePrefix'\n | 'routeFileIgnorePrefix'\n | 'routeFileIgnorePattern'\n | 'disableLogging'\n | 'routeToken'\n | 'indexToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const { routeFilePrefix, routeFileIgnorePrefix, routeFileIgnorePattern } =\n config\n\n const logger = logging({ disabled: config.disableLogging })\n const routeFileIgnoreRegExp = new RegExp(routeFileIgnorePattern ?? '', 'g')\n\n const routeNodes: Array<RouteNode> = []\n const allPhysicalDirectories: Array<string> = []\n\n async function recurse(dir: string) {\n const fullDir = path.resolve(config.routesDirectory, dir)\n let dirList = await fsp.readdir(fullDir, { withFileTypes: true })\n\n dirList = dirList.filter((d) => {\n if (\n d.name.startsWith('.') ||\n (routeFileIgnorePrefix && d.name.startsWith(routeFileIgnorePrefix))\n ) {\n return false\n }\n\n if (routeFilePrefix) {\n if (routeFileIgnorePattern) {\n return (\n d.name.startsWith(routeFilePrefix) &&\n !d.name.match(routeFileIgnoreRegExp)\n )\n }\n\n return d.name.startsWith(routeFilePrefix)\n }\n\n if (routeFileIgnorePattern) {\n return !d.name.match(routeFileIgnoreRegExp)\n }\n\n return true\n })\n\n const virtualConfigFile = dirList.find((dirent) => {\n return dirent.isFile() && isVirtualConfigFile(dirent.name)\n })\n\n if (virtualConfigFile !== undefined) {\n const virtualRouteConfigExport = await loadConfigFile(\n path.resolve(fullDir, virtualConfigFile.name),\n )\n let virtualRouteSubtreeConfig: VirtualRouteSubtreeConfig\n if (typeof virtualRouteConfigExport.default === 'function') {\n virtualRouteSubtreeConfig = await virtualRouteConfigExport.default()\n } else {\n virtualRouteSubtreeConfig = virtualRouteConfigExport.default\n }\n const dummyRoot: VirtualRootRoute = {\n type: 'root',\n file: '',\n children: virtualRouteSubtreeConfig,\n }\n const { routeNodes: virtualRouteNodes, physicalDirectories } =\n await getRouteNodesVirtual(\n {\n ...config,\n routesDirectory: fullDir,\n virtualRouteConfig: dummyRoot,\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(...physicalDirectories)\n virtualRouteNodes.forEach((node) => {\n const normalizedDir = dir === './' ? '' : dir\n const filePath = replaceBackslash(\n path.join(normalizedDir, node.filePath),\n )\n const { routePath: prefixPath, originalRoutePath: originalPrefixPath } =\n normalizedDir\n ? determineInitialRoutePath(normalizedDir)\n : { routePath: '', originalRoutePath: '' }\n const routePath = cleanPath(`${prefixPath}${node.routePath}`)\n\n node.variableName = routePathToVariable(\n cleanPath(`${prefixPath}/${removeExt(node.filePath)}`),\n )\n node._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n prefixPath,\n createRoutePathSegmentMetadata(prefixPath, originalPrefixPath),\n node._routePathSegmentMetadata,\n )\n node.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (node.originalRoutePath) {\n node.originalRoutePath = cleanPath(\n `${originalPrefixPath}${node.originalRoutePath}`,\n )\n }\n node.filePath = filePath\n // Virtual subtree nodes (from __virtual.ts) are embedded in a\n // physical directory tree. They should use path-based parent\n // inference, not the explicit virtual parent tracking. Clear any\n // _virtualParentRoutePath that was set at construction time.\n delete node._virtualParentRoutePath\n })\n\n routeNodes.push(...virtualRouteNodes)\n\n return\n }\n\n await Promise.all(\n dirList.map(async (dirent) => {\n const fullPath = replaceBackslash(path.join(fullDir, dirent.name))\n const relativePath = path.posix.join(dir, dirent.name)\n\n if (dirent.isDirectory()) {\n await recurse(relativePath)\n } else if (fullPath.match(/\\.(tsx|ts|jsx|js|vue)$/)) {\n const filePath = replaceBackslash(path.join(dir, dirent.name))\n const filePathNoExt = removeExt(filePath)\n const {\n routePath: initialRoutePath,\n originalRoutePath: initialOriginalRoutePath,\n } = determineInitialRoutePath(filePathNoExt)\n\n let routePath = initialRoutePath\n let originalRoutePath = initialOriginalRoutePath\n\n if (routeFilePrefix) {\n routePath = routePath.replaceAll(routeFilePrefix, '')\n originalRoutePath = originalRoutePath.replaceAll(\n routeFilePrefix,\n '',\n )\n }\n\n if (disallowedRouteGroupConfiguration.test(dirent.name)) {\n const errorMessage = `A route configuration for a route group was found at \\`${filePath}\\`. This is not supported. Did you mean to use a layout/pathless route instead?`\n logger.error(`ERROR: ${errorMessage}`)\n throw new Error(errorMessage)\n }\n\n const meta = getRouteMeta(routePath, originalRoutePath, tokenRegexes)\n const variableName = meta.variableName\n let routeType: FsRouteType = meta.fsRouteType\n\n if (routeType === 'lazy') {\n routePath = routePath.replace(/\\/lazy$/, '')\n originalRoutePath = originalRoutePath.replace(/\\/lazy$/, '')\n }\n\n // this check needs to happen after the lazy route has been cleaned up\n // since the routePath is used to determine if a route is pathless\n if (\n isValidPathlessLayoutRoute(\n routePath,\n originalRoutePath,\n routeType,\n tokenRegexes,\n )\n ) {\n routeType = 'pathless_layout'\n }\n\n // Only show deprecation warning for .tsx/.ts files, not .vue files\n // Vue files using .component.vue is the Vue-native way\n const isVueFile = filePath.endsWith('.vue')\n if (!isVueFile) {\n ;(\n [\n ['component', 'component'],\n ['errorComponent', 'errorComponent'],\n ['notFoundComponent', 'notFoundComponent'],\n ['pendingComponent', 'pendingComponent'],\n ['loader', 'loader'],\n ] satisfies Array<[FsRouteType, string]>\n ).forEach(([matcher, type]) => {\n if (routeType === matcher) {\n logger.warn(\n `WARNING: The \\`.${type}.tsx\\` suffix used for the ${filePath} file is deprecated. Use the new \\`.lazy.tsx\\` suffix instead.`,\n )\n }\n })\n }\n\n // Get the last segment of originalRoutePath to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegmentForSuffix =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } =\n tokenRegexes\n\n // List of special suffixes that can be escaped\n const specialSuffixes = [\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ]\n\n const routePathSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment =\n routePathSegments[routePathSegments.length - 1] || ''\n\n const suffixToStrip = specialSuffixes.find((suffix) => {\n const endsWithSuffix = routePath.endsWith(`/${suffix}`)\n // A suffix is escaped if wrapped in brackets in the original: [lazy] means literal \"lazy\"\n const isEscaped =\n lastOriginalSegmentForSuffix.startsWith('[') &&\n lastOriginalSegmentForSuffix.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegmentForSuffix) ===\n suffix\n return endsWithSuffix && !isEscaped\n })\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(\n lastOriginalSegmentForSuffix,\n )\n const isRouteTokenEscaped =\n lastOriginalSegmentForSuffix !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n const shouldStripRouteToken =\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n !isRouteTokenEscaped\n\n if (suffixToStrip || shouldStripRouteToken) {\n const stripSegment = suffixToStrip ?? lastRouteSegment\n routePath = routePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n originalRoutePath = originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n }\n\n // Check if the index token should be treated specially or as a literal path\n // Escaping stays literal-only: if the last original segment is bracket-wrapped,\n // treat it as literal even if it matches the token regex.\n const lastOriginalSegment =\n originalRoutePath.split('/').filter(Boolean).pop() || ''\n\n const indexTokenCandidate =\n unwrapBracketWrappedSegment(lastOriginalSegment)\n const isIndexEscaped =\n lastOriginalSegment !== indexTokenCandidate &&\n indexTokenSegmentRegex.test(indexTokenCandidate)\n\n if (!isIndexEscaped) {\n const updatedRouteSegments = routePath.split('/').filter(Boolean)\n const updatedLastRouteSegment =\n updatedRouteSegments[updatedRouteSegments.length - 1] || ''\n\n if (indexTokenSegmentRegex.test(updatedLastRouteSegment)) {\n if (routePathSegments.length === 1) {\n routePath = '/'\n }\n\n // For layout routes, don't use '/' fallback - an empty path means\n // \"layout for the parent path\" which is important for physical() mounts\n // where route.tsx at root should have empty path, not '/'\n const isLayoutRoute = routeType === 'layout'\n\n routePath =\n routePath.replace(\n new RegExp(`/${escapeRegExp(updatedLastRouteSegment)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n\n originalRoutePath =\n originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(indexTokenCandidate)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n }\n }\n\n routeNodes.push({\n filePath,\n fullPath,\n routePath,\n variableName,\n _fsRouteType: routeType,\n originalRoutePath,\n _routePathSegmentMetadata: createRoutePathSegmentMetadata(\n routePath,\n originalRoutePath,\n ),\n })\n }\n }),\n )\n\n return routeNodes\n }\n\n await recurse('./')\n\n // Find the root route node - prefer the actual route file over component/loader files\n const rootRouteNode =\n routeNodes.find(\n (d) =>\n d.routePath === `/${rootPathId}` &&\n ![\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ].includes(d._fsRouteType),\n ) ?? routeNodes.find((d) => d.routePath === `/${rootPathId}`)\n if (rootRouteNode) {\n rootRouteNode._fsRouteType = '__root'\n rootRouteNode.variableName = 'root'\n }\n\n return {\n rootRouteNode,\n routeNodes,\n physicalDirectories: allPhysicalDirectories,\n }\n}\n\n/**\n * Determines the metadata for a given route path based on the provided configuration.\n *\n * @param routePath - The determined initial routePath (with brackets removed).\n * @param originalRoutePath - The original route path (may contain brackets for escaped content).\n * @param tokenRegexes - Pre-compiled token regexes for matching.\n * @returns An object containing the type of the route and the variable name derived from the route path.\n */\nexport function getRouteMeta(\n routePath: string,\n originalRoutePath: string,\n tokenRegexes: TokenRegexBundle,\n): {\n // `__root` is can be more easily determined by filtering down to routePath === /${rootPathId}\n // `pathless` is needs to determined after `lazy` has been cleaned up from the routePath\n fsRouteType: Extract<\n FsRouteType,\n | 'static'\n | 'layout'\n | 'api'\n | 'lazy'\n | 'loader'\n | 'component'\n | 'pendingComponent'\n | 'errorComponent'\n | 'notFoundComponent'\n >\n variableName: string\n} {\n let fsRouteType: FsRouteType = 'static'\n\n // Get the last segment from the original path to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex } = tokenRegexes\n\n // Helper to check if a specific suffix is escaped (literal-only)\n // A suffix is escaped if the original segment is wrapped in brackets: [lazy] means literal \"lazy\"\n const isSuffixEscaped = (suffix: string): boolean => {\n return (\n lastOriginalSegment.startsWith('[') &&\n lastOriginalSegment.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegment) === suffix\n )\n }\n\n const routeSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment = routeSegments[routeSegments.length - 1] || ''\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(lastOriginalSegment)\n const isRouteTokenEscaped =\n lastOriginalSegment !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n if (routeTokenSegmentRegex.test(lastRouteSegment) && !isRouteTokenEscaped) {\n // layout routes, i.e `/foo/route.tsx` or `/foo/_layout/route.tsx`\n fsRouteType = 'layout'\n } else if (routePath.endsWith('/lazy') && !isSuffixEscaped('lazy')) {\n // lazy routes, i.e. `/foo.lazy.tsx`\n fsRouteType = 'lazy'\n } else if (routePath.endsWith('/loader') && !isSuffixEscaped('loader')) {\n // loader routes, i.e. `/foo.loader.tsx`\n fsRouteType = 'loader'\n } else if (\n routePath.endsWith('/component') &&\n !isSuffixEscaped('component')\n ) {\n // component routes, i.e. `/foo.component.tsx`\n fsRouteType = 'component'\n } else if (\n routePath.endsWith('/pendingComponent') &&\n !isSuffixEscaped('pendingComponent')\n ) {\n // pending component routes, i.e. `/foo.pendingComponent.tsx`\n fsRouteType = 'pendingComponent'\n } else if (\n routePath.endsWith('/errorComponent') &&\n !isSuffixEscaped('errorComponent')\n ) {\n // error component routes, i.e. `/foo.errorComponent.tsx`\n fsRouteType = 'errorComponent'\n } else if (\n routePath.endsWith('/notFoundComponent') &&\n !isSuffixEscaped('notFoundComponent')\n ) {\n // not found component routes, i.e. `/foo.notFoundComponent.tsx`\n fsRouteType = 'notFoundComponent'\n }\n\n // Use originalRoutePath for variable name when any segment is fully\n // bracket-wrapped (e.g. [index], [route], [_]auth) to avoid collisions\n // with their non-escaped counterparts that get special token treatment\n const hasFullyEscapedSegment = originalSegments.some(\n (seg) =>\n seg.startsWith('[') &&\n seg.endsWith(']') &&\n !seg.slice(1, -1).includes('[') &&\n !seg.slice(1, -1).includes(']'),\n )\n const variableName = routePathToVariable(\n hasFullyEscapedSegment ? originalRoutePath : routePath,\n )\n\n return { fsRouteType, variableName }\n}\n\n/**\n * Used to validate if a route is a pathless layout route\n * @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route`\n * @param originalRoutePath Original route path with brackets for escaped content\n * @param routeType The route type determined from file extension\n * @param tokenRegexes Pre-compiled token regexes for matching\n * @returns Boolean indicating if the route is a pathless layout route\n */\nfunction isValidPathlessLayoutRoute(\n normalizedRoutePath: string,\n originalRoutePath: string,\n routeType: FsRouteType,\n tokenRegexes: TokenRegexBundle,\n): boolean {\n if (routeType === 'lazy') {\n return false\n }\n\n const segments = normalizedRoutePath.split('/').filter(Boolean)\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n\n if (segments.length === 0) {\n return false\n }\n\n const lastRouteSegment = segments[segments.length - 1]!\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n const secondToLastRouteSegment = segments[segments.length - 2]\n const secondToLastOriginalSegment =\n originalSegments[originalSegments.length - 2]\n\n // If segment === __root, then exit as false\n if (lastRouteSegment === rootPathId) {\n return false\n }\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } = tokenRegexes\n\n // If segment matches routeToken and secondToLastSegment is a string that starts with _, then exit as true\n // Since the route is actually a configuration route for a layout/pathless route\n // i.e. /foo/_layout/route.tsx === /foo/_layout.tsx\n // But if the underscore is escaped, it's not a pathless layout\n if (\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n typeof secondToLastRouteSegment === 'string' &&\n typeof secondToLastOriginalSegment === 'string'\n ) {\n // Check if the underscore is escaped\n if (hasEscapedLeadingUnderscore(secondToLastOriginalSegment)) {\n return false\n }\n return secondToLastRouteSegment.startsWith('_')\n }\n\n // Segment starts with _ but check if it's escaped\n // If the original segment has [_] at the start, the underscore is escaped and it's not a pathless layout\n if (hasEscapedLeadingUnderscore(lastOriginalSegment)) {\n return false\n }\n\n return (\n !indexTokenSegmentRegex.test(lastRouteSegment) &&\n !routeTokenSegmentRegex.test(lastRouteSegment) &&\n lastRouteSegment.startsWith('_')\n )\n}\n"],"mappings":"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"}
@@ -68,19 +68,24 @@ async function getRouteNodesRecursive(tsrConfig, root, fullDir, nodes, tokenRege
68
68
  return {
69
69
  children: (await Promise.all(nodes.map(async (node) => {
70
70
  if (node.type === "physical") {
71
+ const { routePath: routePathPrefix, originalRoutePath: originalRoutePathPrefix } = node.pathPrefix ? require_utils.determineInitialRoutePath(require_utils.removeLeadingSlash(node.pathPrefix)) : {
72
+ routePath: "",
73
+ originalRoutePath: ""
74
+ };
71
75
  const { routeNodes, physicalDirectories } = await require_getRouteNodes.getRouteNodes({
72
76
  ...tsrConfig,
73
77
  routesDirectory: (0, node_path.resolve)(fullDir, node.directory)
74
78
  }, root, tokenRegexes);
75
79
  allPhysicalDirectories.push((0, node_path.resolve)(fullDir, node.directory), ...physicalDirectories);
76
80
  routeNodes.forEach((subtreeNode) => {
77
- const pathPrefix = require_utils.cleanPath(`${parent?.routePath ?? ""}${node.pathPrefix}`);
81
+ const pathPrefix = require_utils.cleanPath(`${parent?.routePath ?? ""}${routePathPrefix}`);
82
+ const originalPathPrefix = require_utils.cleanPath(`${parent?.originalRoutePath ?? parent?.routePath ?? ""}${originalRoutePathPrefix}`);
78
83
  const literalPathPrefixSegments = require_utils.createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true);
79
84
  const routePath = require_utils.cleanPath(`${pathPrefix}${subtreeNode.routePath}`);
80
- subtreeNode.variableName = require_utils.routePathToVariable(`${node.pathPrefix}/${require_utils.removeExt(subtreeNode.filePath)}`);
85
+ subtreeNode.variableName = require_utils.routePathToVariable(`${routePathPrefix}/${require_utils.removeExt(subtreeNode.filePath)}`);
81
86
  subtreeNode._routePathSegmentMetadata = require_utils.joinRoutePathSegmentMetadata(routePath, pathPrefix, literalPathPrefixSegments, subtreeNode._routePathSegmentMetadata);
82
87
  subtreeNode.routePath = routePath;
83
- if (subtreeNode.originalRoutePath) subtreeNode.originalRoutePath = require_utils.cleanPath(`${parent?.originalRoutePath ?? parent?.routePath ?? ""}${node.pathPrefix}${subtreeNode.originalRoutePath}`);
88
+ if (subtreeNode.originalRoutePath) subtreeNode.originalRoutePath = require_utils.cleanPath(`${originalPathPrefix}${subtreeNode.originalRoutePath}`);
84
89
  subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`;
85
90
  });
86
91
  return routeNodes;
@@ -104,6 +109,8 @@ async function getRouteNodesRecursive(tsrConfig, root, fullDir, nodes, tokenRege
104
109
  fullPath,
105
110
  variableName,
106
111
  routePath: `${parentRoutePath}/`,
112
+ originalRoutePath: `${parentOriginalRoutePath}/`,
113
+ _routePathSegmentMetadata: parent?._routePathSegmentMetadata ? [...parent._routePathSegmentMetadata] : void 0,
107
114
  _fsRouteType: "static",
108
115
  _virtualParentRoutePath: virtualParentRoutePath
109
116
  };
@@ -1 +1 @@
1
- {"version":3,"file":"getRouteNodes.cjs","names":[],"sources":["../../../../src/filesystem/virtual/getRouteNodes.ts"],"sourcesContent":["import path, { join, resolve } from 'node:path'\nimport {\n cleanPath,\n createLiteralRoutePathSegmentMetadata,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n joinRoutePathSegmentMetadata,\n removeExt,\n removeLeadingSlash,\n removeTrailingSlash,\n replaceBackslash,\n routePathToVariable,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesPhysical } from '../physical/getRouteNodes'\nimport { rootPathId } from '../physical/rootPathId'\nimport { virtualRootRouteSchema } from './config'\nimport { loadConfigFile } from './loadConfigFile'\nimport type {\n VirtualRootRoute,\n VirtualRouteNode,\n} from '@tanstack/virtual-file-routes'\nimport type { GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\nimport type { TokenRegexBundle } from '../physical/getRouteNodes'\n\nfunction ensureLeadingUnderScore(id: string) {\n if (id.startsWith('_')) {\n return id\n }\n return `_${id}`\n}\n\nfunction flattenTree(node: RouteNode): Array<RouteNode> {\n const result = [node]\n\n if (node.children) {\n for (const child of node.children) {\n result.push(...flattenTree(child))\n }\n }\n delete node.children\n\n return result\n}\n\nexport async function getRouteNodes(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'virtualRouteConfig'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const fullDir = resolve(tsrConfig.routesDirectory)\n if (tsrConfig.virtualRouteConfig === undefined) {\n throw new Error(`virtualRouteConfig is undefined`)\n }\n let virtualRouteConfig: VirtualRootRoute\n if (typeof tsrConfig.virtualRouteConfig === 'string') {\n virtualRouteConfig = await getVirtualRouteConfigFromFileExport(\n tsrConfig,\n root,\n )\n } else {\n virtualRouteConfig = tsrConfig.virtualRouteConfig\n }\n const { children, physicalDirectories } = await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n virtualRouteConfig.children,\n tokenRegexes,\n )\n const allNodes = flattenTree({\n children,\n filePath: virtualRouteConfig.file,\n fullPath: replaceBackslash(join(fullDir, virtualRouteConfig.file)),\n variableName: 'root',\n routePath: `/${rootPathId}`,\n _fsRouteType: '__root',\n })\n\n const rootRouteNode = allNodes[0]\n const routeNodes = allNodes.slice(1)\n\n return { rootRouteNode, routeNodes, physicalDirectories }\n}\n\n/**\n * Get the virtual route config from a file export\n *\n * @example\n * ```ts\n * // routes.ts\n * import { rootRoute } from '@tanstack/virtual-file-routes'\n *\n * export const routes = rootRoute({ ... })\n * // or\n * export default rootRoute({ ... })\n * ```\n *\n */\nasync function getVirtualRouteConfigFromFileExport(\n tsrConfig: Pick<Config, 'virtualRouteConfig'>,\n root: string,\n): Promise<VirtualRootRoute> {\n if (\n tsrConfig.virtualRouteConfig === undefined ||\n typeof tsrConfig.virtualRouteConfig !== 'string' ||\n tsrConfig.virtualRouteConfig === ''\n ) {\n throw new Error(`virtualRouteConfig is undefined or empty`)\n }\n const exports = await loadConfigFile(join(root, tsrConfig.virtualRouteConfig))\n\n if (!('routes' in exports) && !('default' in exports)) {\n throw new Error(\n `routes not found in ${tsrConfig.virtualRouteConfig}. The routes export must be named like 'export const routes = ...' or done using 'export default ...'`,\n )\n }\n\n const virtualRouteConfig =\n 'routes' in exports ? exports.routes : exports.default\n\n return virtualRootRouteSchema.parse(virtualRouteConfig)\n}\n\nexport async function getRouteNodesRecursive(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n fullDir: string,\n nodes: Array<VirtualRouteNode> | undefined,\n tokenRegexes: TokenRegexBundle,\n parent?: RouteNode,\n): Promise<{ children: Array<RouteNode>; physicalDirectories: Array<string> }> {\n if (nodes === undefined) {\n return { children: [], physicalDirectories: [] }\n }\n const allPhysicalDirectories: Array<string> = []\n const children = await Promise.all(\n nodes.map(async (node) => {\n if (node.type === 'physical') {\n const { routeNodes, physicalDirectories } = await getRouteNodesPhysical(\n {\n ...tsrConfig,\n routesDirectory: resolve(fullDir, node.directory),\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(\n resolve(fullDir, node.directory),\n ...physicalDirectories,\n )\n routeNodes.forEach((subtreeNode) => {\n const pathPrefix = cleanPath(\n `${parent?.routePath ?? ''}${node.pathPrefix}`,\n )\n const literalPathPrefixSegments =\n createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true)\n const routePath = cleanPath(`${pathPrefix}${subtreeNode.routePath}`)\n subtreeNode.variableName = routePathToVariable(\n `${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`,\n )\n subtreeNode._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n pathPrefix,\n literalPathPrefixSegments,\n subtreeNode._routePathSegmentMetadata,\n )\n subtreeNode.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (subtreeNode.originalRoutePath) {\n subtreeNode.originalRoutePath = cleanPath(\n `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.originalRoutePath}`,\n )\n }\n subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`\n })\n return routeNodes\n }\n\n function getFile(file: string) {\n const filePath = file\n const variableName = routePathToVariable(removeExt(filePath))\n const fullPath = replaceBackslash(join(fullDir, filePath))\n return { filePath, variableName, fullPath }\n }\n const parentRoutePath = removeTrailingSlash(parent?.routePath ?? '/')\n const parentOriginalRoutePath = removeTrailingSlash(\n parent?.originalRoutePath ?? parent?.routePath ?? '/',\n )\n const virtualParentRoutePath = parent?.routePath ?? `/${rootPathId}`\n\n switch (node.type) {\n case 'index': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n const routePath = `${parentRoutePath}/`\n return {\n filePath,\n fullPath,\n variableName,\n routePath,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n } satisfies RouteNode\n }\n\n case 'route': {\n const lastSegment = node.path\n let routeNode: RouteNode\n\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata =\n createLiteralRoutePathSegmentMetadata(routePath, parent, true)\n\n if (node.file) {\n const { filePath, variableName, fullPath } = getFile(node.file)\n routeNode = {\n filePath,\n fullPath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n } else {\n routeNode = {\n filePath: '',\n fullPath: '',\n variableName: routePathToVariable(routePath),\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n isVirtual: true,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n\n // If the route has children, it should be a layout\n routeNode._fsRouteType = 'layout'\n }\n return routeNode\n }\n case 'layout': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n\n if (node.id !== undefined) {\n node.id = ensureLeadingUnderScore(node.id)\n } else {\n const baseName = path.basename(filePath)\n const fileNameWithoutExt = path.parse(baseName).name\n node.id = ensureLeadingUnderScore(fileNameWithoutExt)\n }\n const lastSegment = node.id\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n // Store the original path with brackets for escape detection\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n parentRoutePath,\n parent?._routePathSegmentMetadata,\n createRoutePathSegmentMetadata(escapedSegment, originalSegment),\n )\n\n const routeNode: RouteNode = {\n fullPath,\n filePath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'pathless_layout',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n }\n return routeNode\n }\n }\n }),\n )\n return {\n children: children.flat(),\n physicalDirectories: allPhysicalDirectories,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"getRouteNodes.cjs","names":[],"sources":["../../../../src/filesystem/virtual/getRouteNodes.ts"],"sourcesContent":["import path, { join, resolve } from 'node:path'\nimport {\n cleanPath,\n createLiteralRoutePathSegmentMetadata,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n joinRoutePathSegmentMetadata,\n removeExt,\n removeLeadingSlash,\n removeTrailingSlash,\n replaceBackslash,\n routePathToVariable,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesPhysical } from '../physical/getRouteNodes'\nimport { rootPathId } from '../physical/rootPathId'\nimport { virtualRootRouteSchema } from './config'\nimport { loadConfigFile } from './loadConfigFile'\nimport type {\n VirtualRootRoute,\n VirtualRouteNode,\n} from '@tanstack/virtual-file-routes'\nimport type { GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\nimport type { TokenRegexBundle } from '../physical/getRouteNodes'\n\nfunction ensureLeadingUnderScore(id: string) {\n if (id.startsWith('_')) {\n return id\n }\n return `_${id}`\n}\n\nfunction flattenTree(node: RouteNode): Array<RouteNode> {\n const result = [node]\n\n if (node.children) {\n for (const child of node.children) {\n result.push(...flattenTree(child))\n }\n }\n delete node.children\n\n return result\n}\n\nexport async function getRouteNodes(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'virtualRouteConfig'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const fullDir = resolve(tsrConfig.routesDirectory)\n if (tsrConfig.virtualRouteConfig === undefined) {\n throw new Error(`virtualRouteConfig is undefined`)\n }\n let virtualRouteConfig: VirtualRootRoute\n if (typeof tsrConfig.virtualRouteConfig === 'string') {\n virtualRouteConfig = await getVirtualRouteConfigFromFileExport(\n tsrConfig,\n root,\n )\n } else {\n virtualRouteConfig = tsrConfig.virtualRouteConfig\n }\n const { children, physicalDirectories } = await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n virtualRouteConfig.children,\n tokenRegexes,\n )\n const allNodes = flattenTree({\n children,\n filePath: virtualRouteConfig.file,\n fullPath: replaceBackslash(join(fullDir, virtualRouteConfig.file)),\n variableName: 'root',\n routePath: `/${rootPathId}`,\n _fsRouteType: '__root',\n })\n\n const rootRouteNode = allNodes[0]\n const routeNodes = allNodes.slice(1)\n\n return { rootRouteNode, routeNodes, physicalDirectories }\n}\n\n/**\n * Get the virtual route config from a file export\n *\n * @example\n * ```ts\n * // routes.ts\n * import { rootRoute } from '@tanstack/virtual-file-routes'\n *\n * export const routes = rootRoute({ ... })\n * // or\n * export default rootRoute({ ... })\n * ```\n *\n */\nasync function getVirtualRouteConfigFromFileExport(\n tsrConfig: Pick<Config, 'virtualRouteConfig'>,\n root: string,\n): Promise<VirtualRootRoute> {\n if (\n tsrConfig.virtualRouteConfig === undefined ||\n typeof tsrConfig.virtualRouteConfig !== 'string' ||\n tsrConfig.virtualRouteConfig === ''\n ) {\n throw new Error(`virtualRouteConfig is undefined or empty`)\n }\n const exports = await loadConfigFile(join(root, tsrConfig.virtualRouteConfig))\n\n if (!('routes' in exports) && !('default' in exports)) {\n throw new Error(\n `routes not found in ${tsrConfig.virtualRouteConfig}. The routes export must be named like 'export const routes = ...' or done using 'export default ...'`,\n )\n }\n\n const virtualRouteConfig =\n 'routes' in exports ? exports.routes : exports.default\n\n return virtualRootRouteSchema.parse(virtualRouteConfig)\n}\n\nexport async function getRouteNodesRecursive(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n fullDir: string,\n nodes: Array<VirtualRouteNode> | undefined,\n tokenRegexes: TokenRegexBundle,\n parent?: RouteNode,\n): Promise<{ children: Array<RouteNode>; physicalDirectories: Array<string> }> {\n if (nodes === undefined) {\n return { children: [], physicalDirectories: [] }\n }\n const allPhysicalDirectories: Array<string> = []\n const children = await Promise.all(\n nodes.map(async (node) => {\n if (node.type === 'physical') {\n const {\n routePath: routePathPrefix,\n originalRoutePath: originalRoutePathPrefix,\n } = node.pathPrefix\n ? determineInitialRoutePath(removeLeadingSlash(node.pathPrefix))\n : { routePath: '', originalRoutePath: '' }\n const { routeNodes, physicalDirectories } = await getRouteNodesPhysical(\n {\n ...tsrConfig,\n routesDirectory: resolve(fullDir, node.directory),\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(\n resolve(fullDir, node.directory),\n ...physicalDirectories,\n )\n routeNodes.forEach((subtreeNode) => {\n const pathPrefix = cleanPath(\n `${parent?.routePath ?? ''}${routePathPrefix}`,\n )\n const originalPathPrefix = cleanPath(\n `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${originalRoutePathPrefix}`,\n )\n const literalPathPrefixSegments =\n createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true)\n const routePath = cleanPath(`${pathPrefix}${subtreeNode.routePath}`)\n subtreeNode.variableName = routePathToVariable(\n `${routePathPrefix}/${removeExt(subtreeNode.filePath)}`,\n )\n subtreeNode._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n pathPrefix,\n literalPathPrefixSegments,\n subtreeNode._routePathSegmentMetadata,\n )\n subtreeNode.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (subtreeNode.originalRoutePath) {\n subtreeNode.originalRoutePath = cleanPath(\n `${originalPathPrefix}${subtreeNode.originalRoutePath}`,\n )\n }\n subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`\n })\n return routeNodes\n }\n\n function getFile(file: string) {\n const filePath = file\n const variableName = routePathToVariable(removeExt(filePath))\n const fullPath = replaceBackslash(join(fullDir, filePath))\n return { filePath, variableName, fullPath }\n }\n const parentRoutePath = removeTrailingSlash(parent?.routePath ?? '/')\n const parentOriginalRoutePath = removeTrailingSlash(\n parent?.originalRoutePath ?? parent?.routePath ?? '/',\n )\n const virtualParentRoutePath = parent?.routePath ?? `/${rootPathId}`\n\n switch (node.type) {\n case 'index': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n const routePath = `${parentRoutePath}/`\n const originalRoutePath = `${parentOriginalRoutePath}/`\n return {\n filePath,\n fullPath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: parent?._routePathSegmentMetadata\n ? [...parent._routePathSegmentMetadata]\n : undefined,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n } satisfies RouteNode\n }\n\n case 'route': {\n const lastSegment = node.path\n let routeNode: RouteNode\n\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata =\n createLiteralRoutePathSegmentMetadata(routePath, parent, true)\n\n if (node.file) {\n const { filePath, variableName, fullPath } = getFile(node.file)\n routeNode = {\n filePath,\n fullPath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n } else {\n routeNode = {\n filePath: '',\n fullPath: '',\n variableName: routePathToVariable(routePath),\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n isVirtual: true,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n\n // If the route has children, it should be a layout\n routeNode._fsRouteType = 'layout'\n }\n return routeNode\n }\n case 'layout': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n\n if (node.id !== undefined) {\n node.id = ensureLeadingUnderScore(node.id)\n } else {\n const baseName = path.basename(filePath)\n const fileNameWithoutExt = path.parse(baseName).name\n node.id = ensureLeadingUnderScore(fileNameWithoutExt)\n }\n const lastSegment = node.id\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n // Store the original path with brackets for escape detection\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n parentRoutePath,\n parent?._routePathSegmentMetadata,\n createRoutePathSegmentMetadata(escapedSegment, originalSegment),\n )\n\n const routeNode: RouteNode = {\n fullPath,\n filePath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'pathless_layout',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n }\n return routeNode\n }\n }\n }),\n )\n return {\n children: children.flat(),\n physicalDirectories: allPhysicalDirectories,\n }\n}\n"],"mappings":"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"}
@@ -51,12 +51,15 @@ async function getRouteNodes(config, root, tokenRegexes) {
51
51
  virtualRouteNodes.forEach((node) => {
52
52
  const normalizedDir = dir === "./" ? "" : dir;
53
53
  const filePath = replaceBackslash(path.join(normalizedDir, node.filePath));
54
- const prefixPath = cleanPath(`/${normalizedDir}`);
55
- const routePath = cleanPath(`/${normalizedDir}${node.routePath}`);
56
- node.variableName = routePathToVariable(cleanPath(`/${normalizedDir}/${removeExt(node.filePath)}`));
57
- node._routePathSegmentMetadata = joinRoutePathSegmentMetadata(routePath, prefixPath, void 0, node._routePathSegmentMetadata);
54
+ const { routePath: prefixPath, originalRoutePath: originalPrefixPath } = normalizedDir ? determineInitialRoutePath(normalizedDir) : {
55
+ routePath: "",
56
+ originalRoutePath: ""
57
+ };
58
+ const routePath = cleanPath(`${prefixPath}${node.routePath}`);
59
+ node.variableName = routePathToVariable(cleanPath(`${prefixPath}/${removeExt(node.filePath)}`));
60
+ node._routePathSegmentMetadata = joinRoutePathSegmentMetadata(routePath, prefixPath, createRoutePathSegmentMetadata(prefixPath, originalPrefixPath), node._routePathSegmentMetadata);
58
61
  node.routePath = routePath;
59
- if (node.originalRoutePath) node.originalRoutePath = cleanPath(`/${normalizedDir}${node.originalRoutePath}`);
62
+ if (node.originalRoutePath) node.originalRoutePath = cleanPath(`${originalPrefixPath}${node.originalRoutePath}`);
60
63
  node.filePath = filePath;
61
64
  delete node._virtualParentRoutePath;
62
65
  });
@@ -131,7 +134,6 @@ async function getRouteNodes(config, root, tokenRegexes) {
131
134
  const updatedLastRouteSegment = updatedRouteSegments[updatedRouteSegments.length - 1] || "";
132
135
  if (indexTokenSegmentRegex.test(updatedLastRouteSegment)) {
133
136
  if (routePathSegments.length === 1) routePath = "/";
134
- if (lastOriginalSegment === updatedLastRouteSegment) originalRoutePath = "/";
135
137
  const isLayoutRoute = routeType === "layout";
136
138
  routePath = routePath.replace(new RegExp(`/${escapeRegExp(updatedLastRouteSegment)}$`), "/") || (isLayoutRoute ? "" : "/");
137
139
  originalRoutePath = originalRoutePath.replace(new RegExp(`/${escapeRegExp(indexTokenCandidate)}$`), "/") || (isLayoutRoute ? "" : "/");
@@ -1 +1 @@
1
- {"version":3,"file":"getRouteNodes.js","names":[],"sources":["../../../../src/filesystem/physical/getRouteNodes.ts"],"sourcesContent":["import path from 'node:path'\nimport * as fsp from 'node:fs/promises'\nimport {\n cleanPath,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n escapeRegExp,\n hasEscapedLeadingUnderscore,\n joinRoutePathSegmentMetadata,\n removeExt,\n replaceBackslash,\n routePathToVariable,\n unwrapBracketWrappedSegment,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesVirtual } from '../virtual/getRouteNodes'\nimport { loadConfigFile } from '../virtual/loadConfigFile'\nimport { logging } from '../../logger'\nimport { rootPathId } from './rootPathId'\nimport type {\n VirtualRootRoute,\n VirtualRouteSubtreeConfig,\n} from '@tanstack/virtual-file-routes'\nimport type { FsRouteType, GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\n\n/**\n * Pre-compiled segment regexes for matching token patterns against route segments.\n * These are created once (in Generator constructor) and passed through to avoid\n * repeated regex compilation during route crawling.\n */\nexport interface TokenRegexBundle {\n indexTokenSegmentRegex: RegExp\n routeTokenSegmentRegex: RegExp\n}\n\nconst disallowedRouteGroupConfiguration = /\\(([^)]+)\\).(ts|js|tsx|jsx|vue)/\n\nconst virtualConfigFileRegExp = /__virtual\\.[mc]?[jt]s$/\nexport function isVirtualConfigFile(fileName: string): boolean {\n return virtualConfigFileRegExp.test(fileName)\n}\n\nexport async function getRouteNodes(\n config: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFilePrefix'\n | 'routeFileIgnorePrefix'\n | 'routeFileIgnorePattern'\n | 'disableLogging'\n | 'routeToken'\n | 'indexToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const { routeFilePrefix, routeFileIgnorePrefix, routeFileIgnorePattern } =\n config\n\n const logger = logging({ disabled: config.disableLogging })\n const routeFileIgnoreRegExp = new RegExp(routeFileIgnorePattern ?? '', 'g')\n\n const routeNodes: Array<RouteNode> = []\n const allPhysicalDirectories: Array<string> = []\n\n async function recurse(dir: string) {\n const fullDir = path.resolve(config.routesDirectory, dir)\n let dirList = await fsp.readdir(fullDir, { withFileTypes: true })\n\n dirList = dirList.filter((d) => {\n if (\n d.name.startsWith('.') ||\n (routeFileIgnorePrefix && d.name.startsWith(routeFileIgnorePrefix))\n ) {\n return false\n }\n\n if (routeFilePrefix) {\n if (routeFileIgnorePattern) {\n return (\n d.name.startsWith(routeFilePrefix) &&\n !d.name.match(routeFileIgnoreRegExp)\n )\n }\n\n return d.name.startsWith(routeFilePrefix)\n }\n\n if (routeFileIgnorePattern) {\n return !d.name.match(routeFileIgnoreRegExp)\n }\n\n return true\n })\n\n const virtualConfigFile = dirList.find((dirent) => {\n return dirent.isFile() && isVirtualConfigFile(dirent.name)\n })\n\n if (virtualConfigFile !== undefined) {\n const virtualRouteConfigExport = await loadConfigFile(\n path.resolve(fullDir, virtualConfigFile.name),\n )\n let virtualRouteSubtreeConfig: VirtualRouteSubtreeConfig\n if (typeof virtualRouteConfigExport.default === 'function') {\n virtualRouteSubtreeConfig = await virtualRouteConfigExport.default()\n } else {\n virtualRouteSubtreeConfig = virtualRouteConfigExport.default\n }\n const dummyRoot: VirtualRootRoute = {\n type: 'root',\n file: '',\n children: virtualRouteSubtreeConfig,\n }\n const { routeNodes: virtualRouteNodes, physicalDirectories } =\n await getRouteNodesVirtual(\n {\n ...config,\n routesDirectory: fullDir,\n virtualRouteConfig: dummyRoot,\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(...physicalDirectories)\n virtualRouteNodes.forEach((node) => {\n const normalizedDir = dir === './' ? '' : dir\n const filePath = replaceBackslash(\n path.join(normalizedDir, node.filePath),\n )\n const prefixPath = cleanPath(`/${normalizedDir}`)\n const routePath = cleanPath(`/${normalizedDir}${node.routePath}`)\n\n node.variableName = routePathToVariable(\n cleanPath(`/${normalizedDir}/${removeExt(node.filePath)}`),\n )\n node._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n prefixPath,\n undefined,\n node._routePathSegmentMetadata,\n )\n node.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (node.originalRoutePath) {\n node.originalRoutePath = cleanPath(\n `/${normalizedDir}${node.originalRoutePath}`,\n )\n }\n node.filePath = filePath\n // Virtual subtree nodes (from __virtual.ts) are embedded in a\n // physical directory tree. They should use path-based parent\n // inference, not the explicit virtual parent tracking. Clear any\n // _virtualParentRoutePath that was set at construction time.\n delete node._virtualParentRoutePath\n })\n\n routeNodes.push(...virtualRouteNodes)\n\n return\n }\n\n await Promise.all(\n dirList.map(async (dirent) => {\n const fullPath = replaceBackslash(path.join(fullDir, dirent.name))\n const relativePath = path.posix.join(dir, dirent.name)\n\n if (dirent.isDirectory()) {\n await recurse(relativePath)\n } else if (fullPath.match(/\\.(tsx|ts|jsx|js|vue)$/)) {\n const filePath = replaceBackslash(path.join(dir, dirent.name))\n const filePathNoExt = removeExt(filePath)\n const {\n routePath: initialRoutePath,\n originalRoutePath: initialOriginalRoutePath,\n } = determineInitialRoutePath(filePathNoExt)\n\n let routePath = initialRoutePath\n let originalRoutePath = initialOriginalRoutePath\n\n if (routeFilePrefix) {\n routePath = routePath.replaceAll(routeFilePrefix, '')\n originalRoutePath = originalRoutePath.replaceAll(\n routeFilePrefix,\n '',\n )\n }\n\n if (disallowedRouteGroupConfiguration.test(dirent.name)) {\n const errorMessage = `A route configuration for a route group was found at \\`${filePath}\\`. This is not supported. Did you mean to use a layout/pathless route instead?`\n logger.error(`ERROR: ${errorMessage}`)\n throw new Error(errorMessage)\n }\n\n const meta = getRouteMeta(routePath, originalRoutePath, tokenRegexes)\n const variableName = meta.variableName\n let routeType: FsRouteType = meta.fsRouteType\n\n if (routeType === 'lazy') {\n routePath = routePath.replace(/\\/lazy$/, '')\n originalRoutePath = originalRoutePath.replace(/\\/lazy$/, '')\n }\n\n // this check needs to happen after the lazy route has been cleaned up\n // since the routePath is used to determine if a route is pathless\n if (\n isValidPathlessLayoutRoute(\n routePath,\n originalRoutePath,\n routeType,\n tokenRegexes,\n )\n ) {\n routeType = 'pathless_layout'\n }\n\n // Only show deprecation warning for .tsx/.ts files, not .vue files\n // Vue files using .component.vue is the Vue-native way\n const isVueFile = filePath.endsWith('.vue')\n if (!isVueFile) {\n ;(\n [\n ['component', 'component'],\n ['errorComponent', 'errorComponent'],\n ['notFoundComponent', 'notFoundComponent'],\n ['pendingComponent', 'pendingComponent'],\n ['loader', 'loader'],\n ] satisfies Array<[FsRouteType, string]>\n ).forEach(([matcher, type]) => {\n if (routeType === matcher) {\n logger.warn(\n `WARNING: The \\`.${type}.tsx\\` suffix used for the ${filePath} file is deprecated. Use the new \\`.lazy.tsx\\` suffix instead.`,\n )\n }\n })\n }\n\n // Get the last segment of originalRoutePath to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegmentForSuffix =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } =\n tokenRegexes\n\n // List of special suffixes that can be escaped\n const specialSuffixes = [\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ]\n\n const routePathSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment =\n routePathSegments[routePathSegments.length - 1] || ''\n\n const suffixToStrip = specialSuffixes.find((suffix) => {\n const endsWithSuffix = routePath.endsWith(`/${suffix}`)\n // A suffix is escaped if wrapped in brackets in the original: [lazy] means literal \"lazy\"\n const isEscaped =\n lastOriginalSegmentForSuffix.startsWith('[') &&\n lastOriginalSegmentForSuffix.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegmentForSuffix) ===\n suffix\n return endsWithSuffix && !isEscaped\n })\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(\n lastOriginalSegmentForSuffix,\n )\n const isRouteTokenEscaped =\n lastOriginalSegmentForSuffix !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n const shouldStripRouteToken =\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n !isRouteTokenEscaped\n\n if (suffixToStrip || shouldStripRouteToken) {\n const stripSegment = suffixToStrip ?? lastRouteSegment\n routePath = routePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n originalRoutePath = originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n }\n\n // Check if the index token should be treated specially or as a literal path\n // Escaping stays literal-only: if the last original segment is bracket-wrapped,\n // treat it as literal even if it matches the token regex.\n const lastOriginalSegment =\n originalRoutePath.split('/').filter(Boolean).pop() || ''\n\n const indexTokenCandidate =\n unwrapBracketWrappedSegment(lastOriginalSegment)\n const isIndexEscaped =\n lastOriginalSegment !== indexTokenCandidate &&\n indexTokenSegmentRegex.test(indexTokenCandidate)\n\n if (!isIndexEscaped) {\n const updatedRouteSegments = routePath.split('/').filter(Boolean)\n const updatedLastRouteSegment =\n updatedRouteSegments[updatedRouteSegments.length - 1] || ''\n\n if (indexTokenSegmentRegex.test(updatedLastRouteSegment)) {\n if (routePathSegments.length === 1) {\n routePath = '/'\n }\n\n if (lastOriginalSegment === updatedLastRouteSegment) {\n originalRoutePath = '/'\n }\n\n // For layout routes, don't use '/' fallback - an empty path means\n // \"layout for the parent path\" which is important for physical() mounts\n // where route.tsx at root should have empty path, not '/'\n const isLayoutRoute = routeType === 'layout'\n\n routePath =\n routePath.replace(\n new RegExp(`/${escapeRegExp(updatedLastRouteSegment)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n\n originalRoutePath =\n originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(indexTokenCandidate)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n }\n }\n\n routeNodes.push({\n filePath,\n fullPath,\n routePath,\n variableName,\n _fsRouteType: routeType,\n originalRoutePath,\n _routePathSegmentMetadata: createRoutePathSegmentMetadata(\n routePath,\n originalRoutePath,\n ),\n })\n }\n }),\n )\n\n return routeNodes\n }\n\n await recurse('./')\n\n // Find the root route node - prefer the actual route file over component/loader files\n const rootRouteNode =\n routeNodes.find(\n (d) =>\n d.routePath === `/${rootPathId}` &&\n ![\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ].includes(d._fsRouteType),\n ) ?? routeNodes.find((d) => d.routePath === `/${rootPathId}`)\n if (rootRouteNode) {\n rootRouteNode._fsRouteType = '__root'\n rootRouteNode.variableName = 'root'\n }\n\n return {\n rootRouteNode,\n routeNodes,\n physicalDirectories: allPhysicalDirectories,\n }\n}\n\n/**\n * Determines the metadata for a given route path based on the provided configuration.\n *\n * @param routePath - The determined initial routePath (with brackets removed).\n * @param originalRoutePath - The original route path (may contain brackets for escaped content).\n * @param tokenRegexes - Pre-compiled token regexes for matching.\n * @returns An object containing the type of the route and the variable name derived from the route path.\n */\nexport function getRouteMeta(\n routePath: string,\n originalRoutePath: string,\n tokenRegexes: TokenRegexBundle,\n): {\n // `__root` is can be more easily determined by filtering down to routePath === /${rootPathId}\n // `pathless` is needs to determined after `lazy` has been cleaned up from the routePath\n fsRouteType: Extract<\n FsRouteType,\n | 'static'\n | 'layout'\n | 'api'\n | 'lazy'\n | 'loader'\n | 'component'\n | 'pendingComponent'\n | 'errorComponent'\n | 'notFoundComponent'\n >\n variableName: string\n} {\n let fsRouteType: FsRouteType = 'static'\n\n // Get the last segment from the original path to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex } = tokenRegexes\n\n // Helper to check if a specific suffix is escaped (literal-only)\n // A suffix is escaped if the original segment is wrapped in brackets: [lazy] means literal \"lazy\"\n const isSuffixEscaped = (suffix: string): boolean => {\n return (\n lastOriginalSegment.startsWith('[') &&\n lastOriginalSegment.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegment) === suffix\n )\n }\n\n const routeSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment = routeSegments[routeSegments.length - 1] || ''\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(lastOriginalSegment)\n const isRouteTokenEscaped =\n lastOriginalSegment !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n if (routeTokenSegmentRegex.test(lastRouteSegment) && !isRouteTokenEscaped) {\n // layout routes, i.e `/foo/route.tsx` or `/foo/_layout/route.tsx`\n fsRouteType = 'layout'\n } else if (routePath.endsWith('/lazy') && !isSuffixEscaped('lazy')) {\n // lazy routes, i.e. `/foo.lazy.tsx`\n fsRouteType = 'lazy'\n } else if (routePath.endsWith('/loader') && !isSuffixEscaped('loader')) {\n // loader routes, i.e. `/foo.loader.tsx`\n fsRouteType = 'loader'\n } else if (\n routePath.endsWith('/component') &&\n !isSuffixEscaped('component')\n ) {\n // component routes, i.e. `/foo.component.tsx`\n fsRouteType = 'component'\n } else if (\n routePath.endsWith('/pendingComponent') &&\n !isSuffixEscaped('pendingComponent')\n ) {\n // pending component routes, i.e. `/foo.pendingComponent.tsx`\n fsRouteType = 'pendingComponent'\n } else if (\n routePath.endsWith('/errorComponent') &&\n !isSuffixEscaped('errorComponent')\n ) {\n // error component routes, i.e. `/foo.errorComponent.tsx`\n fsRouteType = 'errorComponent'\n } else if (\n routePath.endsWith('/notFoundComponent') &&\n !isSuffixEscaped('notFoundComponent')\n ) {\n // not found component routes, i.e. `/foo.notFoundComponent.tsx`\n fsRouteType = 'notFoundComponent'\n }\n\n // Use originalRoutePath for variable name when any segment is fully\n // bracket-wrapped (e.g. [index], [route], [_]auth) to avoid collisions\n // with their non-escaped counterparts that get special token treatment\n const hasFullyEscapedSegment = originalSegments.some(\n (seg) =>\n seg.startsWith('[') &&\n seg.endsWith(']') &&\n !seg.slice(1, -1).includes('[') &&\n !seg.slice(1, -1).includes(']'),\n )\n const variableName = routePathToVariable(\n hasFullyEscapedSegment ? originalRoutePath : routePath,\n )\n\n return { fsRouteType, variableName }\n}\n\n/**\n * Used to validate if a route is a pathless layout route\n * @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route`\n * @param originalRoutePath Original route path with brackets for escaped content\n * @param routeType The route type determined from file extension\n * @param tokenRegexes Pre-compiled token regexes for matching\n * @returns Boolean indicating if the route is a pathless layout route\n */\nfunction isValidPathlessLayoutRoute(\n normalizedRoutePath: string,\n originalRoutePath: string,\n routeType: FsRouteType,\n tokenRegexes: TokenRegexBundle,\n): boolean {\n if (routeType === 'lazy') {\n return false\n }\n\n const segments = normalizedRoutePath.split('/').filter(Boolean)\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n\n if (segments.length === 0) {\n return false\n }\n\n const lastRouteSegment = segments[segments.length - 1]!\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n const secondToLastRouteSegment = segments[segments.length - 2]\n const secondToLastOriginalSegment =\n originalSegments[originalSegments.length - 2]\n\n // If segment === __root, then exit as false\n if (lastRouteSegment === rootPathId) {\n return false\n }\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } = tokenRegexes\n\n // If segment matches routeToken and secondToLastSegment is a string that starts with _, then exit as true\n // Since the route is actually a configuration route for a layout/pathless route\n // i.e. /foo/_layout/route.tsx === /foo/_layout.tsx\n // But if the underscore is escaped, it's not a pathless layout\n if (\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n typeof secondToLastRouteSegment === 'string' &&\n typeof secondToLastOriginalSegment === 'string'\n ) {\n // Check if the underscore is escaped\n if (hasEscapedLeadingUnderscore(secondToLastOriginalSegment)) {\n return false\n }\n return secondToLastRouteSegment.startsWith('_')\n }\n\n // Segment starts with _ but check if it's escaped\n // If the original segment has [_] at the start, the underscore is escaped and it's not a pathless layout\n if (hasEscapedLeadingUnderscore(lastOriginalSegment)) {\n return false\n }\n\n return (\n !indexTokenSegmentRegex.test(lastRouteSegment) &&\n !routeTokenSegmentRegex.test(lastRouteSegment) &&\n lastRouteSegment.startsWith('_')\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"getRouteNodes.js","names":[],"sources":["../../../../src/filesystem/physical/getRouteNodes.ts"],"sourcesContent":["import path from 'node:path'\nimport * as fsp from 'node:fs/promises'\nimport {\n cleanPath,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n escapeRegExp,\n hasEscapedLeadingUnderscore,\n joinRoutePathSegmentMetadata,\n removeExt,\n replaceBackslash,\n routePathToVariable,\n unwrapBracketWrappedSegment,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesVirtual } from '../virtual/getRouteNodes'\nimport { loadConfigFile } from '../virtual/loadConfigFile'\nimport { logging } from '../../logger'\nimport { rootPathId } from './rootPathId'\nimport type {\n VirtualRootRoute,\n VirtualRouteSubtreeConfig,\n} from '@tanstack/virtual-file-routes'\nimport type { FsRouteType, GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\n\n/**\n * Pre-compiled segment regexes for matching token patterns against route segments.\n * These are created once (in Generator constructor) and passed through to avoid\n * repeated regex compilation during route crawling.\n */\nexport interface TokenRegexBundle {\n indexTokenSegmentRegex: RegExp\n routeTokenSegmentRegex: RegExp\n}\n\nconst disallowedRouteGroupConfiguration = /\\(([^)]+)\\).(ts|js|tsx|jsx|vue)/\n\nconst virtualConfigFileRegExp = /__virtual\\.[mc]?[jt]s$/\nexport function isVirtualConfigFile(fileName: string): boolean {\n return virtualConfigFileRegExp.test(fileName)\n}\n\nexport async function getRouteNodes(\n config: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFilePrefix'\n | 'routeFileIgnorePrefix'\n | 'routeFileIgnorePattern'\n | 'disableLogging'\n | 'routeToken'\n | 'indexToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const { routeFilePrefix, routeFileIgnorePrefix, routeFileIgnorePattern } =\n config\n\n const logger = logging({ disabled: config.disableLogging })\n const routeFileIgnoreRegExp = new RegExp(routeFileIgnorePattern ?? '', 'g')\n\n const routeNodes: Array<RouteNode> = []\n const allPhysicalDirectories: Array<string> = []\n\n async function recurse(dir: string) {\n const fullDir = path.resolve(config.routesDirectory, dir)\n let dirList = await fsp.readdir(fullDir, { withFileTypes: true })\n\n dirList = dirList.filter((d) => {\n if (\n d.name.startsWith('.') ||\n (routeFileIgnorePrefix && d.name.startsWith(routeFileIgnorePrefix))\n ) {\n return false\n }\n\n if (routeFilePrefix) {\n if (routeFileIgnorePattern) {\n return (\n d.name.startsWith(routeFilePrefix) &&\n !d.name.match(routeFileIgnoreRegExp)\n )\n }\n\n return d.name.startsWith(routeFilePrefix)\n }\n\n if (routeFileIgnorePattern) {\n return !d.name.match(routeFileIgnoreRegExp)\n }\n\n return true\n })\n\n const virtualConfigFile = dirList.find((dirent) => {\n return dirent.isFile() && isVirtualConfigFile(dirent.name)\n })\n\n if (virtualConfigFile !== undefined) {\n const virtualRouteConfigExport = await loadConfigFile(\n path.resolve(fullDir, virtualConfigFile.name),\n )\n let virtualRouteSubtreeConfig: VirtualRouteSubtreeConfig\n if (typeof virtualRouteConfigExport.default === 'function') {\n virtualRouteSubtreeConfig = await virtualRouteConfigExport.default()\n } else {\n virtualRouteSubtreeConfig = virtualRouteConfigExport.default\n }\n const dummyRoot: VirtualRootRoute = {\n type: 'root',\n file: '',\n children: virtualRouteSubtreeConfig,\n }\n const { routeNodes: virtualRouteNodes, physicalDirectories } =\n await getRouteNodesVirtual(\n {\n ...config,\n routesDirectory: fullDir,\n virtualRouteConfig: dummyRoot,\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(...physicalDirectories)\n virtualRouteNodes.forEach((node) => {\n const normalizedDir = dir === './' ? '' : dir\n const filePath = replaceBackslash(\n path.join(normalizedDir, node.filePath),\n )\n const { routePath: prefixPath, originalRoutePath: originalPrefixPath } =\n normalizedDir\n ? determineInitialRoutePath(normalizedDir)\n : { routePath: '', originalRoutePath: '' }\n const routePath = cleanPath(`${prefixPath}${node.routePath}`)\n\n node.variableName = routePathToVariable(\n cleanPath(`${prefixPath}/${removeExt(node.filePath)}`),\n )\n node._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n prefixPath,\n createRoutePathSegmentMetadata(prefixPath, originalPrefixPath),\n node._routePathSegmentMetadata,\n )\n node.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (node.originalRoutePath) {\n node.originalRoutePath = cleanPath(\n `${originalPrefixPath}${node.originalRoutePath}`,\n )\n }\n node.filePath = filePath\n // Virtual subtree nodes (from __virtual.ts) are embedded in a\n // physical directory tree. They should use path-based parent\n // inference, not the explicit virtual parent tracking. Clear any\n // _virtualParentRoutePath that was set at construction time.\n delete node._virtualParentRoutePath\n })\n\n routeNodes.push(...virtualRouteNodes)\n\n return\n }\n\n await Promise.all(\n dirList.map(async (dirent) => {\n const fullPath = replaceBackslash(path.join(fullDir, dirent.name))\n const relativePath = path.posix.join(dir, dirent.name)\n\n if (dirent.isDirectory()) {\n await recurse(relativePath)\n } else if (fullPath.match(/\\.(tsx|ts|jsx|js|vue)$/)) {\n const filePath = replaceBackslash(path.join(dir, dirent.name))\n const filePathNoExt = removeExt(filePath)\n const {\n routePath: initialRoutePath,\n originalRoutePath: initialOriginalRoutePath,\n } = determineInitialRoutePath(filePathNoExt)\n\n let routePath = initialRoutePath\n let originalRoutePath = initialOriginalRoutePath\n\n if (routeFilePrefix) {\n routePath = routePath.replaceAll(routeFilePrefix, '')\n originalRoutePath = originalRoutePath.replaceAll(\n routeFilePrefix,\n '',\n )\n }\n\n if (disallowedRouteGroupConfiguration.test(dirent.name)) {\n const errorMessage = `A route configuration for a route group was found at \\`${filePath}\\`. This is not supported. Did you mean to use a layout/pathless route instead?`\n logger.error(`ERROR: ${errorMessage}`)\n throw new Error(errorMessage)\n }\n\n const meta = getRouteMeta(routePath, originalRoutePath, tokenRegexes)\n const variableName = meta.variableName\n let routeType: FsRouteType = meta.fsRouteType\n\n if (routeType === 'lazy') {\n routePath = routePath.replace(/\\/lazy$/, '')\n originalRoutePath = originalRoutePath.replace(/\\/lazy$/, '')\n }\n\n // this check needs to happen after the lazy route has been cleaned up\n // since the routePath is used to determine if a route is pathless\n if (\n isValidPathlessLayoutRoute(\n routePath,\n originalRoutePath,\n routeType,\n tokenRegexes,\n )\n ) {\n routeType = 'pathless_layout'\n }\n\n // Only show deprecation warning for .tsx/.ts files, not .vue files\n // Vue files using .component.vue is the Vue-native way\n const isVueFile = filePath.endsWith('.vue')\n if (!isVueFile) {\n ;(\n [\n ['component', 'component'],\n ['errorComponent', 'errorComponent'],\n ['notFoundComponent', 'notFoundComponent'],\n ['pendingComponent', 'pendingComponent'],\n ['loader', 'loader'],\n ] satisfies Array<[FsRouteType, string]>\n ).forEach(([matcher, type]) => {\n if (routeType === matcher) {\n logger.warn(\n `WARNING: The \\`.${type}.tsx\\` suffix used for the ${filePath} file is deprecated. Use the new \\`.lazy.tsx\\` suffix instead.`,\n )\n }\n })\n }\n\n // Get the last segment of originalRoutePath to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegmentForSuffix =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } =\n tokenRegexes\n\n // List of special suffixes that can be escaped\n const specialSuffixes = [\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ]\n\n const routePathSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment =\n routePathSegments[routePathSegments.length - 1] || ''\n\n const suffixToStrip = specialSuffixes.find((suffix) => {\n const endsWithSuffix = routePath.endsWith(`/${suffix}`)\n // A suffix is escaped if wrapped in brackets in the original: [lazy] means literal \"lazy\"\n const isEscaped =\n lastOriginalSegmentForSuffix.startsWith('[') &&\n lastOriginalSegmentForSuffix.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegmentForSuffix) ===\n suffix\n return endsWithSuffix && !isEscaped\n })\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(\n lastOriginalSegmentForSuffix,\n )\n const isRouteTokenEscaped =\n lastOriginalSegmentForSuffix !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n const shouldStripRouteToken =\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n !isRouteTokenEscaped\n\n if (suffixToStrip || shouldStripRouteToken) {\n const stripSegment = suffixToStrip ?? lastRouteSegment\n routePath = routePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n originalRoutePath = originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(stripSegment)}$`),\n '',\n )\n }\n\n // Check if the index token should be treated specially or as a literal path\n // Escaping stays literal-only: if the last original segment is bracket-wrapped,\n // treat it as literal even if it matches the token regex.\n const lastOriginalSegment =\n originalRoutePath.split('/').filter(Boolean).pop() || ''\n\n const indexTokenCandidate =\n unwrapBracketWrappedSegment(lastOriginalSegment)\n const isIndexEscaped =\n lastOriginalSegment !== indexTokenCandidate &&\n indexTokenSegmentRegex.test(indexTokenCandidate)\n\n if (!isIndexEscaped) {\n const updatedRouteSegments = routePath.split('/').filter(Boolean)\n const updatedLastRouteSegment =\n updatedRouteSegments[updatedRouteSegments.length - 1] || ''\n\n if (indexTokenSegmentRegex.test(updatedLastRouteSegment)) {\n if (routePathSegments.length === 1) {\n routePath = '/'\n }\n\n // For layout routes, don't use '/' fallback - an empty path means\n // \"layout for the parent path\" which is important for physical() mounts\n // where route.tsx at root should have empty path, not '/'\n const isLayoutRoute = routeType === 'layout'\n\n routePath =\n routePath.replace(\n new RegExp(`/${escapeRegExp(updatedLastRouteSegment)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n\n originalRoutePath =\n originalRoutePath.replace(\n new RegExp(`/${escapeRegExp(indexTokenCandidate)}$`),\n '/',\n ) || (isLayoutRoute ? '' : '/')\n }\n }\n\n routeNodes.push({\n filePath,\n fullPath,\n routePath,\n variableName,\n _fsRouteType: routeType,\n originalRoutePath,\n _routePathSegmentMetadata: createRoutePathSegmentMetadata(\n routePath,\n originalRoutePath,\n ),\n })\n }\n }),\n )\n\n return routeNodes\n }\n\n await recurse('./')\n\n // Find the root route node - prefer the actual route file over component/loader files\n const rootRouteNode =\n routeNodes.find(\n (d) =>\n d.routePath === `/${rootPathId}` &&\n ![\n 'component',\n 'errorComponent',\n 'notFoundComponent',\n 'pendingComponent',\n 'loader',\n 'lazy',\n ].includes(d._fsRouteType),\n ) ?? routeNodes.find((d) => d.routePath === `/${rootPathId}`)\n if (rootRouteNode) {\n rootRouteNode._fsRouteType = '__root'\n rootRouteNode.variableName = 'root'\n }\n\n return {\n rootRouteNode,\n routeNodes,\n physicalDirectories: allPhysicalDirectories,\n }\n}\n\n/**\n * Determines the metadata for a given route path based on the provided configuration.\n *\n * @param routePath - The determined initial routePath (with brackets removed).\n * @param originalRoutePath - The original route path (may contain brackets for escaped content).\n * @param tokenRegexes - Pre-compiled token regexes for matching.\n * @returns An object containing the type of the route and the variable name derived from the route path.\n */\nexport function getRouteMeta(\n routePath: string,\n originalRoutePath: string,\n tokenRegexes: TokenRegexBundle,\n): {\n // `__root` is can be more easily determined by filtering down to routePath === /${rootPathId}\n // `pathless` is needs to determined after `lazy` has been cleaned up from the routePath\n fsRouteType: Extract<\n FsRouteType,\n | 'static'\n | 'layout'\n | 'api'\n | 'lazy'\n | 'loader'\n | 'component'\n | 'pendingComponent'\n | 'errorComponent'\n | 'notFoundComponent'\n >\n variableName: string\n} {\n let fsRouteType: FsRouteType = 'static'\n\n // Get the last segment from the original path to check for escaping\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n\n const { routeTokenSegmentRegex } = tokenRegexes\n\n // Helper to check if a specific suffix is escaped (literal-only)\n // A suffix is escaped if the original segment is wrapped in brackets: [lazy] means literal \"lazy\"\n const isSuffixEscaped = (suffix: string): boolean => {\n return (\n lastOriginalSegment.startsWith('[') &&\n lastOriginalSegment.endsWith(']') &&\n unwrapBracketWrappedSegment(lastOriginalSegment) === suffix\n )\n }\n\n const routeSegments = routePath.split('/').filter(Boolean)\n const lastRouteSegment = routeSegments[routeSegments.length - 1] || ''\n\n const routeTokenCandidate = unwrapBracketWrappedSegment(lastOriginalSegment)\n const isRouteTokenEscaped =\n lastOriginalSegment !== routeTokenCandidate &&\n routeTokenSegmentRegex.test(routeTokenCandidate)\n\n if (routeTokenSegmentRegex.test(lastRouteSegment) && !isRouteTokenEscaped) {\n // layout routes, i.e `/foo/route.tsx` or `/foo/_layout/route.tsx`\n fsRouteType = 'layout'\n } else if (routePath.endsWith('/lazy') && !isSuffixEscaped('lazy')) {\n // lazy routes, i.e. `/foo.lazy.tsx`\n fsRouteType = 'lazy'\n } else if (routePath.endsWith('/loader') && !isSuffixEscaped('loader')) {\n // loader routes, i.e. `/foo.loader.tsx`\n fsRouteType = 'loader'\n } else if (\n routePath.endsWith('/component') &&\n !isSuffixEscaped('component')\n ) {\n // component routes, i.e. `/foo.component.tsx`\n fsRouteType = 'component'\n } else if (\n routePath.endsWith('/pendingComponent') &&\n !isSuffixEscaped('pendingComponent')\n ) {\n // pending component routes, i.e. `/foo.pendingComponent.tsx`\n fsRouteType = 'pendingComponent'\n } else if (\n routePath.endsWith('/errorComponent') &&\n !isSuffixEscaped('errorComponent')\n ) {\n // error component routes, i.e. `/foo.errorComponent.tsx`\n fsRouteType = 'errorComponent'\n } else if (\n routePath.endsWith('/notFoundComponent') &&\n !isSuffixEscaped('notFoundComponent')\n ) {\n // not found component routes, i.e. `/foo.notFoundComponent.tsx`\n fsRouteType = 'notFoundComponent'\n }\n\n // Use originalRoutePath for variable name when any segment is fully\n // bracket-wrapped (e.g. [index], [route], [_]auth) to avoid collisions\n // with their non-escaped counterparts that get special token treatment\n const hasFullyEscapedSegment = originalSegments.some(\n (seg) =>\n seg.startsWith('[') &&\n seg.endsWith(']') &&\n !seg.slice(1, -1).includes('[') &&\n !seg.slice(1, -1).includes(']'),\n )\n const variableName = routePathToVariable(\n hasFullyEscapedSegment ? originalRoutePath : routePath,\n )\n\n return { fsRouteType, variableName }\n}\n\n/**\n * Used to validate if a route is a pathless layout route\n * @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route`\n * @param originalRoutePath Original route path with brackets for escaped content\n * @param routeType The route type determined from file extension\n * @param tokenRegexes Pre-compiled token regexes for matching\n * @returns Boolean indicating if the route is a pathless layout route\n */\nfunction isValidPathlessLayoutRoute(\n normalizedRoutePath: string,\n originalRoutePath: string,\n routeType: FsRouteType,\n tokenRegexes: TokenRegexBundle,\n): boolean {\n if (routeType === 'lazy') {\n return false\n }\n\n const segments = normalizedRoutePath.split('/').filter(Boolean)\n const originalSegments = originalRoutePath.split('/').filter(Boolean)\n\n if (segments.length === 0) {\n return false\n }\n\n const lastRouteSegment = segments[segments.length - 1]!\n const lastOriginalSegment =\n originalSegments[originalSegments.length - 1] || ''\n const secondToLastRouteSegment = segments[segments.length - 2]\n const secondToLastOriginalSegment =\n originalSegments[originalSegments.length - 2]\n\n // If segment === __root, then exit as false\n if (lastRouteSegment === rootPathId) {\n return false\n }\n\n const { routeTokenSegmentRegex, indexTokenSegmentRegex } = tokenRegexes\n\n // If segment matches routeToken and secondToLastSegment is a string that starts with _, then exit as true\n // Since the route is actually a configuration route for a layout/pathless route\n // i.e. /foo/_layout/route.tsx === /foo/_layout.tsx\n // But if the underscore is escaped, it's not a pathless layout\n if (\n routeTokenSegmentRegex.test(lastRouteSegment) &&\n typeof secondToLastRouteSegment === 'string' &&\n typeof secondToLastOriginalSegment === 'string'\n ) {\n // Check if the underscore is escaped\n if (hasEscapedLeadingUnderscore(secondToLastOriginalSegment)) {\n return false\n }\n return secondToLastRouteSegment.startsWith('_')\n }\n\n // Segment starts with _ but check if it's escaped\n // If the original segment has [_] at the start, the underscore is escaped and it's not a pathless layout\n if (hasEscapedLeadingUnderscore(lastOriginalSegment)) {\n return false\n }\n\n return (\n !indexTokenSegmentRegex.test(lastRouteSegment) &&\n !routeTokenSegmentRegex.test(lastRouteSegment) &&\n lastRouteSegment.startsWith('_')\n )\n}\n"],"mappings":"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"}
@@ -66,19 +66,24 @@ async function getRouteNodesRecursive(tsrConfig, root, fullDir, nodes, tokenRege
66
66
  return {
67
67
  children: (await Promise.all(nodes.map(async (node) => {
68
68
  if (node.type === "physical") {
69
+ const { routePath: routePathPrefix, originalRoutePath: originalRoutePathPrefix } = node.pathPrefix ? determineInitialRoutePath(removeLeadingSlash(node.pathPrefix)) : {
70
+ routePath: "",
71
+ originalRoutePath: ""
72
+ };
69
73
  const { routeNodes, physicalDirectories } = await getRouteNodes$1({
70
74
  ...tsrConfig,
71
75
  routesDirectory: resolve(fullDir, node.directory)
72
76
  }, root, tokenRegexes);
73
77
  allPhysicalDirectories.push(resolve(fullDir, node.directory), ...physicalDirectories);
74
78
  routeNodes.forEach((subtreeNode) => {
75
- const pathPrefix = cleanPath(`${parent?.routePath ?? ""}${node.pathPrefix}`);
79
+ const pathPrefix = cleanPath(`${parent?.routePath ?? ""}${routePathPrefix}`);
80
+ const originalPathPrefix = cleanPath(`${parent?.originalRoutePath ?? parent?.routePath ?? ""}${originalRoutePathPrefix}`);
76
81
  const literalPathPrefixSegments = createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true);
77
82
  const routePath = cleanPath(`${pathPrefix}${subtreeNode.routePath}`);
78
- subtreeNode.variableName = routePathToVariable(`${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`);
83
+ subtreeNode.variableName = routePathToVariable(`${routePathPrefix}/${removeExt(subtreeNode.filePath)}`);
79
84
  subtreeNode._routePathSegmentMetadata = joinRoutePathSegmentMetadata(routePath, pathPrefix, literalPathPrefixSegments, subtreeNode._routePathSegmentMetadata);
80
85
  subtreeNode.routePath = routePath;
81
- if (subtreeNode.originalRoutePath) subtreeNode.originalRoutePath = cleanPath(`${parent?.originalRoutePath ?? parent?.routePath ?? ""}${node.pathPrefix}${subtreeNode.originalRoutePath}`);
86
+ if (subtreeNode.originalRoutePath) subtreeNode.originalRoutePath = cleanPath(`${originalPathPrefix}${subtreeNode.originalRoutePath}`);
82
87
  subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`;
83
88
  });
84
89
  return routeNodes;
@@ -102,6 +107,8 @@ async function getRouteNodesRecursive(tsrConfig, root, fullDir, nodes, tokenRege
102
107
  fullPath,
103
108
  variableName,
104
109
  routePath: `${parentRoutePath}/`,
110
+ originalRoutePath: `${parentOriginalRoutePath}/`,
111
+ _routePathSegmentMetadata: parent?._routePathSegmentMetadata ? [...parent._routePathSegmentMetadata] : void 0,
105
112
  _fsRouteType: "static",
106
113
  _virtualParentRoutePath: virtualParentRoutePath
107
114
  };
@@ -1 +1 @@
1
- {"version":3,"file":"getRouteNodes.js","names":[],"sources":["../../../../src/filesystem/virtual/getRouteNodes.ts"],"sourcesContent":["import path, { join, resolve } from 'node:path'\nimport {\n cleanPath,\n createLiteralRoutePathSegmentMetadata,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n joinRoutePathSegmentMetadata,\n removeExt,\n removeLeadingSlash,\n removeTrailingSlash,\n replaceBackslash,\n routePathToVariable,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesPhysical } from '../physical/getRouteNodes'\nimport { rootPathId } from '../physical/rootPathId'\nimport { virtualRootRouteSchema } from './config'\nimport { loadConfigFile } from './loadConfigFile'\nimport type {\n VirtualRootRoute,\n VirtualRouteNode,\n} from '@tanstack/virtual-file-routes'\nimport type { GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\nimport type { TokenRegexBundle } from '../physical/getRouteNodes'\n\nfunction ensureLeadingUnderScore(id: string) {\n if (id.startsWith('_')) {\n return id\n }\n return `_${id}`\n}\n\nfunction flattenTree(node: RouteNode): Array<RouteNode> {\n const result = [node]\n\n if (node.children) {\n for (const child of node.children) {\n result.push(...flattenTree(child))\n }\n }\n delete node.children\n\n return result\n}\n\nexport async function getRouteNodes(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'virtualRouteConfig'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const fullDir = resolve(tsrConfig.routesDirectory)\n if (tsrConfig.virtualRouteConfig === undefined) {\n throw new Error(`virtualRouteConfig is undefined`)\n }\n let virtualRouteConfig: VirtualRootRoute\n if (typeof tsrConfig.virtualRouteConfig === 'string') {\n virtualRouteConfig = await getVirtualRouteConfigFromFileExport(\n tsrConfig,\n root,\n )\n } else {\n virtualRouteConfig = tsrConfig.virtualRouteConfig\n }\n const { children, physicalDirectories } = await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n virtualRouteConfig.children,\n tokenRegexes,\n )\n const allNodes = flattenTree({\n children,\n filePath: virtualRouteConfig.file,\n fullPath: replaceBackslash(join(fullDir, virtualRouteConfig.file)),\n variableName: 'root',\n routePath: `/${rootPathId}`,\n _fsRouteType: '__root',\n })\n\n const rootRouteNode = allNodes[0]\n const routeNodes = allNodes.slice(1)\n\n return { rootRouteNode, routeNodes, physicalDirectories }\n}\n\n/**\n * Get the virtual route config from a file export\n *\n * @example\n * ```ts\n * // routes.ts\n * import { rootRoute } from '@tanstack/virtual-file-routes'\n *\n * export const routes = rootRoute({ ... })\n * // or\n * export default rootRoute({ ... })\n * ```\n *\n */\nasync function getVirtualRouteConfigFromFileExport(\n tsrConfig: Pick<Config, 'virtualRouteConfig'>,\n root: string,\n): Promise<VirtualRootRoute> {\n if (\n tsrConfig.virtualRouteConfig === undefined ||\n typeof tsrConfig.virtualRouteConfig !== 'string' ||\n tsrConfig.virtualRouteConfig === ''\n ) {\n throw new Error(`virtualRouteConfig is undefined or empty`)\n }\n const exports = await loadConfigFile(join(root, tsrConfig.virtualRouteConfig))\n\n if (!('routes' in exports) && !('default' in exports)) {\n throw new Error(\n `routes not found in ${tsrConfig.virtualRouteConfig}. The routes export must be named like 'export const routes = ...' or done using 'export default ...'`,\n )\n }\n\n const virtualRouteConfig =\n 'routes' in exports ? exports.routes : exports.default\n\n return virtualRootRouteSchema.parse(virtualRouteConfig)\n}\n\nexport async function getRouteNodesRecursive(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n fullDir: string,\n nodes: Array<VirtualRouteNode> | undefined,\n tokenRegexes: TokenRegexBundle,\n parent?: RouteNode,\n): Promise<{ children: Array<RouteNode>; physicalDirectories: Array<string> }> {\n if (nodes === undefined) {\n return { children: [], physicalDirectories: [] }\n }\n const allPhysicalDirectories: Array<string> = []\n const children = await Promise.all(\n nodes.map(async (node) => {\n if (node.type === 'physical') {\n const { routeNodes, physicalDirectories } = await getRouteNodesPhysical(\n {\n ...tsrConfig,\n routesDirectory: resolve(fullDir, node.directory),\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(\n resolve(fullDir, node.directory),\n ...physicalDirectories,\n )\n routeNodes.forEach((subtreeNode) => {\n const pathPrefix = cleanPath(\n `${parent?.routePath ?? ''}${node.pathPrefix}`,\n )\n const literalPathPrefixSegments =\n createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true)\n const routePath = cleanPath(`${pathPrefix}${subtreeNode.routePath}`)\n subtreeNode.variableName = routePathToVariable(\n `${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`,\n )\n subtreeNode._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n pathPrefix,\n literalPathPrefixSegments,\n subtreeNode._routePathSegmentMetadata,\n )\n subtreeNode.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (subtreeNode.originalRoutePath) {\n subtreeNode.originalRoutePath = cleanPath(\n `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.originalRoutePath}`,\n )\n }\n subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`\n })\n return routeNodes\n }\n\n function getFile(file: string) {\n const filePath = file\n const variableName = routePathToVariable(removeExt(filePath))\n const fullPath = replaceBackslash(join(fullDir, filePath))\n return { filePath, variableName, fullPath }\n }\n const parentRoutePath = removeTrailingSlash(parent?.routePath ?? '/')\n const parentOriginalRoutePath = removeTrailingSlash(\n parent?.originalRoutePath ?? parent?.routePath ?? '/',\n )\n const virtualParentRoutePath = parent?.routePath ?? `/${rootPathId}`\n\n switch (node.type) {\n case 'index': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n const routePath = `${parentRoutePath}/`\n return {\n filePath,\n fullPath,\n variableName,\n routePath,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n } satisfies RouteNode\n }\n\n case 'route': {\n const lastSegment = node.path\n let routeNode: RouteNode\n\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata =\n createLiteralRoutePathSegmentMetadata(routePath, parent, true)\n\n if (node.file) {\n const { filePath, variableName, fullPath } = getFile(node.file)\n routeNode = {\n filePath,\n fullPath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n } else {\n routeNode = {\n filePath: '',\n fullPath: '',\n variableName: routePathToVariable(routePath),\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n isVirtual: true,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n\n // If the route has children, it should be a layout\n routeNode._fsRouteType = 'layout'\n }\n return routeNode\n }\n case 'layout': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n\n if (node.id !== undefined) {\n node.id = ensureLeadingUnderScore(node.id)\n } else {\n const baseName = path.basename(filePath)\n const fileNameWithoutExt = path.parse(baseName).name\n node.id = ensureLeadingUnderScore(fileNameWithoutExt)\n }\n const lastSegment = node.id\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n // Store the original path with brackets for escape detection\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n parentRoutePath,\n parent?._routePathSegmentMetadata,\n createRoutePathSegmentMetadata(escapedSegment, originalSegment),\n )\n\n const routeNode: RouteNode = {\n fullPath,\n filePath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'pathless_layout',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n }\n return routeNode\n }\n }\n }),\n )\n return {\n children: children.flat(),\n physicalDirectories: allPhysicalDirectories,\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"getRouteNodes.js","names":[],"sources":["../../../../src/filesystem/virtual/getRouteNodes.ts"],"sourcesContent":["import path, { join, resolve } from 'node:path'\nimport {\n cleanPath,\n createLiteralRoutePathSegmentMetadata,\n createRoutePathSegmentMetadata,\n determineInitialRoutePath,\n joinRoutePathSegmentMetadata,\n removeExt,\n removeLeadingSlash,\n removeTrailingSlash,\n replaceBackslash,\n routePathToVariable,\n} from '../../utils'\nimport { getRouteNodes as getRouteNodesPhysical } from '../physical/getRouteNodes'\nimport { rootPathId } from '../physical/rootPathId'\nimport { virtualRootRouteSchema } from './config'\nimport { loadConfigFile } from './loadConfigFile'\nimport type {\n VirtualRootRoute,\n VirtualRouteNode,\n} from '@tanstack/virtual-file-routes'\nimport type { GetRouteNodesResult, RouteNode } from '../../types'\nimport type { Config } from '../../config'\nimport type { TokenRegexBundle } from '../physical/getRouteNodes'\n\nfunction ensureLeadingUnderScore(id: string) {\n if (id.startsWith('_')) {\n return id\n }\n return `_${id}`\n}\n\nfunction flattenTree(node: RouteNode): Array<RouteNode> {\n const result = [node]\n\n if (node.children) {\n for (const child of node.children) {\n result.push(...flattenTree(child))\n }\n }\n delete node.children\n\n return result\n}\n\nexport async function getRouteNodes(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'virtualRouteConfig'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n tokenRegexes: TokenRegexBundle,\n): Promise<GetRouteNodesResult> {\n const fullDir = resolve(tsrConfig.routesDirectory)\n if (tsrConfig.virtualRouteConfig === undefined) {\n throw new Error(`virtualRouteConfig is undefined`)\n }\n let virtualRouteConfig: VirtualRootRoute\n if (typeof tsrConfig.virtualRouteConfig === 'string') {\n virtualRouteConfig = await getVirtualRouteConfigFromFileExport(\n tsrConfig,\n root,\n )\n } else {\n virtualRouteConfig = tsrConfig.virtualRouteConfig\n }\n const { children, physicalDirectories } = await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n virtualRouteConfig.children,\n tokenRegexes,\n )\n const allNodes = flattenTree({\n children,\n filePath: virtualRouteConfig.file,\n fullPath: replaceBackslash(join(fullDir, virtualRouteConfig.file)),\n variableName: 'root',\n routePath: `/${rootPathId}`,\n _fsRouteType: '__root',\n })\n\n const rootRouteNode = allNodes[0]\n const routeNodes = allNodes.slice(1)\n\n return { rootRouteNode, routeNodes, physicalDirectories }\n}\n\n/**\n * Get the virtual route config from a file export\n *\n * @example\n * ```ts\n * // routes.ts\n * import { rootRoute } from '@tanstack/virtual-file-routes'\n *\n * export const routes = rootRoute({ ... })\n * // or\n * export default rootRoute({ ... })\n * ```\n *\n */\nasync function getVirtualRouteConfigFromFileExport(\n tsrConfig: Pick<Config, 'virtualRouteConfig'>,\n root: string,\n): Promise<VirtualRootRoute> {\n if (\n tsrConfig.virtualRouteConfig === undefined ||\n typeof tsrConfig.virtualRouteConfig !== 'string' ||\n tsrConfig.virtualRouteConfig === ''\n ) {\n throw new Error(`virtualRouteConfig is undefined or empty`)\n }\n const exports = await loadConfigFile(join(root, tsrConfig.virtualRouteConfig))\n\n if (!('routes' in exports) && !('default' in exports)) {\n throw new Error(\n `routes not found in ${tsrConfig.virtualRouteConfig}. The routes export must be named like 'export const routes = ...' or done using 'export default ...'`,\n )\n }\n\n const virtualRouteConfig =\n 'routes' in exports ? exports.routes : exports.default\n\n return virtualRootRouteSchema.parse(virtualRouteConfig)\n}\n\nexport async function getRouteNodesRecursive(\n tsrConfig: Pick<\n Config,\n | 'routesDirectory'\n | 'routeFileIgnorePrefix'\n | 'disableLogging'\n | 'indexToken'\n | 'routeToken'\n >,\n root: string,\n fullDir: string,\n nodes: Array<VirtualRouteNode> | undefined,\n tokenRegexes: TokenRegexBundle,\n parent?: RouteNode,\n): Promise<{ children: Array<RouteNode>; physicalDirectories: Array<string> }> {\n if (nodes === undefined) {\n return { children: [], physicalDirectories: [] }\n }\n const allPhysicalDirectories: Array<string> = []\n const children = await Promise.all(\n nodes.map(async (node) => {\n if (node.type === 'physical') {\n const {\n routePath: routePathPrefix,\n originalRoutePath: originalRoutePathPrefix,\n } = node.pathPrefix\n ? determineInitialRoutePath(removeLeadingSlash(node.pathPrefix))\n : { routePath: '', originalRoutePath: '' }\n const { routeNodes, physicalDirectories } = await getRouteNodesPhysical(\n {\n ...tsrConfig,\n routesDirectory: resolve(fullDir, node.directory),\n },\n root,\n tokenRegexes,\n )\n allPhysicalDirectories.push(\n resolve(fullDir, node.directory),\n ...physicalDirectories,\n )\n routeNodes.forEach((subtreeNode) => {\n const pathPrefix = cleanPath(\n `${parent?.routePath ?? ''}${routePathPrefix}`,\n )\n const originalPathPrefix = cleanPath(\n `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${originalRoutePathPrefix}`,\n )\n const literalPathPrefixSegments =\n createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true)\n const routePath = cleanPath(`${pathPrefix}${subtreeNode.routePath}`)\n subtreeNode.variableName = routePathToVariable(\n `${routePathPrefix}/${removeExt(subtreeNode.filePath)}`,\n )\n subtreeNode._routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n pathPrefix,\n literalPathPrefixSegments,\n subtreeNode._routePathSegmentMetadata,\n )\n subtreeNode.routePath = routePath\n // Keep originalRoutePath aligned with routePath for escape detection\n if (subtreeNode.originalRoutePath) {\n subtreeNode.originalRoutePath = cleanPath(\n `${originalPathPrefix}${subtreeNode.originalRoutePath}`,\n )\n }\n subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`\n })\n return routeNodes\n }\n\n function getFile(file: string) {\n const filePath = file\n const variableName = routePathToVariable(removeExt(filePath))\n const fullPath = replaceBackslash(join(fullDir, filePath))\n return { filePath, variableName, fullPath }\n }\n const parentRoutePath = removeTrailingSlash(parent?.routePath ?? '/')\n const parentOriginalRoutePath = removeTrailingSlash(\n parent?.originalRoutePath ?? parent?.routePath ?? '/',\n )\n const virtualParentRoutePath = parent?.routePath ?? `/${rootPathId}`\n\n switch (node.type) {\n case 'index': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n const routePath = `${parentRoutePath}/`\n const originalRoutePath = `${parentOriginalRoutePath}/`\n return {\n filePath,\n fullPath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: parent?._routePathSegmentMetadata\n ? [...parent._routePathSegmentMetadata]\n : undefined,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n } satisfies RouteNode\n }\n\n case 'route': {\n const lastSegment = node.path\n let routeNode: RouteNode\n\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata =\n createLiteralRoutePathSegmentMetadata(routePath, parent, true)\n\n if (node.file) {\n const { filePath, variableName, fullPath } = getFile(node.file)\n routeNode = {\n filePath,\n fullPath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n } else {\n routeNode = {\n filePath: '',\n fullPath: '',\n variableName: routePathToVariable(routePath),\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n isVirtual: true,\n _fsRouteType: 'static',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n\n // If the route has children, it should be a layout\n routeNode._fsRouteType = 'layout'\n }\n return routeNode\n }\n case 'layout': {\n const { filePath, variableName, fullPath } = getFile(node.file)\n\n if (node.id !== undefined) {\n node.id = ensureLeadingUnderScore(node.id)\n } else {\n const baseName = path.basename(filePath)\n const fileNameWithoutExt = path.parse(baseName).name\n node.id = ensureLeadingUnderScore(fileNameWithoutExt)\n }\n const lastSegment = node.id\n // Process the segment to handle escape sequences like [_]\n const {\n routePath: escapedSegment,\n originalRoutePath: originalSegment,\n } = determineInitialRoutePath(removeLeadingSlash(lastSegment))\n const routePath = `${parentRoutePath}${escapedSegment}`\n // Store the original path with brackets for escape detection\n const originalRoutePath = `${parentOriginalRoutePath}${originalSegment}`\n const routePathSegmentMetadata = joinRoutePathSegmentMetadata(\n routePath,\n parentRoutePath,\n parent?._routePathSegmentMetadata,\n createRoutePathSegmentMetadata(escapedSegment, originalSegment),\n )\n\n const routeNode: RouteNode = {\n fullPath,\n filePath,\n variableName,\n routePath,\n originalRoutePath,\n _routePathSegmentMetadata: routePathSegmentMetadata,\n _fsRouteType: 'pathless_layout',\n _virtualParentRoutePath: virtualParentRoutePath,\n }\n\n if (node.children !== undefined) {\n const { children, physicalDirectories } =\n await getRouteNodesRecursive(\n tsrConfig,\n root,\n fullDir,\n node.children,\n tokenRegexes,\n routeNode,\n )\n routeNode.children = children\n allPhysicalDirectories.push(...physicalDirectories)\n }\n return routeNode\n }\n }\n }),\n )\n return {\n children: children.flat(),\n physicalDirectories: allPhysicalDirectories,\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-generator",
3
- "version": "1.167.7",
3
+ "version": "1.167.8",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -128,23 +128,26 @@ export async function getRouteNodes(
128
128
  const filePath = replaceBackslash(
129
129
  path.join(normalizedDir, node.filePath),
130
130
  )
131
- const prefixPath = cleanPath(`/${normalizedDir}`)
132
- const routePath = cleanPath(`/${normalizedDir}${node.routePath}`)
131
+ const { routePath: prefixPath, originalRoutePath: originalPrefixPath } =
132
+ normalizedDir
133
+ ? determineInitialRoutePath(normalizedDir)
134
+ : { routePath: '', originalRoutePath: '' }
135
+ const routePath = cleanPath(`${prefixPath}${node.routePath}`)
133
136
 
134
137
  node.variableName = routePathToVariable(
135
- cleanPath(`/${normalizedDir}/${removeExt(node.filePath)}`),
138
+ cleanPath(`${prefixPath}/${removeExt(node.filePath)}`),
136
139
  )
137
140
  node._routePathSegmentMetadata = joinRoutePathSegmentMetadata(
138
141
  routePath,
139
142
  prefixPath,
140
- undefined,
143
+ createRoutePathSegmentMetadata(prefixPath, originalPrefixPath),
141
144
  node._routePathSegmentMetadata,
142
145
  )
143
146
  node.routePath = routePath
144
147
  // Keep originalRoutePath aligned with routePath for escape detection
145
148
  if (node.originalRoutePath) {
146
149
  node.originalRoutePath = cleanPath(
147
- `/${normalizedDir}${node.originalRoutePath}`,
150
+ `${originalPrefixPath}${node.originalRoutePath}`,
148
151
  )
149
152
  }
150
153
  node.filePath = filePath
@@ -313,10 +316,6 @@ export async function getRouteNodes(
313
316
  routePath = '/'
314
317
  }
315
318
 
316
- if (lastOriginalSegment === updatedLastRouteSegment) {
317
- originalRoutePath = '/'
318
- }
319
-
320
319
  // For layout routes, don't use '/' fallback - an empty path means
321
320
  // "layout for the parent path" which is important for physical() mounts
322
321
  // where route.tsx at root should have empty path, not '/'
@@ -152,6 +152,12 @@ export async function getRouteNodesRecursive(
152
152
  const children = await Promise.all(
153
153
  nodes.map(async (node) => {
154
154
  if (node.type === 'physical') {
155
+ const {
156
+ routePath: routePathPrefix,
157
+ originalRoutePath: originalRoutePathPrefix,
158
+ } = node.pathPrefix
159
+ ? determineInitialRoutePath(removeLeadingSlash(node.pathPrefix))
160
+ : { routePath: '', originalRoutePath: '' }
155
161
  const { routeNodes, physicalDirectories } = await getRouteNodesPhysical(
156
162
  {
157
163
  ...tsrConfig,
@@ -166,13 +172,16 @@ export async function getRouteNodesRecursive(
166
172
  )
167
173
  routeNodes.forEach((subtreeNode) => {
168
174
  const pathPrefix = cleanPath(
169
- `${parent?.routePath ?? ''}${node.pathPrefix}`,
175
+ `${parent?.routePath ?? ''}${routePathPrefix}`,
176
+ )
177
+ const originalPathPrefix = cleanPath(
178
+ `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${originalRoutePathPrefix}`,
170
179
  )
171
180
  const literalPathPrefixSegments =
172
181
  createLiteralRoutePathSegmentMetadata(pathPrefix, parent, true)
173
182
  const routePath = cleanPath(`${pathPrefix}${subtreeNode.routePath}`)
174
183
  subtreeNode.variableName = routePathToVariable(
175
- `${node.pathPrefix}/${removeExt(subtreeNode.filePath)}`,
184
+ `${routePathPrefix}/${removeExt(subtreeNode.filePath)}`,
176
185
  )
177
186
  subtreeNode._routePathSegmentMetadata = joinRoutePathSegmentMetadata(
178
187
  routePath,
@@ -184,7 +193,7 @@ export async function getRouteNodesRecursive(
184
193
  // Keep originalRoutePath aligned with routePath for escape detection
185
194
  if (subtreeNode.originalRoutePath) {
186
195
  subtreeNode.originalRoutePath = cleanPath(
187
- `${parent?.originalRoutePath ?? parent?.routePath ?? ''}${node.pathPrefix}${subtreeNode.originalRoutePath}`,
196
+ `${originalPathPrefix}${subtreeNode.originalRoutePath}`,
188
197
  )
189
198
  }
190
199
  subtreeNode.filePath = `${node.directory}/${subtreeNode.filePath}`
@@ -208,11 +217,16 @@ export async function getRouteNodesRecursive(
208
217
  case 'index': {
209
218
  const { filePath, variableName, fullPath } = getFile(node.file)
210
219
  const routePath = `${parentRoutePath}/`
220
+ const originalRoutePath = `${parentOriginalRoutePath}/`
211
221
  return {
212
222
  filePath,
213
223
  fullPath,
214
224
  variableName,
215
225
  routePath,
226
+ originalRoutePath,
227
+ _routePathSegmentMetadata: parent?._routePathSegmentMetadata
228
+ ? [...parent._routePathSegmentMetadata]
229
+ : undefined,
216
230
  _fsRouteType: 'static',
217
231
  _virtualParentRoutePath: virtualParentRoutePath,
218
232
  } satisfies RouteNode