@tanstack/router-generator 1.141.9 → 1.142.0
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.
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs +67 -33
- package/dist/cjs/filesystem/physical/getRouteNodes.cjs.map +1 -1
- package/dist/cjs/filesystem/physical/getRouteNodes.d.cts +3 -2
- package/dist/cjs/generator.cjs +25 -3
- package/dist/cjs/generator.cjs.map +1 -1
- package/dist/cjs/utils.cjs +69 -16
- package/dist/cjs/utils.cjs.map +1 -1
- package/dist/cjs/utils.d.cts +41 -0
- package/dist/esm/filesystem/physical/getRouteNodes.d.ts +3 -2
- package/dist/esm/filesystem/physical/getRouteNodes.js +68 -34
- package/dist/esm/filesystem/physical/getRouteNodes.js.map +1 -1
- package/dist/esm/generator.js +26 -4
- package/dist/esm/generator.js.map +1 -1
- package/dist/esm/utils.d.ts +41 -0
- package/dist/esm/utils.js +69 -16
- package/dist/esm/utils.js.map +1 -1
- package/package.json +2 -2
- package/src/filesystem/physical/getRouteNodes.ts +119 -38
- package/src/generator.ts +33 -5
- package/src/utils.ts +153 -17
package/dist/esm/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/prefer-for-of */\nimport * as fsp from 'node:fs/promises'\nimport path from 'node:path'\nimport * as prettier from 'prettier'\nimport { rootPathId } from './filesystem/physical/rootPathId'\nimport type { Config } from './config'\nimport type { ImportDeclaration, RouteNode } from './types'\n\n/**\n * Prefix map for O(1) parent route lookups.\n * Maps each route path prefix to the route node that owns that prefix.\n * Enables finding longest matching parent without linear search.\n */\nexport class RoutePrefixMap {\n private prefixToRoute: Map<string, RouteNode> = new Map()\n private layoutRoutes: Array<RouteNode> = []\n\n constructor(routes: Array<RouteNode>) {\n for (const route of routes) {\n if (!route.routePath || route.routePath === `/${rootPathId}`) continue\n\n // Index by exact path for direct lookups\n this.prefixToRoute.set(route.routePath, route)\n\n if (\n route._fsRouteType === 'pathless_layout' ||\n route._fsRouteType === 'layout' ||\n route._fsRouteType === '__root'\n ) {\n this.layoutRoutes.push(route)\n }\n }\n\n // Sort by path length descending for longest-match-first\n this.layoutRoutes.sort(\n (a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0),\n )\n }\n\n /**\n * Find the longest matching parent route for a given path.\n * O(k) where k is the number of path segments, not O(n) routes.\n */\n findParent(routePath: string): RouteNode | null {\n if (!routePath || routePath === '/') return null\n\n // Walk up the path segments\n let searchPath = routePath\n while (searchPath.length > 0) {\n const lastSlash = searchPath.lastIndexOf('/')\n if (lastSlash <= 0) break\n\n searchPath = searchPath.substring(0, lastSlash)\n const parent = this.prefixToRoute.get(searchPath)\n if (parent && parent.routePath !== routePath) {\n return parent\n }\n }\n return null\n }\n\n /**\n * Check if a route exists at the given path.\n */\n has(routePath: string): boolean {\n return this.prefixToRoute.has(routePath)\n }\n\n /**\n * Get a route by exact path.\n */\n get(routePath: string): RouteNode | undefined {\n return this.prefixToRoute.get(routePath)\n }\n}\n\nexport function multiSortBy<T>(\n arr: Array<T>,\n accessors: Array<(item: T) => any> = [(d) => d],\n): Array<T> {\n const len = arr.length\n // Pre-compute all accessor values to avoid repeated function calls during sort\n const indexed: Array<{ item: T; index: number; keys: Array<any> }> =\n new Array(len)\n for (let i = 0; i < len; i++) {\n const item = arr[i]!\n const keys = new Array(accessors.length)\n for (let j = 0; j < accessors.length; j++) {\n keys[j] = accessors[j]!(item)\n }\n indexed[i] = { item, index: i, keys }\n }\n\n indexed.sort((a, b) => {\n for (let j = 0; j < accessors.length; j++) {\n const ao = a.keys[j]\n const bo = b.keys[j]\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return a.index - b.index\n })\n\n const result: Array<T> = new Array(len)\n for (let i = 0; i < len; i++) {\n result[i] = indexed[i]!.item\n }\n return result\n}\n\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\nexport function removeLeadingSlash(path: string): string {\n return path.replace(/^\\//, '')\n}\n\nexport function removeTrailingSlash(s: string) {\n return s.replace(/\\/$/, '')\n}\n\nconst BRACKET_CONTENT_RE = /\\[(.*?)\\]/g\nconst SPLIT_REGEX = /(?<!\\[)\\.(?!\\])/g\n\nexport function determineInitialRoutePath(routePath: string) {\n const DISALLOWED_ESCAPE_CHARS = new Set([\n '/',\n '\\\\',\n '?',\n '#',\n ':',\n '*',\n '<',\n '>',\n '|',\n '!',\n '$',\n '%',\n '_',\n ])\n\n const originalRoutePath =\n cleanPath(\n `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,\n ) || ''\n\n const parts = routePath.split(SPLIT_REGEX)\n\n // Escape any characters that in square brackets\n // we keep the original path untouched\n const escapedParts = parts.map((part) => {\n // Check if any disallowed characters are used in brackets\n\n let match\n while ((match = BRACKET_CONTENT_RE.exec(part)) !== null) {\n const character = match[1]\n if (character === undefined) continue\n if (DISALLOWED_ESCAPE_CHARS.has(character)) {\n console.error(\n `Error: Disallowed character \"${character}\" found in square brackets in route path \"${routePath}\".\\nYou cannot use any of the following characters in square brackets: ${Array.from(\n DISALLOWED_ESCAPE_CHARS,\n ).join(', ')}\\nPlease remove and/or replace them.`,\n )\n process.exit(1)\n }\n }\n\n // Since this split segment is safe at this point, we can\n // remove the brackets and replace them with the content inside\n return part.replace(BRACKET_CONTENT_RE, '$1')\n })\n\n // If the syntax for prefix/suffix is different, from the path\n // matching internals of router-core, we'd perform those changes here\n // on the `escapedParts` array before it is joined back together in\n // `final`\n\n const final = cleanPath(`/${escapedParts.join('/')}`) || ''\n\n return {\n routePath: final,\n originalRoutePath,\n }\n}\n\nconst backslashRegex = /\\\\/g\n\nexport function replaceBackslash(s: string) {\n return s.replace(backslashRegex, '/')\n}\n\nconst alphanumericRegex = /[a-zA-Z0-9_]/\nconst splatSlashRegex = /\\/\\$\\//g\nconst trailingSplatRegex = /\\$$/g\nconst bracketSplatRegex = /\\$\\{\\$\\}/g\nconst dollarSignRegex = /\\$/g\nconst splitPathRegex = /[/-]/g\nconst leadingDigitRegex = /^(\\d)/g\n\nconst toVariableSafeChar = (char: string): string => {\n if (alphanumericRegex.test(char)) {\n return char // Keep alphanumeric characters and underscores as is\n }\n\n // Replace special characters with meaningful text equivalents\n switch (char) {\n case '.':\n return 'Dot'\n case '-':\n return 'Dash'\n case '@':\n return 'At'\n case '(':\n return '' // Removed since route groups use parentheses\n case ')':\n return '' // Removed since route groups use parentheses\n case ' ':\n return '' // Remove spaces\n default:\n return `Char${char.charCodeAt(0)}` // For any other characters\n }\n}\n\nexport function routePathToVariable(routePath: string): string {\n const cleaned = removeUnderscores(routePath)\n if (!cleaned) return ''\n\n const parts = cleaned\n .replace(splatSlashRegex, '/splat/')\n .replace(trailingSplatRegex, 'splat')\n .replace(bracketSplatRegex, 'splat')\n .replace(dollarSignRegex, '')\n .split(splitPathRegex)\n\n let result = ''\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!\n const segment = i > 0 ? capitalize(part) : part\n for (let j = 0; j < segment.length; j++) {\n result += toVariableSafeChar(segment[j]!)\n }\n }\n\n return result.replace(leadingDigitRegex, 'R$1')\n}\n\nconst underscoreStartEndRegex = /(^_|_$)/gi\nconst underscoreSlashRegex = /(\\/_|_\\/)/gi\n\nexport function removeUnderscores(s?: string) {\n return s\n ?.replace(underscoreStartEndRegex, '')\n .replace(underscoreSlashRegex, '/')\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nexport function removeLeadingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasLeadingUnderscore = routeToken[0] === '_'\n\n const routeTokenToExclude = hasLeadingUnderscore\n ? routeToken.slice(1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const leadingUnderscoreRegex = hasLeadingUnderscore\n ? new RegExp(`(?<=^|\\\\/)_(?!${escapedRouteToken})`, 'g')\n : new RegExp(`(?<=^|\\\\/)_`, 'g')\n\n return s.replaceAll(leadingUnderscoreRegex, '')\n}\n\nexport function removeTrailingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasTrailingUnderscore = routeToken.slice(-1) === '_'\n\n const routeTokenToExclude = hasTrailingUnderscore\n ? routeToken.slice(0, -1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const trailingUnderscoreRegex = hasTrailingUnderscore\n ? new RegExp(`(?<!${escapedRouteToken})_(?=\\\\/|$)`, 'g')\n : new RegExp(`_(?=\\\\/)|_$`, 'g')\n\n return s.replaceAll(trailingUnderscoreRegex, '')\n}\n\nexport function capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nexport function removeExt(d: string, keepExtension: boolean = false) {\n return keepExtension ? d : d.substring(0, d.lastIndexOf('.')) || d\n}\n\n/**\n * This function writes to a file if the content is different.\n *\n * @param filepath The path to the file\n * @param content Original content\n * @param incomingContent New content\n * @param callbacks Callbacks to run before and after writing\n * @returns Whether the file was written\n */\nexport async function writeIfDifferent(\n filepath: string,\n content: string,\n incomingContent: string,\n callbacks?: { beforeWrite?: () => void; afterWrite?: () => void },\n): Promise<boolean> {\n if (content !== incomingContent) {\n callbacks?.beforeWrite?.()\n await fsp.writeFile(filepath, incomingContent)\n callbacks?.afterWrite?.()\n return true\n }\n return false\n}\n\n/**\n * This function formats the source code using the default formatter (Prettier).\n *\n * @param source The content to format\n * @param config The configuration object\n * @returns The formatted content\n */\nexport async function format(\n source: string,\n config: {\n quoteStyle: 'single' | 'double'\n semicolons: boolean\n },\n): Promise<string> {\n const prettierOptions: prettier.Config = {\n semi: config.semicolons,\n singleQuote: config.quoteStyle === 'single',\n parser: 'typescript',\n }\n return prettier.format(source, prettierOptions)\n}\n\n/**\n * This function resets the regex index to 0 so that it can be reused\n * without having to create a new regex object or worry about the last\n * state when using the global flag.\n *\n * @param regex The regex object to reset\n * @returns\n */\nexport function resetRegex(regex: RegExp) {\n regex.lastIndex = 0\n return\n}\n\n/**\n * This function checks if a file exists.\n *\n * @param file The path to the file\n * @returns Whether the file exists\n */\nexport async function checkFileExists(file: string) {\n try {\n await fsp.access(file, fsp.constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n\nconst possiblyNestedRouteGroupPatternRegex = /\\([^/]+\\)\\/?/g\nexport function removeGroups(s: string) {\n return s.replace(possiblyNestedRouteGroupPatternRegex, '')\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string = '/'): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n\n/**\n * The `node.path` is used as the `id` in the route definition.\n * This function checks if the given node has a parent and if so, it determines the correct path for the given node.\n * @param node - The node to determine the path for.\n * @returns The correct path for the given node.\n */\nexport function determineNodePath(node: RouteNode) {\n return (node.path = node.parent\n ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'\n : node.routePath)\n}\n\n/**\n * Removes the last segment from a given path. Segments are considered to be separated by a '/'.\n *\n * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'.\n * @returns {string} The path with the last segment removed.\n * @example\n * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'\n */\nexport function removeLastSegmentFromPath(routePath: string = '/'): string {\n const segments = routePath.split('/')\n segments.pop() // Remove the last segment\n return segments.join('/')\n}\n\n/**\n * Find parent route using RoutePrefixMap for O(k) lookups instead of O(n).\n */\nexport function hasParentRoute(\n prefixMap: RoutePrefixMap,\n node: RouteNode,\n routePathToCheck: string | undefined,\n): RouteNode | null {\n if (!routePathToCheck || routePathToCheck === '/') {\n return null\n }\n\n return prefixMap.findParent(routePathToCheck)\n}\n\n/**\n * Gets the final variable name for a route\n */\nexport const getResolvedRouteNodeVariableName = (\n routeNode: RouteNode,\n): string => {\n return routeNode.children?.length\n ? `${routeNode.variableName}RouteWithChildren`\n : `${routeNode.variableName}Route`\n}\n\n/**\n * Checks if a given RouteNode is valid for augmenting it with typing based on conditions.\n * Also asserts that the RouteNode is defined.\n *\n * @param routeNode - The RouteNode to check.\n * @returns A boolean indicating whether the RouteNode is defined.\n */\nexport function isRouteNodeValidForAugmentation(\n routeNode?: RouteNode,\n): routeNode is RouteNode {\n if (!routeNode || routeNode.isVirtual) {\n return false\n }\n return true\n}\n\n/**\n * Infers the path for use by TS\n */\nexport const inferPath = (routeNode: RouteNode): string => {\n return routeNode.cleanedPath === '/'\n ? routeNode.cleanedPath\n : (routeNode.cleanedPath?.replace(/\\/$/, '') ?? '')\n}\n\n/**\n * Infers the full path for use by TS\n */\nexport const inferFullPath = (routeNode: RouteNode): string => {\n const fullPath = removeGroups(\n removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '',\n )\n\n return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Creates a map from fullPath to routeNode\n */\nexport const createRouteNodesByFullPath = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => [inferFullPath(routeNode), routeNode]),\n )\n}\n\n/**\n * Create a map from 'to' to a routeNode\n */\nexport const createRouteNodesByTo = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n dedupeBranchesAndIndexRoutes(routeNodes).map((routeNode) => [\n inferTo(routeNode),\n routeNode,\n ]),\n )\n}\n\n/**\n * Create a map from 'id' to a routeNode\n */\nexport const createRouteNodesById = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => {\n const id = routeNode.routePath ?? ''\n return [id, routeNode]\n }),\n )\n}\n\n/**\n * Infers to path\n */\nexport const inferTo = (routeNode: RouteNode): string => {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/') return fullPath\n\n return fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Dedupes branches and index routes\n */\nexport const dedupeBranchesAndIndexRoutes = (\n routes: Array<RouteNode>,\n): Array<RouteNode> => {\n return routes.filter((route) => {\n if (route.children?.find((child) => child.cleanedPath === '/')) return false\n return true\n })\n}\n\nfunction checkUnique<TElement>(routes: Array<TElement>, key: keyof TElement) {\n // Check no two routes have the same `key`\n // if they do, throw an error with the conflicting filePaths\n const keys = routes.map((d) => d[key])\n const uniqueKeys = new Set(keys)\n if (keys.length !== uniqueKeys.size) {\n const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i)\n const conflictingFiles = routes.filter((d) =>\n duplicateKeys.includes(d[key]),\n )\n return conflictingFiles\n }\n return undefined\n}\n\nexport function checkRouteFullPathUniqueness(\n _routes: Array<RouteNode>,\n config: Config,\n) {\n const routes = _routes.map((d) => {\n const inferredFullPath = inferFullPath(d)\n return { ...d, inferredFullPath }\n })\n\n const conflictingFiles = checkUnique(routes, 'inferredFullPath')\n\n if (conflictingFiles !== undefined) {\n const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles\n .map((p) => `\"${p.inferredFullPath}\"`)\n .join(', ')}.\nPlease ensure each Route has a unique full path.\nConflicting files: \\n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n}\n\nexport function buildRouteTreeConfig(\n nodes: Array<RouteNode>,\n disableTypes: boolean,\n depth = 1,\n): Array<string> {\n const children = nodes.map((node) => {\n if (node._fsRouteType === '__root') {\n return\n }\n\n if (node._fsRouteType === 'pathless_layout' && !node.children?.length) {\n return\n }\n\n const route = `${node.variableName}`\n\n if (node.children?.length) {\n const childConfigs = buildRouteTreeConfig(\n node.children,\n disableTypes,\n depth + 1,\n )\n\n const childrenDeclaration = disableTypes\n ? ''\n : `interface ${route}RouteChildren {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const children = `const ${route}RouteChildren${disableTypes ? '' : `: ${route}RouteChildren`} = {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)`\n\n return [\n childConfigs.join('\\n'),\n childrenDeclaration,\n children,\n routeWithChildren,\n ].join('\\n\\n')\n }\n\n return undefined\n })\n\n return children.filter((x) => x !== undefined)\n}\n\nexport function buildImportString(\n importDeclaration: ImportDeclaration,\n): string {\n const { source, specifiers, importKind } = importDeclaration\n return specifiers.length\n ? `import ${importKind === 'type' ? 'type ' : ''}{ ${specifiers.map((s) => (s.local ? `${s.imported} as ${s.local}` : s.imported)).join(', ')} } from '${source}'`\n : ''\n}\n\nexport function lowerCaseFirstChar(value: string) {\n if (!value[0]) {\n return value\n }\n\n return value[0].toLowerCase() + value.slice(1)\n}\n\nexport function mergeImportDeclarations(\n imports: Array<ImportDeclaration>,\n): Array<ImportDeclaration> {\n const merged = new Map<string, ImportDeclaration>()\n\n for (const imp of imports) {\n const key = `${imp.source}-${imp.importKind ?? ''}`\n let existing = merged.get(key)\n if (!existing) {\n existing = { ...imp, specifiers: [] }\n merged.set(key, existing)\n }\n\n const existingSpecs = existing.specifiers\n for (const specifier of imp.specifiers) {\n let found = false\n for (let i = 0; i < existingSpecs.length; i++) {\n const e = existingSpecs[i]!\n if (e.imported === specifier.imported && e.local === specifier.local) {\n found = true\n break\n }\n }\n if (!found) {\n existingSpecs.push(specifier)\n }\n }\n }\n\n return [...merged.values()]\n}\n\nexport const findParent = (node: RouteNode | undefined): string => {\n if (!node) {\n return `rootRouteImport`\n }\n if (node.parent) {\n return `${node.parent.variableName}Route`\n }\n return findParent(node.parent)\n}\n\nexport function buildFileRoutesByPathInterface(opts: {\n routeNodes: Array<RouteNode>\n module: string\n interfaceName: string\n config?: Pick<Config, 'routeToken'>\n}): string {\n return `declare module '${opts.module}' {\n interface ${opts.interfaceName} {\n ${opts.routeNodes\n .map((routeNode) => {\n const filePathId = routeNode.routePath\n const preloaderRoute = `typeof ${routeNode.variableName}RouteImport`\n\n const parent = findParent(routeNode)\n\n return `'${filePathId}': {\n id: '${filePathId}'\n path: '${inferPath(routeNode)}'\n fullPath: '${inferFullPath(routeNode)}'\n preLoaderRoute: ${preloaderRoute}\n parentRoute: typeof ${parent}\n }`\n })\n .join('\\n')}\n }\n}`\n}\n\nexport function getImportPath(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n): string {\n return replaceBackslash(\n removeExt(\n path.relative(\n path.dirname(generatedRouteTreePath),\n path.resolve(config.routesDirectory, node.filePath),\n ),\n config.addExtensions,\n ),\n )\n}\n\nexport function getImportForRouteNode(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n root: string,\n): ImportDeclaration {\n let source = ''\n if (config.importRoutesUsingAbsolutePaths) {\n source = replaceBackslash(\n removeExt(\n path.resolve(root, config.routesDirectory, node.filePath),\n config.addExtensions,\n ),\n )\n } else {\n source = `./${getImportPath(node, config, generatedRouteTreePath)}`\n }\n return {\n source,\n specifiers: [\n {\n imported: 'Route',\n local: `${node.variableName}RouteImport`,\n },\n ],\n } satisfies ImportDeclaration\n}\n"],"names":["path","children"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["../../src/utils.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/prefer-for-of */\nimport * as fsp from 'node:fs/promises'\nimport path from 'node:path'\nimport * as prettier from 'prettier'\nimport { rootPathId } from './filesystem/physical/rootPathId'\nimport type { Config } from './config'\nimport type { ImportDeclaration, RouteNode } from './types'\n\n/**\n * Prefix map for O(1) parent route lookups.\n * Maps each route path prefix to the route node that owns that prefix.\n * Enables finding longest matching parent without linear search.\n */\nexport class RoutePrefixMap {\n private prefixToRoute: Map<string, RouteNode> = new Map()\n private layoutRoutes: Array<RouteNode> = []\n\n constructor(routes: Array<RouteNode>) {\n for (const route of routes) {\n if (!route.routePath || route.routePath === `/${rootPathId}`) continue\n\n // Index by exact path for direct lookups\n this.prefixToRoute.set(route.routePath, route)\n\n if (\n route._fsRouteType === 'pathless_layout' ||\n route._fsRouteType === 'layout' ||\n route._fsRouteType === '__root'\n ) {\n this.layoutRoutes.push(route)\n }\n }\n\n // Sort by path length descending for longest-match-first\n this.layoutRoutes.sort(\n (a, b) => (b.routePath?.length ?? 0) - (a.routePath?.length ?? 0),\n )\n }\n\n /**\n * Find the longest matching parent route for a given path.\n * O(k) where k is the number of path segments, not O(n) routes.\n */\n findParent(routePath: string): RouteNode | null {\n if (!routePath || routePath === '/') return null\n\n // Walk up the path segments\n let searchPath = routePath\n while (searchPath.length > 0) {\n const lastSlash = searchPath.lastIndexOf('/')\n if (lastSlash <= 0) break\n\n searchPath = searchPath.substring(0, lastSlash)\n const parent = this.prefixToRoute.get(searchPath)\n if (parent && parent.routePath !== routePath) {\n return parent\n }\n }\n return null\n }\n\n /**\n * Check if a route exists at the given path.\n */\n has(routePath: string): boolean {\n return this.prefixToRoute.has(routePath)\n }\n\n /**\n * Get a route by exact path.\n */\n get(routePath: string): RouteNode | undefined {\n return this.prefixToRoute.get(routePath)\n }\n}\n\nexport function multiSortBy<T>(\n arr: Array<T>,\n accessors: Array<(item: T) => any> = [(d) => d],\n): Array<T> {\n const len = arr.length\n // Pre-compute all accessor values to avoid repeated function calls during sort\n const indexed: Array<{ item: T; index: number; keys: Array<any> }> =\n new Array(len)\n for (let i = 0; i < len; i++) {\n const item = arr[i]!\n const keys = new Array(accessors.length)\n for (let j = 0; j < accessors.length; j++) {\n keys[j] = accessors[j]!(item)\n }\n indexed[i] = { item, index: i, keys }\n }\n\n indexed.sort((a, b) => {\n for (let j = 0; j < accessors.length; j++) {\n const ao = a.keys[j]\n const bo = b.keys[j]\n\n if (typeof ao === 'undefined') {\n if (typeof bo === 'undefined') {\n continue\n }\n return 1\n }\n\n if (ao === bo) {\n continue\n }\n\n return ao > bo ? 1 : -1\n }\n\n return a.index - b.index\n })\n\n const result: Array<T> = new Array(len)\n for (let i = 0; i < len; i++) {\n result[i] = indexed[i]!.item\n }\n return result\n}\n\nexport function cleanPath(path: string) {\n // remove double slashes\n return path.replace(/\\/{2,}/g, '/')\n}\n\nexport function trimPathLeft(path: string) {\n return path === '/' ? path : path.replace(/^\\/{1,}/, '')\n}\n\nexport function removeLeadingSlash(path: string): string {\n return path.replace(/^\\//, '')\n}\n\nexport function removeTrailingSlash(s: string) {\n return s.replace(/\\/$/, '')\n}\n\nconst BRACKET_CONTENT_RE = /\\[(.*?)\\]/g\nconst SPLIT_REGEX = /(?<!\\[)\\.(?!\\])/g\n\n/**\n * Characters that cannot be escaped in square brackets.\n * These are characters that would cause issues in URLs or file systems.\n */\nconst DISALLOWED_ESCAPE_CHARS = new Set([\n '/',\n '\\\\',\n '?',\n '#',\n ':',\n '*',\n '<',\n '>',\n '|',\n '!',\n '$',\n '%',\n])\n\nexport function determineInitialRoutePath(routePath: string) {\n const originalRoutePath =\n cleanPath(\n `/${(cleanPath(routePath) || '').split(SPLIT_REGEX).join('/')}`,\n ) || ''\n\n const parts = routePath.split(SPLIT_REGEX)\n\n // Escape any characters that in square brackets\n // we keep the original path untouched\n const escapedParts = parts.map((part) => {\n // Check if any disallowed characters are used in brackets\n\n let match\n while ((match = BRACKET_CONTENT_RE.exec(part)) !== null) {\n const character = match[1]\n if (character === undefined) continue\n if (DISALLOWED_ESCAPE_CHARS.has(character)) {\n console.error(\n `Error: Disallowed character \"${character}\" found in square brackets in route path \"${routePath}\".\\nYou cannot use any of the following characters in square brackets: ${Array.from(\n DISALLOWED_ESCAPE_CHARS,\n ).join(', ')}\\nPlease remove and/or replace them.`,\n )\n process.exit(1)\n }\n }\n\n // Since this split segment is safe at this point, we can\n // remove the brackets and replace them with the content inside\n return part.replace(BRACKET_CONTENT_RE, '$1')\n })\n\n // If the syntax for prefix/suffix is different, from the path\n // matching internals of router-core, we'd perform those changes here\n // on the `escapedParts` array before it is joined back together in\n // `final`\n\n const final = cleanPath(`/${escapedParts.join('/')}`) || ''\n\n return {\n routePath: final,\n originalRoutePath,\n }\n}\n\n/**\n * Checks if a segment is fully escaped (entirely wrapped in brackets with no nested brackets).\n * E.g., \"[index]\" -> true, \"[_layout]\" -> true, \"foo[.]bar\" -> false, \"index\" -> false\n */\nfunction isFullyEscapedSegment(originalSegment: string): boolean {\n return (\n originalSegment.startsWith('[') &&\n originalSegment.endsWith(']') &&\n !originalSegment.slice(1, -1).includes('[') &&\n !originalSegment.slice(1, -1).includes(']')\n )\n}\n\n/**\n * Checks if the leading underscore in a segment is escaped.\n * Returns true if:\n * - Segment starts with [_] pattern: \"[_]layout\" -> \"_layout\"\n * - Segment is fully escaped and content starts with _: \"[_1nd3x]\" -> \"_1nd3x\"\n */\nexport function hasEscapedLeadingUnderscore(originalSegment: string): boolean {\n // Pattern: [_]something or [_something]\n return (\n originalSegment.startsWith('[_]') ||\n (originalSegment.startsWith('[_') && isFullyEscapedSegment(originalSegment))\n )\n}\n\n/**\n * Checks if the trailing underscore in a segment is escaped.\n * Returns true if:\n * - Segment ends with [_] pattern: \"blog[_]\" -> \"blog_\"\n * - Segment is fully escaped and content ends with _: \"[_r0ut3_]\" -> \"_r0ut3_\"\n */\nexport function hasEscapedTrailingUnderscore(originalSegment: string): boolean {\n // Pattern: something[_] or [something_]\n return (\n originalSegment.endsWith('[_]') ||\n (originalSegment.endsWith('_]') && isFullyEscapedSegment(originalSegment))\n )\n}\n\nconst backslashRegex = /\\\\/g\n\nexport function replaceBackslash(s: string) {\n return s.replace(backslashRegex, '/')\n}\n\nconst alphanumericRegex = /[a-zA-Z0-9_]/\nconst splatSlashRegex = /\\/\\$\\//g\nconst trailingSplatRegex = /\\$$/g\nconst bracketSplatRegex = /\\$\\{\\$\\}/g\nconst dollarSignRegex = /\\$/g\nconst splitPathRegex = /[/-]/g\nconst leadingDigitRegex = /^(\\d)/g\n\nconst toVariableSafeChar = (char: string): string => {\n if (alphanumericRegex.test(char)) {\n return char // Keep alphanumeric characters and underscores as is\n }\n\n // Replace special characters with meaningful text equivalents\n switch (char) {\n case '.':\n return 'Dot'\n case '-':\n return 'Dash'\n case '@':\n return 'At'\n case '(':\n return '' // Removed since route groups use parentheses\n case ')':\n return '' // Removed since route groups use parentheses\n case ' ':\n return '' // Remove spaces\n default:\n return `Char${char.charCodeAt(0)}` // For any other characters\n }\n}\n\nexport function routePathToVariable(routePath: string): string {\n const cleaned = removeUnderscores(routePath)\n if (!cleaned) return ''\n\n const parts = cleaned\n .replace(splatSlashRegex, '/splat/')\n .replace(trailingSplatRegex, 'splat')\n .replace(bracketSplatRegex, 'splat')\n .replace(dollarSignRegex, '')\n .split(splitPathRegex)\n\n let result = ''\n for (let i = 0; i < parts.length; i++) {\n const part = parts[i]!\n const segment = i > 0 ? capitalize(part) : part\n for (let j = 0; j < segment.length; j++) {\n result += toVariableSafeChar(segment[j]!)\n }\n }\n\n return result.replace(leadingDigitRegex, 'R$1')\n}\n\nconst underscoreStartEndRegex = /(^_|_$)/gi\nconst underscoreSlashRegex = /(\\/_|_\\/)/gi\n\nexport function removeUnderscores(s?: string) {\n return s\n ?.replace(underscoreStartEndRegex, '')\n .replace(underscoreSlashRegex, '/')\n}\n\n/**\n * Removes underscores from a path, but preserves underscores that were escaped\n * in the original path (indicated by [_] syntax).\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped underscores removed\n */\nexport function removeUnderscoresWithEscape(\n routePath?: string,\n originalPath?: string,\n): string {\n if (!routePath) return ''\n if (!originalPath) return removeUnderscores(routePath) ?? ''\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n const newSegments = routeSegments.map((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n\n // Check if leading underscore is escaped\n const leadingEscaped = hasEscapedLeadingUnderscore(originalSegment)\n // Check if trailing underscore is escaped\n const trailingEscaped = hasEscapedTrailingUnderscore(originalSegment)\n\n let result = segment\n\n // Remove leading underscore only if not escaped\n if (result.startsWith('_') && !leadingEscaped) {\n result = result.slice(1)\n }\n\n // Remove trailing underscore only if not escaped\n if (result.endsWith('_') && !trailingEscaped) {\n result = result.slice(0, -1)\n }\n\n return result\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Removes layout segments (segments starting with underscore) from a path,\n * but preserves segments where the underscore was escaped.\n *\n * @param routePath - The path with brackets removed\n * @param originalPath - The original path that may contain [_] escape sequences\n * @returns The path with non-escaped layout segments removed\n */\nexport function removeLayoutSegmentsWithEscape(\n routePath: string = '/',\n originalPath?: string,\n): string {\n if (!originalPath) return removeLayoutSegments(routePath)\n\n const routeSegments = routePath.split('/')\n const originalSegments = originalPath.split('/')\n\n // Keep segments that are NOT pathless (i.e., don't start with unescaped underscore)\n const newSegments = routeSegments.filter((segment, i) => {\n const originalSegment = originalSegments[i] || ''\n return !isSegmentPathless(segment, originalSegment)\n })\n\n return newSegments.join('/')\n}\n\n/**\n * Checks if a segment should be treated as a pathless/layout segment.\n * A segment is pathless if it starts with underscore and the underscore is not escaped.\n *\n * @param segment - The segment from routePath (brackets removed)\n * @param originalSegment - The segment from originalRoutePath (may contain brackets)\n * @returns true if the segment is pathless (has non-escaped leading underscore)\n */\nexport function isSegmentPathless(\n segment: string,\n originalSegment: string,\n): boolean {\n if (!segment.startsWith('_')) return false\n return !hasEscapedLeadingUnderscore(originalSegment)\n}\n\nfunction escapeRegExp(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nexport function removeLeadingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasLeadingUnderscore = routeToken[0] === '_'\n\n const routeTokenToExclude = hasLeadingUnderscore\n ? routeToken.slice(1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const leadingUnderscoreRegex = hasLeadingUnderscore\n ? new RegExp(`(?<=^|\\\\/)_(?!${escapedRouteToken})`, 'g')\n : new RegExp(`(?<=^|\\\\/)_`, 'g')\n\n return s.replaceAll(leadingUnderscoreRegex, '')\n}\n\nexport function removeTrailingUnderscores(s: string, routeToken: string) {\n if (!s) return s\n\n const hasTrailingUnderscore = routeToken.slice(-1) === '_'\n\n const routeTokenToExclude = hasTrailingUnderscore\n ? routeToken.slice(0, -1)\n : routeToken\n\n const escapedRouteToken = escapeRegExp(routeTokenToExclude)\n\n const trailingUnderscoreRegex = hasTrailingUnderscore\n ? new RegExp(`(?<!${escapedRouteToken})_(?=\\\\/|$)`, 'g')\n : new RegExp(`_(?=\\\\/)|_$`, 'g')\n\n return s.replaceAll(trailingUnderscoreRegex, '')\n}\n\nexport function capitalize(s: string) {\n if (typeof s !== 'string') return ''\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nexport function removeExt(d: string, keepExtension: boolean = false) {\n return keepExtension ? d : d.substring(0, d.lastIndexOf('.')) || d\n}\n\n/**\n * This function writes to a file if the content is different.\n *\n * @param filepath The path to the file\n * @param content Original content\n * @param incomingContent New content\n * @param callbacks Callbacks to run before and after writing\n * @returns Whether the file was written\n */\nexport async function writeIfDifferent(\n filepath: string,\n content: string,\n incomingContent: string,\n callbacks?: { beforeWrite?: () => void; afterWrite?: () => void },\n): Promise<boolean> {\n if (content !== incomingContent) {\n callbacks?.beforeWrite?.()\n await fsp.writeFile(filepath, incomingContent)\n callbacks?.afterWrite?.()\n return true\n }\n return false\n}\n\n/**\n * This function formats the source code using the default formatter (Prettier).\n *\n * @param source The content to format\n * @param config The configuration object\n * @returns The formatted content\n */\nexport async function format(\n source: string,\n config: {\n quoteStyle: 'single' | 'double'\n semicolons: boolean\n },\n): Promise<string> {\n const prettierOptions: prettier.Config = {\n semi: config.semicolons,\n singleQuote: config.quoteStyle === 'single',\n parser: 'typescript',\n }\n return prettier.format(source, prettierOptions)\n}\n\n/**\n * This function resets the regex index to 0 so that it can be reused\n * without having to create a new regex object or worry about the last\n * state when using the global flag.\n *\n * @param regex The regex object to reset\n * @returns\n */\nexport function resetRegex(regex: RegExp) {\n regex.lastIndex = 0\n return\n}\n\n/**\n * This function checks if a file exists.\n *\n * @param file The path to the file\n * @returns Whether the file exists\n */\nexport async function checkFileExists(file: string) {\n try {\n await fsp.access(file, fsp.constants.F_OK)\n return true\n } catch {\n return false\n }\n}\n\nconst possiblyNestedRouteGroupPatternRegex = /\\([^/]+\\)\\/?/g\nexport function removeGroups(s: string) {\n return s.replace(possiblyNestedRouteGroupPatternRegex, '')\n}\n\n/**\n * Removes all segments from a given path that start with an underscore ('_').\n *\n * @param {string} routePath - The path from which to remove segments. Defaults to '/'.\n * @returns {string} The path with all underscore-prefixed segments removed.\n * @example\n * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo'\n */\nexport function removeLayoutSegments(routePath: string = '/'): string {\n const segments = routePath.split('/')\n const newSegments = segments.filter((segment) => !segment.startsWith('_'))\n return newSegments.join('/')\n}\n\n/**\n * The `node.path` is used as the `id` in the route definition.\n * This function checks if the given node has a parent and if so, it determines the correct path for the given node.\n * @param node - The node to determine the path for.\n * @returns The correct path for the given node.\n */\nexport function determineNodePath(node: RouteNode) {\n return (node.path = node.parent\n ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/'\n : node.routePath)\n}\n\n/**\n * Removes the last segment from a given path. Segments are considered to be separated by a '/'.\n *\n * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'.\n * @returns {string} The path with the last segment removed.\n * @example\n * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth'\n */\nexport function removeLastSegmentFromPath(routePath: string = '/'): string {\n const segments = routePath.split('/')\n segments.pop() // Remove the last segment\n return segments.join('/')\n}\n\n/**\n * Find parent route using RoutePrefixMap for O(k) lookups instead of O(n).\n */\nexport function hasParentRoute(\n prefixMap: RoutePrefixMap,\n node: RouteNode,\n routePathToCheck: string | undefined,\n): RouteNode | null {\n if (!routePathToCheck || routePathToCheck === '/') {\n return null\n }\n\n return prefixMap.findParent(routePathToCheck)\n}\n\n/**\n * Gets the final variable name for a route\n */\nexport const getResolvedRouteNodeVariableName = (\n routeNode: RouteNode,\n): string => {\n return routeNode.children?.length\n ? `${routeNode.variableName}RouteWithChildren`\n : `${routeNode.variableName}Route`\n}\n\n/**\n * Checks if a given RouteNode is valid for augmenting it with typing based on conditions.\n * Also asserts that the RouteNode is defined.\n *\n * @param routeNode - The RouteNode to check.\n * @returns A boolean indicating whether the RouteNode is defined.\n */\nexport function isRouteNodeValidForAugmentation(\n routeNode?: RouteNode,\n): routeNode is RouteNode {\n if (!routeNode || routeNode.isVirtual) {\n return false\n }\n return true\n}\n\n/**\n * Infers the path for use by TS\n */\nexport const inferPath = (routeNode: RouteNode): string => {\n return routeNode.cleanedPath === '/'\n ? routeNode.cleanedPath\n : (routeNode.cleanedPath?.replace(/\\/$/, '') ?? '')\n}\n\n/**\n * Infers the full path for use by TS\n */\nexport const inferFullPath = (routeNode: RouteNode): string => {\n const fullPath = removeGroups(\n removeUnderscoresWithEscape(\n removeLayoutSegmentsWithEscape(\n routeNode.routePath,\n routeNode.originalRoutePath,\n ),\n routeNode.originalRoutePath,\n ),\n )\n\n return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Creates a map from fullPath to routeNode\n */\nexport const createRouteNodesByFullPath = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => [inferFullPath(routeNode), routeNode]),\n )\n}\n\n/**\n * Create a map from 'to' to a routeNode\n */\nexport const createRouteNodesByTo = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n dedupeBranchesAndIndexRoutes(routeNodes).map((routeNode) => [\n inferTo(routeNode),\n routeNode,\n ]),\n )\n}\n\n/**\n * Create a map from 'id' to a routeNode\n */\nexport const createRouteNodesById = (\n routeNodes: Array<RouteNode>,\n): Map<string, RouteNode> => {\n return new Map(\n routeNodes.map((routeNode) => {\n const id = routeNode.routePath ?? ''\n return [id, routeNode]\n }),\n )\n}\n\n/**\n * Infers to path\n */\nexport const inferTo = (routeNode: RouteNode): string => {\n const fullPath = inferFullPath(routeNode)\n\n if (fullPath === '/') return fullPath\n\n return fullPath.replace(/\\/$/, '')\n}\n\n/**\n * Dedupes branches and index routes\n */\nexport const dedupeBranchesAndIndexRoutes = (\n routes: Array<RouteNode>,\n): Array<RouteNode> => {\n return routes.filter((route) => {\n if (route.children?.find((child) => child.cleanedPath === '/')) return false\n return true\n })\n}\n\nfunction checkUnique<TElement>(routes: Array<TElement>, key: keyof TElement) {\n // Check no two routes have the same `key`\n // if they do, throw an error with the conflicting filePaths\n const keys = routes.map((d) => d[key])\n const uniqueKeys = new Set(keys)\n if (keys.length !== uniqueKeys.size) {\n const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i)\n const conflictingFiles = routes.filter((d) =>\n duplicateKeys.includes(d[key]),\n )\n return conflictingFiles\n }\n return undefined\n}\n\nexport function checkRouteFullPathUniqueness(\n _routes: Array<RouteNode>,\n config: Config,\n) {\n const routes = _routes.map((d) => {\n const inferredFullPath = inferFullPath(d)\n return { ...d, inferredFullPath }\n })\n\n const conflictingFiles = checkUnique(routes, 'inferredFullPath')\n\n if (conflictingFiles !== undefined) {\n const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles\n .map((p) => `\"${p.inferredFullPath}\"`)\n .join(', ')}.\nPlease ensure each Route has a unique full path.\nConflicting files: \\n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\\n ')}\\n`\n throw new Error(errorMessage)\n }\n}\n\nexport function buildRouteTreeConfig(\n nodes: Array<RouteNode>,\n disableTypes: boolean,\n depth = 1,\n): Array<string> {\n const children = nodes.map((node) => {\n if (node._fsRouteType === '__root') {\n return\n }\n\n if (node._fsRouteType === 'pathless_layout' && !node.children?.length) {\n return\n }\n\n const route = `${node.variableName}`\n\n if (node.children?.length) {\n const childConfigs = buildRouteTreeConfig(\n node.children,\n disableTypes,\n depth + 1,\n )\n\n const childrenDeclaration = disableTypes\n ? ''\n : `interface ${route}RouteChildren {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const children = `const ${route}RouteChildren${disableTypes ? '' : `: ${route}RouteChildren`} = {\n ${node.children\n .map(\n (child) =>\n `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`,\n )\n .join(',')}\n}`\n\n const routeWithChildren = `const ${route}RouteWithChildren = ${route}Route._addFileChildren(${route}RouteChildren)`\n\n return [\n childConfigs.join('\\n'),\n childrenDeclaration,\n children,\n routeWithChildren,\n ].join('\\n\\n')\n }\n\n return undefined\n })\n\n return children.filter((x) => x !== undefined)\n}\n\nexport function buildImportString(\n importDeclaration: ImportDeclaration,\n): string {\n const { source, specifiers, importKind } = importDeclaration\n return specifiers.length\n ? `import ${importKind === 'type' ? 'type ' : ''}{ ${specifiers.map((s) => (s.local ? `${s.imported} as ${s.local}` : s.imported)).join(', ')} } from '${source}'`\n : ''\n}\n\nexport function lowerCaseFirstChar(value: string) {\n if (!value[0]) {\n return value\n }\n\n return value[0].toLowerCase() + value.slice(1)\n}\n\nexport function mergeImportDeclarations(\n imports: Array<ImportDeclaration>,\n): Array<ImportDeclaration> {\n const merged = new Map<string, ImportDeclaration>()\n\n for (const imp of imports) {\n const key = `${imp.source}-${imp.importKind ?? ''}`\n let existing = merged.get(key)\n if (!existing) {\n existing = { ...imp, specifiers: [] }\n merged.set(key, existing)\n }\n\n const existingSpecs = existing.specifiers\n for (const specifier of imp.specifiers) {\n let found = false\n for (let i = 0; i < existingSpecs.length; i++) {\n const e = existingSpecs[i]!\n if (e.imported === specifier.imported && e.local === specifier.local) {\n found = true\n break\n }\n }\n if (!found) {\n existingSpecs.push(specifier)\n }\n }\n }\n\n return [...merged.values()]\n}\n\nexport const findParent = (node: RouteNode | undefined): string => {\n if (!node) {\n return `rootRouteImport`\n }\n if (node.parent) {\n return `${node.parent.variableName}Route`\n }\n return findParent(node.parent)\n}\n\nexport function buildFileRoutesByPathInterface(opts: {\n routeNodes: Array<RouteNode>\n module: string\n interfaceName: string\n config?: Pick<Config, 'routeToken'>\n}): string {\n return `declare module '${opts.module}' {\n interface ${opts.interfaceName} {\n ${opts.routeNodes\n .map((routeNode) => {\n const filePathId = routeNode.routePath\n const preloaderRoute = `typeof ${routeNode.variableName}RouteImport`\n\n const parent = findParent(routeNode)\n\n return `'${filePathId}': {\n id: '${filePathId}'\n path: '${inferPath(routeNode)}'\n fullPath: '${inferFullPath(routeNode)}'\n preLoaderRoute: ${preloaderRoute}\n parentRoute: typeof ${parent}\n }`\n })\n .join('\\n')}\n }\n}`\n}\n\nexport function getImportPath(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n): string {\n return replaceBackslash(\n removeExt(\n path.relative(\n path.dirname(generatedRouteTreePath),\n path.resolve(config.routesDirectory, node.filePath),\n ),\n config.addExtensions,\n ),\n )\n}\n\nexport function getImportForRouteNode(\n node: RouteNode,\n config: Config,\n generatedRouteTreePath: string,\n root: string,\n): ImportDeclaration {\n let source = ''\n if (config.importRoutesUsingAbsolutePaths) {\n source = replaceBackslash(\n removeExt(\n path.resolve(root, config.routesDirectory, node.filePath),\n config.addExtensions,\n ),\n )\n } else {\n source = `./${getImportPath(node, config, generatedRouteTreePath)}`\n }\n return {\n source,\n specifiers: [\n {\n imported: 'Route',\n local: `${node.variableName}RouteImport`,\n },\n ],\n } satisfies ImportDeclaration\n}\n"],"names":["path","children"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/router-generator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.142.0",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"source-map": "^0.7.4",
|
|
55
55
|
"tsx": "^4.19.2",
|
|
56
56
|
"zod": "^3.24.2",
|
|
57
|
-
"@tanstack/router-core": "1.141.8",
|
|
58
57
|
"@tanstack/router-utils": "1.141.0",
|
|
58
|
+
"@tanstack/router-core": "1.141.8",
|
|
59
59
|
"@tanstack/virtual-file-routes": "1.141.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
@@ -2,6 +2,7 @@ import path from 'node:path'
|
|
|
2
2
|
import * as fsp from 'node:fs/promises'
|
|
3
3
|
import {
|
|
4
4
|
determineInitialRoutePath,
|
|
5
|
+
hasEscapedLeadingUnderscore,
|
|
5
6
|
removeExt,
|
|
6
7
|
replaceBackslash,
|
|
7
8
|
routePathToVariable,
|
|
@@ -153,7 +154,7 @@ export async function getRouteNodes(
|
|
|
153
154
|
throw new Error(errorMessage)
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
const meta = getRouteMeta(routePath, config)
|
|
157
|
+
const meta = getRouteMeta(routePath, originalRoutePath, config)
|
|
157
158
|
const variableName = meta.variableName
|
|
158
159
|
let routeType: FsRouteType = meta.fsRouteType
|
|
159
160
|
|
|
@@ -164,7 +165,14 @@ export async function getRouteNodes(
|
|
|
164
165
|
|
|
165
166
|
// this check needs to happen after the lazy route has been cleaned up
|
|
166
167
|
// since the routePath is used to determine if a route is pathless
|
|
167
|
-
if (
|
|
168
|
+
if (
|
|
169
|
+
isValidPathlessLayoutRoute(
|
|
170
|
+
routePath,
|
|
171
|
+
originalRoutePath,
|
|
172
|
+
routeType,
|
|
173
|
+
config,
|
|
174
|
+
)
|
|
175
|
+
) {
|
|
168
176
|
routeType = 'pathless_layout'
|
|
169
177
|
}
|
|
170
178
|
|
|
@@ -189,37 +197,64 @@ export async function getRouteNodes(
|
|
|
189
197
|
})
|
|
190
198
|
}
|
|
191
199
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
'',
|
|
204
|
-
|
|
200
|
+
// Get the last segment of originalRoutePath to check for escaping
|
|
201
|
+
const originalSegments = originalRoutePath.split('/').filter(Boolean)
|
|
202
|
+
const lastOriginalSegmentForSuffix =
|
|
203
|
+
originalSegments[originalSegments.length - 1] || ''
|
|
204
|
+
|
|
205
|
+
// List of special suffixes that can be escaped
|
|
206
|
+
const specialSuffixes = [
|
|
207
|
+
'component',
|
|
208
|
+
'errorComponent',
|
|
209
|
+
'notFoundComponent',
|
|
210
|
+
'pendingComponent',
|
|
211
|
+
'loader',
|
|
212
|
+
config.routeToken,
|
|
213
|
+
'lazy',
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
// Only strip the suffix if it wasn't escaped (not wrapped in brackets)
|
|
217
|
+
const suffixToStrip = specialSuffixes.find((suffix) => {
|
|
218
|
+
const endsWithSuffix = routePath.endsWith(`/${suffix}`)
|
|
219
|
+
const isEscaped = lastOriginalSegmentForSuffix === `[${suffix}]`
|
|
220
|
+
return endsWithSuffix && !isEscaped
|
|
221
|
+
})
|
|
205
222
|
|
|
206
|
-
if (
|
|
207
|
-
routePath = '
|
|
223
|
+
if (suffixToStrip) {
|
|
224
|
+
routePath = routePath.replace(new RegExp(`/${suffixToStrip}$`), '')
|
|
225
|
+
originalRoutePath = originalRoutePath.replace(
|
|
226
|
+
new RegExp(`/${suffixToStrip}$`),
|
|
227
|
+
'',
|
|
228
|
+
)
|
|
208
229
|
}
|
|
209
230
|
|
|
210
|
-
if
|
|
211
|
-
|
|
231
|
+
// Check if the index token should be treated specially or as a literal path
|
|
232
|
+
// If it's escaped (wrapped in brackets in originalRoutePath), it should be literal
|
|
233
|
+
const lastOriginalSegment =
|
|
234
|
+
originalRoutePath.split('/').filter(Boolean).pop() || ''
|
|
235
|
+
const isIndexEscaped =
|
|
236
|
+
lastOriginalSegment === `[${config.indexToken}]`
|
|
237
|
+
|
|
238
|
+
if (!isIndexEscaped) {
|
|
239
|
+
if (routePath === config.indexToken) {
|
|
240
|
+
routePath = '/'
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (originalRoutePath === config.indexToken) {
|
|
244
|
+
originalRoutePath = '/'
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
routePath =
|
|
248
|
+
routePath.replace(new RegExp(`/${config.indexToken}$`), '/') ||
|
|
249
|
+
'/'
|
|
250
|
+
|
|
251
|
+
originalRoutePath =
|
|
252
|
+
originalRoutePath.replace(
|
|
253
|
+
new RegExp(`/${config.indexToken}$`),
|
|
254
|
+
'/',
|
|
255
|
+
) || '/'
|
|
212
256
|
}
|
|
213
257
|
|
|
214
|
-
routePath =
|
|
215
|
-
routePath.replace(new RegExp(`/${config.indexToken}$`), '/') || '/'
|
|
216
|
-
|
|
217
|
-
originalRoutePath =
|
|
218
|
-
originalRoutePath.replace(
|
|
219
|
-
new RegExp(`/${config.indexToken}$`),
|
|
220
|
-
'/',
|
|
221
|
-
) || '/'
|
|
222
|
-
|
|
223
258
|
routeNodes.push({
|
|
224
259
|
filePath,
|
|
225
260
|
fullPath,
|
|
@@ -266,12 +301,14 @@ export async function getRouteNodes(
|
|
|
266
301
|
/**
|
|
267
302
|
* Determines the metadata for a given route path based on the provided configuration.
|
|
268
303
|
*
|
|
269
|
-
* @param routePath - The determined initial routePath.
|
|
304
|
+
* @param routePath - The determined initial routePath (with brackets removed).
|
|
305
|
+
* @param originalRoutePath - The original route path (may contain brackets for escaped content).
|
|
270
306
|
* @param config - The user configuration object.
|
|
271
307
|
* @returns An object containing the type of the route and the variable name derived from the route path.
|
|
272
308
|
*/
|
|
273
309
|
export function getRouteMeta(
|
|
274
310
|
routePath: string,
|
|
311
|
+
originalRoutePath: string,
|
|
275
312
|
config: Pick<Config, 'routeToken' | 'indexToken'>,
|
|
276
313
|
): {
|
|
277
314
|
// `__root` is can be more easily determined by filtering down to routePath === /${rootPathId}
|
|
@@ -292,25 +329,50 @@ export function getRouteMeta(
|
|
|
292
329
|
} {
|
|
293
330
|
let fsRouteType: FsRouteType = 'static'
|
|
294
331
|
|
|
295
|
-
|
|
332
|
+
// Get the last segment from the original path to check for escaping
|
|
333
|
+
const originalSegments = originalRoutePath.split('/').filter(Boolean)
|
|
334
|
+
const lastOriginalSegment =
|
|
335
|
+
originalSegments[originalSegments.length - 1] || ''
|
|
336
|
+
|
|
337
|
+
// Helper to check if a specific suffix is escaped
|
|
338
|
+
const isSuffixEscaped = (suffix: string): boolean => {
|
|
339
|
+
return lastOriginalSegment === `[${suffix}]`
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (
|
|
343
|
+
routePath.endsWith(`/${config.routeToken}`) &&
|
|
344
|
+
!isSuffixEscaped(config.routeToken)
|
|
345
|
+
) {
|
|
296
346
|
// layout routes, i.e `/foo/route.tsx` or `/foo/_layout/route.tsx`
|
|
297
347
|
fsRouteType = 'layout'
|
|
298
|
-
} else if (routePath.endsWith('/lazy')) {
|
|
348
|
+
} else if (routePath.endsWith('/lazy') && !isSuffixEscaped('lazy')) {
|
|
299
349
|
// lazy routes, i.e. `/foo.lazy.tsx`
|
|
300
350
|
fsRouteType = 'lazy'
|
|
301
|
-
} else if (routePath.endsWith('/loader')) {
|
|
351
|
+
} else if (routePath.endsWith('/loader') && !isSuffixEscaped('loader')) {
|
|
302
352
|
// loader routes, i.e. `/foo.loader.tsx`
|
|
303
353
|
fsRouteType = 'loader'
|
|
304
|
-
} else if (
|
|
354
|
+
} else if (
|
|
355
|
+
routePath.endsWith('/component') &&
|
|
356
|
+
!isSuffixEscaped('component')
|
|
357
|
+
) {
|
|
305
358
|
// component routes, i.e. `/foo.component.tsx`
|
|
306
359
|
fsRouteType = 'component'
|
|
307
|
-
} else if (
|
|
360
|
+
} else if (
|
|
361
|
+
routePath.endsWith('/pendingComponent') &&
|
|
362
|
+
!isSuffixEscaped('pendingComponent')
|
|
363
|
+
) {
|
|
308
364
|
// pending component routes, i.e. `/foo.pendingComponent.tsx`
|
|
309
365
|
fsRouteType = 'pendingComponent'
|
|
310
|
-
} else if (
|
|
366
|
+
} else if (
|
|
367
|
+
routePath.endsWith('/errorComponent') &&
|
|
368
|
+
!isSuffixEscaped('errorComponent')
|
|
369
|
+
) {
|
|
311
370
|
// error component routes, i.e. `/foo.errorComponent.tsx`
|
|
312
371
|
fsRouteType = 'errorComponent'
|
|
313
|
-
} else if (
|
|
372
|
+
} else if (
|
|
373
|
+
routePath.endsWith('/notFoundComponent') &&
|
|
374
|
+
!isSuffixEscaped('notFoundComponent')
|
|
375
|
+
) {
|
|
314
376
|
// not found component routes, i.e. `/foo.notFoundComponent.tsx`
|
|
315
377
|
fsRouteType = 'notFoundComponent'
|
|
316
378
|
}
|
|
@@ -323,11 +385,14 @@ export function getRouteMeta(
|
|
|
323
385
|
/**
|
|
324
386
|
* Used to validate if a route is a pathless layout route
|
|
325
387
|
* @param normalizedRoutePath Normalized route path, i.e `/foo/_layout/route.tsx` and `/foo._layout.route.tsx` to `/foo/_layout/route`
|
|
388
|
+
* @param originalRoutePath Original route path with brackets for escaped content
|
|
389
|
+
* @param routeType The route type determined from file extension
|
|
326
390
|
* @param config The `router-generator` configuration object
|
|
327
391
|
* @returns Boolean indicating if the route is a pathless layout route
|
|
328
392
|
*/
|
|
329
393
|
function isValidPathlessLayoutRoute(
|
|
330
394
|
normalizedRoutePath: string,
|
|
395
|
+
originalRoutePath: string,
|
|
331
396
|
routeType: FsRouteType,
|
|
332
397
|
config: Pick<Config, 'routeToken' | 'indexToken'>,
|
|
333
398
|
): boolean {
|
|
@@ -336,13 +401,18 @@ function isValidPathlessLayoutRoute(
|
|
|
336
401
|
}
|
|
337
402
|
|
|
338
403
|
const segments = normalizedRoutePath.split('/').filter(Boolean)
|
|
404
|
+
const originalSegments = originalRoutePath.split('/').filter(Boolean)
|
|
339
405
|
|
|
340
406
|
if (segments.length === 0) {
|
|
341
407
|
return false
|
|
342
408
|
}
|
|
343
409
|
|
|
344
410
|
const lastRouteSegment = segments[segments.length - 1]!
|
|
411
|
+
const lastOriginalSegment =
|
|
412
|
+
originalSegments[originalSegments.length - 1] || ''
|
|
345
413
|
const secondToLastRouteSegment = segments[segments.length - 2]
|
|
414
|
+
const secondToLastOriginalSegment =
|
|
415
|
+
originalSegments[originalSegments.length - 2]
|
|
346
416
|
|
|
347
417
|
// If segment === __root, then exit as false
|
|
348
418
|
if (lastRouteSegment === rootPathId) {
|
|
@@ -352,14 +422,25 @@ function isValidPathlessLayoutRoute(
|
|
|
352
422
|
// If segment === config.routeToken and secondToLastSegment is a string that starts with _, then exit as true
|
|
353
423
|
// Since the route is actually a configuration route for a layout/pathless route
|
|
354
424
|
// i.e. /foo/_layout/route.tsx === /foo/_layout.tsx
|
|
425
|
+
// But if the underscore is escaped, it's not a pathless layout
|
|
355
426
|
if (
|
|
356
427
|
lastRouteSegment === config.routeToken &&
|
|
357
|
-
typeof secondToLastRouteSegment === 'string'
|
|
428
|
+
typeof secondToLastRouteSegment === 'string' &&
|
|
429
|
+
typeof secondToLastOriginalSegment === 'string'
|
|
358
430
|
) {
|
|
431
|
+
// Check if the underscore is escaped
|
|
432
|
+
if (hasEscapedLeadingUnderscore(secondToLastOriginalSegment)) {
|
|
433
|
+
return false
|
|
434
|
+
}
|
|
359
435
|
return secondToLastRouteSegment.startsWith('_')
|
|
360
436
|
}
|
|
361
437
|
|
|
362
|
-
// Segment starts with _
|
|
438
|
+
// Segment starts with _ but check if it's escaped
|
|
439
|
+
// If the original segment has [_] at the start, the underscore is escaped and it's not a pathless layout
|
|
440
|
+
if (hasEscapedLeadingUnderscore(lastOriginalSegment)) {
|
|
441
|
+
return false
|
|
442
|
+
}
|
|
443
|
+
|
|
363
444
|
return (
|
|
364
445
|
lastRouteSegment !== config.indexToken &&
|
|
365
446
|
lastRouteSegment !== config.routeToken &&
|
package/src/generator.ts
CHANGED
|
@@ -28,14 +28,15 @@ import {
|
|
|
28
28
|
getResolvedRouteNodeVariableName,
|
|
29
29
|
hasParentRoute,
|
|
30
30
|
isRouteNodeValidForAugmentation,
|
|
31
|
+
isSegmentPathless,
|
|
31
32
|
mergeImportDeclarations,
|
|
32
33
|
multiSortBy,
|
|
33
34
|
removeExt,
|
|
34
35
|
removeGroups,
|
|
35
36
|
removeLastSegmentFromPath,
|
|
36
|
-
|
|
37
|
+
removeLayoutSegmentsWithEscape,
|
|
37
38
|
removeTrailingSlash,
|
|
38
|
-
|
|
39
|
+
removeUnderscoresWithEscape,
|
|
39
40
|
replaceBackslash,
|
|
40
41
|
trimPathLeft,
|
|
41
42
|
} from './utils'
|
|
@@ -1363,16 +1364,30 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved
|
|
|
1363
1364
|
node.path = determineNodePath(node)
|
|
1364
1365
|
|
|
1365
1366
|
const trimmedPath = trimPathLeft(node.path ?? '')
|
|
1367
|
+
const trimmedOriginalPath = trimPathLeft(
|
|
1368
|
+
node.originalRoutePath?.replace(
|
|
1369
|
+
node.parent?.originalRoutePath ?? '',
|
|
1370
|
+
'',
|
|
1371
|
+
) ?? '',
|
|
1372
|
+
)
|
|
1366
1373
|
|
|
1367
1374
|
const split = trimmedPath.split('/')
|
|
1375
|
+
const originalSplit = trimmedOriginalPath.split('/')
|
|
1368
1376
|
const lastRouteSegment = split[split.length - 1] ?? trimmedPath
|
|
1377
|
+
const lastOriginalSegment =
|
|
1378
|
+
originalSplit[originalSplit.length - 1] ?? trimmedOriginalPath
|
|
1369
1379
|
|
|
1380
|
+
// A segment is non-path if it starts with underscore AND the underscore is not escaped
|
|
1370
1381
|
node.isNonPath =
|
|
1371
|
-
lastRouteSegment
|
|
1382
|
+
isSegmentPathless(lastRouteSegment, lastOriginalSegment) ||
|
|
1372
1383
|
split.every((part) => this.routeGroupPatternRegex.test(part))
|
|
1373
1384
|
|
|
1385
|
+
// Use escape-aware functions to compute cleanedPath
|
|
1374
1386
|
node.cleanedPath = removeGroups(
|
|
1375
|
-
|
|
1387
|
+
removeUnderscoresWithEscape(
|
|
1388
|
+
removeLayoutSegmentsWithEscape(node.path, node.originalRoutePath),
|
|
1389
|
+
node.originalRoutePath,
|
|
1390
|
+
),
|
|
1376
1391
|
)
|
|
1377
1392
|
|
|
1378
1393
|
if (
|
|
@@ -1431,6 +1446,8 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved
|
|
|
1431
1446
|
if (!node.isVirtual && isPathlessLayoutWithPath) {
|
|
1432
1447
|
const immediateParentPath =
|
|
1433
1448
|
removeLastSegmentFromPath(node.routePath) || '/'
|
|
1449
|
+
const immediateParentOriginalPath =
|
|
1450
|
+
removeLastSegmentFromPath(node.originalRoutePath) || '/'
|
|
1434
1451
|
let searchPath = immediateParentPath
|
|
1435
1452
|
|
|
1436
1453
|
// Find nearest real (non-virtual, non-index) parent
|
|
@@ -1442,8 +1459,19 @@ ${acc.routeTree.map((child) => `${child.variableName}Route: typeof ${getResolved
|
|
|
1442
1459
|
node.routePath?.replace(candidate.routePath ?? '', '') || '/'
|
|
1443
1460
|
const pathRelativeToParent =
|
|
1444
1461
|
immediateParentPath.replace(candidate.routePath ?? '', '') || '/'
|
|
1462
|
+
const originalPathRelativeToParent =
|
|
1463
|
+
immediateParentOriginalPath.replace(
|
|
1464
|
+
candidate.originalRoutePath ?? '',
|
|
1465
|
+
'',
|
|
1466
|
+
) || '/'
|
|
1445
1467
|
node.cleanedPath = removeGroups(
|
|
1446
|
-
|
|
1468
|
+
removeUnderscoresWithEscape(
|
|
1469
|
+
removeLayoutSegmentsWithEscape(
|
|
1470
|
+
pathRelativeToParent,
|
|
1471
|
+
originalPathRelativeToParent,
|
|
1472
|
+
),
|
|
1473
|
+
originalPathRelativeToParent,
|
|
1474
|
+
),
|
|
1447
1475
|
)
|
|
1448
1476
|
break
|
|
1449
1477
|
}
|