Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@tanstack/router-generator

Package Overview
Dependencies
Maintainers
2
Versions
94
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tanstack/router-generator - npm Package Compare versions

Comparing version 1.49.3 to 1.51.0

3

dist/esm/config.d.ts

@@ -15,2 +15,3 @@ import { z } from 'zod';

disableManifestGeneration: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
apiBase: z.ZodDefault<z.ZodOptional<z.ZodString>>;
routeTreeFileHeader: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;

@@ -36,2 +37,3 @@ routeTreeFileFooter: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>;

disableManifestGeneration: boolean;
apiBase: string;
routeTreeFileHeader: string[];

@@ -57,2 +59,3 @@ routeTreeFileFooter: string[];

disableManifestGeneration?: boolean | undefined;
apiBase?: string | undefined;
routeTreeFileHeader?: string[] | undefined;

@@ -59,0 +62,0 @@ routeTreeFileFooter?: string[] | undefined;

@@ -16,2 +16,3 @@ import path from "node:path";

disableManifestGeneration: z.boolean().optional().default(false),
apiBase: z.string().optional().default("/api"),
routeTreeFileHeader: z.array(z.string()).optional().default([

@@ -18,0 +19,0 @@ "/* prettier-ignore-start */",

@@ -17,2 +17,3 @@ import { Config } from './config.js';

isRoute?: boolean;
isAPIRoute?: boolean;
isLoader?: boolean;

@@ -49,1 +50,14 @@ isComponent?: boolean;

export declare const inferPath: (routeNode: RouteNode) => string;
export type StartAPIRoutePathSegment = {
value: string;
type: 'path' | 'param' | 'splat';
};
/**
* This function takes in a path in the format accepted by TanStack Router
* and returns an array of path segments that can be used to generate
* the pathname of the TanStack Start API route.
*
* @param src
* @returns
*/
export declare function startAPIRouteSegmentsFromTSRFilePath(src: string): Array<StartAPIRoutePathSegment>;

169

dist/esm/generator.js

@@ -41,3 +41,3 @@ import path from "node:path";

const filePathNoExt = removeExt(filePath);
let routePath = cleanPath(`/${filePathNoExt.split(".").join("/")}`) || "";
let routePath = determineInitialRoutePath(filePathNoExt);
if (routeFilePrefix) {

@@ -61,2 +61,5 @@ routePath = routePath.replaceAll(routeFilePrefix, "");

const isLoader = routePath.endsWith("/loader");
const isAPIRoute = routePath.startsWith(
`${removeTrailingSlash(config.apiBase)}/`
);
const segments = routePath.split("/");

@@ -95,3 +98,4 @@ const isLayout = ((_a = segments[segments.length - 1]) == null ? void 0 : _a.startsWith("_")) || false;

isLazy,
isLayout
isLayout,
isAPIRoute
});

@@ -311,6 +315,48 @@ }

};
for (const node of preRouteNodes) {
for (const node of preRouteNodes.filter((d) => !d.isAPIRoute)) {
await handleNode(node);
}
function buildRouteConfig(nodes, depth = 1) {
const startAPIRouteNodes = checkStartAPIRoutes(
preRouteNodes.filter((d) => d.isAPIRoute)
);
const handleAPINode = async (node) => {
var _a;
const routeCode = fs.readFileSync(node.fullPath, "utf-8");
const escapedRoutePath = removeTrailingUnderscores(
((_a = node.routePath) == null ? void 0 : _a.replaceAll("$", "$$")) ?? ""
);
if (!routeCode) {
const replaced = `import { json } from '@tanstack/start'
import { createAPIFileRoute } from '@tanstack/start/api'
export const Route = createAPIFileRoute('${escapedRoutePath}')({
GET: ({ request, params }) => {
return json({ message: 'Hello ${escapedRoutePath}' })
},
})
`;
logger.log(`🟡 Creating ${node.fullPath}`);
fs.writeFileSync(
node.fullPath,
await prettier.format(replaced, prettierOptions)
);
} else {
const copied = routeCode.replace(
/(createAPIFileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`
);
if (copied !== routeCode) {
logger.log(`🟡 Updating ${node.fullPath}`);
await fsp.writeFile(
node.fullPath,
await prettier.format(copied, prettierOptions)
);
}
}
};
for (const node of startAPIRouteNodes) {
await handleAPINode(node);
}
function buildRouteTreeConfig(nodes, depth = 1) {
const children = nodes.map((node) => {

@@ -326,3 +372,3 @@ var _a, _b;

if ((_b = node.children) == null ? void 0 : _b.length) {
const childConfigs = buildRouteConfig(node.children, depth + 1);
const childConfigs = buildRouteTreeConfig(node.children, depth + 1);
return `${route}: ${route}.addChildren({${spaces(depth * 4)}${childConfigs}})`;

@@ -334,3 +380,3 @@ }

}
const routeConfigChildrenText = buildRouteConfig(routeTree);
const routeConfigChildrenText = buildRouteTreeConfig(routeTree);
const sortedRouteNodes = multiSortBy(routeNodes, [

@@ -481,34 +527,35 @@ (d) => {

].filter(Boolean).join("\n\n");
const createRouteManifest = () => JSON.stringify(
{
routes: {
__root__: {
filePath: rootRouteNode == null ? void 0 : rootRouteNode.filePath,
children: routeTree.map(
(d) => getFilePathIdAndRouteIdFromPath(d.routePath)[1]
)
},
...Object.fromEntries(
routeNodes.map((d) => {
var _a, _b;
const [filePathId, routeId] = getFilePathIdAndRouteIdFromPath(
d.routePath
);
return [
routeId,
{
filePath: d.filePath,
parent: ((_a = d.parent) == null ? void 0 : _a.routePath) ? getFilePathIdAndRouteIdFromPath(d.parent.routePath)[1] : void 0,
children: (_b = d.children) == null ? void 0 : _b.map(
(childRoute) => getFilePathIdAndRouteIdFromPath(childRoute.routePath)[1]
)
}
];
})
const createRouteManifest = () => {
const routesManifest = {
__root__: {
filePath: rootRouteNode == null ? void 0 : rootRouteNode.filePath,
children: routeTree.map(
(d) => getFilePathIdAndRouteIdFromPath(d.routePath)[1]
)
}
},
null,
2
);
},
...Object.fromEntries(
routeNodes.map((d) => {
var _a, _b;
const [_, routeId] = getFilePathIdAndRouteIdFromPath(d.routePath);
return [
routeId,
{
filePath: d.filePath,
parent: ((_a = d.parent) == null ? void 0 : _a.routePath) ? getFilePathIdAndRouteIdFromPath(d.parent.routePath)[1] : void 0,
children: (_b = d.children) == null ? void 0 : _b.map(
(childRoute) => getFilePathIdAndRouteIdFromPath(childRoute.routePath)[1]
)
}
];
})
)
};
return JSON.stringify(
{
routes: routesManifest
},
null,
2
);
};
const routeConfigFileContent = await prettier.format(

@@ -592,2 +639,8 @@ config.disableManifestGeneration ? routeImports : [

}
function removeTrailingSlash(s) {
return s.replace(/\/$/, "");
}
function determineInitialRoutePath(routePath) {
return cleanPath(`/${routePath.split(".").join("/")}`) || "";
}
function determineNodePath(node) {

@@ -641,2 +694,41 @@ var _a;

}
function checkStartAPIRoutes(_routes) {
if (_routes.length === 0) {
return [];
}
const routes = _routes.map((d) => {
const routePath = removeTrailingSlash(d.routePath ?? "");
return { ...d, routePath };
});
const routePaths = routes.map((d) => d.routePath);
const uniqueRoutePaths = new Set(routePaths);
if (routePaths.length !== uniqueRoutePaths.size) {
const duplicateRoutePaths = routePaths.filter(
(d, i) => routePaths.indexOf(d) !== i
);
const conflictingFiles = routes.filter((d) => duplicateRoutePaths.includes(d.routePath)).map((d) => `${d.fullPath}`);
const errorMessage = `Conflicting configuration paths was for found for the following API route${duplicateRoutePaths.length > 1 ? "s" : ""}: ${duplicateRoutePaths.map((p) => `"${p}"`).join(", ")}.
Please ensure each API route has a unique route path.
Conflicting files:
${conflictingFiles.join("\n ")}
`;
throw new Error(errorMessage);
}
return routes;
}
function startAPIRouteSegmentsFromTSRFilePath(src) {
const routePath = determineInitialRoutePath(src);
const parts = routePath.replaceAll(".", "/").split("/").filter((p) => !!p && p !== "index");
const segments = parts.map((part) => {
if (part.startsWith("$")) {
if (part === "$") {
return { value: part, type: "splat" };
}
part.replaceAll("$", "");
return { value: part, type: "param" };
}
return { value: part, type: "path" };
});
return segments;
}
export {

@@ -650,4 +742,5 @@ generator,

removeLastSegmentFromPath,
rootPathId
rootPathId,
startAPIRouteSegmentsFromTSRFilePath
};
//# sourceMappingURL=generator.js.map

@@ -1,2 +0,4 @@

export { type Config, configSchema, getConfig } from './config.js';
export { generator } from './generator.js';
export { configSchema, getConfig } from './config.js';
export type { Config } from './config.js';
export { generator, startAPIRouteSegmentsFromTSRFilePath } from './generator.js';
export type { StartAPIRoutePathSegment } from './generator.js';
import { configSchema, getConfig } from "./config.js";
import { generator } from "./generator.js";
import { generator, startAPIRouteSegmentsFromTSRFilePath } from "./generator.js";
export {
configSchema,
generator,
getConfig
getConfig,
startAPIRouteSegmentsFromTSRFilePath
};
//# sourceMappingURL=index.js.map
{
"name": "@tanstack/router-generator",
"version": "1.49.3",
"version": "1.51.0",
"description": "Modern and scalable routing for React applications",

@@ -5,0 +5,0 @@ "author": "Tanner Linsley",

@@ -17,2 +17,3 @@ import path from 'node:path'

disableManifestGeneration: z.boolean().optional().default(false),
apiBase: z.string().optional().default('/api'),
routeTreeFileHeader: z

@@ -19,0 +20,0 @@ .array(z.string())

@@ -27,2 +27,3 @@ import path from 'node:path'

isRoute?: boolean
isAPIRoute?: boolean
isLoader?: boolean

@@ -80,4 +81,3 @@ isComponent?: boolean

const filePathNoExt = removeExt(filePath)
let routePath =
cleanPath(`/${filePathNoExt.split('.').join('/')}`) || ''
let routePath = determineInitialRoutePath(filePathNoExt)

@@ -110,2 +110,5 @@ if (routeFilePrefix) {

const isLoader = routePath.endsWith('/loader')
const isAPIRoute = routePath.startsWith(
`${removeTrailingSlash(config.apiBase)}/`,
)

@@ -154,2 +157,3 @@ const segments = routePath.split('/')

isLayout,
isAPIRoute,
})

@@ -465,7 +469,55 @@ }

for (const node of preRouteNodes) {
for (const node of preRouteNodes.filter((d) => !d.isAPIRoute)) {
await handleNode(node)
}
function buildRouteConfig(nodes: Array<RouteNode>, depth = 1): string {
const startAPIRouteNodes: Array<RouteNode> = checkStartAPIRoutes(
preRouteNodes.filter((d) => d.isAPIRoute),
)
const handleAPINode = async (node: RouteNode) => {
const routeCode = fs.readFileSync(node.fullPath, 'utf-8')
const escapedRoutePath = removeTrailingUnderscores(
node.routePath?.replaceAll('$', '$$') ?? '',
)
if (!routeCode) {
const replaced = `import { json } from '@tanstack/start'
import { createAPIFileRoute } from '@tanstack/start/api'
export const Route = createAPIFileRoute('${escapedRoutePath}')({
GET: ({ request, params }) => {
return json({ message: 'Hello ${escapedRoutePath}' })
},
})
`
logger.log(`🟡 Creating ${node.fullPath}`)
fs.writeFileSync(
node.fullPath,
await prettier.format(replaced, prettierOptions),
)
} else {
const copied = routeCode.replace(
/(createAPIFileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g,
(_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`,
)
if (copied !== routeCode) {
logger.log(`🟡 Updating ${node.fullPath}`)
await fsp.writeFile(
node.fullPath,
await prettier.format(copied, prettierOptions),
)
}
}
}
for (const node of startAPIRouteNodes) {
await handleAPINode(node)
}
function buildRouteTreeConfig(nodes: Array<RouteNode>, depth = 1): string {
const children = nodes.map((node) => {

@@ -483,3 +535,3 @@ if (node.isRoot) {

if (node.children?.length) {
const childConfigs = buildRouteConfig(node.children, depth + 1)
const childConfigs = buildRouteTreeConfig(node.children, depth + 1)
return `${route}: ${route}.addChildren({${spaces(depth * 4)}${childConfigs}})`

@@ -494,3 +546,3 @@ }

const routeConfigChildrenText = buildRouteConfig(routeTree)
const routeConfigChildrenText = buildRouteTreeConfig(routeTree)

@@ -681,34 +733,34 @@ const sortedRouteNodes = multiSortBy(routeNodes, [

const createRouteManifest = () =>
JSON.stringify(
const createRouteManifest = () => {
const routesManifest = {
__root__: {
filePath: rootRouteNode?.filePath,
children: routeTree.map(
(d) => getFilePathIdAndRouteIdFromPath(d.routePath!)[1],
),
},
...Object.fromEntries(
routeNodes.map((d) => {
const [_, routeId] = getFilePathIdAndRouteIdFromPath(d.routePath!)
return [
routeId,
{
filePath: d.filePath,
parent: d.parent?.routePath
? getFilePathIdAndRouteIdFromPath(d.parent.routePath)[1]
: undefined,
children: d.children?.map(
(childRoute) =>
getFilePathIdAndRouteIdFromPath(childRoute.routePath!)[1],
),
},
]
}),
),
}
return JSON.stringify(
{
routes: {
__root__: {
filePath: rootRouteNode?.filePath,
children: routeTree.map(
(d) => getFilePathIdAndRouteIdFromPath(d.routePath!)[1],
),
},
...Object.fromEntries(
routeNodes.map((d) => {
const [filePathId, routeId] = getFilePathIdAndRouteIdFromPath(
d.routePath!,
)
return [
routeId,
{
filePath: d.filePath,
parent: d.parent?.routePath
? getFilePathIdAndRouteIdFromPath(d.parent.routePath)[1]
: undefined,
children: d.children?.map(
(childRoute) =>
getFilePathIdAndRouteIdFromPath(childRoute.routePath!)[1],
),
},
]
}),
),
},
routes: routesManifest,
},

@@ -718,2 +770,3 @@ null,

)
}

@@ -845,2 +898,10 @@ const routeConfigFileContent = await prettier.format(

function removeTrailingSlash(s: string) {
return s.replace(/\/$/, '')
}
function determineInitialRoutePath(routePath: string) {
return cleanPath(`/${routePath.split('.').join('/')}`) || ''
}
/**

@@ -944,1 +1005,75 @@ * The `node.path` is used as the `id` in the route definition.

}
function checkStartAPIRoutes(_routes: Array<RouteNode>) {
if (_routes.length === 0) {
return []
}
// Make sure these are valid URLs
// Route Groups and Layout Routes aren't being removed since
// you may want to have an API route that starts with an underscore
// or be wrapped in parentheses
const routes = _routes.map((d) => {
const routePath = removeTrailingSlash(d.routePath ?? '')
return { ...d, routePath }
})
// Check no two API routes have the same routePath
// if they do, throw an error with the conflicting filePaths
const routePaths = routes.map((d) => d.routePath)
const uniqueRoutePaths = new Set(routePaths)
if (routePaths.length !== uniqueRoutePaths.size) {
const duplicateRoutePaths = routePaths.filter(
(d, i) => routePaths.indexOf(d) !== i,
)
const conflictingFiles = routes
.filter((d) => duplicateRoutePaths.includes(d.routePath))
.map((d) => `${d.fullPath}`)
const errorMessage = `Conflicting configuration paths was for found for the following API route${duplicateRoutePaths.length > 1 ? 's' : ''}: ${duplicateRoutePaths
.map((p) => `"${p}"`)
.join(', ')}.
Please ensure each API route has a unique route path.
Conflicting files: \n ${conflictingFiles.join('\n ')}\n`
throw new Error(errorMessage)
}
return routes
}
export type StartAPIRoutePathSegment = {
value: string
type: 'path' | 'param' | 'splat'
}
/**
* This function takes in a path in the format accepted by TanStack Router
* and returns an array of path segments that can be used to generate
* the pathname of the TanStack Start API route.
*
* @param src
* @returns
*/
export function startAPIRouteSegmentsFromTSRFilePath(
src: string,
): Array<StartAPIRoutePathSegment> {
const routePath = determineInitialRoutePath(src)
const parts = routePath
.replaceAll('.', '/')
.split('/')
.filter((p) => !!p && p !== 'index')
const segments: Array<StartAPIRoutePathSegment> = parts.map((part) => {
if (part.startsWith('$')) {
if (part === '$') {
return { value: part, type: 'splat' }
}
part.replaceAll('$', '')
return { value: part, type: 'param' }
}
return { value: part, type: 'path' }
})
return segments
}

@@ -1,2 +0,5 @@

export { type Config, configSchema, getConfig } from './config'
export { generator } from './generator'
export { configSchema, getConfig } from './config'
export type { Config } from './config'
export { generator, startAPIRouteSegmentsFromTSRFilePath } from './generator'
export type { StartAPIRoutePathSegment } from './generator'

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc