On Unix/macOS, pass args to spawn to avoid escaping issues

This commit is contained in:
Hans Ott 2025-10-09 16:33:40 +02:00 committed by Sander Declerck
parent 486a4b8f68
commit 7e72ae7d3d
No known key found for this signature in database

View file

@ -1,4 +1,4 @@
import { spawnSync, spawn } from "child_process";
import { spawn, execSync } from "child_process";
function escapeArg(arg) {
// Shell metacharacters that need escaping
@ -16,18 +16,39 @@ function escapeArg(arg) {
function buildCommand(command, args) {
const escapedArgs = args.map(escapeArg);
return `${command} ${escapedArgs.join(" ")}`;
}
export function safeSpawnSync(command, args, options = {}) {
const fullCommand = buildCommand(command, args);
return spawnSync(fullCommand, { ...options, shell: true });
function resolveCommandPath(command) {
// command will be "npm", "yarn", etc.
// Use 'command -v' to find the full path
const fullPath = execSync(`command -v ${command}`, {
encoding: "utf8",
shell: true,
}).trim();
if (!fullPath) {
throw new Error(`Command not found: ${command}`);
}
return fullPath;
}
export async function safeSpawn(command, args, options = {}) {
const fullCommand = buildCommand(command, args);
return new Promise((resolve, reject) => {
const child = spawn(fullCommand, { ...options, shell: true });
// Windows requires shell: true because .bat and .cmd files are not executable
// without a terminal. On Unix/macOS, we resolve the full path first, then use
// array args (safer, no escaping needed).
// See: https://nodejs.org/api/child_process.html#child_processspawncommand-args-options
let child;
if (process.platform === "win32") {
const fullCommand = buildCommand(command, args);
child = spawn(fullCommand, { ...options, shell: true });
} else {
const fullPath = resolveCommandPath(command);
child = spawn(fullPath, args, options);
}
child.on("close", (code) => {
resolve({