=== 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
232 lines
6.4 KiB
TypeScript
232 lines
6.4 KiB
TypeScript
/**
|
|
* 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<string, DeployStep> = {
|
|
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<DeployEvent, DeployResult, void> {
|
|
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<any, DeployResult, void>;
|
|
|
|
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 };
|