import fs from 'node:fs'; import path from 'node:path'; import { z } from 'zod'; import { config } from '../../config.js'; import { detectProject } from '../../lib/frameworks/detect.js'; import { camelCase, kebabCase, pascalCase } from '../../lib/strings.js'; import { errorResult, jsonResult, targetSchema, toErrorMessage } from '../shared.js'; const METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; export function registerGenerateApiRouteTool(server) { server.registerTool('generate_api_route', { title: 'Generate API Route', description: 'Scaffold a backend route + handler for the detected framework: controller + routes/api.php entry for Laravel, controller + Routes.php entry for CodeIgniter, an app/pages API route file for Next.js, or a standalone Express router file otherwise.', inputSchema: { name: z.string().min(1).describe('Resource/route name, e.g. "posts" or "UserProfile".'), method: z.enum(METHODS).optional().default('GET'), target: targetSchema, }, }, async ({ name, method, target }) => { try { const detected = detectProject(config.workspaceRoot); const adapter = target === 'frontend' ? detected.frontend : target === 'backend' ? detected.backend : (detected.backend ?? detected.frontend ?? detected.primary); if (!adapter) return errorResult(`target "${target}" was requested but no such project was detected.`); switch (adapter.kind) { case 'laravel': return jsonResult(generateLaravelRoute(adapter.root, name, method)); case 'codeigniter': return jsonResult(generateCodeIgniterRoute(adapter.root, name, method)); case 'react': return jsonResult(generateNextApiRoute(adapter.root, name, method)); default: return jsonResult(generateExpressRoute(adapter.root, name, method)); } } catch (error) { return errorResult(toErrorMessage(error)); } }); } function appendUnlessPresent(file, header, line) { fs.mkdirSync(path.dirname(file), { recursive: true }); if (!fs.existsSync(file)) { fs.writeFileSync(file, header); } const existing = fs.readFileSync(file, 'utf8'); if (!existing.includes(line.trim())) { fs.appendFileSync(file, `${line}\n`); } } function writeIfMissing(file, contents) { fs.mkdirSync(path.dirname(file), { recursive: true }); if (fs.existsSync(file)) return false; fs.writeFileSync(file, contents); return true; } function generateLaravelRoute(root, name, method) { const controllerName = `${pascalCase(name)}Controller`; const controllerFile = path.join(root, 'app', 'Http', 'Controllers', `${controllerName}.php`); const controllerCreated = writeIfMissing(controllerFile, `json([]); } } `); const routesFile = path.join(root, 'routes', 'api.php'); appendUnlessPresent(routesFile, `response->setJSON([]); } } `); const routesFile = path.join(root, 'app', 'Config', 'Routes.php'); appendUnlessPresent(routesFile, `${method.toLowerCase()}('${kebabCase(name)}', '${controllerName}::index');`); return { kind: 'codeigniter', controllerFile, controllerCreated, routesFile, route: `${method} /${kebabCase(name)}` }; } function generateNextApiRoute(root, name, method) { const appDir = path.join(root, 'app'); const srcAppDir = path.join(root, 'src', 'app'); const pagesDir = path.join(root, 'pages'); const srcPagesDir = path.join(root, 'src', 'pages'); const appRoot = fs.existsSync(appDir) ? appDir : fs.existsSync(srcAppDir) ? srcAppDir : null; const pagesRoot = fs.existsSync(pagesDir) ? pagesDir : fs.existsSync(srcPagesDir) ? srcPagesDir : null; if (appRoot) { const file = path.join(appRoot, 'api', kebabCase(name), 'route.ts'); const created = writeIfMissing(file, `import { NextResponse } from 'next/server'; export async function ${method}() { return NextResponse.json({}); } `); return { kind: 'next-app-router', file, created, route: `${method} /api/${kebabCase(name)}` }; } if (pagesRoot) { const file = path.join(pagesRoot, 'api', `${kebabCase(name)}.ts`); const created = writeIfMissing(file, `import type { NextApiRequest, NextApiResponse } from 'next'; export default function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== '${method}') { res.status(405).end(); return; } res.status(200).json({}); } `); return { kind: 'next-pages-router', file, created, route: `${method} /api/${kebabCase(name)}` }; } throw new Error('The detected React project has no "app" or "pages" directory, so it has no built-in API routes (this looks like a plain Vite React app, not Next.js). ' + 'Scaffold a separate backend (Laravel/CodeIgniter/express-api) instead, or use target: "backend" if one already exists.'); } function generateExpressRoute(root, name, method) { const file = path.join(root, 'routes', `${kebabCase(name)}.js`); const created = writeIfMissing(file, `import { Router } from 'express'; const router = Router(); router.${method.toLowerCase()}('/', (req, res) => { res.json({}); }); export default router; `); const varName = `${camelCase(name)}Router`; return { kind: 'express', file, created, route: `${method} /api/${kebabCase(name)}`, wiring: `Not auto-mounted. Add to your app entry point: import ${varName} from './routes/${kebabCase(name)}.js'; app.use('/api/${kebabCase(name)}', ${varName});`, }; } //# sourceMappingURL=generateApiRoute.js.map