Files
2026-07-31 13:12:54 -04:00

97 lines
4.6 KiB
JavaScript

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