import fs from 'node:fs'; import path from 'node:path'; import { z } from 'zod'; import { resolveWorkspacePath } from '../../lib/sandbox.js'; import { pascalCase } from '../../lib/strings.js'; import { errorResult, jsonResult, toErrorMessage } from '../shared.js'; import { requireReact } from './requireReact.js'; function resolveAppDir(root) { if (fs.existsSync(path.join(root, 'src', 'app'))) return path.join(root, 'src', 'app'); return path.join(root, 'app'); } function routeSegments(routePath) { return routePath.split('/').filter(Boolean); } function assertNextAppRouter(root) { const appDir = resolveAppDir(root); const hasLayout = fs.existsSync(path.join(appDir, 'layout.tsx')) || fs.existsSync(path.join(appDir, 'layout.jsx')) || fs.existsSync(path.join(root, 'next.config.js')) || fs.existsSync(path.join(root, 'next.config.mjs')) || fs.existsSync(path.join(root, 'next.config.ts')); if (!hasLayout && !fs.existsSync(appDir)) { throw new Error('Next.js App Router not detected. Expected an app/ (or src/app/) directory or next.config.*.'); } } export function registerGenerateNextPageTool(server) { server.registerTool('generate_next_page', { title: 'Generate Next.js Page', description: 'Scaffold a Next.js App Router page.tsx under app/{path}/ (or src/app/). Optionally also creates loading.tsx and error.tsx.', inputSchema: { path: z.string().min(1).describe('URL path, e.g. "/users" or "/blog/[slug]".'), title: z.string().optional().describe('Optional heading text; defaults to a PascalCase name from the path.'), withLoading: z.boolean().optional().default(false).describe('Also create loading.tsx.'), withError: z.boolean().optional().default(false).describe('Also create error.tsx.'), overwrite: z.boolean().optional().default(false), }, }, async ({ path: routePath, title, withLoading, withError, overwrite }) => { try { const react = requireReact(); assertNextAppRouter(react.root); const typescript = fs.existsSync(path.join(react.root, 'tsconfig.json')); const ext = typescript ? 'tsx' : 'jsx'; const appDir = resolveAppDir(react.root); const dir = resolveWorkspacePath(appDir, path.join(...routeSegments(routePath))); fs.mkdirSync(dir, { recursive: true }); const heading = title ?? pascalCase(routeSegments(routePath).join(' ') || 'Page'); const files = []; const pageFile = path.join(dir, `page.${ext}`); if (!overwrite && fs.existsSync(pageFile)) { return errorResult(`"${pageFile}" already exists. Pass overwrite: true to replace it.`); } fs.writeFileSync(pageFile, `export default function ${heading.replace(/[^A-Za-z0-9]/g, '') || 'Page'}Page() { return (

${heading}

); } `); files.push(pageFile); if (withLoading) { const loadingFile = path.join(dir, `loading.${ext}`); if (overwrite || !fs.existsSync(loadingFile)) { fs.writeFileSync(loadingFile, `export default function Loading() {\n return

Loading…

;\n}\n`); files.push(loadingFile); } } if (withError) { const errorFile = path.join(dir, `error.${ext}`); if (overwrite || !fs.existsSync(errorFile)) { fs.writeFileSync(errorFile, `'use client';\n\nexport default function Error({ error, reset }: { error: Error; reset: () => void }) {\n return (\n
\n

{error.message}

\n \n
\n );\n}\n`); files.push(errorFile); } } return jsonResult({ router: 'app', path: routePath, files }); } catch (error) { return errorResult(toErrorMessage(error)); } }); } export function registerGenerateNextLayoutTool(server) { server.registerTool('generate_next_layout', { title: 'Generate Next.js Layout', description: 'Scaffold a Next.js App Router layout.tsx under app/{path}/ (or src/app/).', inputSchema: { path: z .string() .optional() .default('') .describe('URL path segment for a nested layout, e.g. "/dashboard". Empty for the root layout.'), overwrite: z.boolean().optional().default(false), }, }, async ({ path: routePath, overwrite }) => { try { const react = requireReact(); assertNextAppRouter(react.root); const typescript = fs.existsSync(path.join(react.root, 'tsconfig.json')); const ext = typescript ? 'tsx' : 'jsx'; const appDir = resolveAppDir(react.root); const segments = routeSegments(routePath ?? ''); const dir = segments.length === 0 ? appDir : resolveWorkspacePath(appDir, path.join(...segments)); fs.mkdirSync(dir, { recursive: true }); const layoutFile = path.join(dir, `layout.${ext}`); if (!overwrite && fs.existsSync(layoutFile)) { return errorResult(`"${layoutFile}" already exists. Pass overwrite: true to replace it.`); } const childrenType = typescript ? '{ children }: { children: React.ReactNode }' : '{ children }'; fs.writeFileSync(layoutFile, `export default function Layout(${childrenType}) { return (
{children}
); } `); return jsonResult({ router: 'app', path: routePath || '/', file: layoutFile }); } catch (error) { return errorResult(toErrorMessage(error)); } }); } //# sourceMappingURL=generateNextFiles.js.map