UNPKG

4.26 kBJavaScriptView Raw
1// Licensed to the Software Freedom Conservancy (SFC) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The SFC licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18'use strict';
19
20const childProcess = require('child_process');
21
22
23/**
24 * A hash with configuration options for an executed command.
25 *
26 * - `args` - Command line arguments.
27 * - `env` - Command environment; will inherit from the current process if
28 * missing.
29 * - `stdio` - IO configuration for the spawned server process. For more
30 * information, refer to the documentation of `child_process.spawn`.
31 *
32 * @typedef {{
33 * args: (!Array<string>|undefined),
34 * env: (!Object<string, string>|undefined),
35 * stdio: (string|!Array<string|number|!stream.Stream|null|undefined>|
36 * undefined)
37 * }}
38 */
39var Options;
40
41
42/**
43 * Describes a command's termination conditions.
44 */
45class Result {
46 /**
47 * @param {?number} code The exit code, or {@code null} if the command did not
48 * exit normally.
49 * @param {?string} signal The signal used to kill the command, or
50 * {@code null}.
51 */
52 constructor(code, signal) {
53 /** @type {?number} */
54 this.code = code;
55
56 /** @type {?string} */
57 this.signal = signal;
58 }
59
60 /** @override */
61 toString() {
62 return `Result(code=${this.code}, signal=${this.signal})`;
63 }
64}
65
66
67const COMMAND_RESULT = /** !WeakMap<!Command, !Promise<!Result>> */new WeakMap;
68const KILL_HOOK = /** !WeakMap<!Command, function(string)> */new WeakMap;
69
70/**
71 * Represents a command running in a sub-process.
72 */
73class Command {
74 /**
75 * @param {!Promise<!Result>} result The command result.
76 * @param {function(string)} onKill The function to call when {@link #kill()}
77 * is called.
78 */
79 constructor(result, onKill) {
80 COMMAND_RESULT.set(this, result);
81 KILL_HOOK.set(this, onKill);
82 }
83
84 /**
85 * @return {!Promise<!Result>} A promise for the result of this
86 * command.
87 */
88 result() {
89 return /** @type {!Promise<!Result>} */(COMMAND_RESULT.get(this));
90 }
91
92 /**
93 * Sends a signal to the underlying process.
94 * @param {string=} opt_signal The signal to send; defaults to `SIGTERM`.
95 */
96 kill(opt_signal) {
97 KILL_HOOK.get(this)(opt_signal || 'SIGTERM');
98 }
99}
100
101
102// PUBLIC API
103
104
105/**
106 * Spawns a child process. The returned {@link Command} may be used to wait
107 * for the process result or to send signals to the process.
108 *
109 * @param {string} command The executable to spawn.
110 * @param {Options=} opt_options The command options.
111 * @return {!Command} The launched command.
112 */
113module.exports = function exec(command, opt_options) {
114 var options = opt_options || {};
115
116 var proc = childProcess.spawn(command, options.args || [], {
117 env: options.env || process.env,
118 stdio: options.stdio || 'ignore'
119 });
120
121 // This process should not wait on the spawned child, however, we do
122 // want to ensure the child is killed when this process exits.
123 proc.unref();
124 process.once('exit', onProcessExit);
125
126 let result = new Promise(resolve => {
127 proc.once('exit', (code, signal) => {
128 proc = null;
129 process.removeListener('exit', onProcessExit);
130 resolve(new Result(code, signal));
131 });
132 });
133 return new Command(result, killCommand);
134
135 function onProcessExit() {
136 killCommand('SIGTERM');
137 }
138
139 function killCommand(signal) {
140 process.removeListener('exit', onProcessExit);
141 if (proc) {
142 proc.kill(signal);
143 proc = null;
144 }
145 }
146};
147
148// Exported to improve generated API documentation.
149
150module.exports.Command = Command;
151/** @typedef {!Options} */
152module.exports.Options = Options;
153module.exports.Result = Result;