| 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 |  | 
| 20 | var childProcess = require('child_process'); | 
| 21 |  | 
| 22 | var promise = require('..').promise; | 
| 23 |  | 
| 24 |  | 
| 25 | /** | 
| 26 | * A hash with configuration options for an executed command. | 
| 27 | * | 
| 28 | * - `args` - Command line arguments. | 
| 29 | * - `env` - Command environment; will inherit from the current process if | 
| 30 | *     missing. | 
| 31 | * - `stdio` - IO configuration for the spawned server process. For more | 
| 32 | *     information, refer to the documentation of `child_process.spawn`. | 
| 33 | * | 
| 34 | * @typedef {{ | 
| 35 | *   args: (!Array.<string>|undefined), | 
| 36 | *   env: (!Object.<string, string>|undefined), | 
| 37 | *   stdio: (string|!Array.<string|number|!Stream|null|undefined>|undefined) | 
| 38 | * }} | 
| 39 | */ | 
| 40 | var Options; | 
| 41 |  | 
| 42 |  | 
| 43 | /** | 
| 44 | * Describes a command's termination conditions. | 
| 45 | * @param {?number} code The exit code, or {@code null} if the command did not | 
| 46 | *     exit normally. | 
| 47 | * @param {?string} signal The signal used to kill the command, or | 
| 48 | *     {@code null}. | 
| 49 | * @constructor | 
| 50 | */ | 
| 51 | var Result = function(code, signal) { | 
| 52 | /** @type {?number} */ | 
| 53 | this.code = code; | 
| 54 |  | 
| 55 | /** @type {?string} */ | 
| 56 | this.signal = signal; | 
| 57 | }; | 
| 58 |  | 
| 59 |  | 
| 60 | /** @override */ | 
| 61 | Result.prototype.toString = function() { | 
| 62 | return 'Result(code=' + this.code + ', signal=' + this.signal + ')'; | 
| 63 | }; | 
| 64 |  | 
| 65 |  | 
| 66 |  | 
| 67 | /** | 
| 68 | * Represents a command running in a sub-process. | 
| 69 | * @param {!promise.Promise.<!Result>} result The command result. | 
| 70 | * @constructor | 
| 71 | */ | 
| 72 | var Command = function(result, onKill) { | 
| 73 | /** @return {boolean} Whether this command is still running. */ | 
| 74 | this.isRunning = function() { | 
| 75 | return result.isPending(); | 
| 76 | }; | 
| 77 |  | 
| 78 | /** | 
| 79 | * @return {!promise.Promise.<!Result>} A promise for the result of this | 
| 80 | *     command. | 
| 81 | */ | 
| 82 | this.result = function() { | 
| 83 | return result; | 
| 84 | }; | 
| 85 |  | 
| 86 | /** | 
| 87 | * Sends a signal to the underlying process. | 
| 88 | * @param {string=} opt_signal The signal to send; defaults to | 
| 89 | *     {@code SIGTERM}. | 
| 90 | */ | 
| 91 | this.kill = function(opt_signal) { | 
| 92 | onKill(opt_signal || 'SIGTERM'); | 
| 93 | }; | 
| 94 | }; | 
| 95 |  | 
| 96 |  | 
| 97 | // PUBLIC API | 
| 98 |  | 
| 99 |  | 
| 100 | /** | 
| 101 | * Spawns a child process. The returned {@link Command} may be used to wait | 
| 102 | * for the process result or to send signals to the process. | 
| 103 | * | 
| 104 | * @param {string} command The executable to spawn. | 
| 105 | * @param {Options=} opt_options The command options. | 
| 106 | * @return {!Command} The launched command. | 
| 107 | */ | 
| 108 | module.exports = function(command, opt_options) { | 
| 109 | var options = opt_options || {}; | 
| 110 |  | 
| 111 | var proc = childProcess.spawn(command, options.args || [], { | 
| 112 | env: options.env || process.env, | 
| 113 | stdio: options.stdio || 'ignore' | 
| 114 | }).once('exit', onExit); | 
| 115 |  | 
| 116 | // This process should not wait on the spawned child, however, we do | 
| 117 | // want to ensure the child is killed when this process exits. | 
| 118 | proc.unref(); | 
| 119 | process.once('exit', killCommand); | 
| 120 |  | 
| 121 | var result = promise.defer(); | 
| 122 | var cmd = new Command(result.promise, function(signal) { | 
| 123 | if (!result.isPending() || !proc) { | 
| 124 | return;  // No longer running. | 
| 125 | } | 
| 126 | proc.kill(signal); | 
| 127 | }); | 
| 128 | return cmd; | 
| 129 |  | 
| 130 | function onExit(code, signal) { | 
| 131 | proc = null; | 
| 132 | process.removeListener('exit', killCommand); | 
| 133 | result.fulfill(new Result(code, signal)); | 
| 134 | } | 
| 135 |  | 
| 136 | function killCommand() { | 
| 137 | process.removeListener('exit', killCommand); | 
| 138 | proc && proc.kill('SIGTERM'); | 
| 139 | } | 
| 140 | }; |