119 lines
5.3 KiB
JavaScript
119 lines
5.3 KiB
JavaScript
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
|