@atomist/automation-client
Version:
Atomist API for software low-level client
150 lines • 5.92 kB
JavaScript
;
/*
* Copyright © 2018 Atomist, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const spawn = require("cross-spawn");
const path = require("path");
const sprintf_js_1 = require("sprintf-js");
const strip_ansi = require("strip-ansi");
const logger_1 = require("../internal/util/logger");
/**
* Default ErrorFinder that regards return code 0 as success
* @param {number} code
* @return {boolean}
* @constructor
*/
exports.SuccessIsReturn0ErrorFinder = code => code !== 0;
/**
* Spawn a process and watch
* @param {SpawnCommand} spawnCommand
* @param options options
* @param {ProgressLog} log
* @param {Partial<SpawnWatchOptions>} spOpts
* @return {Promise<ChildProcessResult>}
*/
function spawnAndWatch(spawnCommand, options, log, spOpts = {}) {
return __awaiter(this, void 0, void 0, function* () {
const childProcess = spawn(spawnCommand.command, spawnCommand.args || [], options);
if (spOpts.logCommand === false) {
logger_1.logger.debug(`${options.cwd || path.resolve(".")} > ${stringifySpawnCommand(spawnCommand)} (pid '${childProcess.pid}')`);
}
else {
log.write(`/--\n`);
log.write(`${options.cwd || path.resolve(".")} > ${stringifySpawnCommand(spawnCommand)} (pid '${childProcess.pid}')\n`);
log.write(`\\--\n`);
}
return watchSpawned(childProcess, log, spOpts);
});
}
exports.spawnAndWatch = spawnAndWatch;
/**
* Handle the result of a spawned process, streaming back
* output to log
* @param childProcess
* @param {ProgressLog} log to write stdout and stderr to
* @param opts: Options for error parsing, ANSI code stripping etc.
* @return {Promise<ChildProcessResult>}
*/
function watchSpawned(childProcess, log, opts = {}) {
let timer;
let running = true;
if (opts.timeout) {
timer = setTimeout(() => {
if (running) {
logger_1.logger.warn("Spawn timeout expired. Killing command with pid '%s'", childProcess.pid);
childProcess.kill();
}
}, opts.timeout);
}
return new Promise((resolve, reject) => {
const optsToUse = Object.assign({ errorFinder: exports.SuccessIsReturn0ErrorFinder, stripAnsi: false }, opts);
if (!optsToUse.errorFinder) {
// The caller specified undefined, which is an error. Ignore them, for they know not what they do.
optsToUse.errorFinder = exports.SuccessIsReturn0ErrorFinder;
}
function sendToLog(data) {
const formatted = optsToUse.stripAnsi ? strip_ansi(data.toString()) : data.toString();
return log.write(formatted);
}
childProcess.stdout.on("data", sendToLog);
childProcess.stderr.on("data", sendToLog);
childProcess.addListener("exit", (code, signal) => {
running = false;
logger_1.logger.info("Spawn exit with pid '%d': code '%d', signal '%d'", childProcess.pid, code, signal);
clearTimeout(timer);
resolve({
error: optsToUse.errorFinder(code, signal, log),
code,
childProcess,
});
});
childProcess.addListener("error", err => {
running = false;
// Process could not be spawned or killed
logger_1.logger.warn("Spawn failure: %s", err);
clearTimeout(timer);
reject(err);
});
});
}
/**
* toString for a SpawnCommand. Used for logging.
* @param {SpawnCommand} sc
* @return {string}
*/
function stringifySpawnCommand(sc) {
return sprintf_js_1.sprintf("%s %s", sc.command, !!sc.args ? sc.args.join(" ") : "");
}
exports.stringifySpawnCommand = stringifySpawnCommand;
/**
* Convenient function to create a spawn command from a sentence such as "npm run compile"
* Does not respect quoted arguments
* @param {string} sentence
* @param options
* @return {SpawnCommand}
*/
function asSpawnCommand(sentence, options = {}) {
const split = sentence.split(" ");
return {
command: split[0],
args: split.slice(1),
options,
};
}
exports.asSpawnCommand = asSpawnCommand;
/**
* Kill the child process and wait for it to shut down. This can take a while as child processes
* may have shut down hooks.
* @param {module:child_process.ChildProcess} childProcess
* @return {Promise<any>}
*/
function poisonAndWait(childProcess) {
childProcess.kill();
return new Promise((resolve, reject) => childProcess.on("close", () => {
resolve();
}));
}
exports.poisonAndWait = poisonAndWait;
//# sourceMappingURL=spawned.js.map