mirror of
https://github.com/denoland/deno.git
synced 2025-09-29 13:44:47 +00:00
fix(node): support passing parent stdio streams (#19171)
This is a bit bare bones but gets `npm-run-all` working. For full stdio compatibility with node more work is needed which is probably better done in follow up PRs. Fixes #19159
This commit is contained in:
parent
9dc3ae8523
commit
695b5de6cb
3 changed files with 77 additions and 10 deletions
|
@ -577,3 +577,25 @@ Deno.test(
|
||||||
assertStringIncludes(output, "typescript");
|
assertStringIncludes(output, "typescript");
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Deno.test(
|
||||||
|
"[node/child_process spawn] supports stdio array option",
|
||||||
|
async () => {
|
||||||
|
const cmdFinished = deferred();
|
||||||
|
let output = "";
|
||||||
|
const script = path.join(
|
||||||
|
path.dirname(path.fromFileUrl(import.meta.url)),
|
||||||
|
"testdata",
|
||||||
|
"child_process_stdio.js",
|
||||||
|
);
|
||||||
|
const cp = spawn(Deno.execPath(), ["run", "-A", script]);
|
||||||
|
cp.stdout?.on("data", (data) => {
|
||||||
|
output += data;
|
||||||
|
});
|
||||||
|
cp.on("close", () => cmdFinished.resolve());
|
||||||
|
await cmdFinished;
|
||||||
|
|
||||||
|
assertStringIncludes(output, "foo");
|
||||||
|
assertStringIncludes(output, "close");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
15
cli/tests/unit_node/testdata/child_process_stdio.js
vendored
Normal file
15
cli/tests/unit_node/testdata/child_process_stdio.js
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import childProcess from "node:child_process";
|
||||||
|
import process from "node:process";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
const script = path.join(
|
||||||
|
path.dirname(path.fromFileUrl(import.meta.url)),
|
||||||
|
"node_modules",
|
||||||
|
"foo",
|
||||||
|
"index.js",
|
||||||
|
);
|
||||||
|
|
||||||
|
const child = childProcess.spawn(process.execPath, [script], {
|
||||||
|
stdio: [process.stdin, process.stdout, process.stderr],
|
||||||
|
});
|
||||||
|
child.on("close", () => console.log("close"));
|
|
@ -177,9 +177,9 @@ export class ChildProcess extends EventEmitter {
|
||||||
args: cmdArgs,
|
args: cmdArgs,
|
||||||
cwd,
|
cwd,
|
||||||
env: stringEnv,
|
env: stringEnv,
|
||||||
stdin: toDenoStdio(stdin as NodeStdio | number),
|
stdin: toDenoStdio(stdin),
|
||||||
stdout: toDenoStdio(stdout as NodeStdio | number),
|
stdout: toDenoStdio(stdout),
|
||||||
stderr: toDenoStdio(stderr as NodeStdio | number),
|
stderr: toDenoStdio(stderr),
|
||||||
windowsRawArguments: windowsVerbatimArguments,
|
windowsRawArguments: windowsVerbatimArguments,
|
||||||
}).spawn();
|
}).spawn();
|
||||||
this.pid = this.#process.pid;
|
this.pid = this.#process.pid;
|
||||||
|
@ -189,6 +189,16 @@ export class ChildProcess extends EventEmitter {
|
||||||
this.stdin = Writable.fromWeb(this.#process.stdin);
|
this.stdin = Writable.fromWeb(this.#process.stdin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (stdin instanceof Stream) {
|
||||||
|
this.stdin = stdin;
|
||||||
|
}
|
||||||
|
if (stdout instanceof Stream) {
|
||||||
|
this.stdout = stdout;
|
||||||
|
}
|
||||||
|
if (stderr instanceof Stream) {
|
||||||
|
this.stderr = stderr;
|
||||||
|
}
|
||||||
|
|
||||||
if (stdout === "pipe") {
|
if (stdout === "pipe") {
|
||||||
assert(this.#process.stdout);
|
assert(this.#process.stdout);
|
||||||
this.stdout = Readable.fromWeb(this.#process.stdout);
|
this.stdout = Readable.fromWeb(this.#process.stdout);
|
||||||
|
@ -285,15 +295,22 @@ export class ChildProcess extends EventEmitter {
|
||||||
|
|
||||||
async #_waitForChildStreamsToClose() {
|
async #_waitForChildStreamsToClose() {
|
||||||
const promises = [] as Array<Promise<void>>;
|
const promises = [] as Array<Promise<void>>;
|
||||||
if (this.stdin && !this.stdin.destroyed) {
|
// Don't close parent process stdin if that's passed through
|
||||||
|
if (this.stdin && !this.stdin.destroyed && this.stdin !== process.stdin) {
|
||||||
assert(this.stdin);
|
assert(this.stdin);
|
||||||
this.stdin.destroy();
|
this.stdin.destroy();
|
||||||
promises.push(waitForStreamToClose(this.stdin));
|
promises.push(waitForStreamToClose(this.stdin));
|
||||||
}
|
}
|
||||||
if (this.stdout && !this.stdout.destroyed) {
|
// Only readable streams need to be closed
|
||||||
|
if (
|
||||||
|
this.stdout && !this.stdout.destroyed && this.stdout instanceof Readable
|
||||||
|
) {
|
||||||
promises.push(waitForReadableToClose(this.stdout));
|
promises.push(waitForReadableToClose(this.stdout));
|
||||||
}
|
}
|
||||||
if (this.stderr && !this.stderr.destroyed) {
|
// Only readable streams need to be closed
|
||||||
|
if (
|
||||||
|
this.stderr && !this.stderr.destroyed && this.stderr instanceof Readable
|
||||||
|
) {
|
||||||
promises.push(waitForReadableToClose(this.stderr));
|
promises.push(waitForReadableToClose(this.stderr));
|
||||||
}
|
}
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
@ -317,9 +334,13 @@ const supportedNodeStdioTypes: NodeStdio[] = ["pipe", "ignore", "inherit"];
|
||||||
function toDenoStdio(
|
function toDenoStdio(
|
||||||
pipe: NodeStdio | number | Stream | null | undefined,
|
pipe: NodeStdio | number | Stream | null | undefined,
|
||||||
): DenoStdio {
|
): DenoStdio {
|
||||||
|
if (pipe instanceof Stream) {
|
||||||
|
return "inherit";
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!supportedNodeStdioTypes.includes(pipe as NodeStdio) ||
|
!supportedNodeStdioTypes.includes(pipe as NodeStdio) ||
|
||||||
typeof pipe === "number" || pipe instanceof Stream
|
typeof pipe === "number"
|
||||||
) {
|
) {
|
||||||
notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`);
|
notImplemented(`toDenoStdio pipe=${typeof pipe} (${pipe})`);
|
||||||
}
|
}
|
||||||
|
@ -441,8 +462,17 @@ function normalizeStdioOption(
|
||||||
"pipe",
|
"pipe",
|
||||||
"pipe",
|
"pipe",
|
||||||
],
|
],
|
||||||
) {
|
): [
|
||||||
|
Stream | NodeStdio | number,
|
||||||
|
Stream | NodeStdio | number,
|
||||||
|
Stream | NodeStdio | number,
|
||||||
|
...Array<Stream | NodeStdio | number>,
|
||||||
|
] {
|
||||||
if (Array.isArray(stdio)) {
|
if (Array.isArray(stdio)) {
|
||||||
|
// At least 3 stdio must be created to match node
|
||||||
|
while (stdio.length < 3) {
|
||||||
|
ArrayPrototypePush(stdio, undefined);
|
||||||
|
}
|
||||||
return stdio;
|
return stdio;
|
||||||
} else {
|
} else {
|
||||||
switch (stdio) {
|
switch (stdio) {
|
||||||
|
@ -796,8 +826,8 @@ export function spawnSync(
|
||||||
args,
|
args,
|
||||||
cwd,
|
cwd,
|
||||||
env,
|
env,
|
||||||
stdout: toDenoStdio(normalizedStdio[1] as NodeStdio | number),
|
stdout: toDenoStdio(normalizedStdio[1]),
|
||||||
stderr: toDenoStdio(normalizedStdio[2] as NodeStdio | number),
|
stderr: toDenoStdio(normalizedStdio[2]),
|
||||||
uid,
|
uid,
|
||||||
gid,
|
gid,
|
||||||
windowsRawArguments: windowsVerbatimArguments,
|
windowsRawArguments: windowsVerbatimArguments,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue