From 33b9bdb11e9371dfa6891ba1be47a12b958f493d Mon Sep 17 00:00:00 2001 From: Abhi <43648792+abhipatel12@users.noreply.github.com> Date: Sun, 17 Aug 2025 00:02:54 -0400 Subject: feat(cli): Introduce arguments for shell execution in custom commands (#5966) --- packages/core/src/utils/shell-utils.ts | 96 ++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) (limited to 'packages/core/src/utils/shell-utils.ts') diff --git a/packages/core/src/utils/shell-utils.ts b/packages/core/src/utils/shell-utils.ts index 4164cdca..2c818c8e 100644 --- a/packages/core/src/utils/shell-utils.ts +++ b/packages/core/src/utils/shell-utils.ts @@ -5,6 +5,102 @@ */ import { Config } from '../config/config.js'; +import os from 'os'; +import { quote } from 'shell-quote'; + +/** + * An identifier for the shell type. + */ +export type ShellType = 'cmd' | 'powershell' | 'bash'; + +/** + * Defines the configuration required to execute a command string within a specific shell. + */ +export interface ShellConfiguration { + /** The path or name of the shell executable (e.g., 'bash', 'cmd.exe'). */ + executable: string; + /** + * The arguments required by the shell to execute a subsequent string argument. + */ + argsPrefix: string[]; + /** An identifier for the shell type. */ + shell: ShellType; +} + +/** + * Determines the appropriate shell configuration for the current platform. + * + * This ensures we can execute command strings predictably and securely across platforms + * using the `spawn(executable, [...argsPrefix, commandString], { shell: false })` pattern. + * + * @returns The ShellConfiguration for the current environment. + */ +export function getShellConfiguration(): ShellConfiguration { + if (isWindows()) { + const comSpec = process.env.ComSpec || 'cmd.exe'; + const executable = comSpec.toLowerCase(); + + if ( + executable.endsWith('powershell.exe') || + executable.endsWith('pwsh.exe') + ) { + // For PowerShell, the arguments are different. + // -NoProfile: Speeds up startup. + // -Command: Executes the following command. + return { + executable: comSpec, + argsPrefix: ['-NoProfile', '-Command'], + shell: 'powershell', + }; + } + + // Default to cmd.exe for anything else on Windows. + // Flags for CMD: + // /d: Skip execution of AutoRun commands. + // /s: Modifies the treatment of the command string (important for quoting). + // /c: Carries out the command specified by the string and then terminates. + return { + executable: comSpec, + argsPrefix: ['/d', '/s', '/c'], + shell: 'cmd', + }; + } + + // Unix-like systems (Linux, macOS) + return { executable: 'bash', argsPrefix: ['-c'], shell: 'bash' }; +} + +/** + * Export the platform detection constant for use in process management (e.g., killing processes). + */ +export const isWindows = () => os.platform() === 'win32'; + +/** + * Escapes a string so that it can be safely used as a single argument + * in a shell command, preventing command injection. + * + * @param arg The argument string to escape. + * @param shell The type of shell the argument is for. + * @returns The shell-escaped string. + */ +export function escapeShellArg(arg: string, shell: ShellType): string { + if (!arg) { + return ''; + } + + switch (shell) { + case 'powershell': + // For PowerShell, wrap in single quotes and escape internal single quotes by doubling them. + return `'${arg.replace(/'/g, "''")}'`; + case 'cmd': + // Simple Windows escaping for cmd.exe: wrap in double quotes and escape inner double quotes. + return `"${arg.replace(/"/g, '""')}"`; + case 'bash': + default: + // POSIX shell escaping using shell-quote. + return quote([arg]); + } +} /** * Splits a shell command into a list of individual commands, respecting quotes. -- cgit v1.2.3