core/packages/core-cli/src/utils/docker-interactive.ts
2025-07-18 22:29:42 +05:30

108 lines
2.9 KiB
TypeScript

import { spawn, ChildProcess } from 'child_process';
import { spinner } from '@clack/prompts';
export interface DockerCommandOptions {
cwd: string;
message: string;
showOutput?: boolean;
}
export function executeDockerCommandInteractive(
command: string,
options: DockerCommandOptions
): Promise<void> {
return new Promise((resolve, reject) => {
const s = spinner();
s.start(options.message);
// Split command into parts
const parts = command.split(' ');
const cmd = parts[0];
const args = parts.slice(1);
if (!cmd) {
reject(new Error('Invalid command'));
return;
}
const child: ChildProcess = spawn(cmd, args, {
cwd: options.cwd,
stdio: options.showOutput ? ['ignore', 'pipe', 'pipe'] : 'ignore',
detached: false
});
let output = '';
// Handle stdout
if (child.stdout && options.showOutput) {
child.stdout.on('data', (data: Buffer) => {
const text = data.toString();
output += text;
// Update spinner with latest output line
const lines = text.trim().split('\n');
const lastLine = lines[lines.length - 1];
if (lastLine && lastLine.trim()) {
s.message(`${options.message}\n${lastLine.trim()}`);
}
});
}
// Handle stderr
if (child.stderr && options.showOutput) {
child.stderr.on('data', (data: Buffer) => {
const text = data.toString();
output += text;
// Update spinner with error output
const lines = text.trim().split('\n');
const lastLine = lines[lines.length - 1];
if (lastLine && lastLine.trim()) {
s.message(`${options.message}\n❌ ${lastLine.trim()}`);
}
});
}
// Handle process exit
child.on('exit', (code: number | null) => {
if (code === 0) {
s.stop(`${options.message.replace(/\.\.\.$/, '')} completed`);
resolve();
} else {
s.stop(`${options.message.replace(/\.\.\.$/, '')} failed (exit code: ${code})`);
if (options.showOutput && output) {
console.log('\nOutput:');
console.log(output);
}
reject(new Error(`Command failed with exit code ${code}`));
}
});
// Handle errors
child.on('error', (error: Error) => {
s.stop(`${options.message.replace(/\.\.\.$/, '')} failed`);
reject(error);
});
// Handle Ctrl+C
const handleSigint = () => {
s.stop(`⏹️ ${options.message.replace(/\.\.\.$/, '')} interrupted`);
child.kill('SIGTERM');
// Give the process time to clean up
setTimeout(() => {
if (child.killed === false) {
child.kill('SIGKILL');
}
process.exit(130); // Standard exit code for SIGINT
}, 5000);
};
process.on('SIGINT', handleSigint);
// Clean up event listener when done
child.on('exit', () => {
process.off('SIGINT', handleSigint);
});
});
}