diff --git a/frontends/nextjs/src/app/api/packages/installed/handlers/post-installed-package.ts b/frontends/nextjs/src/app/api/packages/installed/handlers/post-installed-package.ts index 29b623c45..bf7dc6b3b 100644 --- a/frontends/nextjs/src/app/api/packages/installed/handlers/post-installed-package.ts +++ b/frontends/nextjs/src/app/api/packages/installed/handlers/post-installed-package.ts @@ -19,22 +19,16 @@ type InstallPackagePayload = { export async function POST(request: NextRequest) { try { const body = await readJson(request) - if (!body) { - return NextResponse.json({ error: 'Invalid JSON payload' }, { status: 400 }) - } + if (!body) return NextResponse.json({ error: 'Invalid JSON payload' }, { status: 400 }) const packageId = typeof body.packageId === 'string' ? body.packageId.trim() : (typeof body.manifest?.id === 'string' ? body.manifest.id : '') - if (!packageId) { - return NextResponse.json({ error: 'Package ID is required' }, { status: 400 }) - } + if (!packageId) return NextResponse.json({ error: 'Package ID is required' }, { status: 400 }) const entry = getPackageCatalogEntry(packageId) const content = body.content ?? entry?.content - if (!content) { - return NextResponse.json({ error: 'Package content not found' }, { status: 404 }) - } + if (!content) return NextResponse.json({ error: 'Package content not found' }, { status: 404 }) const installed = await getInstalledPackages() if (installed.some((pkg) => pkg.packageId === packageId)) { @@ -43,14 +37,12 @@ export async function POST(request: NextRequest) { await installPackageContent(packageId, content) - const installedPackage: InstalledPackage = { - packageId, - installedAt: typeof body.installedAt === 'number' ? body.installedAt : Date.now(), - version: typeof body.version === 'string' - ? body.version - : (body.manifest?.version ?? entry?.manifest.version ?? '0.0.0'), - enabled: typeof body.enabled === 'boolean' ? body.enabled : true, - } + const installedAt = typeof body.installedAt === 'number' ? body.installedAt : Date.now() + const version = typeof body.version === 'string' + ? body.version + : (body.manifest?.version ?? entry?.manifest.version ?? '0.0.0') + const enabled = typeof body.enabled === 'boolean' ? body.enabled : true + const installedPackage: InstalledPackage = { packageId, installedAt, version, enabled } await installPackage(installedPackage) return NextResponse.json({ installed: installedPackage }, { status: 201 }) diff --git a/frontends/nextjs/src/lib/nerd-mode-ide/build-react-app-template.ts b/frontends/nextjs/src/lib/nerd-mode-ide/build-react-app-template.ts new file mode 100644 index 000000000..5abe68748 --- /dev/null +++ b/frontends/nextjs/src/lib/nerd-mode-ide/build-react-app-template.ts @@ -0,0 +1,147 @@ +import { createFileNode } from './create-file-node' +import { createFolderNode } from './create-folder-node' +import type { PackageTemplate, ReactAppTemplateConfig } from './types' + +const appLayout = [ + "import './globals.css'", + '', + 'export const metadata = {', + " title: 'MetaBuilder App',", + " description: 'Generated Next.js starter',", + '}', + '', + 'export default function RootLayout({ children }: { children: React.ReactNode }) {', + ' return (', + ' ', + ' {children}', + ' ', + ' )', + '}', +].join('\n') + +const appPage = [ + "import { Hero } from '@/components/Hero'", + '', + 'export default function HomePage() {', + ' return ', + '}', +].join('\n') + +const heroComponent = [ + 'export function Hero() {', + ' return (', + '
', + '

MetaBuilder App

', + '

', + ' Ship fast with a generated Next.js starter, prewired for package exports.', + '

', + '
', + ' )', + '}', +].join('\n') + +const packageJson = [ + '{', + ' "name": "metabuilder-web-app",', + ' "version": "0.1.0",', + ' "private": true,', + ' "scripts": {', + ' "dev": "next dev",', + ' "build": "next build",', + ' "start": "next start"', + ' },', + ' "dependencies": {', + ' "next": "latest",', + ' "react": "latest",', + ' "react-dom": "latest"', + ' }', + '}', +].join('\n') + +const nextConfig = [ + 'const nextConfig = {', + ' reactStrictMode: true,', + '}', + '', + 'export default nextConfig', +].join('\n') + +const tsConfig = [ + '{', + ' "compilerOptions": {', + ' "target": "ES2020",', + ' "lib": ["DOM", "DOM.Iterable", "ES2020"],', + ' "module": "ESNext",', + ' "moduleResolution": "Bundler",', + ' "jsx": "react-jsx",', + ' "strict": true', + ' }', + '}', +].join('\n') + +const readme = [ + '# MetaBuilder Web App', + '', + 'Generated starter with a hero component and app router layout.', + 'Use the Package IDE to export this project as a zip.', +].join('\n') + +export function buildReactAppTemplate(config: ReactAppTemplateConfig): PackageTemplate { + const srcFolder = createFolderNode({ + name: 'src', + expanded: true, + children: [ + createFolderNode({ + name: 'app', + expanded: true, + children: [ + createFileNode({ name: 'layout.tsx', content: appLayout }), + createFileNode({ name: 'page.tsx', content: appPage }), + ], + }), + createFolderNode({ + name: 'components', + expanded: true, + children: [createFileNode({ name: 'Hero.tsx', content: heroComponent })], + }), + createFolderNode({ + name: 'styles', + expanded: true, + children: [ + createFileNode({ + name: 'globals.css', + content: 'body { margin: 0; background: #f8f9fb; color: #111827; }', + }), + ], + }), + ], + }) + + const publicFolder = createFolderNode({ + name: 'public', + expanded: true, + children: [createFileNode({ name: 'robots.txt', content: 'User-agent: *\nAllow: /' })], + }) + + const root = createFolderNode({ + name: config.rootName, + expanded: true, + children: [ + srcFolder, + publicFolder, + createFileNode({ name: 'package.json', content: packageJson }), + createFileNode({ name: 'next.config.ts', content: nextConfig }), + createFileNode({ name: 'tsconfig.json', content: tsConfig }), + createFileNode({ name: 'README.md', content: readme }), + ], + }) + + return { + id: config.id, + name: config.name, + description: config.description, + rootName: config.rootName, + tree: [root], + tags: config.tags, + } +} diff --git a/frontends/nextjs/src/lib/nerd-mode-ide/get-package-templates.ts b/frontends/nextjs/src/lib/nerd-mode-ide/get-package-templates.ts new file mode 100644 index 000000000..4ae93f0e7 --- /dev/null +++ b/frontends/nextjs/src/lib/nerd-mode-ide/get-package-templates.ts @@ -0,0 +1,10 @@ +import { buildPackageTemplate } from './build-package-template' +import { buildReactAppTemplate } from './build-react-app-template' +import { PACKAGE_TEMPLATE_CONFIGS, REACT_APP_TEMPLATE_CONFIG } from './template-configs' +import type { PackageTemplate } from './types' + +export function getPackageTemplates(): PackageTemplate[] { + const packageTemplates = PACKAGE_TEMPLATE_CONFIGS.map((config) => buildPackageTemplate(config)) + const reactTemplate = buildReactAppTemplate(REACT_APP_TEMPLATE_CONFIG) + return [reactTemplate, ...packageTemplates] +}