code: nextjs,frontends,package (3 files)

This commit is contained in:
2025-12-26 00:29:15 +00:00
parent 61275bb6a1
commit 95ffb9c583
3 changed files with 166 additions and 17 deletions

View File

@@ -19,22 +19,16 @@ type InstallPackagePayload = {
export async function POST(request: NextRequest) {
try {
const body = await readJson<InstallPackagePayload>(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 })

View File

@@ -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 (',
' <html lang="en">',
' <body>{children}</body>',
' </html>',
' )',
'}',
].join('\n')
const appPage = [
"import { Hero } from '@/components/Hero'",
'',
'export default function HomePage() {',
' return <Hero />',
'}',
].join('\n')
const heroComponent = [
'export function Hero() {',
' return (',
' <main style={{ padding: "64px", fontFamily: "system-ui" }}>',
' <h1 style={{ fontSize: "40px", marginBottom: "16px" }}>MetaBuilder App</h1>',
' <p style={{ maxWidth: "560px", lineHeight: 1.6 }}>',
' Ship fast with a generated Next.js starter, prewired for package exports.',
' </p>',
' </main>',
' )',
'}',
].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,
}
}

View File

@@ -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]
}