/** * Scaffolder template catalog. * * Templates are pure data — `(projectName) => { [relativePath]: contents }`. New * templates are added by appending to `TEMPLATES`; the rest of the scaffolder * (validation, IO, command UX) does not need to change. * * This intentionally mirrors the static/vite-vanilla/vite-react options that * Connect_origin's Developer agent ships, but split out of the giant inline * function so each template body is grep-friendly. */ export type ProjectTemplateId = 'static' | 'vite-vanilla' | 'vite-react'; export interface ProjectTemplate { id: ProjectTemplateId; label: string; detail: string; /** Returns map of `relativePath -> fileContents`. Project name is already sanitized. */ files(name: string): Record; } const README = (name: string, template: string) => `# ${name}\n\n` + `Astra의 Project Scaffolder가 ${new Date().toISOString().slice(0, 10)}에 \`${template}\` 템플릿으로 생성한 프로젝트입니다.\n`; export const TEMPLATES: ProjectTemplate[] = [ { id: 'static', label: 'static', detail: 'index.html 한 장 (Tailwind CDN)', files: (name) => ({ 'site/index.html': ` ${name}

${name}

Astra · Project Scaffolder

`, 'README.md': README(name, 'static'), }), }, { id: 'vite-vanilla', label: 'vite-vanilla', detail: 'Vite + 순수 JS', files: (name) => ({ 'site/package.json': JSON.stringify({ name, private: true, type: 'module', scripts: { dev: 'vite', build: 'vite build', preview: 'vite preview' }, devDependencies: { vite: '^5.0.0' }, }, null, 2) + '\n', 'site/index.html': ` ${name}

${name}

`, 'site/main.js': `document.querySelector('h1').addEventListener('click', () => { console.log('hi from ${name}'); }); `, 'README.md': README(name, 'vite-vanilla'), }), }, { id: 'vite-react', label: 'vite-react', detail: 'Vite + React + TypeScript', files: (name) => ({ 'site/package.json': JSON.stringify({ name, private: true, type: 'module', scripts: { dev: 'vite', build: 'tsc && vite build', preview: 'vite preview' }, dependencies: { react: '^18.3.0', 'react-dom': '^18.3.0' }, devDependencies: { '@types/react': '^18.3.0', '@types/react-dom': '^18.3.0', '@vitejs/plugin-react': '^4.3.0', typescript: '^5.4.0', vite: '^5.0.0', }, }, null, 2) + '\n', 'site/tsconfig.json': JSON.stringify({ compilerOptions: { target: 'ES2020', useDefineForClassFields: true, lib: ['ES2020', 'DOM', 'DOM.Iterable'], module: 'ESNext', skipLibCheck: true, moduleResolution: 'bundler', allowImportingTsExtensions: true, resolveJsonModule: true, isolatedModules: true, noEmit: true, jsx: 'react-jsx', strict: true, }, include: ['src'], }, null, 2) + '\n', 'site/vite.config.ts': `import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()] }); `, 'site/index.html': ` ${name}
`, 'site/src/main.tsx': `import React from 'react'; import { createRoot } from 'react-dom/client'; function App() { return

${name}

; } createRoot(document.getElementById('root')!).render(); `, 'README.md': README(name, 'vite-react'), }), }, ]; export function findTemplate(id: string): ProjectTemplate | undefined { return TEMPLATES.find(t => t.id === id); }