Skip to content

Commit 3b7a9a2

Browse files
committed
child_process: add windowsHide option
This commit exposes the UV_PROCESS_WINDOWS_HIDE flag in Node as a windowsHide option to the child_process methods. The option is only applicable to Windows, and is ignored elsewhere. Fixes: #15217 PR-URL: #15380 Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent e2015b5 commit 3b7a9a2

8 files changed

Lines changed: 122 additions & 0 deletions

‎doc/api/child_process.md‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ exec('"my script.cmd" a b', (err, stdout, stderr) => {
127127
### child_process.exec(command[, options][, callback])
128128
<!-- YAML
129129
added: v0.1.90
130+
changes:
131+
- version: REPLACEME
132+
pr-url: https://github.com/nodejs/node/pull/15380
133+
description: The `windowsHide` option is supported now.
130134
-->
131135

132136
* `command` {string} The command to run, with space-separated arguments.
@@ -145,6 +149,8 @@ added: v0.1.90
145149
* `killSignal` {string|integer} **Default:** `'SIGTERM'`
146150
* `uid` {number} Sets the user identity of the process (see setuid(2)).
147151
* `gid` {number} Sets the group identity of the process (see setgid(2)).
152+
* `windowsHide` {boolean} Hide the subprocess console window that would
153+
normally be created on Windows systems. **Default:** `false`.
148154
* `callback` {Function} called with the output when process terminates.
149155
* `error` {Error}
150156
* `stdout` {string|Buffer}
@@ -238,6 +244,10 @@ lsExample();
238244
### child_process.execFile(file[, args][, options][, callback])
239245
<!-- YAML
240246
added: v0.1.91
247+
changes:
248+
- version: REPLACEME
249+
pr-url: https://github.com/nodejs/node/pull/15380
250+
description: The `windowsHide` option is supported now.
241251
-->
242252

243253
* `file` {string} The name or path of the executable file to run.
@@ -253,6 +263,8 @@ added: v0.1.91
253263
* `killSignal` {string|integer} **Default:** `'SIGTERM'`
254264
* `uid` {number} Sets the user identity of the process (see setuid(2)).
255265
* `gid` {number} Sets the group identity of the process (see setgid(2)).
266+
* `windowsHide` {boolean} Hide the subprocess console window that would
267+
normally be created on Windows systems. **Default:** `false`.
256268
* `callback` {Function} Called with the output when process terminates.
257269
* `error` {Error}
258270
* `stdout` {string|Buffer}
@@ -364,6 +376,9 @@ supported by `child_process.fork()` and will be ignored if set.
364376
<!-- YAML
365377
added: v0.1.90
366378
changes:
379+
- version: REPLACEME
380+
pr-url: https://github.com/nodejs/node/pull/15380
381+
description: The `windowsHide` option is supported now.
367382
- version: v6.4.0
368383
pr-url: https://github.com/nodejs/node/pull/7696
369384
description: The `argv0` option is supported now.
@@ -390,6 +405,8 @@ changes:
390405
`'/bin/sh'` on UNIX, and `process.env.ComSpec` on Windows. A different
391406
shell can be specified as a string. See [Shell Requirements][] and
392407
[Default Windows Shell][]. **Default:** `false` (no shell).
408+
* `windowsHide` {boolean} Hide the subprocess console window that would
409+
normally be created on Windows systems. **Default:** `false`.
393410
* Returns: {ChildProcess}
394411

395412
The `child_process.spawn()` method spawns a new process using the given
@@ -649,6 +666,9 @@ configuration at startup.
649666
<!-- YAML
650667
added: v0.11.12
651668
changes:
669+
- version: REPLACEME
670+
pr-url: https://github.com/nodejs/node/pull/15380
671+
description: The `windowsHide` option is supported now.
652672
- version: v8.0.0
653673
pr-url: https://github.com/nodejs/node/pull/10653
654674
description: The `input` option can now be a `Uint8Array`.
@@ -678,6 +698,8 @@ changes:
678698
stderr. **Default:** `200*1024` If exceeded, the child process is terminated.
679699
See caveat at [`maxBuffer` and Unicode][].
680700
* `encoding` {string} The encoding used for all stdio inputs and outputs. **Default:** `'buffer'`
701+
* `windowsHide` {boolean} Hide the subprocess console window that would
702+
normally be created on Windows systems. **Default:** `false`.
681703
* Returns: {Buffer|string} The stdout from the command.
682704

683705
The `child_process.execFileSync()` method is generally identical to
@@ -698,6 +720,9 @@ throw an [`Error`][] that will include the full result of the underlying
698720
<!-- YAML
699721
added: v0.11.12
700722
changes:
723+
- version: REPLACEME
724+
pr-url: https://github.com/nodejs/node/pull/15380
725+
description: The `windowsHide` option is supported now.
701726
- version: v8.0.0
702727
pr-url: https://github.com/nodejs/node/pull/10653
703728
description: The `input` option can now be a `Uint8Array`.
@@ -727,6 +752,8 @@ changes:
727752
See caveat at [`maxBuffer` and Unicode][].
728753
* `encoding` {string} The encoding used for all stdio inputs and outputs.
729754
**Default:** `'buffer'`
755+
* `windowsHide` {boolean} Hide the subprocess console window that would
756+
normally be created on Windows systems. **Default:** `false`.
730757
* Returns: {Buffer|string} The stdout from the command.
731758

732759
The `child_process.execSync()` method is generally identical to
@@ -749,6 +776,9 @@ execution.
749776
<!-- YAML
750777
added: v0.11.12
751778
changes:
779+
- version: REPLACEME
780+
pr-url: https://github.com/nodejs/node/pull/15380
781+
description: The `windowsHide` option is supported now.
752782
- version: v8.0.0
753783
pr-url: https://github.com/nodejs/node/pull/10653
754784
description: The `input` option can now be a `Uint8Array`.
@@ -784,6 +814,8 @@ changes:
784814
`'/bin/sh'` on UNIX, and `process.env.ComSpec` on Windows. A different
785815
shell can be specified as a string. See [Shell Requirements][] and
786816
[Default Windows Shell][]. **Default:** `false` (no shell).
817+
* `windowsHide` {boolean} Hide the subprocess console window that would
818+
normally be created on Windows systems. **Default:** `false`.
787819
* Returns: {Object}
788820
* `pid` {number} Pid of the child process.
789821
* `output` {Array} Array of results from stdio output.

‎lib/child_process.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ exports.execFile = function(file /*, args, options, callback*/) {
210210
gid: options.gid,
211211
uid: options.uid,
212212
shell: options.shell,
213+
windowsHide: !!options.windowsHide,
213214
windowsVerbatimArguments: !!options.windowsVerbatimArguments
214215
});
215216

@@ -428,6 +429,12 @@ function normalizeSpawnArguments(file, args, options) {
428429
throw new TypeError('"argv0" must be a string');
429430
}
430431

432+
// Validate windowsHide, if present.
433+
if (options.windowsHide != null &&
434+
typeof options.windowsHide !== 'boolean') {
435+
throw new TypeError('"windowsHide" must be a boolean');
436+
}
437+
431438
// Validate windowsVerbatimArguments, if present.
432439
if (options.windowsVerbatimArguments != null &&
433440
typeof options.windowsVerbatimArguments !== 'boolean') {
@@ -493,6 +500,7 @@ var spawn = exports.spawn = function(/*file, args, options*/) {
493500
file: opts.file,
494501
args: opts.args,
495502
cwd: options.cwd,
503+
windowsHide: !!options.windowsHide,
496504
windowsVerbatimArguments: !!options.windowsVerbatimArguments,
497505
detached: !!options.detached,
498506
envPairs: opts.envPairs,

‎src/env.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ class ModuleWrap;
286286
V(verify_error_string, "verifyError") \
287287
V(version_string, "version") \
288288
V(weight_string, "weight") \
289+
V(windows_hide_string, "windowsHide") \
289290
V(windows_verbatim_arguments_string, "windowsVerbatimArguments") \
290291
V(wrap_string, "wrap") \
291292
V(writable_string, "writable") \

‎src/process_wrap.cc‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,13 @@ class ProcessWrap : public HandleWrap {
211211
// options.stdio
212212
ParseStdioOptions(env, js_options, &options);
213213

214+
// options.windowsHide
215+
Local<String> windows_hide_key = env->windows_hide_string();
216+
217+
if (js_options->Get(windows_hide_key)->IsTrue()) {
218+
options.flags |= UV_PROCESS_WINDOWS_HIDE;
219+
}
220+
214221
// options.windows_verbatim_arguments
215222
Local<String> windows_verbatim_arguments_key =
216223
env->windows_verbatim_arguments_string();

‎src/spawn_sync.cc‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,11 @@ int SyncProcessRunner::ParseOptions(Local<Value> js_value) {
777777
if (js_options->Get(env()->detached_string())->BooleanValue())
778778
uv_process_options_.flags |= UV_PROCESS_DETACHED;
779779

780+
Local<String> win_hide = env()->windows_hide_string();
781+
782+
if (js_options->Get(win_hide)->BooleanValue())
783+
uv_process_options_.flags |= UV_PROCESS_WINDOWS_HIDE;
784+
780785
Local<String> wba = env()->windows_verbatim_arguments_string();
781786

782787
if (js_options->Get(wba)->BooleanValue())

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ assert.strictEqual(env.stdout.toString().trim(), 'buzz');
6666
assert.strictEqual(opts.options.shell, shell);
6767
assert.strictEqual(opts.options.file, opts.file);
6868
assert.deepStrictEqual(opts.options.args, opts.args);
69+
assert.strictEqual(opts.options.windowsHide, undefined);
6970
assert.strictEqual(opts.options.windowsVerbatimArguments,
7071
windowsVerbatim);
7172
});

‎test/parallel/test-child-process-spawnsync-validation-errors.js‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,22 @@ if (!common.isWindows) {
124124
fail('argv0', common.mustNotCall(), err);
125125
}
126126

127+
{
128+
// Validate the windowsHide option
129+
const err = /^TypeError: "windowsHide" must be a boolean$/;
130+
131+
pass('windowsHide', undefined);
132+
pass('windowsHide', null);
133+
pass('windowsHide', true);
134+
pass('windowsHide', false);
135+
fail('windowsHide', 0, err);
136+
fail('windowsHide', 1, err);
137+
fail('windowsHide', __dirname, err);
138+
fail('windowsHide', [], err);
139+
fail('windowsHide', {}, err);
140+
fail('windowsHide', common.mustNotCall(), err);
141+
}
142+
127143
{
128144
// Validate the windowsVerbatimArguments option
129145
const err = /^TypeError: "windowsVerbatimArguments" must be a boolean$/;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Flags: --expose_internals
2+
'use strict';
3+
const common = require('../common');
4+
const assert = require('assert');
5+
const cp = require('child_process');
6+
const internalCp = require('internal/child_process');
7+
const cmd = process.execPath;
8+
const args = ['-p', '42'];
9+
const options = { windowsHide: true };
10+
11+
// Since windowsHide isn't really observable, monkey patch spawn() and
12+
// spawnSync() to verify that the flag is being passed through correctly.
13+
const originalSpawn = internalCp.ChildProcess.prototype.spawn;
14+
const originalSpawnSync = internalCp.spawnSync;
15+
16+
internalCp.ChildProcess.prototype.spawn = common.mustCall(function(options) {
17+
assert.strictEqual(options.windowsHide, true);
18+
return originalSpawn.apply(this, arguments);
19+
}, 2);
20+
21+
internalCp.spawnSync = common.mustCall(function(options) {
22+
assert.strictEqual(options.options.windowsHide, true);
23+
return originalSpawnSync.apply(this, arguments);
24+
});
25+
26+
{
27+
const child = cp.spawnSync(cmd, args, options);
28+
29+
assert.strictEqual(child.status, 0);
30+
assert.strictEqual(child.signal, null);
31+
assert.strictEqual(child.stdout.toString().trim(), '42');
32+
assert.strictEqual(child.stderr.toString().trim(), '');
33+
}
34+
35+
{
36+
const child = cp.spawn(cmd, args, options);
37+
38+
child.on('exit', common.mustCall((code, signal) => {
39+
assert.strictEqual(code, 0);
40+
assert.strictEqual(signal, null);
41+
}));
42+
}
43+
44+
{
45+
const callback = common.mustCall((err, stdout, stderr) => {
46+
assert.ifError(err);
47+
assert.strictEqual(stdout.trim(), '42');
48+
assert.strictEqual(stderr.trim(), '');
49+
});
50+
51+
cp.execFile(cmd, args, options, callback);
52+
}

0 commit comments

Comments
 (0)