Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
docs: address review feedback on comments
Per @vicb's review:
- helpers.ts: rewrite quoteShellMeta docstring so it describes both the
  Windows and POSIX branches with equal weight, rather than treating
  POSIX as a footnote. The function works on both platforms.
- helpers.ts: simplify the inner POSIX comment to "POSIX shell quoting"
  per the suggestion.
- run-wrangler.ts: hoist the shell:false / DEP0190 explanation to the
  top-level comment block so it covers both branches, then describe
  the POSIX path and the Windows path in turn. Drop the now-redundant
  inner comment in the POSIX spawn call.
- run-wrangler.ts: restore the stdio explanatory comment that was lost
  when stdio was hoisted to a const in an earlier refactor.

No behavioral changes.
  • Loading branch information
kirkouimet committed May 6, 2026
commit 5a5a7682a3d2cc444c6c7efc2348cc6aa19b2e43
30 changes: 15 additions & 15 deletions packages/cloudflare/src/cli/commands/utils/helpers.ts
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -73,32 +73,32 @@ export async function getEnvFromPlatformProxy(options: GetPlatformProxyOptions,
}

/**
* Escape a single argument so it survives the `cmd.exe /d /s /c "<command line>"` invocation
* used by `runWrangler` on Windows (paired with `windowsVerbatimArguments: true`).
* Escape a single argument for shell-style invocation. Cross-platform.
*
* The algorithm matches `cross-spawn`'s `escapeArgument`:
* On Windows, `runWrangler` invokes `cmd.exe /d /s /c "<command line>"` with
* `windowsVerbatimArguments: true`. The algorithm matches `cross-spawn`'s `escapeArgument`:
* 1. Wrap the value in `"..."`, doubling backslash sequences that precede a quote per the
* Windows command-line argument rules.
* 2. Caret-escape every cmd.exe metacharacter, including the wrapping quotes themselves.
* The wrapping carets are needed for the OUTER `cmd /c "..."` parse: cmd.exe consumes them
* and the receiving program sees a normally-quoted argument. Crucially, `^` must NOT be
* applied inside an un-caret-escaped quoted region, because cmd.exe treats `^` as literal
* inside double quotes (this was the bug an earlier shell-quote-style implementation hit).
*
* The carets are needed for the OUTER `cmd /c "..."` parse (where the inner quotes would
* otherwise terminate the command-line string); cmd.exe consumes them and the receiving
* program sees a normally-quoted argument. Crucially, `^` must NOT be applied inside an
* un-caret-escaped quoted region: `cmd.exe` treats `^` as literal inside double quotes,
* which is the bug an earlier shell-quote-style implementation suffered from.
* On POSIX, the value is quoted using standard POSIX shell rules (single quotes by default,
* double quotes when the value contains a single quote, backslash-escapes for bare values).
* `runWrangler` itself does not invoke a shell on POSIX, but this branch is preserved so the
* function behaves usefully for any direct caller.
*
* Not used on POSIX, where `runWrangler` spawns the package manager directly with
* `shell: false` and each argv entry is passed verbatim.
*
* Reference: https://github.com/moxystudio/node-cross-spawn/blob/master/lib/util/escape.js
* https://qntm.org/cmd
* References: https://github.com/moxystudio/node-cross-spawn/blob/master/lib/util/escape.js
* https://qntm.org/cmd
*
* @param arg
* @returns escaped arg suitable for embedding in a `cmd /c "..."` command line
* @returns escaped arg
*/
export function quoteShellMeta(arg: string) {
if (process.platform !== "win32") {
// POSIX fallback (preserved for any external callers): standard shell quoting.
// POSIX shell quoting.
if (/["\s]/.test(arg) && !/'/.test(arg)) {
return `'${arg.replace(/(['\\])/g, "\\$1")}'`;
}
Expand Down
25 changes: 14 additions & 11 deletions packages/cloudflare/src/cli/commands/utils/run-wrangler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ export function runWrangler(
]),
];

// Always pipe stderr so that we can capture it for inspection.
// Keep stdin and stdout as "inherit" when not piping logs to maintain TTY detection
// (wrangler checks `process.stdin.isTTY && process.stdout.isTTY` for interactive mode).
const stdio: ("inherit" | "ignore" | "pipe")[] =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please re-add the former comment on this

shouldPipeLogs || noLogs ? ["ignore", "pipe", "pipe"] : ["inherit", "inherit", "pipe"];

Expand All @@ -124,16 +127,18 @@ export function runWrangler(
CLOUDFLARE_LOAD_DEV_VARS_FROM_DOT_ENV: "false",
};

// On Windows the package manager binary is typically a `.cmd` shim. Per Node.js docs
// https://nodejs.org/api/child_process.html#spawning-bat-and-cmd-files-on-windows the
// recommended way to invoke a `.cmd`/`.bat` is to spawn `cmd.exe` directly with `/c`,
// rather than relying on `shell: true` (which emits DEP0190 and would re-tokenize values).
// We pre-escape every token with `quoteShellMeta` and pass the joined command line as a
// single quoted string with `windowsVerbatimArguments: true` so Node does not re-escape it.
// This mirrors how `cross-spawn` handles non-`.exe` commands on Windows.
// We use `shell: false` everywhere to avoid DEP0190.
// See https://nodejs.org/api/deprecations.html#dep0190-passing-args-to-nodechild-process-execfilespawn-with-shell-option
//
// On POSIX we spawn the package manager directly with `shell: false`, which avoids DEP0190
// and lets each value be passed verbatim as its own argv entry.
// On POSIX we can spawn the package manager binary directly: each value is passed verbatim
// as its own argv entry.
//
// On Windows the package manager is typically a `.cmd` shim. Per Node.js docs
// (https://nodejs.org/api/child_process.html#spawning-bat-and-cmd-files-on-windows) the
// recommended way to invoke a `.cmd`/`.bat` without `shell: true` is to spawn `cmd.exe`
// directly with `/c`. We pre-escape every token with `quoteShellMeta` and pass the joined
// command line as a single quoted string with `windowsVerbatimArguments: true` so Node
// does not re-escape it. This mirrors how `cross-spawn` handles non-`.exe` commands.
const result = isWin
? spawnSync(
"cmd.exe",
Expand All @@ -151,8 +156,6 @@ export function runWrangler(
}
)
: spawnSync(options.packager, packagerArgs, {
// Setting `shell` to `false` to avoid DEP0190.
// See https://nodejs.org/api/deprecations.html#dep0190-passing-args-to-nodechild-process-execfilespawn-with-shell-option
shell: false,
stdio,
env,
Expand Down
Loading