[GH-ISSUE #1095] vinext deploy fails on Windows with spawnSync wrangler ENOENT #234

Closed
opened 2026-05-06 12:38:24 +02:00 by BreizhHardware · 0 comments

Originally created by @piffie on GitHub (May 6, 2026).
Original GitHub issue: https://github.com/cloudflare/vinext/issues/1095

Version observed: vinext@0.0.47
Existing issues searched: None found. Closed #23 was a different Windows issue (ESM URL scheme on vinext dev, fixed in #31).

Symptom

On Windows, vinext deploy (or vinext deploy --preview) builds the app successfully but fails when invoking wrangler deploy:

<ref *1> Error: spawnSync C:\dev\git\ledger\apps\ledger\node_modules\.bin\wrangler ENOENT
    at Object.spawnSync (node:internal/child_process:1120:20)
    at spawnSync (node:child_process:911:24)
    at execFileSync (node:child_process:954:15)
    at runWranglerDeploy (file:///.../vinext/dist/deploy.js:927:17)
    at deploy (file:///.../vinext/dist/deploy.js:1013:14)
    at async deployCommand (file:///.../vinext/dist/cli.js:347:2) {
  errno: -4058,
  code: 'ENOENT',
  syscall: 'spawnSync C:\...\node_modules\.bin\wrangler',
  path: 'C:\...\node_modules\.bin\wrangler',
  spawnargs: [ 'deploy', '--env', 'preview' ],
}

The wrangler binary IS in node_modules/.bin/. The ENOENT is misleading — it means "no executable matched", not "file not found".

Root cause

In dist/deploy.js:

// Line 918:
const wranglerBin = findInNodeModules(root, ".bin/wrangler") ??
                    path.join(root, "node_modules", ".bin", "wrangler");

// Line 927:
const output = execFileSync(wranglerBin, args, execOpts);

On Windows, node_modules/.bin/ contains three files for each binary:

  • wrangler — Unix shebang shell script (cannot be executed by Windows directly)
  • wrangler.CMD — the actual Windows-executable shim
  • wrangler.ps1 — PowerShell variant

Node's execFileSync / spawnSync without shell: true uses CreateProcess() on Windows, which only resolves .exe, .com, .bat, .cmd extensions via PATHEXT. A bare-name file with no recognised extension fails with ENOENT regardless of whether it exists. The wrangler (no extension) file is a Unix shebang script — Windows cannot execute it.

This is the well-known npm / cross-platform child_process gotcha. Every PM (npm, yarn, pnpm) and most CLI tools (Vite, Next.js, etc.) handle this either by appending .CMD on Windows or by using cross-spawn.

Reproducer

  1. On Windows, scaffold any Next.js App Router project, run vinext init, then vinext deploy --preview.
  2. Build phase succeeds.
  3. runWranglerDeploy throws ENOENT before invoking wrangler.

Workaround

Run the deploy step manually after vinext build:

pnpm run build:vinext
npx wrangler deploy

Suggested fix

Option 1 (smallest): append .cmd on Windows when constructing the bin path

const isWin = process.platform === "win32";
const binName = isWin ? ".bin/wrangler.cmd" : ".bin/wrangler";
const wranglerBin = findInNodeModules(root, binName) ??
                    path.join(root, "node_modules", binName);

Apply the same logic to any other execFileSync call that targets a node_modules/.bin/ shim. The pm install calls on lines 836 and 956 may have the same issue, since pm resolves to pnpm / npm / yarn / bun — all of which are typically global on Windows so are usually found via PATH and don't hit this bug, but worth auditing.

Option 2 (more robust): use cross-spawn

import { spawnSync } from "cross-spawn";

cross-spawn is a drop-in replacement that handles Windows shim resolution transparently.

Option 3 (least preferred): { shell: true }

Works but introduces shell-injection surface — args need quoting. Not recommended.

Test coverage suggestion

Closed #23 added a Windows runner to CI for vinext dev. Extending that runner to also exercise vinext deploy --dry-run (or the underlying runWranglerDeploy with a stub wrangler) would catch this regression.

Environment

  • OS: Windows 11 Home 10.0.26220
  • Node: v24.13.0
  • Package manager: pnpm 10.0.0 (workspace)
  • Shell: PowerShell 7+ (also reproduces in Git Bash since the issue is at the Node child_process layer, not the shell)
  • Project: Next.js 15 App Router, Tailwind v4, Turborepo workspace

I'm happy to open a PR for Option 1 — would value a steer if maintainers prefer Option 2 (cross-spawn) before I write the patch.

Originally created by @piffie on GitHub (May 6, 2026). Original GitHub issue: https://github.com/cloudflare/vinext/issues/1095 **Version observed:** `vinext@0.0.47` **Existing issues searched:** None found. Closed #23 was a different Windows issue (ESM URL scheme on `vinext dev`, fixed in #31). ## Symptom On Windows, `vinext deploy` (or `vinext deploy --preview`) builds the app successfully but fails when invoking `wrangler deploy`: ``` <ref *1> Error: spawnSync C:\dev\git\ledger\apps\ledger\node_modules\.bin\wrangler ENOENT at Object.spawnSync (node:internal/child_process:1120:20) at spawnSync (node:child_process:911:24) at execFileSync (node:child_process:954:15) at runWranglerDeploy (file:///.../vinext/dist/deploy.js:927:17) at deploy (file:///.../vinext/dist/deploy.js:1013:14) at async deployCommand (file:///.../vinext/dist/cli.js:347:2) { errno: -4058, code: 'ENOENT', syscall: 'spawnSync C:\...\node_modules\.bin\wrangler', path: 'C:\...\node_modules\.bin\wrangler', spawnargs: [ 'deploy', '--env', 'preview' ], } ``` The `wrangler` binary IS in `node_modules/.bin/`. The ENOENT is misleading — it means "no executable matched", not "file not found". ## Root cause In `dist/deploy.js`: ```js // Line 918: const wranglerBin = findInNodeModules(root, ".bin/wrangler") ?? path.join(root, "node_modules", ".bin", "wrangler"); // Line 927: const output = execFileSync(wranglerBin, args, execOpts); ``` On Windows, `node_modules/.bin/` contains three files for each binary: - `wrangler` — Unix shebang shell script (cannot be executed by Windows directly) - `wrangler.CMD` — the actual Windows-executable shim - `wrangler.ps1` — PowerShell variant Node's `execFileSync` / `spawnSync` without `shell: true` uses `CreateProcess()` on Windows, which only resolves `.exe`, `.com`, `.bat`, `.cmd` extensions via PATHEXT. A bare-name file with no recognised extension fails with ENOENT regardless of whether it exists. The `wrangler` (no extension) file is a Unix shebang script — Windows cannot execute it. This is the well-known npm / cross-platform `child_process` gotcha. Every PM (`npm`, `yarn`, `pnpm`) and most CLI tools (Vite, Next.js, etc.) handle this either by appending `.CMD` on Windows or by using `cross-spawn`. ## Reproducer 1. On Windows, scaffold any Next.js App Router project, run `vinext init`, then `vinext deploy --preview`. 2. Build phase succeeds. 3. `runWranglerDeploy` throws ENOENT before invoking wrangler. ## Workaround Run the deploy step manually after `vinext build`: ```powershell pnpm run build:vinext npx wrangler deploy ``` ## Suggested fix ### Option 1 (smallest): append `.cmd` on Windows when constructing the bin path ```js const isWin = process.platform === "win32"; const binName = isWin ? ".bin/wrangler.cmd" : ".bin/wrangler"; const wranglerBin = findInNodeModules(root, binName) ?? path.join(root, "node_modules", binName); ``` Apply the same logic to any other `execFileSync` call that targets a `node_modules/.bin/` shim. The `pm` install calls on lines 836 and 956 may have the same issue, since `pm` resolves to `pnpm` / `npm` / `yarn` / `bun` — all of which are typically global on Windows so are usually found via PATH and don't hit this bug, but worth auditing. ### Option 2 (more robust): use `cross-spawn` ```js import { spawnSync } from "cross-spawn"; ``` `cross-spawn` is a drop-in replacement that handles Windows shim resolution transparently. ### Option 3 (least preferred): `{ shell: true }` Works but introduces shell-injection surface — args need quoting. Not recommended. ## Test coverage suggestion Closed #23 added a Windows runner to CI for `vinext dev`. Extending that runner to also exercise `vinext deploy --dry-run` (or the underlying `runWranglerDeploy` with a stub wrangler) would catch this regression. ## Environment - OS: Windows 11 Home 10.0.26220 - Node: v24.13.0 - Package manager: pnpm 10.0.0 (workspace) - Shell: PowerShell 7+ (also reproduces in Git Bash since the issue is at the Node `child_process` layer, not the shell) - Project: Next.js 15 App Router, Tailwind v4, Turborepo workspace I'm happy to open a PR for Option 1 — would value a steer if maintainers prefer Option 2 (cross-spawn) before I write the patch.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#234
No description provided.