"use strict"; const Promise = require("bluebird"); const execa = require("execa"); const chalk = require("chalk"); const isExecaError = require("./is-execa-error"); // TODO: Eventually turn this into a generic restartableProcess abstraction? module.exports = function createProcessManager(processPath, processArguments, { onOutput, onError, onStart }) { let running = false; let queuedRestart = false; let currentProcess; function reportEnded() { currentProcess = null; running = false; if (queuedRestart === true) { console.log(chalk.blue.bold(`Restarting process...`)); return startProcess(); } } function killProcess() { if (currentProcess != null) { currentProcess.cancel(); } } function startProcess() { return Promise.try(() => { if (!running) { running = true; onStart(); let process = execa("node", [ processPath, ... processArguments ]); currentProcess = process; return process; } else { throw new Error(`Tried to start already-running process; this is a bug and should never happen`); } }).then(({ stdout, stderr }) => { console.error(stderr); // TODO: Think about whether to keep this console.log(chalk.blue.bold(`Process exited normally; it will be restarted upon changes`)); try { onOutput(JSON.parse(stdout)); } catch (error) { if (error instanceof SyntaxError) { // Show the original unparseable output, for debugging purposes console.error(chalk.bold("Process output:\n"), stdout); } throw error; } return reportEnded(); }).catch(isExecaError, (error) => { let { stdout, stderr, exitCode, isCanceled } = error; if (exitCode != null && exitCode !== 0) { onError({ reason: `Process exited with exit code ${exitCode}`, output: (error.stderr + error.stdout).trim() }); console.error(stdout); console.error(stderr); console.log(chalk.red.bold(`Process exited with exit code ${exitCode}; it will be restarted upon changes`)); } else if (isCanceled === true) { // Ignore } else { throw error; } return reportEnded(); }).catch(SyntaxError, (error) => { onError({ reason: "Failed to parse process output", output: error.stack }); console.error(error.stack); console.log(chalk.red.bold(`Failed to parse output of the process; it will be restarted upon changes`)); return reportEnded(); }); } return { kill: killProcess, restart: function (reason) { console.log(chalk.green.bold(`Change detected in ${reason}; running ${processPath}...`)); if (running === true) { queuedRestart = true; return this.kill(); } else { return this.start(); } }, start: function () { if (!running) { return startProcess(); } // TODO: Make this a possible error condition, rather than silently ignored? } }; };