Path: blob/trunk/javascript/selenium-webdriver/io/exec.js
2884 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617'use strict'1819const childProcess = require('node:child_process')2021/**22* Options for configuring an executed command.23*24* @record25*/26class Options {27constructor() {28/**29* Command line arguments for the child process, if any.30* @type (!Array<string>|undefined)31*/32this.args3334/**35* Environment variables for the spawned process. If unspecified, the36* child will inherit this process' environment.37*38* @type {(!Object<string, string>|undefined)}39*/40this.env4142/**43* IO configuration for the spawned server child process. If unspecified,44* the child process' IO output will be ignored.45*46* @type {(string|!Array<string|number|!stream.Stream|null|undefined>|47* undefined)}48* @see <https://nodejs.org/dist/latest-v8.x/docs/api/child_process.html#child_process_options_stdio>49*/50this.stdio51}52}5354/**55* Describes a command's termination conditions.56*/57class Result {58/**59* @param {?number} code The exit code, or {@code null} if the command did not60* exit normally.61* @param {?string} signal The signal used to kill the command, or62* {@code null}.63*/64constructor(code, signal) {65/** @type {?number} */66this.code = code6768/** @type {?string} */69this.signal = signal70}7172/** @override */73toString() {74return `Result(code=${this.code}, signal=${this.signal})`75}76}7778const COMMAND_RESULT = /** !WeakMap<!Command, !Promise<!Result>> */ new WeakMap()79const KILL_HOOK = /** !WeakMap<!Command, function(string)> */ new WeakMap()8081/**82* Represents a command running in a sub-process.83*/84class Command {85/**86* @param {!Promise<!Result>} result The command result.87* @param {function(string)} onKill The function to call when {@link #kill()}88* is called.89*/90constructor(result, onKill) {91COMMAND_RESULT.set(this, result)92KILL_HOOK.set(this, onKill)93}9495/**96* @return {!Promise<!Result>} A promise for the result of this97* command.98*/99result() {100return /** @type {!Promise<!Result>} */ (COMMAND_RESULT.get(this))101}102103/**104* Sends a signal to the underlying process.105* @param {string=} opt_signal The signal to send; defaults to `SIGTERM`.106*/107kill(opt_signal) {108KILL_HOOK.get(this)(opt_signal || 'SIGTERM')109}110}111112// PUBLIC API113114/**115* Spawns a child process. The returned {@link Command} may be used to wait116* for the process result or to send signals to the process.117*118* @param {string} command The executable to spawn.119* @param {Options=} opt_options The command options.120* @return {!Command} The launched command.121*/122function exec(command, opt_options) {123const options = opt_options || {}124125let proc = childProcess.spawn(command, options.args || [], {126env: options.env || process.env,127stdio: options.stdio || 'ignore',128})129130// This process should not wait on the spawned child, however, we do131// want to ensure the child is killed when this process exits.132proc.unref()133process.once('exit', onProcessExit)134135const result = new Promise((resolve, reject) => {136proc.once('exit', (code, signal) => {137proc = null138process.removeListener('exit', onProcessExit)139resolve(new Result(code, signal))140})141142proc.once('error', (err) => {143reject(err)144})145})146return new Command(result, killCommand)147148function onProcessExit() {149killCommand('SIGTERM')150}151152function killCommand(signal) {153process.removeListener('exit', onProcessExit)154if (proc) {155proc.kill(signal)156proc = null157}158}159}160161// Exported to improve generated API documentation.162163module.exports = {164Command,165Options,166Result,167exec,168}169170171