import fs from 'node:fs'; import path from 'node:path'; import { z } from 'zod'; import { config } from '../../config.js'; import { resolveWorkspacePath } from '../../lib/sandbox.js'; import { runCommand } from '../../lib/runCommand.js'; import { errorResult, jsonResult, toErrorMessage } from '../shared.js'; import { writeStaticHtmlTemplate } from './templates/staticHtml.js'; import { writeExpressApiTemplate } from './templates/expressApi.js'; const TEMPLATES = [ 'vite-react', 'vite-vue', 'next', 'express-api', 'static-html', 'laravel', 'codeigniter', 'laravel-react', 'codeigniter-react', ]; export function registerScaffoldProjectTool(server) { server.registerTool('scaffold_project', { title: 'Scaffold Project', description: `Create a new project from a template. Supported templates: ${TEMPLATES.join(', ')}. "laravel-react" and "codeigniter-react" create a PHP backend in "/backend" and a separate Vite+React frontend in "/frontend".`, inputSchema: { template: z.enum(TEMPLATES), name: z.string().min(1).describe('Project/folder name to create.'), directory: z .string() .optional() .describe('Parent directory (relative to the workspace root) to create the project in. Defaults to the workspace root.'), }, }, async ({ template, name, directory }) => { try { const parentDir = resolveWorkspacePath(config.workspaceRoot, directory ?? '.'); fs.mkdirSync(parentDir, { recursive: true }); const targetDir = path.join(parentDir, name); if (fs.existsSync(targetDir) && fs.readdirSync(targetDir).length > 0) { return errorResult(`"${path.relative(config.workspaceRoot, targetDir)}" already exists and is not empty.`); } const result = await runScaffold(template, name, parentDir); return jsonResult(result); } catch (error) { return errorResult(toErrorMessage(error)); } }); } async function runScaffold(template, name, parentDir) { switch (template) { case 'vite-react': return scaffoldVite(name, parentDir, 'react-ts'); case 'vite-vue': return scaffoldVite(name, parentDir, 'vue-ts'); case 'next': return scaffoldNext(name, parentDir); case 'express-api': return { template, path: writeExpressApiTemplate(path.join(parentDir, name), name) }; case 'static-html': return { template, path: writeStaticHtmlTemplate(path.join(parentDir, name), name) }; case 'laravel': return scaffoldComposerProject('laravel/laravel', name, parentDir, template); case 'codeigniter': return scaffoldComposerProject('codeigniter4/appstarter', name, parentDir, template); case 'laravel-react': return scaffoldCombined('laravel/laravel', name, parentDir, template); case 'codeigniter-react': return scaffoldCombined('codeigniter4/appstarter', name, parentDir, template); default: { const exhaustive = template; throw new Error(`Unhandled template: ${exhaustive}`); } } } async function scaffoldVite(name, parentDir, viteTemplate) { const result = await runCommand('npm', ['create', 'vite@latest', name, '--', '--template', viteTemplate], { cwd: parentDir, timeoutMs: config.scaffoldCommandTimeoutMs, }); return { template: viteTemplate === 'react-ts' ? 'vite-react' : 'vite-vue', path: path.join(parentDir, name), ...result }; } async function scaffoldNext(name, parentDir) { const result = await runCommand('npx', [ '--yes', 'create-next-app@latest', name, '--ts', '--eslint', '--app', '--src-dir', '--import-alias', '@/*', '--use-npm', '--no-tailwind', '--yes', ], { cwd: parentDir, timeoutMs: config.scaffoldCommandTimeoutMs }); return { template: 'next', path: path.join(parentDir, name), ...result }; } async function scaffoldComposerProject(composerPackage, name, parentDir, template) { const result = await runCommand('composer', ['create-project', composerPackage, name], { cwd: parentDir, timeoutMs: config.scaffoldCommandTimeoutMs, }); return { template, path: path.join(parentDir, name), ...result }; } async function scaffoldCombined(composerPackage, name, parentDir, template) { const projectDir = path.join(parentDir, name); fs.mkdirSync(projectDir, { recursive: true }); const backendResult = await runCommand('composer', ['create-project', composerPackage, 'backend'], { cwd: projectDir, timeoutMs: config.scaffoldCommandTimeoutMs, }); const frontendResult = await runCommand('npm', ['create', 'vite@latest', 'frontend', '--', '--template', 'react-ts'], { cwd: projectDir, timeoutMs: config.scaffoldCommandTimeoutMs }); return { template, path: projectDir, backend: { path: path.join(projectDir, 'backend'), ...backendResult }, frontend: { path: path.join(projectDir, 'frontend'), ...frontendResult }, note: 'Backend and frontend were scaffolded as independent siblings (not wired together via Inertia). Point the frontend at the backend API base URL as a next step.', }; } //# sourceMappingURL=scaffoldProject.js.map