UNPKG

git-aiflow

Version:

🚀 An AI-powered workflow automation tool for effortless Git-based development, combining smart GitLab/GitHub merge & pull request creation with Conan package management.

120 lines • 5.18 kB
import { spawnSync } from "node:child_process"; import { logger } from './logger.js'; import { platform } from 'os'; import { parse } from 'shell-quote'; /** * Cross-platform shell executor * - Windows: PowerShell Core (pwsh) or Windows PowerShell (powershell) * - macOS/Linux: bash or zsh */ export class Shell { constructor() { logger.debug(`Initializing Shell, platform: ${platform()} process.cwd: ${process.cwd()}`); } static instance() { const pwd = process.cwd(); if (Shell.instances.get(pwd)) { return Shell.instances.get(pwd); } const shell = new Shell(); Shell.instances.set(pwd, shell); return shell; } /** * Execute a shell command and return stdout as string * @param command The command to execute */ run(shell_command, ...args) { logger.debug(`Executing shell command: \n----\n${shell_command}${args.length > 0 ? ` ${args.join(" ")}` : ""}\n----`); const startTime = Date.now(); try { let command; let commandArgs; if (args.length > 0) { // Prefer explicit (executable, ...args) command = shell_command; commandArgs = args; } else { // Safely split the shell_command string into command and args const parsed = parse(shell_command).filter((x) => typeof x === "string"); if (parsed.length === 0) { throw new Error("No command to execute."); } command = parsed[0]; commandArgs = parsed.slice(1); } // Always use shell: false to avoid command injection const result = spawnSync(command, commandArgs, { encoding: "utf-8", shell: false, cwd: process.cwd(), maxBuffer: 1024 * 1024 * 10, }); const duration = Date.now() - startTime; if (result.error) { logger.error(`Command failed (${duration}ms): ${shell_command}${args.length > 0 ? ` ${args.join(" ")}` : ""}`, result.error); return result.error.message; } const output = result.stdout.replace(/[\r\n]+/g, "\n").trimEnd() || result.stderr.replace(/[\r\n]+/g, "\n").trimEnd(); logger.debug(`Command output: \n----\n${output}\n----`); logger.debug(`Command completed (${duration}ms) ${JSON.stringify({ command: shell_command + (args.length > 0 ? ` ${args.join(" ")}` : ""), output, outputLength: output.length, exitCode: result.status })}`); return output; } catch (error) { const duration = Date.now() - startTime; logger.error(`Shell execution failed after ${duration}ms`, error); return error instanceof Error ? error.message : String(error); } } runProcess(process, ...args) { return this.run(process, ...args); } /** * Execute a shell command and return detailed result including exit code * @param command The command to execute * @param args Command arguments * @returns Object containing success status, output, and exit code */ runWithExitCode(command, ...args) { logger.debug(`Executing shell command: \n----\n${command}${args.length > 0 ? ` ${args.join(" ")}` : ""}\n----`); const startTime = Date.now(); try { // Always use shell: false to avoid command injection const result = spawnSync(command, args, { encoding: "utf-8", shell: false, cwd: process.cwd(), maxBuffer: 1024 * 1024 * 10, }); const duration = Date.now() - startTime; const exitCode = result.status || 0; const success = exitCode === 0 && !result.error; if (result.error) { logger.debug(`Command failed with error (${duration}ms): ${result.error.message}`); return { success: false, output: result.error.message, exitCode: -1 }; } const output = result.stdout.replace(/[\r\n]+/g, "\n").trimEnd() || result.stderr.replace(/[\r\n]+/g, "\n").trimEnd(); logger.debug(`Command output: \n----\n${output}\n----`); logger.debug(`Command completed (${duration}ms) ${JSON.stringify({ command: command + (args.length > 0 ? ` ${args.join(" ")}` : ""), output, outputLength: output.length, exitCode, success })}`); return { success, output, exitCode }; } catch (error) { const duration = Date.now() - startTime; logger.error(`Shell execution failed after ${duration}ms`, error); const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, output: errorMessage, exitCode: -1 }; } } } Shell.instances = new Map(); //# sourceMappingURL=shell.js.map