97 lines
4.6 KiB
JavaScript
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
|