@xec-sh/core
Version:
Universal shell execution engine
933 lines • 35.3 kB
JavaScript
import { Readable } from 'node:stream';
import { spawn } from 'node:child_process';
import { statSync, existsSync } from 'node:fs';
import { StreamHandler } from '../utils/stream.js';
import { BaseAdapter } from './base-adapter.js';
import { ExecutionResultImpl } from '../core/result.js';
import { DockerError, AdapterError, sanitizeCommandForError } from '../core/error.js';
export class DockerAdapter extends BaseAdapter {
constructor(config = {}) {
super(config);
this.adapterName = 'docker';
this.tempContainers = new Set();
this.name = this.adapterName;
this.dockerConfig = {
...config,
defaultExecOptions: {
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Tty: false,
...config.defaultExecOptions
},
autoCreate: {
enabled: false,
image: 'alpine:latest',
autoRemove: true,
...config.autoCreate
}
};
}
findDockerPath() {
const paths = [
'/usr/local/bin/docker',
'/usr/bin/docker',
'/opt/homebrew/bin/docker',
'docker'
];
for (const path of paths) {
try {
if (path === 'docker')
return path;
if (existsSync(path) && statSync(path).isFile()) {
return path;
}
}
catch {
}
}
return 'docker';
}
async isAvailable() {
try {
const result = await this.executeDockerCommand(['version', '--format', 'json'], {});
return result.exitCode === 0;
}
catch (error) {
return false;
}
}
async execute(command) {
const mergedCommand = this.mergeCommand(command);
const dockerOptions = this.extractDockerOptions(mergedCommand);
if (!dockerOptions) {
throw new AdapterError(this.adapterName, 'execute', new Error('Docker container options not provided'));
}
const startTime = Date.now();
let containerName = dockerOptions.container;
try {
let result;
const effectiveRunMode = this.determineRunMode(dockerOptions);
if (effectiveRunMode === 'run') {
if (!dockerOptions.image) {
throw new AdapterError(this.adapterName, 'execute', new Error('Image must be specified for run mode'));
}
this.emitAdapterEvent('docker:run', {
image: dockerOptions.image,
container: dockerOptions.container,
command: this.buildCommandString(mergedCommand)
});
const runArgs = this.buildDockerRunArgs(dockerOptions, mergedCommand);
result = await this.executeDockerCommand(runArgs, mergedCommand);
}
else {
this.validateContainerName(dockerOptions.container);
if (this.dockerConfig.autoCreate?.enabled && !await this.containerExists(containerName)) {
containerName = await this.createTempContainer();
this.emitAdapterEvent('docker:run', {
image: this.dockerConfig.autoCreate.image,
container: containerName,
command: 'sh'
});
}
if (!await this.containerExists(containerName)) {
throw new DockerError(containerName, 'execute', new Error(`Container '${containerName}' not found`));
}
this.emitAdapterEvent('docker:exec', {
container: containerName,
command: this.buildCommandString(mergedCommand)
});
const dockerArgs = this.buildDockerExecArgs(containerName, dockerOptions, mergedCommand);
result = await this.executeDockerCommand(dockerArgs, mergedCommand);
}
const endTime = Date.now();
return await this.createResult(result.stdout, result.stderr, result.exitCode, result.signal ?? undefined, this.buildCommandString(mergedCommand), startTime, endTime, { container: containerName, originalCommand: mergedCommand });
}
catch (error) {
if (error instanceof DockerError) {
throw error;
}
throw new DockerError(dockerOptions.container, 'execute', error instanceof Error ? error : new Error(String(error)));
}
}
extractDockerOptions(command) {
if (command.adapterOptions?.type === 'docker') {
return command.adapterOptions;
}
return null;
}
validateContainerName(containerName) {
if (!containerName || containerName.trim() === '') {
throw new DockerError(containerName, 'validate', new Error('Container name cannot be empty'));
}
const dangerousChars = /[;&|`$(){}[\]<>'"\\]/;
if (dangerousChars.test(containerName)) {
throw new DockerError(containerName, 'validate', new Error('Container name contains invalid characters'));
}
if (containerName.includes('..') ||
containerName.startsWith('/') ||
containerName.match(/^[A-Za-z]:\\/)) {
throw new DockerError(containerName, 'validate', new Error('Container name contains invalid path characters'));
}
const validNamePattern = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
if (!validNamePattern.test(containerName)) {
throw new DockerError(containerName, 'validate', new Error('Container name must start with alphanumeric and contain only alphanumeric, underscore, period, or hyphen'));
}
}
supportsTTY() {
return process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY;
}
getTTYSettings(dockerOptions, command) {
const envSupportsTTY = this.supportsTTY();
const requestedTTY = dockerOptions.tty ?? this.dockerConfig.defaultExecOptions?.Tty ?? false;
const hasStdin = !!command.stdin;
if (requestedTTY && !envSupportsTTY) {
console.warn('TTY requested but not available in current environment');
}
return {
interactive: hasStdin || (requestedTTY && envSupportsTTY),
tty: requestedTTY && envSupportsTTY
};
}
async containerExists(container) {
try {
const result = await this.executeDockerCommand(['inspect', container], {});
return result.exitCode === 0;
}
catch {
return false;
}
}
async createTempContainer() {
const containerName = `temp-ush-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const createArgs = [
'create',
'--name', containerName,
'-it'
];
if (this.dockerConfig.autoCreate?.volumes) {
for (const volume of this.dockerConfig.autoCreate.volumes) {
createArgs.push('-v', volume);
}
}
createArgs.push(this.dockerConfig.autoCreate.image, 'sh');
const createResult = await this.executeDockerCommand(createArgs, {});
if (createResult.exitCode !== 0) {
throw new DockerError(containerName, 'create', new Error(createResult.stderr));
}
const startResult = await this.executeDockerCommand(['start', containerName], {});
if (startResult.exitCode !== 0) {
await this.executeDockerCommand(['rm', '-f', containerName], {});
throw new DockerError(containerName, 'start', new Error(startResult.stderr));
}
this.tempContainers.add(containerName);
return containerName;
}
determineRunMode(options) {
if (options.runMode) {
return options.runMode;
}
return options.image ? 'run' : 'exec';
}
buildDockerExecArgs(container, dockerOptions, command) {
const args = ['exec'];
const ttySettings = this.getTTYSettings(dockerOptions, command);
if (ttySettings.interactive) {
args.push('-i');
}
if (ttySettings.tty) {
args.push('-t');
}
const user = dockerOptions.user || this.dockerConfig.defaultExecOptions?.User;
if (user) {
args.push('-u', user);
}
const workdir = dockerOptions.workdir || this.dockerConfig.defaultExecOptions?.WorkingDir;
if (workdir) {
args.push('-w', workdir);
}
const defaultEnv = this.dockerConfig.defaultExecOptions?.Env || [];
const envFromDefaults = {};
for (const envVar of defaultEnv) {
const [key, value] = envVar.split('=', 2);
if (key && value !== undefined) {
envFromDefaults[key] = value;
}
}
const envToSet = { ...this.config.defaultEnv, ...envFromDefaults, ...command.env };
for (const [key, value] of Object.entries(envToSet)) {
args.push('-e', `${key}=${value}`);
}
if (this.dockerConfig.defaultExecOptions?.Privileged) {
args.push('--privileged');
}
args.push(container);
if (command.shell) {
args.push('sh', '-c', this.buildCommandString(command));
}
else {
args.push(command.command);
if (command.args) {
args.push(...command.args);
}
}
return args;
}
buildDockerRunArgs(dockerOptions, command) {
const args = ['run'];
if (dockerOptions.autoRemove !== false) {
args.push('--rm');
}
const ttySettings = this.getTTYSettings(dockerOptions, command);
if (ttySettings.interactive) {
args.push('-i');
}
if (ttySettings.tty) {
args.push('-t');
}
if (dockerOptions.user) {
args.push('-u', dockerOptions.user);
}
if (dockerOptions.workdir) {
args.push('-w', dockerOptions.workdir);
}
if (dockerOptions.volumes) {
for (const volume of dockerOptions.volumes) {
args.push('-v', volume);
}
}
if (command.env) {
for (const [key, value] of Object.entries(command.env)) {
if (key && value !== undefined) {
args.push('-e', `${key}=${value}`);
}
}
}
if (dockerOptions.container && dockerOptions.container !== 'ephemeral') {
args.push('--name', dockerOptions.container);
}
if (command.shell) {
const cmdString = this.buildCommandString(command);
args.push('--entrypoint', 'sh');
args.push(dockerOptions.image);
args.push('-c', cmdString);
}
else {
args.push(dockerOptions.image);
args.push(command.command);
if (command.args) {
args.push(...command.args);
}
}
return args;
}
async executeDockerCommand(args, command) {
const timeout = command.timeout;
const stdoutHandler = new StreamHandler({
encoding: this.config.encoding,
maxBuffer: this.config.maxBuffer
});
const stderrHandler = new StreamHandler({
encoding: this.config.encoding,
maxBuffer: this.config.maxBuffer
});
const env = args[0] === 'compose' && command.env
? { ...process.env, ...command.env }
: process.env;
const hasTTY = args.includes('-t');
const hasInteractive = args.includes('-i');
const useInheritStdin = hasTTY && hasInteractive && process.stdin.isTTY;
const dockerPath = this.findDockerPath();
const child = spawn(dockerPath, args, {
env,
cwd: command.cwd || process.cwd(),
windowsHide: true,
stdio: useInheritStdin ? ['inherit', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe']
});
if (child.stdin && command.stdin) {
if (typeof command.stdin === 'string' || Buffer.isBuffer(command.stdin)) {
child.stdin.write(command.stdin);
child.stdin.end();
}
else if (command.stdin instanceof Readable) {
command.stdin.pipe(child.stdin);
}
}
if (child.stdout) {
const stdoutTransform = stdoutHandler.createTransform();
child.stdout.pipe(stdoutTransform);
stdoutTransform.on('data', () => { });
}
if (child.stderr) {
const stderrTransform = stderrHandler.createTransform();
child.stderr.pipe(stderrTransform);
stderrTransform.on('data', () => { });
}
return new Promise((resolve, reject) => {
let timeoutId;
let timedOut = false;
if (timeout && timeout > 0) {
timeoutId = setTimeout(() => {
timedOut = true;
child.kill('SIGTERM');
setTimeout(() => {
if (!child.killed) {
child.kill('SIGKILL');
}
}, 1000);
}, timeout);
}
child.on('error', (error) => {
if (timeoutId)
clearTimeout(timeoutId);
reject(error);
});
child.on('exit', (code, signal) => {
if (timeoutId)
clearTimeout(timeoutId);
if (timedOut) {
reject(new Error(`Command timed out after ${timeout}ms`));
return;
}
const result = {
stdout: stdoutHandler.getContent(),
stderr: stderrHandler.getContent(),
exitCode: code ?? 0,
signal
};
resolve(result);
});
});
}
async createResult(stdout, stderr, exitCode, signal, command, startTime, endTime, context) {
const maskedCommand = this.maskSensitiveData(command);
const maskedStdout = this.maskSensitiveData(stdout);
const maskedStderr = this.maskSensitiveData(stderr);
const result = new ExecutionResultImpl(maskedStdout, maskedStderr, exitCode, signal, maskedCommand, endTime - startTime, new Date(startTime), new Date(endTime), this.adapterName, context?.host, context?.container);
const commandForThrowCheck = context?.originalCommand ?? command;
if (this.shouldThrowOnNonZeroExit(commandForThrowCheck, exitCode)) {
const container = context?.container || 'unknown';
throw new DockerError(container, 'execute', new Error(`Command failed with exit code ${exitCode}: ${sanitizeCommandForError(command)}`));
}
return result;
}
async dispose() {
if (this.dockerConfig.autoCreate?.autoRemove) {
for (const container of this.tempContainers) {
try {
this.emitAdapterEvent('temp:cleanup', {
path: container,
type: 'directory'
});
await this.executeDockerCommand(['rm', '-f', container], {});
}
catch (error) {
}
}
}
this.tempContainers.clear();
}
async listContainers(all = false) {
const args = ['ps'];
if (all)
args.push('-a');
args.push('--format', '{{.Names}}');
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError('', 'list', new Error(result.stderr));
}
return result.stdout.trim().split('\n').filter(Boolean);
}
async createContainer(options) {
const args = ['create', '--name', options.name];
if (options.volumes) {
for (const volume of options.volumes) {
args.push('-v', volume);
}
}
if (options.env) {
for (const [key, value] of Object.entries(options.env)) {
args.push('-e', `${key}=${value}`);
}
}
if (options.ports) {
for (const port of options.ports) {
args.push('-p', port);
}
}
args.push(options.image);
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError(options.name, 'create', new Error(result.stderr));
}
}
async startContainer(container) {
const result = await this.executeDockerCommand(['start', container], {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'start', new Error(result.stderr));
}
}
async runContainer(options) {
const args = ['run', '-d', '--name', options.name];
if (options.volumes) {
for (const volume of options.volumes) {
args.push('-v', volume);
}
}
if (options.env) {
for (const [key, value] of Object.entries(options.env)) {
args.push('-e', `${key}=${value}`);
}
}
if (options.ports) {
for (const port of options.ports) {
args.push('-p', port);
}
}
if (options.network) {
args.push('--network', options.network);
}
if (options.restart) {
args.push('--restart', options.restart);
}
if (options.workdir) {
args.push('-w', options.workdir);
}
if (options.user) {
args.push('-u', options.user);
}
if (options.labels) {
for (const [key, value] of Object.entries(options.labels)) {
args.push('--label', `${key}=${value}`);
}
}
if (options.privileged) {
args.push('--privileged');
}
if (options.healthcheck) {
const hc = options.healthcheck;
if (Array.isArray(hc.test)) {
args.push('--health-cmd', hc.test.join(' '));
}
else {
args.push('--health-cmd', hc.test);
}
if (hc.interval)
args.push('--health-interval', hc.interval);
if (hc.timeout)
args.push('--health-timeout', hc.timeout);
if (hc.retries)
args.push('--health-retries', String(hc.retries));
if (hc.startPeriod)
args.push('--health-start-period', hc.startPeriod);
}
args.push(options.image);
if (options.command) {
args.push(...options.command);
}
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError(options.name, 'run', new Error(`Docker run failed: ${result.stderr || result.stdout}`));
}
}
async stopContainer(container) {
const result = await this.executeDockerCommand(['stop', container], {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'stop', new Error(result.stderr));
}
}
async removeContainer(container, force = false) {
const args = ['rm'];
if (force)
args.push('-f');
args.push(container);
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'remove', new Error(result.stderr));
}
}
async buildImage(options) {
const args = ['build'];
if (options.tag) {
args.push('-t', options.tag);
}
if (options.dockerfile) {
args.push('-f', options.dockerfile);
}
if (options.buildArgs) {
for (const [key, value] of Object.entries(options.buildArgs)) {
args.push('--build-arg', `${key}=${value}`);
}
}
if (options.target) {
args.push('--target', options.target);
}
if (options.noCache) {
args.push('--no-cache');
}
if (options.pull) {
args.push('--pull');
}
if (options.platform) {
args.push('--platform', options.platform);
}
args.push('.');
const result = await this.executeDockerCommand(args, {
cwd: options.context || process.cwd()
});
if (result.exitCode !== 0) {
throw new DockerError('', 'build', new Error(result.stderr));
}
}
async pushImage(image) {
const result = await this.executeDockerCommand(['push', image], {});
if (result.exitCode !== 0) {
throw new DockerError(image, 'push', new Error(result.stderr));
}
}
async pullImage(image) {
const result = await this.executeDockerCommand(['pull', image], {});
if (result.exitCode !== 0) {
throw new DockerError(image, 'pull', new Error(result.stderr));
}
}
async tagImage(source, target) {
const result = await this.executeDockerCommand(['tag', source, target], {});
if (result.exitCode !== 0) {
throw new DockerError(source, 'tag', new Error(result.stderr));
}
}
async listImages(filter) {
const args = ['images', '--format', '{{.Repository}}:{{.Tag}}'];
if (filter) {
if (!filter.includes('=')) {
args.push('--filter', `reference=${filter}*`);
}
else {
args.push('--filter', filter);
}
}
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError('', 'images', new Error(result.stderr));
}
return result.stdout.trim().split('\n').filter(Boolean);
}
async removeImage(image, force = false) {
const args = ['rmi'];
if (force)
args.push('-f');
args.push(image);
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError(image, 'rmi', new Error(result.stderr));
}
}
async getLogs(container, options = {}) {
const args = ['logs'];
if (options.follow) {
args.push('-f');
}
if (options.tail !== undefined) {
args.push('--tail', String(options.tail));
}
if (options.since) {
args.push('--since', options.since);
}
if (options.until) {
args.push('--until', options.until);
}
if (options.timestamps) {
args.push('-t');
}
args.push(container);
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'logs', new Error(result.stderr));
}
return result.stdout;
}
async streamLogs(container, onData, options = {}) {
const args = ['logs'];
if (options.follow) {
args.push('-f');
}
if (options.tail !== undefined) {
args.push('--tail', String(options.tail));
}
if (options.timestamps) {
args.push('-t');
}
args.push(container);
return new Promise((resolve, reject) => {
const child = spawn('docker', args);
let buffer = '';
let resolved = false;
const cleanup = () => {
if (!child.killed) {
child.kill('SIGTERM');
}
};
const safeOnData = (data) => {
try {
onData(data);
}
catch (error) {
if (!resolved) {
resolved = true;
cleanup();
reject(new DockerError(container, 'logs', error instanceof Error ? error : new Error(String(error))));
}
}
};
const processData = (data) => {
try {
buffer += data;
const lines = buffer.split('\n');
buffer = lines.pop() || '';
lines.forEach(line => {
if (line && !resolved) {
safeOnData(line + '\n');
}
});
}
catch (error) {
if (!resolved) {
resolved = true;
cleanup();
reject(new DockerError(container, 'logs', error instanceof Error ? error : new Error(String(error))));
}
}
};
child.stdout?.on('data', (chunk) => {
if (!resolved) {
processData(chunk.toString());
}
});
child.stderr?.on('data', (chunk) => {
if (!resolved) {
processData(chunk.toString());
}
});
child.stdout?.on('error', (error) => {
if (!resolved) {
resolved = true;
cleanup();
reject(new DockerError(container, 'logs', error));
}
});
child.stderr?.on('error', (error) => {
if (!resolved) {
resolved = true;
cleanup();
reject(new DockerError(container, 'logs', error));
}
});
child.on('error', (error) => {
if (!resolved) {
resolved = true;
cleanup();
reject(new DockerError(container, 'logs', error));
}
});
child.on('exit', (code) => {
if (!resolved) {
resolved = true;
try {
if (buffer) {
safeOnData(buffer);
}
if (code === 0 || code === 143) {
resolve();
}
else {
reject(new DockerError(container, 'logs', new Error('Log streaming failed')));
}
}
catch (error) {
reject(new DockerError(container, 'logs', error instanceof Error ? error : new Error(String(error))));
}
}
});
});
}
async copyToContainer(src, container, dest) {
const result = await this.executeDockerCommand(['cp', src, `${container}:${dest}`], {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'cp', new Error(result.stderr));
}
}
async copyFromContainer(container, src, dest) {
const result = await this.executeDockerCommand(['cp', `${container}:${src}`, dest], {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'cp', new Error(result.stderr));
}
}
async inspectContainer(container) {
const result = await this.executeDockerCommand(['inspect', container], {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'inspect', new Error(result.stderr));
}
return JSON.parse(result.stdout)[0];
}
async getStats(container) {
const result = await this.executeDockerCommand([
'stats',
'--no-stream',
'--format',
'json',
container
], {});
if (result.exitCode !== 0) {
throw new DockerError(container, 'stats', new Error(result.stderr));
}
return JSON.parse(result.stdout);
}
async createNetwork(name, options = {}) {
const existingNetworks = await this.listNetworks();
if (existingNetworks.includes(name)) {
return;
}
const args = ['network', 'create'];
if (options.driver) {
args.push('--driver', options.driver);
}
if (options.subnet) {
args.push('--subnet', options.subnet);
}
if (options.gateway) {
args.push('--gateway', options.gateway);
}
if (options.ipRange) {
args.push('--ip-range', options.ipRange);
}
if (options.attachable) {
args.push('--attachable');
}
if (options.internal) {
args.push('--internal');
}
args.push(name);
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
if (result.stderr.includes('already exists')) {
return;
}
throw new DockerError(name, 'network create', new Error(result.stderr));
}
}
async removeNetwork(name) {
const result = await this.executeDockerCommand(['network', 'rm', name], {});
if (result.exitCode !== 0) {
throw new DockerError(name, 'network rm', new Error(result.stderr));
}
}
async listNetworks() {
const result = await this.executeDockerCommand([
'network',
'ls',
'--format',
'{{.Name}}'
], {});
if (result.exitCode !== 0) {
throw new DockerError('', 'network ls', new Error(result.stderr));
}
return result.stdout.trim().split('\n').filter(Boolean);
}
async createVolume(name, options = {}) {
const args = ['volume', 'create'];
if (options.driver) {
args.push('--driver', options.driver);
}
if (options.driverOpts) {
for (const [key, value] of Object.entries(options.driverOpts)) {
args.push('--opt', `${key}=${value}`);
}
}
if (options.labels) {
for (const [key, value] of Object.entries(options.labels)) {
args.push('--label', `${key}=${value}`);
}
}
args.push(name);
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError(name, 'volume create', new Error(result.stderr));
}
}
async removeVolume(name, force = false) {
const args = ['volume', 'rm'];
if (force)
args.push('-f');
args.push(name);
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError(name, 'volume rm', new Error(result.stderr));
}
}
async listVolumes() {
const result = await this.executeDockerCommand([
'volume',
'ls',
'--format',
'{{.Name}}'
], {});
if (result.exitCode !== 0) {
throw new DockerError('', 'volume ls', new Error(result.stderr));
}
return result.stdout.trim().split('\n').filter(Boolean);
}
async composeUp(options = {}) {
const args = this.buildComposeArgs(options);
args.push('up', '-d');
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError('', 'compose up', new Error(result.stderr));
}
}
async composeDown(options = {}) {
const args = this.buildComposeArgs(options);
args.push('down');
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError('', 'compose down', new Error(result.stderr));
}
}
async composePs(options = {}) {
const args = this.buildComposeArgs(options);
args.push('ps');
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError('', 'compose ps', new Error(result.stderr));
}
return result.stdout;
}
async composeLogs(service, options = {}) {
const args = this.buildComposeArgs(options);
args.push('logs');
if (service) {
args.push(service);
}
const result = await this.executeDockerCommand(args, {});
if (result.exitCode !== 0) {
throw new DockerError('', 'compose logs', new Error(result.stderr));
}
return result.stdout;
}
async waitForHealthy(container, timeout = 30000) {
const startTime = Date.now();
while (Date.now() - startTime < timeout) {
try {
const info = await this.inspectContainer(container);
const health = info.State?.Health?.Status;
if (health === 'healthy') {
return;
}
else if (health === 'unhealthy') {
throw new DockerError(container, 'health', new Error('Container is unhealthy'));
}
}
catch (error) {
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
throw new DockerError(container, 'health', new Error('Timeout waiting for container to be healthy'));
}
async execJson(container, command) {
if (!command || command.length === 0) {
throw new DockerError(container, 'exec', new Error('Command array is empty'));
}
const [cmd, ...args] = command;
if (!cmd) {
throw new DockerError(container, 'exec', new Error('Command is empty'));
}
const result = await this.execute({
command: cmd,
args,
adapterOptions: { type: 'docker', container }
});
if (result.exitCode !== 0) {
throw new DockerError(container, 'exec', new Error(result.stderr));
}
try {
return JSON.parse(result.stdout);
}
catch (error) {
throw new DockerError(container, 'exec', new Error('Failed to parse JSON output'));
}
}
buildComposeArgs(options) {
const args = ['compose'];
if (options.file) {
const files = Array.isArray(options.file) ? options.file : [options.file];
for (const file of files) {
args.push('-f', file);
}
}
if (options.projectName) {
args.push('-p', options.projectName);
}
return args;
}
}
//# sourceMappingURL=docker-adapter.js.map