Skip to content

fix(create-void): spawn package managers with a shell on Windows - #231

Merged
Brooooooklyn merged 1 commit into
mainfrom
06-25-fix_create-void_spawn_package_managers_with_a_shell_on_windows
Jun 29, 2026
Merged

Brooooooklyn merged 1 commit into
mainfrom
06-25-fix_create-void_spawn_package_managers_with_a_shell_on_windows

Conversation

@shulaoda

Copy link
Copy Markdown
Member

Summary

create-void is unusable on Windows: scaffolding a project always fails right after the project directory is created.

run() in packages/create-void/src/pm.ts spawns the package manager (npm install, npx void init, and the pnpm/yarn/bun equivalents) via node:child_process without the shell option. On Windows the npm/npx/pnpm/yarn/bunx entry points are .cmd/.ps1 shims that spawn() cannot resolve without a shell, so the child emits an error event with ENOENT. run() rejects, and because the directory + package.json are already written, the user is left with a half-created project and a stack trace.

Fix

Pass shell: process.platform === 'win32' to spawn:

const child = spawn(cmd, args, { stdio: 'inherit', cwd, shell: process.platform === 'win32' });

On Windows the command now runs through cmd.exe, which resolves the .cmd shim via PATHEXT. On macOS/Linux behavior is unchanged (shell stays off).

Why this is safe (no injection surface)

shell: true only becomes a shell-injection risk when untrusted input reaches the command string, which does not happen here. Every argv value is a hardcoded literal: pmInstall() returns [pm, 'install'] and pmExec() returns ['npx'] / ['pnpm','exec'] / etc., where pm is a fixed enum from detectPM(). The only user-controlled value, the target directory, is passed as the cwd spawn option, not interpolated into the command line.

A comment in the code records "argv must stay literal" as the invariant; if a dynamic argument is ever added, switch to cross-spawn (or escape) at that point.

Alternatives considered

cross-spawn (the approach create-vite uses) resolves .cmd shims and escapes arguments without a shell. Its real value is injection-safe escaping of dynamic arguments, which this scaffolder doesn't have, so adding a runtime dependency (plus its 5 transitive deps and bundler onlyBundle upkeep) to spawn a fixed command wasn't worth it here.

@Brooooooklyn
Brooooooklyn merged commit 29d48f9 into main Jun 29, 2026
6 checks passed
@Brooooooklyn
Brooooooklyn deleted the 06-25-fix_create-void_spawn_package_managers_with_a_shell_on_windows branch June 29, 2026 16:15
cpojer pushed a commit that referenced this pull request Sep 11, 2026
Route-aware ISR TTL resolution is fully landed and deployed: framework
manifest.pages/pathRevalidate producer (#272) + api ingest/prewarm and dispatch
route-aware resolution (#228). Deferred follow-ups tracked in #230.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants