mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 20:29:11 +00:00
fix(ext/node): support input option in spawnSync (#28792)
This commit is contained in:
parent
9bc9faff49
commit
cb00561e97
4 changed files with 99 additions and 3 deletions
|
@ -59,6 +59,7 @@ import { Socket } from "node:net";
|
||||||
import {
|
import {
|
||||||
kDetached,
|
kDetached,
|
||||||
kExtraStdio,
|
kExtraStdio,
|
||||||
|
kInputOption,
|
||||||
kIpc,
|
kIpc,
|
||||||
kNeedsNpmProcessState,
|
kNeedsNpmProcessState,
|
||||||
} from "ext:deno_process/40_process.js";
|
} from "ext:deno_process/40_process.js";
|
||||||
|
@ -985,6 +986,27 @@ function parseSpawnSyncOutputStreams(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeInput(input: unknown) {
|
||||||
|
if (input == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (typeof input === "string") {
|
||||||
|
return Buffer.from(input);
|
||||||
|
}
|
||||||
|
if (input instanceof Uint8Array) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
if (input instanceof DataView) {
|
||||||
|
return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
||||||
|
}
|
||||||
|
throw new ERR_INVALID_ARG_TYPE("input", [
|
||||||
|
"string",
|
||||||
|
"Buffer",
|
||||||
|
"TypedArray",
|
||||||
|
"DataView",
|
||||||
|
], input);
|
||||||
|
}
|
||||||
|
|
||||||
export function spawnSync(
|
export function spawnSync(
|
||||||
command: string,
|
command: string,
|
||||||
args: string[],
|
args: string[],
|
||||||
|
@ -992,6 +1014,7 @@ export function spawnSync(
|
||||||
): SpawnSyncResult {
|
): SpawnSyncResult {
|
||||||
const {
|
const {
|
||||||
env = Deno.env.toObject(),
|
env = Deno.env.toObject(),
|
||||||
|
input,
|
||||||
stdio = ["pipe", "pipe", "pipe"],
|
stdio = ["pipe", "pipe", "pipe"],
|
||||||
shell = false,
|
shell = false,
|
||||||
cwd,
|
cwd,
|
||||||
|
@ -1008,6 +1031,7 @@ export function spawnSync(
|
||||||
_channel, // TODO(kt3k): handle this correctly
|
_channel, // TODO(kt3k): handle this correctly
|
||||||
] = normalizeStdioOption(stdio);
|
] = normalizeStdioOption(stdio);
|
||||||
[command, args] = buildCommand(command, args ?? [], shell);
|
[command, args] = buildCommand(command, args ?? [], shell);
|
||||||
|
const input_ = normalizeInput(input);
|
||||||
|
|
||||||
const result: SpawnSyncResult = {};
|
const result: SpawnSyncResult = {};
|
||||||
try {
|
try {
|
||||||
|
@ -1021,6 +1045,7 @@ export function spawnSync(
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
windowsRawArguments: windowsVerbatimArguments,
|
windowsRawArguments: windowsVerbatimArguments,
|
||||||
|
[kInputOption]: input_,
|
||||||
}).outputSync();
|
}).outputSync();
|
||||||
|
|
||||||
const status = output.signal ? null : output.code;
|
const status = output.signal ? null : output.code;
|
||||||
|
|
|
@ -41,6 +41,9 @@ import {
|
||||||
writableStreamForRid,
|
writableStreamForRid,
|
||||||
} from "ext:deno_web/06_streams.js";
|
} from "ext:deno_web/06_streams.js";
|
||||||
|
|
||||||
|
// The key for private `input` option for `Deno.Command`
|
||||||
|
const kInputOption = Symbol("kInputOption");
|
||||||
|
|
||||||
function opKill(pid, signo, apiName) {
|
function opKill(pid, signo, apiName) {
|
||||||
op_kill(pid, signo, apiName);
|
op_kill(pid, signo, apiName);
|
||||||
}
|
}
|
||||||
|
@ -404,6 +407,7 @@ function spawnSync(command, {
|
||||||
stdout = "piped",
|
stdout = "piped",
|
||||||
stderr = "piped",
|
stderr = "piped",
|
||||||
windowsRawArguments = false,
|
windowsRawArguments = false,
|
||||||
|
[kInputOption]: input,
|
||||||
} = { __proto__: null }) {
|
} = { __proto__: null }) {
|
||||||
if (stdin === "piped") {
|
if (stdin === "piped") {
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
|
@ -425,6 +429,7 @@ function spawnSync(command, {
|
||||||
extraStdio: [],
|
extraStdio: [],
|
||||||
detached: false,
|
detached: false,
|
||||||
needsNpmProcessState: false,
|
needsNpmProcessState: false,
|
||||||
|
input,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
success: result.status.success,
|
success: result.status.success,
|
||||||
|
@ -484,4 +489,4 @@ class Command {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ChildProcess, Command, kill, Process, run };
|
export { ChildProcess, Command, kill, kInputOption, Process, run };
|
||||||
|
|
|
@ -20,6 +20,7 @@ use deno_core::op2;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::AsyncMutFuture;
|
use deno_core::AsyncMutFuture;
|
||||||
use deno_core::AsyncRefCell;
|
use deno_core::AsyncRefCell;
|
||||||
|
use deno_core::JsBuffer;
|
||||||
use deno_core::OpState;
|
use deno_core::OpState;
|
||||||
use deno_core::RcRef;
|
use deno_core::RcRef;
|
||||||
use deno_core::Resource;
|
use deno_core::Resource;
|
||||||
|
@ -193,6 +194,8 @@ pub struct SpawnArgs {
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
stdio: ChildStdio,
|
stdio: ChildStdio,
|
||||||
|
|
||||||
|
input: Option<JsBuffer>,
|
||||||
|
|
||||||
extra_stdio: Vec<Stdio>,
|
extra_stdio: Vec<Stdio>,
|
||||||
detached: bool,
|
detached: bool,
|
||||||
needs_npm_process_state: bool,
|
needs_npm_process_state: bool,
|
||||||
|
@ -437,6 +440,8 @@ fn create_command(
|
||||||
|
|
||||||
if args.stdio.stdin.is_ipc() {
|
if args.stdio.stdin.is_ipc() {
|
||||||
args.ipc = Some(0);
|
args.ipc = Some(0);
|
||||||
|
} else if args.input.is_some() {
|
||||||
|
command.stdin(std::process::Stdio::piped());
|
||||||
} else {
|
} else {
|
||||||
command.stdin(args.stdio.stdin.as_stdio(state)?);
|
command.stdin(args.stdio.stdin.as_stdio(state)?);
|
||||||
}
|
}
|
||||||
|
@ -936,13 +941,31 @@ fn op_spawn_sync(
|
||||||
) -> Result<SpawnOutput, ProcessError> {
|
) -> Result<SpawnOutput, ProcessError> {
|
||||||
let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped));
|
let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped));
|
||||||
let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped));
|
let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped));
|
||||||
|
let input = args.input.clone();
|
||||||
let (mut command, _, _, _) =
|
let (mut command, _, _, _) =
|
||||||
create_command(state, args, "Deno.Command().outputSync()")?;
|
create_command(state, args, "Deno.Command().outputSync()")?;
|
||||||
let output = command.output().map_err(|e| ProcessError::SpawnFailed {
|
|
||||||
|
let mut child = command.spawn().map_err(|e| ProcessError::SpawnFailed {
|
||||||
|
command: command.get_program().to_string_lossy().to_string(),
|
||||||
|
error: Box::new(e.into()),
|
||||||
|
})?;
|
||||||
|
if let Some(input) = input {
|
||||||
|
let mut stdin = child.stdin.take().ok_or_else(|| {
|
||||||
|
ProcessError::Io(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::Other,
|
||||||
|
"stdin is not available",
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
stdin.write_all(&input)?;
|
||||||
|
stdin.flush()?;
|
||||||
|
}
|
||||||
|
let output =
|
||||||
|
child
|
||||||
|
.wait_with_output()
|
||||||
|
.map_err(|e| ProcessError::SpawnFailed {
|
||||||
command: command.get_program().to_string_lossy().to_string(),
|
command: command.get_program().to_string_lossy().to_string(),
|
||||||
error: Box::new(e.into()),
|
error: Box::new(e.into()),
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(SpawnOutput {
|
Ok(SpawnOutput {
|
||||||
status: output.status.try_into()?,
|
status: output.status.try_into()?,
|
||||||
stdout: if stdout {
|
stdout: if stdout {
|
||||||
|
|
|
@ -1128,3 +1128,46 @@ Deno.test(async function noWarningsFlag() {
|
||||||
|
|
||||||
await timeout.promise;
|
await timeout.promise;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test({
|
||||||
|
name: "[node/child_process] spawnSync supports input option",
|
||||||
|
fn() {
|
||||||
|
const text = " console.log('hello')";
|
||||||
|
const expected = `console.log("hello");\n`;
|
||||||
|
{
|
||||||
|
const { stdout } = spawnSync(Deno.execPath(), ["fmt", "-"], {
|
||||||
|
input: text,
|
||||||
|
});
|
||||||
|
assertEquals(stdout.toString(), expected);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const { stdout } = spawnSync(Deno.execPath(), ["fmt", "-"], {
|
||||||
|
input: Buffer.from(text),
|
||||||
|
});
|
||||||
|
assertEquals(stdout.toString(), expected);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const { stdout } = spawnSync(Deno.execPath(), ["fmt", "-"], {
|
||||||
|
input: new TextEncoder().encode(text),
|
||||||
|
});
|
||||||
|
assertEquals(stdout.toString(), expected);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const { stdout } = spawnSync(Deno.execPath(), ["fmt", "-"], {
|
||||||
|
input: new DataView(Buffer.from(text).buffer),
|
||||||
|
});
|
||||||
|
assertEquals(stdout.toString(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThrows(
|
||||||
|
() => {
|
||||||
|
spawnSync(Deno.execPath(), ["fmt", "-"], {
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
input: {} as any,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
'The "input" argument must be of type string or an instance of Buffer, TypedArray, or DataView. Received an instance of Object',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue