UNPKG

node-os-utils

Version:

Advanced cross-platform operating system monitoring utilities with TypeScript support

325 lines 12.2 kB
"use strict"; 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