docker-pilot
Version:
A powerful, scalable Docker CLI library for managing containerized applications of any size
365 lines • 12.5 kB
JavaScript
;
/**
* 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