/** * MCPEngine Studio — Deploy API Route * * POST /api/deploy * Accepts { projectId, target, config } * Loads project bundle from DB, runs deploy pipeline, * streams progress via SSE, saves deployment record on completion. */ import { NextRequest, NextResponse } from 'next/server'; import { deploy } from '@/lib/deploy'; import type { DeployTarget, DeployConfig, ServerBundle, } from '@mcpengine/ai-pipeline/types'; // --------------------------------------------------------------------------- // POST /api/deploy — Start deployment (SSE stream) // --------------------------------------------------------------------------- export async function POST(req: NextRequest) { try { const body = await req.json(); const { projectId, target, config } = body as { projectId: string; target: DeployTarget; config?: Partial; }; if (!projectId) { return NextResponse.json( { error: 'projectId is required' }, { status: 400 }, ); } if (!target) { return NextResponse.json( { error: 'target is required' }, { status: 400 }, ); } // ── Load project from DB ───────────────────────────────────────────── // TODO: Replace with real DB query using drizzle // import { db } from '@/lib/db'; // import { projects, deployments } from '@mcpengine/db/schema'; // import { eq } from 'drizzle-orm'; // const project = await db.query.projects.findFirst({ // where: eq(projects.id, projectId), // }); // For now, create a mock bundle if no DB yet const bundle: ServerBundle = await loadProjectBundle(projectId); if (!bundle || !bundle.files.length) { return NextResponse.json( { error: 'Project has no server bundle. Generate code first.' }, { status: 400 }, ); } // ── Build deploy config ────────────────────────────────────────────── const deployConfig: DeployConfig = { target, slug: config?.slug ?? projectId.slice(0, 12), envVars: config?.envVars ?? {}, customDomain: config?.customDomain, }; // ── Stream deploy events via SSE ───────────────────────────────────── const encoder = new TextEncoder(); const stream = new ReadableStream({ async start(controller) { try { const pipeline = deploy(bundle, deployConfig); let deployResult; for await (const event of pipeline) { const sseData = `data: ${JSON.stringify(event)}\n\n`; controller.enqueue(encoder.encode(sseData)); if (event.type === 'complete' && event.result) { deployResult = event.result; } } // ── Save deployment record to DB ───────────────────────────── if (deployResult) { await saveDeploymentRecord(projectId, deployResult); // Send final event with saved record const finalEvent = { type: 'saved', message: 'Deployment record saved', percent: 100, level: 'success', timestamp: new Date().toISOString(), result: deployResult, }; controller.enqueue( encoder.encode(`data: ${JSON.stringify(finalEvent)}\n\n`), ); } controller.close(); } catch (error) { const errMsg = error instanceof Error ? error.message : 'Unknown error'; const errorEvent = { type: 'error', message: errMsg, percent: 0, level: 'error', timestamp: new Date().toISOString(), }; controller.enqueue( encoder.encode(`data: ${JSON.stringify(errorEvent)}\n\n`), ); controller.close(); } }, }); return new Response(stream, { headers: { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', 'X-Accel-Buffering': 'no', }, }); } catch (error) { console.error('Deploy route error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 }, ); } } // --------------------------------------------------------------------------- // Helpers — TODO: Replace with real DB operations // --------------------------------------------------------------------------- async function loadProjectBundle(projectId: string): Promise { // TODO: Real implementation: // const project = await db.query.projects.findFirst({ // where: eq(projects.id, projectId), // }); // return project?.serverBundle as ServerBundle; // Mock bundle for development return { files: [ { path: 'index.ts', content: ` import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; const server = new Server({ name: 'mcp-server', version: '1.0.0' }, { capabilities: { tools: {} }, }); export const listTools = () => []; export const callTool = async (name, args) => ({ content: [{ type: 'text', text: 'OK' }] }); `, language: 'typescript', }, ], packageJson: { name: 'mcp-server', version: '1.0.0' }, tsConfig: {}, entryPoint: 'index.ts', toolCount: 0, }; } async function saveDeploymentRecord( projectId: string, result: any, ): Promise { // TODO: Real implementation: // await db.insert(deployments).values({ // projectId, // userId: currentUser.id, // from auth context // target: result.target, // status: result.status, // url: result.url, // endpoint: result.endpoint, // logs: result.logs, // }); console.log('[deploy] Saved deployment record:', { projectId, deployId: result.id, target: result.target, status: result.status, url: result.url, }); }