fix(process): resolve npm/pnpm spawn ENOENT on Windows

On Windows, non-.exe commands like npm, pnpm, yarn, npx require
their .cmd extension when using spawn(). This adds a resolveCommand()
helper that automatically appends .cmd on Windows for these commands.

Fixes #5773
This commit is contained in:
Jhin
2026-02-01 01:03:35 +00:00
committed by Tak Hoffman
parent 01d76e4799
commit 5c8880ed3f

View File

@@ -7,6 +7,23 @@ import { resolveCommandStdio } from "./spawn-utils.js";
const execFileAsync = promisify(execFile);
/**
* Resolves a command for Windows compatibility.
* On Windows, non-.exe commands (like npm, pnpm) require their .cmd extension.
*/
function resolveCommand(command: string): string {
if (process.platform !== "win32") {
return command;
}
const basename = path.basename(command).toLowerCase();
// Common npm-related commands that need .cmd extension on Windows
const cmdCommands = ["npm", "pnpm", "yarn", "npx"];
if (cmdCommands.includes(basename)) {
return `${command}.cmd`;
}
return command;
}
// Simple promise-wrapped execFile with optional verbosity logging.
export async function runExec(
command: string,
@@ -22,7 +39,7 @@ export async function runExec(
encoding: "utf8" as const,
};
try {
const { stdout, stderr } = await execFileAsync(command, args, options);
const { stdout, stderr } = await execFileAsync(resolveCommand(command), args, options);
if (shouldLogVerbose()) {
if (stdout.trim()) {
logDebug(stdout.trim());
@@ -89,7 +106,7 @@ export async function runCommandWithTimeout(
}
const stdio = resolveCommandStdio({ hasInput, preferInherit: true });
const child = spawn(argv[0], argv.slice(1), {
const child = spawn(resolveCommand(argv[0]), argv.slice(1), {
stdio,
cwd,
env: resolvedEnv,