import fs from 'node:fs'; import { z } from 'zod'; import { config } from '../../config.js'; import { resolveWorkspacePath } from '../../lib/sandbox.js'; import { runCommand } from '../../lib/runCommand.js'; import { errorResult, jsonResult, targetSchema, toErrorMessage } from '../shared.js'; /** * Deploy is deliberately opt-in and project-defined: rather than guessing at * Vercel/Netlify/Forge credentials, it runs a script the project itself * defines (deploy.ps1 on Windows, deploy.sh elsewhere) at the workspace * root. Optionally pushes a Docker image when registryPush is set. */ export function registerDeployProjectTool(server) { server.registerTool('deploy_project', { title: 'Deploy Project', description: 'Run the project-defined deploy step: deploy.ps1 (Windows) or deploy.sh (macOS/Linux) at the workspace root. ' + 'Requires confirm: true. Optional registryPush runs `docker push` after the deploy script (also confirm-gated). ' + 'The server never holds deployment credentials itself.', inputSchema: { target: targetSchema, confirm: z .boolean() .optional() .describe('Must be true; deliberate opt-in for a destructive/external-effect operation.'), registryPush: z .string() .optional() .describe('Optional Docker image reference to push after the deploy script succeeds.'), }, }, async ({ confirm, target, registryPush }) => { void target; if (confirm !== true) return errorResult('confirm: true is required to run deploy_project.'); try { let deployResult; if (process.platform === 'win32') { const scriptPath = resolveWorkspacePath(config.workspaceRoot, 'deploy.ps1'); if (!fs.existsSync(scriptPath)) { return errorResult(`No deploy.ps1 found at the workspace root (${config.workspaceRoot}). Add one that performs your deployment, then retry.`); } const result = await runCommand('powershell', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', scriptPath], { cwd: config.workspaceRoot, timeoutMs: config.scaffoldCommandTimeoutMs }); deployResult = { ranScript: 'deploy.ps1', exitCode: result.exitCode, timedOut: result.timedOut, failed: result.failed, output: result.output, cwd: result.cwd, }; if (result.failed) return jsonResult({ ...deployResult, registryPush: null }); } else { const scriptPath = resolveWorkspacePath(config.workspaceRoot, 'deploy.sh'); if (!fs.existsSync(scriptPath)) { return errorResult(`No deploy.sh found at the workspace root (${config.workspaceRoot}). Add one that performs your deployment, then retry.`); } const result = await runCommand('sh', [scriptPath], { cwd: config.workspaceRoot, timeoutMs: config.scaffoldCommandTimeoutMs, }); deployResult = { ranScript: 'deploy.sh', exitCode: result.exitCode, timedOut: result.timedOut, failed: result.failed, output: result.output, cwd: result.cwd, }; if (result.failed) return jsonResult({ ...deployResult, registryPush: null }); } let pushResult = null; if (registryPush) { if (registryPush.includes('--force') || /\s/.test(registryPush)) { return errorResult('Invalid registryPush image reference.'); } const pushed = await runCommand('docker', ['push', registryPush], { cwd: config.workspaceRoot, timeoutMs: config.scaffoldCommandTimeoutMs, }); pushResult = { image: registryPush, exitCode: pushed.exitCode, failed: pushed.failed, timedOut: pushed.timedOut, output: pushed.output, }; } return jsonResult({ ...deployResult, registryPush: pushResult }); } catch (error) { return errorResult(toErrorMessage(error)); } }); } //# sourceMappingURL=deployProject.js.map