=== NEW === - studio/ — MCPEngine Studio scaffold (Next.js monorepo, build plan) - docs/FACTORY-V2.md — Factory v2 architecture doc - docs/CALENDLY_MCP_BUILD_SUMMARY.md — Calendly MCP build report === UPDATED SERVERS === - fieldedge: Added jobs-tools, UI build script, main entry update - lightspeed: Updated main + server entry points - squarespace: Added collection-browser + page-manager apps - toast: Added main + server entry points === INFRA === - infra/command-center/state.json — Updated pipeline state - infra/command-center/FACTORY-V2.md — Factory v2 operator playbook
204 lines
6.2 KiB
TypeScript
204 lines
6.2 KiB
TypeScript
/**
|
|
* 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<DeployConfig>;
|
|
};
|
|
|
|
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<ServerBundle> {
|
|
// 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<void> {
|
|
// 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,
|
|
});
|
|
}
|