import fs from 'node:fs'; import path from 'node:path'; import { z } from 'zod'; import { errorResult, jsonResult, toErrorMessage } from '../shared.js'; import { requireReact } from './requireReact.js'; export function registerAddReactRouteTool(server) { server.registerTool('add_react_route', { title: 'Add React Route', description: 'Add a route entry for the detected React router: Next.js App Router (creates a page file), Next.js Pages Router (creates a page file), react-router (appends a route to a routes file), or TanStack Router (appends a file-based route under src/routes).', inputSchema: { path: z.string().min(1).describe('URL path, e.g. "/users" or "/users/:id".'), component: z .string() .min(1) .describe('Component name to import, e.g. "UserList" or a relative path like "./pages/UserList".'), file: z.string().optional().describe('Override the file to write/edit. Auto-detected by default.'), }, }, async ({ path: routePath, component, file: fileOverride }) => { try { const react = requireReact(); const router = detectRouterType(react.root); switch (router) { case 'next-app': case 'next-pages': { const file = fileOverride ?? path.join(react.root, router === 'next-app' ? 'app' : 'pages', ...routePath.split('/').filter(Boolean), 'page.tsx'); const created = writeNextPage(file, component, router === 'next-app'); return jsonResult({ router, file, created }); } case 'react-router': case 'tanstack-router': { const file = fileOverride ?? (router === 'react-router' ? path.join(react.root, 'src', 'routes.tsx') : path.join(react.root, 'src', 'routes', `${routePath.replace(/[^a-z0-9]/gi, '-')}.tsx`)); const wired = appendRoute(file, routePath, component, router); return jsonResult({ router, file, wired }); } default: return jsonResult({ router: 'unknown', message: 'No known React router detected (Next.js app/pages, react-router, or TanStack Router). ' + 'Provide the file argument explicitly to add a route entry.', }); } } catch (error) { return errorResult(toErrorMessage(error)); } }); } function detectRouterType(root) { if (fs.existsSync(path.join(root, 'app', 'layout.tsx')) || fs.existsSync(path.join(root, 'app', 'layout.jsx'))) { return 'next-app'; } if (fs.existsSync(path.join(root, 'pages', 'index.tsx')) || fs.existsSync(path.join(root, 'pages', 'index.jsx'))) { return 'next-pages'; } if (fs.existsSync(path.join(root, 'src', 'app', 'layout.tsx')) || fs.existsSync(path.join(root, 'src', 'app', 'layout.jsx'))) { return 'next-app'; } if (fs.existsSync(path.join(root, 'src', 'pages', 'index.tsx')) || fs.existsSync(path.join(root, 'src', 'pages', 'index.jsx'))) { return 'next-pages'; } const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); const deps = { ...pkg.dependencies, ...pkg.devDependencies }; if (deps['@tanstack/react-router']) return 'tanstack-router'; if (deps['react-router-dom'] || deps['react-router']) return 'react-router'; return 'unknown'; } function writeNextPage(file, component, appRouter) { fs.mkdirSync(path.dirname(file), { recursive: true }); if (fs.existsSync(file)) return false; const importPath = component.startsWith('.') ? component : `./${component}`; const contents = appRouter ? `import { ${component} } from '${importPath}'; export default function Page() { return <${component} />; } ` : `import { ${component} } from '${importPath}'; export default function ${component}Page() { return <${component} />; } `; fs.writeFileSync(file, contents); return true; } function appendRoute(file, routePath, component, router) { fs.mkdirSync(path.dirname(file), { recursive: true }); const importPath = component.startsWith('.') ? component : `./${component}`; const routeLine = router === 'tanstack-router' ? `// TODO: route ${routePath} -> <${component} /> (TanStack Router uses file-based routing; this file was created).` : `{ path: '${routePath}', element: <${component} /> },`; if (!fs.existsSync(file)) { fs.writeFileSync(file, `import { ${component} } from '${importPath}'; // Add this route to your router configuration: ${routeLine} `); return true; } const existing = fs.readFileSync(file, 'utf8'); if (existing.includes(routeLine.trim())) return false; let next = existing; if (!new RegExp(`import\\s+\\{[^}]*\\b${component}\\b[^}]*\\}\\s+from\\s+['"]${importPath.replace(/\./g, '\\.')}['"];`).test(next)) { next = `import { ${component} } from '${importPath}';\n${next}`; } fs.writeFileSync(file, `${next.trimEnd()}\n${routeLine}\n`); return true; } //# sourceMappingURL=addReactRoute.js.map