var debug = require('debug')('nodemon:run'); const statSync = require('fs').statSync; var utils = require('../utils'); var bus = utils.bus; var childProcess = require('child_process'); var spawn = childProcess.spawn; var exec = childProcess.exec; var execSync = childProcess.execSync; var fork = childProcess.fork; var watch = require('./watch').watch; var config = require('../config'); var child = null; // the actual child process we spawn var killedAfterChange = false; var noop = () => {}; var restart = null; var psTree = require('pstree.remy'); var path = require('path'); var signals = require('./signals'); const osRelease = parseInt(require('os').release().split('.')[0], 10); function run(options) { var cmd = config.command.raw; // moved up // we need restart function below in the global scope for run.kill /*jshint validthis:true*/ restart = run.bind(this, options); run.restart = restart; // binding options with instance of run // so that we can use it in run.kill run.options = options; var runCmd = !options.runOnChangeOnly || config.lastStarted !== 0; if (runCmd) { utils.log.status('starting `' + config.command.string + '`'); } else { // should just watch file if command is not to be run // had another alternate approach // to stop process being forked/spawned in the below code // but this approach does early exit and makes code cleaner debug('start watch on: %s', config.options.watch); if (config.options.watch !== false) { watch(); return; } } config.lastStarted = Date.now(); var stdio = ['pipe', 'pipe', 'pipe']; if (config.options.stdout) { stdio = ['pipe', process.stdout, process.stderr]; } if (config.options.stdin === false) { stdio = [process.stdin, process.stdout, process.stderr]; } var sh = 'sh'; var shFlag = '-c'; const binPath = process.cwd() + '/node_modules/.bin'; const spawnOptions = { env: Object.assign({}, process.env, options.execOptions.env, { PATH: binPath + ':' + process.env.PATH, }), stdio: stdio, }; var executable = cmd.executable; if (utils.isWindows) { // if the exec includes a forward slash, reverse it for windows compat // but *only* apply to the first command, and none of the arguments. // ref #1251 and #1236 if (executable.indexOf('/') !== -1) { executable = executable .split(' ') .map((e, i) => { if (i === 0) { return path.normalize(e); } return e; }) .join(' '); } // taken from npm's cli: https://git.io/vNFD4 sh = process.env.comspec || 'cmd'; shFlag = '/d /s /c'; spawnOptions.windowsVerbatimArguments = true; } var args = runCmd ? utils.stringify(executable, cmd.args) : ':'; var spawnArgs = [sh, [shFlag, args], spawnOptions]; const firstArg = cmd.args[0] || ''; var inBinPath = false; try { inBinPath = statSync(`${binPath}/${executable}`).isFile(); } catch (e) {} // hasStdio allows us to correctly handle stdin piping // see: https://git.io/vNtX3 const hasStdio = utils.satisfies('>= 6.4.0 || < 5'); // forking helps with sub-process handling and tends to clean up better // than spawning, but it should only be used under specific conditions const shouldFork = !config.options.spawn && !inBinPath && !(firstArg.indexOf('-') === 0) && // don't fork if there's a node exec arg firstArg !== 'inspect' && // don't fork it's `inspect` debugger executable === 'node' && // only fork if node utils.version.major > 4; // only fork if node version > 4 if (shouldFork) { // this assumes the first argument is the script and slices it out, since // we're forking var forkArgs = cmd.args.slice(1); var env = utils.merge(options.execOptions.env, process.env); stdio.push('ipc'); child = fork(options.execOptions.script, forkArgs, { env: env, stdio: stdio, silent: !hasStdio, }); utils.log.detail('forking'); debug('fork', sh, shFlag, args); } else { utils.log.detail('spawning'); child = spawn.apply(null, spawnArgs); debug('spawn', sh, shFlag, args); } if (config.required) { var emit = { stdout: function (data) { bus.emit('stdout', data); }, stderr: function (data) { bus.emit('stderr', data); }, }; // now work out what to bind to... if (config.options.stdout) { child.on('stdout', emit.stdout).on('stderr', emit.stderr); } else { child.stdout.on('data', emit.stdout); child.stderr.on('data', emit.stderr); bus.stdout = child.stdout; bus.stderr = child.stderr; } if (shouldFork) { child.on('message', function (message, sendHandle) { bus.emit('message', message, sendHandle); }); } } bus.emit('start'); utils.log.detail('child pid: ' + child.pid); child.on('error', function (error) { bus.emit('error', error); if (error.code === 'ENOENT') { utils.log.error('unable to run executable: "' + cmd.executable + '"'); process.exit(1); } else { utils.log.error('failed to start child process: ' + error.code); throw error; } }); child.on('exit', function (code, signal) { if (child && child.stdin) { process.stdin.unpipe(child.stdin); } if (code === 127) { utils.log.error( 'failed to start process, "' + cmd.executable + '" exec not found' ); bus.emit('error', code); process.exit(); } // If the command failed with code 2, it may or may not be a syntax error // See: http://git.io/fNOAR // We will only assume a parse error, if the child failed quickly if (code === 2 && Date.now() < config.lastStarted + 500) { utils.log.error('process failed, unhandled exit code (2)'); utils.log.error(''); utils.log.error('Either the command has a syntax error,'); utils.log.error('or it is exiting with reserved code 2.'); utils.log.error(''); utils.log.error('To keep nodemon running even after a code 2,'); utils.log.error('add this to the end of your command: || exit 1'); utils.log.error(''); utils.log.error('Read more here: https://git.io/fNOAG'); utils.log.error(''); utils.log.error('nodemon will stop now so that you can fix the command.'); utils.log.error(''); bus.emit('error', code); process.exit(); } // In case we killed the app ourselves, set the signal thusly if (killedAfterChange) { killedAfterChange = false; signal = config.signal; } // this is nasty, but it gives it windows support if (utils.isWindows && signal === 'SIGTERM') { signal = config.signal; } if (signal === config.signal || code === 0) { // this was a clean exit, so emit exit, rather than crash debug('bus.emit(exit) via ' + config.signal); bus.emit('exit', signal); // exit the monitor, but do it gracefully if (signal === config.signal) { return restart(); } if (code === 0) { // clean exit - wait until file change to restart if (runCmd) { utils.log.status('clean exit - waiting for changes before restart'); } child = null; } } else { bus.emit('crash'); if (options.exitcrash) { utils.log.fail('app crashed'); if (!config.required) { process.exit(1); } } else { utils.log.fail( 'app crashed - waiting for file changes before' + ' starting...' ); child = null; } } if (config.options.restartable) { // stdin needs to kick in again to be able to listen to the // restart command process.stdin.resume(); } }); // moved the run.kill outside to handle both the cases // intial start // no start // connect stdin to the child process (options.stdin is on by default) if (options.stdin) { process.stdin.resume(); // FIXME decide whether or not we need to decide the encoding // process.stdin.setEncoding('utf8'); // swallow the stdin error if it happens // ref: https://github.com/remy/nodemon/issues/1195 if (hasStdio) { child.stdin.on('error', () => {}); process.stdin.pipe(child.stdin); } else { if (child.stdout) { child.stdout.pipe(process.stdout); } else { utils.log.error( 'running an unsupported version of node ' + process.version ); utils.log.error( 'nodemon may not work as expected - ' + 'please consider upgrading to LTS' ); } } bus.once('exit', function () { if (child && process.stdin.unpipe) { // node > 0.8 process.stdin.unpipe(child.stdin); } }); } debug('start watch on: %s', config.options.watch); if (config.options.watch !== false) { watch(); } } function waitForSubProcesses(pid, callback) { debug('checking ps tree for pids of ' + pid); psTree(pid, (err, pids) => { if (!pids.length) { return callback(); } utils.log.status( `still waiting for ${pids.length} sub-process${ pids.length > 2 ? 'es' : '' } to finish...` ); setTimeout(() => waitForSubProcesses(pid, callback), 1000); }); } function kill(child, signal, callback) { if (!callback) { callback = noop; } if (utils.isWindows) { const taskKill = () => { try { exec('taskkill /pid ' + child.pid + ' /T /F'); } catch (e) { utils.log.error('Could not shutdown sub process cleanly'); } }; // We are handling a 'SIGKILL' , 'SIGUSR2' and 'SIGUSR1' POSIX signal under Windows the // same way it is handled on a UNIX system: We are performing // a hard shutdown without waiting for the process to clean-up. if (signal === 'SIGKILL' || osRelease < 10 || signal === 'SIGUSR2' || signal==="SIGUSR1" ) { debug('terminating process group by force: %s', child.pid); // We are using the taskkill utility to terminate the whole // process group ('/t') of the child ('/pid') by force ('/f'). // We need to end all sub processes, because the 'child' // process in this context is actually a cmd.exe wrapper. taskKill(); callback(); return; } try { // We are using the Windows Management Instrumentation Command-line // (wmic.exe) to resolve the sub-child process identifier, because the // 'child' process in this context is actually a cmd.exe wrapper. // We want to send the termination signal directly to the node process. // The '2> nul' silences the no process found error message. const resultBuffer = execSync( `wmic process where (ParentProcessId=${child.pid}) get ProcessId 2> nul` ); const result = resultBuffer.toString().match(/^[0-9]+/m); // If there is no sub-child process we fall back to the child process. const processId = Array.isArray(result) ? result[0] : child.pid; debug('sending kill signal SIGINT to process: %s', processId); // We are using the standalone 'windows-kill' executable to send the // standard POSIX signal 'SIGINT' to the node process. This fixes #1720. const windowsKill = path.normalize( `${__dirname}/../../bin/windows-kill.exe` ); // We have to detach the 'windows-kill' execution completely from this // process group to avoid terminating the nodemon process itself. // See: https://github.com/alirdn/windows-kill#how-it-works--limitations // // Therefore we are using 'start' to create a new cmd.exe context. // The '/min' option hides the new terminal window and the '/wait' // option lets the process wait for the command to finish. execSync( `start "windows-kill" /min /wait "${windowsKill}" -SIGINT ${processId}` ); } catch (e) { taskKill(); } callback(); } else { // we use psTree to kill the full subtree of nodemon, because when // spawning processes like `coffee` under the `--debug` flag, it'll spawn // it's own child, and that can't be killed by nodemon, so psTree gives us // an array of PIDs that have spawned under nodemon, and we send each the // configured signal (default: SIGUSR2) signal, which fixes #335 // note that psTree also works if `ps` is missing by looking in /proc let sig = signal.replace('SIG', ''); psTree(child.pid, function (err, pids) { // if ps isn't native to the OS, then we need to send the numeric value // for the signal during the kill, `signals` is a lookup table for that. if (!psTree.hasPS) { sig = signals[signal]; } // the sub processes need to be killed from smallest to largest debug('sending kill signal to ' + pids.join(', ')); child.kill(signal); pids.sort().forEach((pid) => exec(`kill -${sig} ${pid}`, noop)); waitForSubProcesses(child.pid, () => { // finally kill the main user process exec(`kill -${sig} ${child.pid}`, callback); }); }); } } run.kill = function (noRestart, callback) { // I hate code like this :( - Remy (author of said code) if (typeof noRestart === 'function') { callback = noRestart; noRestart = false; } if (!callback) { callback = noop; } if (child !== null) { // if the stdin piping is on, we need to unpipe, but also close stdin on // the child, otherwise linux can throw EPIPE or ECONNRESET errors. if (run.options.stdin) { process.stdin.unpipe(child.stdin); } // For the on('exit', ...) handler above the following looks like a // crash, so we set the killedAfterChange flag if a restart is planned if (!noRestart) { killedAfterChange = true; } /* Now kill the entire subtree of processes belonging to nodemon */ var oldPid = child.pid; if (child) { kill(child, config.signal, function () { // this seems to fix the 0.11.x issue with the "rs" restart command, // though I'm unsure why. it seems like more data is streamed in to // stdin after we close. if (child && run.options.stdin && child.stdin && oldPid === child.pid) { child.stdin.end(); } callback(); }); } } else if (!noRestart) { // if there's no child, then we need to manually start the process // this is because as there was no child, the child.on('exit') event // handler doesn't exist which would normally trigger the restart. bus.once('start', callback); run.restart(); } else { callback(); } }; run.restart = noop; bus.on('quit', function onQuit(code) { if (code === undefined) { code = 0; } // remove event listener var exitTimer = null; var exit = function () { clearTimeout(exitTimer); exit = noop; // null out in case of race condition child = null; if (!config.required) { // Execute all other quit listeners. bus.listeners('quit').forEach(function (listener) { if (listener !== onQuit) { listener(); } }); process.exit(code); } else { bus.emit('exit'); } }; // if we're not running already, don't bother with trying to kill if (config.run === false) { return exit(); } // immediately try to stop any polling config.run = false; if (child) { // give up waiting for the kids after 10 seconds exitTimer = setTimeout(exit, 10 * 1000); child.removeAllListeners('exit'); child.once('exit', exit); kill(child, 'SIGINT'); } else { exit(); } }); bus.on('restart', function () { // run.kill will send a SIGINT to the child process, which will cause it // to terminate, which in turn uses the 'exit' event handler to restart run.kill(); }); // remove the child file on exit process.on('exit', function () { utils.log.detail('exiting'); if (child) { child.kill(); } }); // because windows borks when listening for the SIG* events if (!utils.isWindows) { bus.once('boot', () => { // usual suspect: ctrl+c exit process.once('SIGINT', () => bus.emit('quit', 130)); process.once('SIGTERM', () => { bus.emit('quit', 143); if (child) { child.kill('SIGTERM'); } }); }); } module.exports = run;