/** * MCPEngine Studio — Deploy Orchestrator * * Main entry point for the deployment pipeline. * Takes a DeployTarget + config, routes to the correct target, * and yields progress events via async generator. */ import type { DeployTarget, DeployConfig, DeployResult, ServerBundle, } from '@mcpengine/ai-pipeline/types'; import { deployToMCPEngine } from './targets/mcpengine'; import { deployAsDownload } from './targets/download'; // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- export interface DeployEvent { type: 'progress' | 'log' | 'complete' | 'error'; step?: DeployStep; message: string; percent: number; level: 'info' | 'success' | 'warning' | 'error'; timestamp: string; result?: DeployResult; } export type DeployStep = 'build' | 'test' | 'package' | 'deploy' | 'verify'; const STEP_ORDER: DeployStep[] = ['build', 'test', 'package', 'deploy', 'verify']; // Map internal steps from targets to canonical DeploySteps function mapStep(raw: string): DeployStep { const mapping: Record = { compile: 'build', build: 'build', test: 'test', package: 'package', deploy: 'deploy', upload: 'deploy', verify: 'verify', }; return mapping[raw] ?? 'build'; } // --------------------------------------------------------------------------- // Orchestrator // --------------------------------------------------------------------------- export async function* deploy( bundle: ServerBundle, config: DeployConfig, ): AsyncGenerator { const ts = () => new Date().toISOString(); // ── Pre-flight ───────────────────────────────────────────────────────── yield { type: 'log', message: `Starting deployment to ${config.target}…`, percent: 0, level: 'info', timestamp: ts(), }; yield { type: 'progress', step: 'build', message: 'Initializing build pipeline…', percent: 5, level: 'info', timestamp: ts(), }; // ── Build step (validate bundle) ─────────────────────────────────────── if (!bundle.files.length) { yield { type: 'error', step: 'build', message: 'Server bundle is empty — nothing to deploy', percent: 0, level: 'error', timestamp: ts(), }; throw new Error('Empty server bundle'); } yield { type: 'log', message: `Bundle: ${bundle.files.length} files, ${bundle.toolCount} tools, entry: ${bundle.entryPoint}`, percent: 8, level: 'info', timestamp: ts(), }; // ── Test step (placeholder — real tests come from test pipeline) ────── yield { type: 'progress', step: 'test', message: 'Running pre-deploy checks…', percent: 15, level: 'info', timestamp: ts(), }; // Quick static checks const hasEntry = bundle.files.some((f) => f.path === bundle.entryPoint); if (!hasEntry) { yield { type: 'error', step: 'test', message: `Entry point "${bundle.entryPoint}" not found in bundle`, percent: 15, level: 'error', timestamp: ts(), }; throw new Error('Missing entry point'); } yield { type: 'progress', step: 'test', message: 'Pre-deploy checks passed', percent: 20, level: 'success', timestamp: ts(), }; // ── Route to target ──────────────────────────────────────────────────── let innerGen: AsyncGenerator; switch (config.target) { case 'mcpengine': case 'cloudflare': innerGen = deployToMCPEngine( config.slug ?? 'mcp-server', bundle, config, ); break; case 'download': innerGen = deployAsDownload(bundle, config.slug); break; case 'npm': // TODO: npm publish pipeline yield { type: 'log', message: 'npm deployment is not yet implemented — falling back to download', percent: 25, level: 'warning', timestamp: ts(), }; innerGen = deployAsDownload(bundle, config.slug); break; case 'docker': // TODO: Docker build/push pipeline yield { type: 'log', message: 'Docker deployment is not yet implemented — falling back to download', percent: 25, level: 'warning', timestamp: ts(), }; innerGen = deployAsDownload(bundle, config.slug); break; default: throw new Error(`Unknown deploy target: ${config.target}`); } // ── Stream inner target events ───────────────────────────────────────── let result: DeployResult | undefined; while (true) { const { value, done } = await innerGen.next(); if (done) { result = value; break; } // Forward progress events with normalized step names yield { type: 'progress', step: mapStep(value.step), message: value.message, percent: Math.min(20 + Math.round(value.percent * 0.75), 95), level: value.level, timestamp: ts(), }; yield { type: 'log', message: value.message, percent: Math.min(20 + Math.round(value.percent * 0.75), 95), level: value.level, timestamp: ts(), }; } if (!result) { throw new Error('Deploy target did not return a result'); } // ── Complete ─────────────────────────────────────────────────────────── yield { type: 'complete', step: 'verify', message: `Deployment complete! ${result.url ?? ''}`, percent: 100, level: 'success', timestamp: ts(), result, }; return result; } // Re-exports for convenience export { deployToMCPEngine } from './targets/mcpengine'; export { deployAsDownload } from './targets/download'; export { compileServer, compileWithMeta } from './compiler'; export { generateWorkerScript } from './worker-template'; export { STEP_ORDER }; export type { DeployTarget, DeployConfig, DeployResult };