node-os-utils
Version:
Advanced cross-platform operating system monitoring utilities with TypeScript support
325 lines • 12.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandExecutor = void 0;
const child_process_1 = require("child_process");
const util_1 = require("util");
const errors_1 = require("../types/errors");
const execAsync = (0, util_1.promisify)(child_process_1.exec);
/**
* 命令执行器
*
* 负责在不同平台上执行系统命令,提供统一的接口和错误处理
*/
class CommandExecutor {
constructor(platform, defaultOptions = {}) {
this.platform = platform;
this.defaultOptions = {
timeout: 10000,
shell: true,
encoding: 'utf8',
maxBuffer: 1024 * 1024, // 1MB
env: {
...process.env,
LC_ALL: 'en_US.UTF-8',
LANG: 'en_US.UTF-8',
LANGUAGE: 'en_US:en'
},
...defaultOptions
};
}
/**
* 执行命令并返回结果
*/
async execute(command, options = {}) {
const mergedOptions = { ...this.defaultOptions, ...options };
const startTime = Date.now();
try {
const result = await this.executeWithTimeout(command, mergedOptions);
const executionTime = Date.now() - startTime;
return {
stdout: result.stdout || '',
stderr: result.stderr || '',
exitCode: 0,
platform: this.platform,
executionTime,
command
};
}
catch (error) {
const executionTime = Date.now() - startTime;
// 处理非对象异常(如字符串、数字、null、undefined)——Deno 兼容层可能抛出这类值
if (error === null || error === undefined || typeof error !== 'object') {
throw new errors_1.MonitorError(`Command failed: ${String(error)}`, errors_1.ErrorCode.COMMAND_FAILED, this.platform, { command, executionTime, rawError: String(error) });
}
// 处理不同类型的错误
if (error.killed && error.signal) {
// 超时或被杀死的进程
throw new errors_1.MonitorError(`Command was killed with signal ${error.signal}`, errors_1.ErrorCode.TIMEOUT, this.platform, {
command,
signal: error.signal,
killed: error.killed,
executionTime
});
}
if (error.code === 'ENOENT') {
// 命令不存在
throw new errors_1.MonitorError(`Command not found: ${command}`, errors_1.ErrorCode.COMMAND_FAILED, this.platform, {
command,
code: error.code,
executionTime
});
}
if (error.code === 'EACCES') {
// 权限不足
throw new errors_1.MonitorError(`Permission denied: ${command}`, errors_1.ErrorCode.PERMISSION_DENIED, this.platform, {
command,
code: error.code,
executionTime
});
}
const timeoutMs = mergedOptions.timeout ?? this.defaultOptions.timeout ?? 10000;
if (error.name === 'AbortError' || error.code === 'ABORT_ERR' || error.code === 'ERR_CANCELED') {
throw new errors_1.MonitorError(`Command timed out after ${timeoutMs}ms`, errors_1.ErrorCode.TIMEOUT, this.platform, {
command,
executionTime,
timeout: timeoutMs
});
}
// 命令执行失败但有输出
const result = {
stdout: error.stdout || '',
stderr: error.stderr || '',
exitCode: error.code || 1,
platform: this.platform,
executionTime,
command
};
// 如果有标准输出,即使退出码非零也返回结果
if (result.stdout && result.stdout.trim()) {
return result;
}
// 否则抛出错误
throw new errors_1.MonitorError(`Command failed with exit code ${result.exitCode}: ${result.stderr || 'Unknown error'}`, errors_1.ErrorCode.COMMAND_FAILED, this.platform, {
command,
exitCode: result.exitCode,
stderr: result.stderr,
stdout: result.stdout,
executionTime
});
}
}
/**
* 执行多个命令
*/
async executeMultiple(commands, options = {}) {
const results = [];
for (const command of commands) {
try {
const result = await this.execute(command, options);
results.push(result);
}
catch (error) {
// 继续执行其他命令,但记录错误
const errorResult = {
stdout: '',
stderr: error instanceof Error ? error.message : String(error),
exitCode: 1,
platform: this.platform,
executionTime: 0,
command
};
results.push(errorResult);
}
}
return results;
}
/**
* 并发执行多个命令
*/
async executeConcurrent(commands, options = {}) {
const promises = commands.map(command => this.execute(command, options).catch(error => {
// 转换错误为结果对象
return {
stdout: '',
stderr: error instanceof Error ? error.message : String(error),
exitCode: 1,
platform: this.platform,
executionTime: 0,
command
};
}));
return Promise.all(promises);
}
/**
* 执行命令并流式处理输出
*/
async executeStream(command, onData, options = {}) {
const mergedOptions = { ...this.defaultOptions, ...options };
const startTime = Date.now();
return new Promise((resolve, reject) => {
const spawnOptions = {
shell: mergedOptions.shell,
env: mergedOptions.env,
cwd: mergedOptions.cwd,
timeout: mergedOptions.timeout
};
let child;
if (spawnOptions.shell) {
child = (0, child_process_1.spawn)(command, spawnOptions);
}
else {
const tokens = this.tokenizeCommand(command);
const executable = tokens.shift();
if (!executable) {
reject(new errors_1.MonitorError('Invalid command provided for execution', errors_1.ErrorCode.INVALID_CONFIG, this.platform, { command }));
return;
}
child = (0, child_process_1.spawn)(executable, tokens, spawnOptions);
}
let stdout = '';
let stderr = '';
child.stdout?.on('data', (data) => {
const text = data.toString();
stdout += text;
onData(text, false);
});
child.stderr?.on('data', (data) => {
const text = data.toString();
stderr += text;
onData(text, true);
});
child.on('close', (code) => {
const executionTime = Date.now() - startTime;
const result = {
stdout,
stderr,
exitCode: code || 0,
platform: this.platform,
executionTime,
command
};
if (code === 0 || stdout.trim()) {
resolve(result);
}
else {
reject(new errors_1.MonitorError(`Command failed with exit code ${code}`, errors_1.ErrorCode.COMMAND_FAILED, this.platform, result));
}
});
child.on('error', (error) => {
const executionTime = Date.now() - startTime;
reject(new errors_1.MonitorError(`Command execution error: ${error.message}`, errors_1.ErrorCode.COMMAND_FAILED, this.platform, {
command,
error: error.message,
executionTime
}));
});
});
}
/**
* 检查命令是否可用
*/
async isCommandAvailable(command) {
const testCommand = this.platform === 'win32'
? `where ${command}`
: `which ${command}`;
try {
await this.execute(testCommand, { timeout: 5000 });
return true;
}
catch {
return false;
}
}
/**
* 获取命令的版本信息
*/
async getCommandVersion(command, versionFlag = '--version') {
try {
const result = await this.execute(`${command} ${versionFlag}`, { timeout: 5000 });
return result.stdout.trim();
}
catch (error) {
throw new errors_1.MonitorError(`Failed to get version for command: ${command}`, errors_1.ErrorCode.COMMAND_FAILED, this.platform, { command, versionFlag, error });
}
}
/**
* 设置默认选项
*/
setDefaultOptions(options) {
this.defaultOptions = { ...this.defaultOptions, ...options };
}
/**
* 获取默认选项
*/
getDefaultOptions() {
return { ...this.defaultOptions };
}
/**
* 转义命令参数
*/
escapeArgument(arg) {
if (this.platform === 'win32') {
// Windows 命令行转义
return `"${arg.replace(/"/g, '""')}"`;
}
else {
// Unix-like 系统转义
return `'${arg.replace(/'/g, "'\"'\"'")}'`;
}
}
/**
* 构建安全的命令字符串
*/
buildCommand(command, args = []) {
const escapedArgs = args.map(arg => this.escapeArgument(arg));
return [command, ...escapedArgs].join(' ');
}
tokenizeCommand(command) {
const matches = command.match(/"[^"\\]*(?:\\.[^"\\]*)*"|'[^'\\]*(?:\\.[^'\\]*)*'|[^\s]+/g);
if (!matches) {
return [];
}
return matches.map(token => {
const startsWithQuote = token.startsWith('"') || token.startsWith("'");
const endsWithQuote = token.endsWith('"') || token.endsWith("'");
if (startsWithQuote && endsWithQuote && token.length >= 2) {
const unquoted = token.slice(1, -1);
return unquoted.replace(/\\([\\'" ])/g, '$1');
}
return token;
});
}
/**
* 带超时执行命令
*/
async executeWithTimeout(command, options) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), options.timeout || 10000);
try {
const execOptions = {
...options,
signal: controller.signal
};
// 确保 shell 选项在不同平台下正确设置
if (execOptions.shell === true) {
if (process.platform === 'win32') {
execOptions.shell = process.env.ComSpec || 'cmd.exe';
}
else {
// 尝试使用当前 SHELL,若不存在再回退到常见 POSIX shell
const fallbackShells = [process.env.SHELL, '/bin/bash', '/bin/sh'];
execOptions.shell = fallbackShells.find(Boolean);
}
}
const result = await execAsync(command, execOptions);
clearTimeout(timeoutId);
return result;
}
catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
}
exports.CommandExecutor = CommandExecutor;
//# sourceMappingURL=command-executor.js.map