Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Haze <hazeone@users.noreply.github.com>
90 lines
3.0 KiB
TypeScript
90 lines
3.0 KiB
TypeScript
/**
|
|
* Windows shell quoting utilities for child_process.spawn().
|
|
*
|
|
* When spawn() is called with `shell: true` on Windows, the command and
|
|
* arguments are concatenated and passed to cmd.exe. Paths containing spaces
|
|
* must be wrapped in double-quotes to prevent cmd.exe from splitting them
|
|
* into separate tokens.
|
|
*
|
|
* This module is intentionally dependency-free so it can be unit-tested
|
|
* without mocking Electron.
|
|
*/
|
|
import path from 'path';
|
|
|
|
/**
|
|
* Quote a path/value for safe use with Windows cmd.exe (shell: true in spawn).
|
|
*
|
|
* When Node.js spawn is called with `shell: true` on Windows, cmd.exe
|
|
* interprets spaces as argument separators. Wrapping the value in double
|
|
* quotes prevents this. On non-Windows platforms the value is returned
|
|
* unchanged so this function can be called unconditionally.
|
|
*/
|
|
export function quoteForCmd(value: string): string {
|
|
if (process.platform !== 'win32') return value;
|
|
if (!value.includes(' ')) return value;
|
|
if (value.startsWith('"') && value.endsWith('"')) return value;
|
|
return `"${value}"`;
|
|
}
|
|
|
|
/**
|
|
* Determine whether a spawn call needs `shell: true` on Windows.
|
|
*
|
|
* Full (absolute) paths can be executed directly by the OS via
|
|
* CreateProcessW, which handles spaces correctly without a shell.
|
|
* Simple command names (e.g. 'uv', 'node') need shell for PATH/PATHEXT
|
|
* resolution on Windows.
|
|
*/
|
|
export function needsWinShell(bin: string): boolean {
|
|
if (process.platform !== 'win32') return false;
|
|
return !path.win32.isAbsolute(bin);
|
|
}
|
|
|
|
/**
|
|
* Prepare command and args for spawn(), handling Windows paths with spaces.
|
|
*
|
|
* Returns the shell option, the (possibly quoted) command, and the
|
|
* (possibly quoted) args array ready for child_process.spawn().
|
|
*/
|
|
export function prepareWinSpawn(
|
|
command: string,
|
|
args: string[],
|
|
forceShell?: boolean,
|
|
): { shell: boolean; command: string; args: string[] } {
|
|
const isWin = process.platform === 'win32';
|
|
const useShell = forceShell ?? (isWin && !path.win32.isAbsolute(command));
|
|
|
|
if (!useShell || !isWin) {
|
|
return { shell: useShell, command, args };
|
|
}
|
|
|
|
return {
|
|
shell: true,
|
|
command: quoteForCmd(command),
|
|
args: args.map(a => quoteForCmd(a)),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Normalize a module path for NODE_OPTIONS `--require` usage.
|
|
*
|
|
* Node parses NODE_OPTIONS using shell-like escaping rules. On Windows,
|
|
* a quoted path with backslashes (e.g. "C:\Users\...") loses separators
|
|
* because backslashes are interpreted as escapes. Using forward slashes
|
|
* keeps the absolute path intact while still being valid on Windows.
|
|
*/
|
|
export function normalizeNodeRequirePathForNodeOptions(modulePath: string): string {
|
|
if (process.platform !== 'win32') return modulePath;
|
|
return modulePath.replace(/\\/g, '/');
|
|
}
|
|
|
|
/**
|
|
* Append a `--require` preload module path to NODE_OPTIONS safely.
|
|
*/
|
|
export function appendNodeRequireToNodeOptions(
|
|
nodeOptions: string | undefined,
|
|
modulePath: string,
|
|
): string {
|
|
const normalized = normalizeNodeRequirePathForNodeOptions(modulePath);
|
|
return `${nodeOptions ?? ''} --require "${normalized}"`.trim();
|
|
}
|