exec.js 4.2 KB

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