UNPKG

4.48 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 * Options for configuring an executed command.
24 *
25 * @record
26 */
27class Options {
28 constructor() {
29 /**
30 * Command line arguments for the child process, if any.
31 * @type (!Array<string>|undefined)
32 */
33 this.args
34
35 /**
36 * Environment variables for the spawned process. If unspecified, the
37 * child will inherit this process' environment.
38 *
39 * @type {(!Object<string, string>|undefined)}
40 */
41 this.env
42
43 /**
44 * IO conifguration for the spawned server child process. If unspecified,
45 * the child process' IO output will be ignored.
46 *
47 * @type {(string|!Array<string|number|!stream.Stream|null|undefined>|
48 * undefined)}
49 * @see <https://nodejs.org/dist/latest-v8.x/docs/api/child_process.html#child_process_options_stdio>
50 */
51 this.stdio
52 }
53}
54
55/**
56 * Describes a command's termination conditions.
57 */
58class Result {
59 /**
60 * @param {?number} code The exit code, or {@code null} if the command did not
61 * exit normally.
62 * @param {?string} signal The signal used to kill the command, or
63 * {@code null}.
64 */
65 constructor(code, signal) {
66 /** @type {?number} */
67 this.code = code
68
69 /** @type {?string} */
70 this.signal = signal
71 }
72
73 /** @override */
74 toString() {
75 return `Result(code=${this.code}, signal=${this.signal})`
76 }
77}
78
79const COMMAND_RESULT = /** !WeakMap<!Command, !Promise<!Result>> */ new WeakMap()
80const KILL_HOOK = /** !WeakMap<!Command, function(string)> */ new WeakMap()
81
82/**
83 * Represents a command running in a sub-process.
84 */
85class Command {
86 /**
87 * @param {!Promise<!Result>} result The command result.
88 * @param {function(string)} onKill The function to call when {@link #kill()}
89 * is called.
90 */
91 constructor(result, onKill) {
92 COMMAND_RESULT.set(this, result)
93 KILL_HOOK.set(this, onKill)
94 }
95
96 /**
97 * @return {!Promise<!Result>} A promise for the result of this
98 * command.
99 */
100 result() {
101 return /** @type {!Promise<!Result>} */ (COMMAND_RESULT.get(this))
102 }
103
104 /**
105 * Sends a signal to the underlying process.
106 * @param {string=} opt_signal The signal to send; defaults to `SIGTERM`.
107 */
108 kill(opt_signal) {
109 KILL_HOOK.get(this)(opt_signal || 'SIGTERM')
110 }
111}
112
113// PUBLIC API
114
115/**
116 * Spawns a child process. The returned {@link Command} may be used to wait
117 * for the process result or to send signals to the process.
118 *
119 * @param {string} command The executable to spawn.
120 * @param {Options=} opt_options The command options.
121 * @return {!Command} The launched command.
122 */
123module.exports = function exec(command, opt_options) {
124 const options = opt_options || {}
125
126 let proc = childProcess.spawn(command, options.args || [], {
127 env: options.env || process.env,
128 stdio: options.stdio || 'ignore',
129 })
130
131 // This process should not wait on the spawned child, however, we do
132 // want to ensure the child is killed when this process exits.
133 proc.unref()
134 process.once('exit', onProcessExit)
135
136 const result = new Promise((resolve) => {
137 proc.once('exit', (code, signal) => {
138 proc = null
139 process.removeListener('exit', onProcessExit)
140 resolve(new Result(code, signal))
141 })
142 })
143 return new Command(result, killCommand)
144
145 function onProcessExit() {
146 killCommand('SIGTERM')
147 }
148
149 function killCommand(signal) {
150 process.removeListener('exit', onProcessExit)
151 if (proc) {
152 proc.kill(signal)
153 proc = null
154 }
155 }
156}
157
158// Exported to improve generated API documentation.
159
160module.exports.Command = Command
161module.exports.Options = Options
162module.exports.Result = Result