diff --git a/.changeset/windows-cmd-spawn-via-cmd-exe.md b/.changeset/windows-cmd-spawn-via-cmd-exe.md new file mode 100644 index 000000000000..59f88b353869 --- /dev/null +++ b/.changeset/windows-cmd-spawn-via-cmd-exe.md @@ -0,0 +1,5 @@ +--- +'create-astro': patch +--- + +Fixes dependency installation failing on Windows when running `npm create astro@latest`. The previous fix for DEP0190 warnings incorrectly assumed `.cmd` shims could be spawned directly without a shell — on Windows, `.cmd` files require `cmd.exe` to execute. Package manager commands are now invoked via `cmd.exe /d /s /c` on Windows. Also fixes the `[object Object]` error message that appeared when installation failed, replacing it with the actual error. diff --git a/packages/create-astro/src/shell.ts b/packages/create-astro/src/shell.ts index ac2b5004a77b..1e121f110e65 100644 --- a/packages/create-astro/src/shell.ts +++ b/packages/create-astro/src/shell.ts @@ -20,10 +20,19 @@ interface Output { const text = (stream: NodeJS.ReadableStream | Readable | null) => stream ? textFromStream(stream).then((t) => t.trimEnd()) : ''; -function resolveCommand(command: string) { - if (process.platform !== 'win32') return command; - if (command.includes('/') || command.includes('\\') || command.includes('.')) return command; - return WINDOWS_CMD_SHIMS.has(command.toLowerCase()) ? `${command}.cmd` : command; +/** + * On Windows, `.cmd` and `.bat` files cannot be spawned directly without a shell. + * For known package manager shims, we invoke them via `cmd.exe /d /s /c` instead. + * Returns [resolvedCommand, resolvedFlags] to use with spawn. + */ +function resolveCommand(command: string, flags: string[]): [string, string[]] { + if (process.platform !== 'win32') return [command, flags]; + if (command.includes('/') || command.includes('\\') || command.includes('.')) + return [command, flags]; + if (WINDOWS_CMD_SHIMS.has(command.toLowerCase())) { + return ['cmd.exe', ['/d', '/s', '/c', `${command}.cmd`, ...flags]]; + } + return [command, flags]; } export async function shell( @@ -35,7 +44,8 @@ export async function shell( let stdout = ''; let stderr = ''; try { - child = spawn(resolveCommand(command), flags, { + const [resolvedCommand, resolvedFlags] = resolveCommand(command, flags); + child = spawn(resolvedCommand, resolvedFlags, { cwd: opts.cwd, stdio: opts.stdio, timeout: opts.timeout, @@ -45,15 +55,16 @@ export async function shell( child.once('close', () => resolve()); }); [stdout, stderr] = await Promise.all([text(child.stdout), text(child.stderr), done]); - } catch { - throw { stdout, stderr, exitCode: 1 }; + } catch (e) { + const message = e instanceof Error ? e.message : stderr || 'Unknown error'; + throw new Error(message); } const { exitCode } = child; if (exitCode === null) { throw new Error('Timeout'); } if (exitCode !== 0) { - throw new Error(stderr); + throw new Error(stderr || `Process exited with code ${exitCode}`); } return { stdout, stderr, exitCode }; }