92 lines
3.7 KiB
JavaScript
92 lines
3.7 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { z } from 'zod';
|
|
import { config } from '../../config.js';
|
|
import { resolveWorkspacePath } from '../../lib/sandbox.js';
|
|
import { errorResult, jsonResult, toErrorMessage } from '../shared.js';
|
|
import { dockerManager } from './dockerManager.js';
|
|
export function registerDockerBuildImageTool(server) {
|
|
server.registerTool('docker_build_image', {
|
|
title: 'Docker Build Image',
|
|
description: 'Build a Docker image from a Dockerfile in the workspace. Streams the build output back and returns the resulting image tag.',
|
|
inputSchema: {
|
|
tag: z.string().min(1).describe('Image tag, e.g. "my-app:latest".'),
|
|
dockerfile: z
|
|
.string()
|
|
.optional()
|
|
.default('Dockerfile')
|
|
.describe('Path to the Dockerfile, relative to the workspace root.'),
|
|
buildContext: z
|
|
.string()
|
|
.optional()
|
|
.default('.')
|
|
.describe('Build context directory, relative to the workspace root.'),
|
|
},
|
|
}, async ({ tag, dockerfile, buildContext }) => {
|
|
try {
|
|
await dockerManager.ensureReachable();
|
|
const dockerfilePath = resolveWorkspacePath(config.workspaceRoot, dockerfile);
|
|
const contextPath = resolveWorkspacePath(config.workspaceRoot, buildContext);
|
|
if (!fs.existsSync(dockerfilePath)) {
|
|
return errorResult(`Dockerfile not found: ${dockerfilePath}`);
|
|
}
|
|
const stream = await dockerManager.getDocker().buildImage({
|
|
context: contextPath,
|
|
src: [path.basename(dockerfilePath), ...listContextFiles(contextPath)],
|
|
}, { t: tag, dockerfile: path.basename(dockerfilePath) });
|
|
const output = [];
|
|
await new Promise((resolve, reject) => {
|
|
dockerManager
|
|
.getDocker()
|
|
.modem.followProgress(stream, (err, res) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
const last = res?.[res.length - 1];
|
|
if (last && 'error' in last && last.error) {
|
|
reject(new Error(String(last.error)));
|
|
return;
|
|
}
|
|
resolve();
|
|
}, (event) => {
|
|
const line = typeof event === 'string' ? event : JSON.stringify(event);
|
|
output.push(line);
|
|
if (output.length > config.maxBufferedLogLines) {
|
|
output.splice(0, output.length - config.maxBufferedLogLines);
|
|
}
|
|
});
|
|
});
|
|
return jsonResult({
|
|
tag,
|
|
dockerfile: dockerfilePath,
|
|
context: contextPath,
|
|
output: output.join('\n').slice(-config.maxToolOutputChars),
|
|
});
|
|
}
|
|
catch (error) {
|
|
return errorResult(toErrorMessage(error));
|
|
}
|
|
});
|
|
}
|
|
function listContextFiles(root) {
|
|
const files = [];
|
|
const walk = (dir) => {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
for (const entry of entries) {
|
|
const full = path.join(dir, entry.name);
|
|
const rel = path.relative(root, full);
|
|
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === 'dist')
|
|
continue;
|
|
if (entry.isDirectory()) {
|
|
walk(full);
|
|
}
|
|
else {
|
|
files.push(rel);
|
|
}
|
|
}
|
|
};
|
|
walk(root);
|
|
return files;
|
|
}
|
|
//# sourceMappingURL=buildImage.js.map
|