UNPKG

docker-pilot

Version:

A powerful, scalable Docker CLI library for managing containerized applications of any size

365 lines 12.5 kB
"use strict"; /** * Command Runner for Docker Pilot * Executes system commands with proper error handling and logging */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CommandRunner = void 0; const child_process_1 = require("child_process"); const util_1 = require("util"); const types_1 = require("../types"); const Logger_1 = require("../utils/Logger"); const execAsync = (0, util_1.promisify)(child_process_1.exec); class CommandRunner { constructor(logger, defaultOptions = {}) { this.logger = logger || new Logger_1.Logger(); this.defaultOptions = { timeout: 300000, // 5 minutes silent: false, cwd: process.cwd(), env: process.env, shell: true, ...defaultOptions }; } /** * Execute command synchronously */ execSync(command, options = {}) { const startTime = Date.now(); const mergedOptions = { ...this.defaultOptions, ...options }; if (!mergedOptions.silent) { this.logger.command(command); } try { const result = (0, child_process_1.execSync)(command, { encoding: options.encoding || 'utf8', stdio: mergedOptions.silent ? 'pipe' : 'inherit', cwd: mergedOptions.cwd, env: mergedOptions.env, timeout: mergedOptions.timeout }); const executionTime = Date.now() - startTime; if (!mergedOptions.silent) { this.logger.success(`Command completed in ${executionTime}ms`); } return { success: true, output: result.toString(), executionTime }; } catch (error) { const executionTime = Date.now() - startTime; const result = { success: false, error: error.message || 'Unknown error', exitCode: error.status || error.code || 1, executionTime }; if (!mergedOptions.silent) { this.logger.error(`Command failed after ${executionTime}ms: ${error.message}`); } return result; } } /** * Execute command asynchronously */ async exec(command, options = {}) { const startTime = Date.now(); const mergedOptions = { ...this.defaultOptions, ...options }; if (!mergedOptions.silent) { this.logger.command(command); } try { const { stdout, stderr } = await execAsync(command, { cwd: mergedOptions.cwd, env: mergedOptions.env, timeout: mergedOptions.timeout }); const executionTime = Date.now() - startTime; const output = stdout || stderr || ''; if (!mergedOptions.silent && output) { console.log(output); } if (!mergedOptions.silent) { this.logger.success(`Command completed in ${executionTime}ms`); } return { success: true, output, executionTime }; } catch (error) { const executionTime = Date.now() - startTime; const result = { success: false, error: error.message || 'Unknown error', exitCode: error.code || 1, executionTime, output: error.stdout || '' }; if (!mergedOptions.silent) { this.logger.error(`Command failed after ${executionTime}ms: ${error.message}`); if (error.stderr) { console.error(error.stderr); } } return result; } } /** * Spawn a process for interactive or long-running commands */ spawn(command, args = [], options = {}) { const mergedOptions = { ...this.defaultOptions, ...options }; if (!mergedOptions.silent) { this.logger.command(`${command} ${args.join(' ')}`); } const childProcess = (0, child_process_1.spawn)(command, args, { cwd: mergedOptions.cwd, env: mergedOptions.env, shell: mergedOptions.shell, stdio: options.interactive ? 'inherit' : 'pipe' }); // Handle output if not interactive if (!options.interactive) { if (childProcess.stdout) { childProcess.stdout.on('data', (data) => { const output = data.toString(); if (options.onOutput) { options.onOutput(output); } else if (!mergedOptions.silent) { process.stdout.write(output); } }); } if (childProcess.stderr) { childProcess.stderr.on('data', (data) => { const error = data.toString(); if (options.onError) { options.onError(error); } else if (!mergedOptions.silent) { process.stderr.write(error); } }); } } // Handle process close childProcess.on('close', (code, signal) => { if (options.onClose) { options.onClose(code, signal); } else if (!mergedOptions.silent) { if (code === 0) { this.logger.success(`Process completed successfully`); } else { this.logger.error(`Process exited with code ${code}${signal ? ` (signal: ${signal})` : ''}`); } } }); // Handle process error childProcess.on('error', (error) => { if (!mergedOptions.silent) { this.logger.error(`Process error: ${error.message}`); } }); return childProcess; } /** * Execute Docker command */ async execDocker(subcommand, args = [], options = {}) { const command = `docker ${subcommand} ${args.join(' ')}`.trim(); return this.exec(command, options); } /** * Execute Docker Compose command */ async execDockerCompose(subcommand, args = [], options = {}) { const composeArgs = []; if (options.composeFile) { composeArgs.push('-f', options.composeFile); } if (options.projectName) { composeArgs.push('-p', options.projectName); } composeArgs.push(subcommand, ...args); const command = `docker compose ${composeArgs.join(' ')}`.trim(); return this.exec(command, options); } /** * Execute Docker Compose command synchronously */ execDockerComposeSync(subcommand, args = [], options = {}) { const composeArgs = []; if (options.composeFile) { composeArgs.push('-f', options.composeFile); } if (options.projectName) { composeArgs.push('-p', options.projectName); } composeArgs.push(subcommand, ...args); const command = `docker compose ${composeArgs.join(' ')}`.trim(); return this.execSync(command, options); } /** * Spawn Docker Compose command for interactive use */ spawnDockerCompose(subcommand, args = [], options = {}) { const composeArgs = []; if (options.composeFile) { composeArgs.push('-f', options.composeFile); } if (options.projectName) { composeArgs.push('-p', options.projectName); } composeArgs.push(subcommand, ...args); return this.spawn('docker', ['compose', ...composeArgs], options); } /** * Execute command with retry logic */ async execWithRetry(command, options = {}) { const maxRetries = options.maxRetries || 3; const retryDelay = options.retryDelay || 1000; const retryCondition = options.retryCondition || ((result) => !result.success); let lastResult; for (let attempt = 1; attempt <= maxRetries; attempt++) { lastResult = await this.exec(command, { ...options, silent: options.silent || attempt > 1 }); if (!retryCondition(lastResult)) { return lastResult; } if (attempt < maxRetries) { if (!options.silent) { this.logger.warn(`Command failed (attempt ${attempt}/${maxRetries}), retrying in ${retryDelay}ms...`); } await this.sleep(retryDelay); } } if (!options.silent) { this.logger.error(`Command failed after ${maxRetries} attempts`); } return lastResult; } /** * Execute multiple commands in sequence */ async execSequence(commands, options = {}) { const results = []; const stopOnError = options.stopOnError ?? true; for (const command of commands) { const result = await this.exec(command, options); results.push(result); if (!result.success && stopOnError) { if (!options.silent) { this.logger.error(`Sequence stopped due to command failure: ${command}`); } break; } } return results; } /** * Execute multiple commands in parallel */ async execParallel(commands, options = {}) { const promises = commands.map(command => this.exec(command, options)); return Promise.all(promises); } /** * Check if command exists in system PATH */ async commandExists(command) { try { const checkCommand = process.platform === 'win32' ? `where ${command}` : `which ${command}`; const result = await this.exec(checkCommand, { silent: true }); return result.success; } catch { return false; } } /** * Get command version */ async getCommandVersion(command, versionFlag = '--version') { try { const result = await this.exec(`${command} ${versionFlag}`, { silent: true }); return result.success ? result.output?.trim() || null : null; } catch { return null; } } /** * Kill process by PID */ async killProcess(pid, signal = 'SIGTERM') { try { const killCommand = process.platform === 'win32' ? `taskkill /PID ${pid} /F` : `kill -${signal} ${pid}`; const result = await this.exec(killCommand, { silent: true }); return result.success; } catch { return false; } } /** * Execute command with timeout */ async execWithTimeout(command, timeoutMs, options = {}) { return this.exec(command, { ...options, timeout: timeoutMs }); } /** * Execute command and return only success status */ async execQuiet(command, options = {}) { const result = await this.exec(command, { ...options, silent: true }); return result.success; } /** * Execute command and throw error if it fails */ async execOrThrow(command, options = {}) { const result = await this.exec(command, options); if (!result.success) { throw new types_1.CommandExecutionError(`Command failed: ${command}`, { command, exitCode: result.exitCode, error: result.error, output: result.output }); } return result.output || ''; } /** * Set default options for all commands */ setDefaultOptions(options) { this.defaultOptions = { ...this.defaultOptions, ...options }; } /** * Get current default options */ getDefaultOptions() { return { ...this.defaultOptions }; } /** * Sleep utility for retry logic */ sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } } exports.CommandRunner = CommandRunner; //# sourceMappingURL=CommandRunner.js.map