init project

This commit is contained in:
root
2026-07-31 13:12:54 -04:00
parent 0da92d5e02
commit f3863f760c
7215 changed files with 1860260 additions and 1 deletions
+125
View File
@@ -0,0 +1,125 @@
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 (
<main>
<h1>${heading}</h1>
</main>
);
}
`);
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 <p>Loading…</p>;\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 <div>\n <p>{error.message}</p>\n <button type="button" onClick={() => reset()}>\n Try again\n </button>\n </div>\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 (
<section>
{children}
</section>
);
}
`);
return jsonResult({ router: 'app', path: routePath || '/', file: layoutFile });
}
catch (error) {
return errorResult(toErrorMessage(error));
}
});
}
//# sourceMappingURL=generateNextFiles.js.map