init project
This commit is contained in:
+122
@@ -0,0 +1,122 @@
|
||||
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 "<name>/backend" and a separate Vite+React frontend in "<name>/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
|
||||
Reference in New Issue
Block a user