Skip to content

Commit 79576d8

Browse files
committed
child_process: spawnSync option stdio stdout support stream
Refs: #52776
1 parent 052aec7 commit 79576d8

File tree

4 files changed

+66
-5
lines changed

4 files changed

+66
-5
lines changed

doc/api/packages.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1012,7 +1012,7 @@ added: v0.4.0
10121012
The `"main"` field defines the entry point of a package when imported by name
10131013
via a `node_modules` lookup. Its value is a path.
10141014

1015-
The [`"exports"`][] field, if it exists, takes precedence over the
1015+
The [`"exports"`][] field, if it exists, takes precedence over the
10161016
`"main"` field when importing the package by name.
10171017

10181018
It also defines the script that is used when the [package directory is loaded

src/spawn_sync.cc

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "node_errors.h"
2626
#include "node_external_reference.h"
2727
#include "node_internals.h"
28+
#include "stream_wrap.h"
2829
#include "string_bytes.h"
2930
#include "util-inl.h"
3031

@@ -1063,12 +1064,26 @@ Maybe<int> SyncProcessRunner::ParseStdioOption(int child_fd,
10631064
return Nothing<int>();
10641065
}
10651066
return Just(AddStdioInheritFD(child_fd, inherit_fd));
1066-
}
1067+
} else if (js_type->StrictEquals(env()->wrap_string())) {
1068+
Local<Value> val;
1069+
if (!js_stdio_option->Get(context, env()->handle_string()).ToLocal(&val)) {
1070+
return Nothing<int>();
1071+
}
1072+
if (!val->IsObject()) {
1073+
return Just<int>(UV_EINVAL);
1074+
}
10671075

1068-
Utf8Value stdio_type(env()->isolate(), js_type);
1069-
fprintf(stderr, "invalid child stdio type: %s\n", stdio_type.out());
1076+
Local<Object> handle = val.As<Object>();
1077+
Local<v8::FunctionTemplate> sw = env()->libuv_stream_wrap_ctor_template();
1078+
if (sw.IsEmpty() || !sw->HasInstance(handle)) {
1079+
return Just<int>(UV_EINVAL);
1080+
}
10701081

1071-
UNREACHABLE();
1082+
uv_stream_t* stream = LibuvStreamWrap::From(env(), handle)->stream();
1083+
return Just(AddStdioInheritStream(child_fd, stream));
1084+
}
1085+
1086+
return Just<int>(UV_EINVAL);
10721087
}
10731088

10741089
int SyncProcessRunner::AddStdioIgnore(uint32_t child_fd) {
@@ -1116,6 +1131,17 @@ int SyncProcessRunner::AddStdioInheritFD(uint32_t child_fd, int inherit_fd) {
11161131
return 0;
11171132
}
11181133

1134+
int SyncProcessRunner::AddStdioInheritStream(uint32_t child_fd,
1135+
uv_stream_t* stream) {
1136+
CHECK_LT(child_fd, stdio_count_);
1137+
CHECK(!stdio_pipes_[child_fd]);
1138+
1139+
uv_stdio_containers_[child_fd].flags = UV_INHERIT_STREAM;
1140+
uv_stdio_containers_[child_fd].data.stream = stream;
1141+
1142+
return 0;
1143+
}
1144+
11191145
Maybe<int> SyncProcessRunner::CopyJsString(Local<Value> js_value,
11201146
const char** target) {
11211147
Isolate* isolate = env()->isolate();

src/spawn_sync.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ class SyncProcessRunner {
185185
bool writable,
186186
uv_buf_t input_buffer);
187187
inline int AddStdioInheritFD(uint32_t child_fd, int inherit_fd);
188+
inline int AddStdioInheritStream(uint32_t child_fd, uv_stream_t* stream);
188189

189190
static bool IsSet(v8::Local<v8::Value> value);
190191
v8::Maybe<int> CopyJsString(v8::Local<v8::Value> js_value,

test/parallel/test-child-process-spawnsync.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,37 @@ assert.deepStrictEqual(ret_err.spawnargs, ['bar']);
6565
];
6666
assert.deepStrictEqual(retUTF8.output, stringifiedDefault);
6767
}
68+
69+
{
70+
// Verify support for stdio.wrap via using another child process stream.
71+
// This is the same logic as in ch_sy.js, expressed as a deterministic test.
72+
if (common.isWindows) {
73+
common.skip('Not applicable on Windows');
74+
}
75+
76+
const { spawn } = require('child_process');
77+
const childA = spawn(
78+
process.execPath,
79+
['-e', 'process.stdin.pipe(process.stdout);'],
80+
{ stdio: ['pipe', 'pipe', 'ignore'] }
81+
);
82+
83+
let collected = '';
84+
childA.stdout.setEncoding('utf8');
85+
childA.stdout.on('data', (chunk) => { collected += chunk; });
86+
87+
childA.on('exit', common.mustCall((code) => {
88+
assert.strictEqual(code, 0);
89+
assert.strictEqual(collected, 'hi');
90+
}));
91+
92+
const result = spawnSync(
93+
process.execPath,
94+
['-e', 'process.stdout.write("hi")'],
95+
{ stdio: ['inherit', childA.stdin, 'inherit'] }
96+
);
97+
98+
assert.strictEqual(result.status, 0);
99+
// Explicitly close the wrapped stream on the parent side so childA receives EOF.
100+
childA.stdin.end();
101+
}

0 commit comments

Comments
 (0)