import Docker from 'dockerode'; import { resolve } from 'path'; export class DockerManager { docker; constructor() { this.docker = new Docker(); } async pullImage(image) { const stream = await this.docker.pull(image); return new Promise((resolve, reject) => { this.docker.modem.followProgress(stream, (err) => { if (err) reject(err); else resolve(); }); }); } async createContainer(image, options) { const absWorkspace = resolve(options.workspace); const container = await this.docker.createContainer({ Image: image, Cmd: ['sleep', '3600'], WorkingDir: options.workingDir || '/github/workspace', HostConfig: { Binds: [`${absWorkspace}:/github/workspace`], AutoRemove: true, }, Env: Object.entries(options.env).map(([k, v]) => `${k}=${v}`), Entrypoint: ['sh', '-c', 'while true; do sleep 3600; done'], }); await container.start(); return container.id; } async execInContainer(containerId, step, modifiedCommand) { const container = this.docker.getContainer(containerId); const cmd = modifiedCommand ?? step.run ?? 'true'; const shell = step.shell ?? 'bash'; const workingDir = step.workingDirectory ?? '/github/workspace'; // Handle 'uses' steps by converting to a comment if (!modifiedCommand && step.uses) { const usesCmd = `echo "[CISandbox] Action step: ${step.uses}" && echo "Note: 'uses' actions are not yet supported in CISandbox. Convert to 'run' or modify the step to debug."`; return this.runShell(container, usesCmd, workingDir, shell, step.env); } return this.runShell(container, cmd, workingDir, shell, step.env); } async runShell(container, cmd, workingDir, shell, extraEnv) { const exec = await container.exec({ Cmd: [shell, '-c', cmd], WorkingDir: workingDir, AttachStdout: true, AttachStderr: true, Env: extraEnv ? Object.entries(extraEnv).map(([k, v]) => `${k}=${v}`) : undefined, }); const stream = await exec.start({ hijack: true, stdin: false }); let stdout = ''; let stderr = ''; return new Promise((resolve, reject) => { stream.on('data', (chunk) => { // Docker multiplexed stream: first byte is stream type const type = chunk[0]; const payload = chunk.slice(8).toString('utf-8'); if (type === 1) stdout += payload; else stderr += payload; }); stream.on('end', async () => { try { const inspect = await exec.inspect(); resolve({ exitCode: inspect.ExitCode ?? 0, stdout: stdout.trimEnd(), stderr: stderr.trimEnd(), }); } catch (err) { reject(err); } }); stream.on('error', reject); }); } async stopContainer(containerId) { try { const container = this.docker.getContainer(containerId); await container.stop({ t: 2 }); } catch { // Already stopped or doesn't exist } } async imageExists(image) { try { await this.docker.getImage(image).inspect(); return true; } catch { return false; } } } //# sourceMappingURL=docker.js.map