diff --git a/cli/dts/lib.deno.unstable.d.ts b/cli/dts/lib.deno.unstable.d.ts index 6eb7153bad..1feb11e977 100644 --- a/cli/dts/lib.deno.unstable.d.ts +++ b/cli/dts/lib.deno.unstable.d.ts @@ -1542,6 +1542,10 @@ declare namespace Deno { stdout?: "piped" | "inherit" | "null"; /** Defaults to "piped". */ stderr?: "piped" | "inherit" | "null"; + + /** Skips quoting and escaping of the arguments on windows. This option + * is ignored on non-windows platforms. Defaults to "false". */ + windowsRawArguments?: boolean; } /** **UNSTABLE**: New API, yet to be vetted. diff --git a/cli/tests/unit/spawn_test.ts b/cli/tests/unit/spawn_test.ts index df95e333b3..149886a1ce 100644 --- a/cli/tests/unit/spawn_test.ts +++ b/cli/tests/unit/spawn_test.ts @@ -733,7 +733,7 @@ const child = await Deno.spawnChild(Deno.execPath(), { }); const readable = child.stdout.pipeThrough(new TextDecoderStream()); const reader = readable.getReader(); -// set up an interval that will end after reading a few messages from stdout, +// set up an interval that will end after reading a few messages from stdout, // to verify that stdio streams are properly unrefed let count = 0; let interval; @@ -787,3 +787,28 @@ setInterval(() => { }, Deno.errors.NotFound); }, ); + +Deno.test( + { ignore: Deno.build.os !== "windows" }, + async function spawnWindowsRawArguments() { + let { success, stdout } = await Deno.spawn("cmd", { + args: ["/d", "/s", "/c", '"deno ^"--version^""'], + windowsRawArguments: true, + }); + assert(success); + let stdoutText = new TextDecoder().decode(stdout); + assertStringIncludes(stdoutText, "deno"); + assertStringIncludes(stdoutText, "v8"); + assertStringIncludes(stdoutText, "typescript"); + + ({ success, stdout } = Deno.spawnSync("cmd", { + args: ["/d", "/s", "/c", '"deno ^"--version^""'], + windowsRawArguments: true, + })); + assert(success); + stdoutText = new TextDecoder().decode(stdout); + assertStringIncludes(stdoutText, "deno"); + assertStringIncludes(stdoutText, "v8"); + assertStringIncludes(stdoutText, "typescript"); + }, +); diff --git a/runtime/js/40_spawn.js b/runtime/js/40_spawn.js index a0283f0ffb..a9a968ba32 100644 --- a/runtime/js/40_spawn.js +++ b/runtime/js/40_spawn.js @@ -36,6 +36,7 @@ stdout = "piped", stderr = "piped", signal = undefined, + windowsRawArguments = false, } = {}) { const child = ops.op_spawn_child({ cmd: pathFromURL(command), @@ -48,6 +49,7 @@ stdin, stdout, stderr, + windowsRawArguments, }, apiName); return new Child(illegalConstructorKey, { ...child, @@ -243,6 +245,7 @@ stdin = "null", stdout = "piped", stderr = "piped", + windowsRawArguments = false, } = {}) { if (stdin === "piped") { throw new TypeError( @@ -260,6 +263,7 @@ stdin, stdout, stderr, + windowsRawArguments, }); return { success: result.status.success, diff --git a/runtime/ops/spawn.rs b/runtime/ops/spawn.rs index 9286e6d0c0..7fe77302ae 100644 --- a/runtime/ops/spawn.rs +++ b/runtime/ops/spawn.rs @@ -17,6 +17,8 @@ use serde::Deserialize; use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; +#[cfg(windows)] +use std::os::windows::process::CommandExt; use std::process::ExitStatus; use std::rc::Rc; @@ -55,6 +57,8 @@ pub struct SpawnArgs { gid: Option, #[cfg(unix)] uid: Option, + #[cfg(windows)] + windows_raw_arguments: bool, #[serde(flatten)] stdio: ChildStdio, @@ -131,6 +135,17 @@ fn create_command( .check(&args.cmd, Some(api_name))?; let mut command = std::process::Command::new(args.cmd); + + #[cfg(windows)] + if args.windows_raw_arguments { + for arg in args.args.iter() { + command.raw_arg(arg); + } + } else { + command.args(args.args); + } + + #[cfg(not(windows))] command.args(args.args); if let Some(cwd) = args.cwd {