opusdev/vector-similarity-api
1
1var debug = require('debug')('nodemon:run');2const statSync = require('fs').statSync;3var utils = require('../utils');4var bus = utils.bus;5var childProcess = require('child_process');6var spawn = childProcess.spawn;7var exec = childProcess.exec;8var execSync = childProcess.execSync;9var fork = childProcess.fork;10var watch = require('./watch').watch;11var config = require('../config');12var child = null; // the actual child process we spawn13var killedAfterChange = false;14var noop = () => {};15var restart = null;16var psTree = require('pstree.remy');17var path = require('path');18var signals = require('./signals');19const undefsafe = require('undefsafe');20const osRelease = parseInt(require('os').release().split('.')[0], 10);21 22function run(options) {23 var cmd = config.command.raw;24 // moved up25 // we need restart function below in the global scope for run.kill26 /*jshint validthis:true*/27 restart = run.bind(this, options);28 run.restart = restart;29 30 // binding options with instance of run31 // so that we can use it in run.kill32 run.options = options;33 34 var runCmd = !options.runOnChangeOnly || config.lastStarted !== 0;35 if (runCmd) {36 utils.log.status('starting `' + config.command.string + '`');37 } else {38 // should just watch file if command is not to be run39 // had another alternate approach40 // to stop process being forked/spawned in the below code41 // but this approach does early exit and makes code cleaner42 debug('start watch on: %s', config.options.watch);43 if (config.options.watch !== false) {44 watch();45 return;46 }47 }48 49 config.lastStarted = Date.now();50 51 var stdio = ['pipe', 'pipe', 'pipe'];52 53 if (config.options.stdout) {54 stdio = ['pipe', process.stdout, process.stderr];55 }56 57 if (config.options.stdin === false) {58 stdio = [process.stdin, process.stdout, process.stderr];59 }60 61 var sh = 'sh';62 var shFlag = '-c';63 64 const binPath = process.cwd() + '/node_modules/.bin';65 66 const spawnOptions = {67 env: Object.assign({}, options.execOptions.env, process.env, {68 PATH:69 binPath +70 path.delimiter +71 (undefsafe(options, '.execOptions.env.PATH') || process.env.PATH),72 }),73 stdio: stdio,74 };75 76 var executable = cmd.executable;77 78 if (utils.isWindows) {79 // if the exec includes a forward slash, reverse it for windows compat80 // but *only* apply to the first command, and none of the arguments.81 // ref #1251 and #123682 if (executable.indexOf('/') !== -1) {83 executable = executable84 .split(' ')85 .map((e, i) => {86 if (i === 0) {87 return path.normalize(e);88 }89 return e;90 })91 .join(' ');92 }93 // taken from npm's cli: https://git.io/vNFD494 sh = process.env.comspec || 'cmd';95 shFlag = '/d /s /c';96 spawnOptions.windowsVerbatimArguments = true;97 spawnOptions.windowsHide = true;98 }99 100 var args = runCmd ? utils.stringify(executable, cmd.args) : ':';101 var spawnArgs = [sh, [shFlag, args], spawnOptions];102 103 const firstArg = cmd.args[0] || '';104 105 var inBinPath = false;106 try {107 inBinPath = statSync(`${binPath}/${executable}`).isFile();108 } catch (e) {}109 110 // hasStdio allows us to correctly handle stdin piping111 // see: https://git.io/vNtX3112 const hasStdio = utils.satisfies('>= 6.4.0 || < 5');113 114 // forking helps with sub-process handling and tends to clean up better115 // than spawning, but it should only be used under specific conditions116 const shouldFork =117 !config.options.spawn &&118 !inBinPath &&119 !(firstArg.indexOf('-') === 0) && // don't fork if there's a node exec arg120 firstArg !== 'inspect' && // don't fork it's `inspect` debugger121 executable === 'node' && // only fork if node122 utils.version.major > 4; // only fork if node version > 4123 124 if (shouldFork) {125 // this assumes the first argument is the script and slices it out, since126 // we're forking127 var forkArgs = cmd.args.slice(1);128 var env = utils.merge(options.execOptions.env, process.env);129 stdio.push('ipc');130 const forkOptions = {131 env: env,132 stdio: stdio,133 silent: !hasStdio,134 };135 if (utils.isWindows) {136 forkOptions.windowsHide = true;137 }138 child = fork(options.execOptions.script, forkArgs, forkOptions);139 utils.log.detail('forking');140 debug('fork', sh, shFlag, args);141 } else {142 utils.log.detail('spawning');143 child = spawn.apply(null, spawnArgs);144 debug('spawn', sh, shFlag, args);145 }146 147 if (config.required) {148 var emit = {149 stdout: function (data) {150 bus.emit('stdout', data);151 },152 stderr: function (data) {153 bus.emit('stderr', data);154 },155 };156 157 // now work out what to bind to...158 if (config.options.stdout) {159 child.on('stdout', emit.stdout).on('stderr', emit.stderr);160 } else {161 child.stdout.on('data', emit.stdout);162 child.stderr.on('data', emit.stderr);163 164 bus.stdout = child.stdout;165 bus.stderr = child.stderr;166 }167 168 if (shouldFork) {169 child.on('message', function (message, sendHandle) {170 bus.emit('message', message, sendHandle);171 });172 }173 }174 175 bus.emit('start');176 177 utils.log.detail('child pid: ' + child.pid);178 179 child.on('error', function (error) {180 bus.emit('error', error);181 if (error.code === 'ENOENT') {182 utils.log.error('unable to run executable: "' + cmd.executable + '"');183 process.exit(1);184 } else {185 utils.log.error('failed to start child process: ' + error.code);186 throw error;187 }188 });189 190 child.on('exit', function (code, signal) {191 if (child && child.stdin) {192 process.stdin.unpipe(child.stdin);193 }194 195 if (code === 127) {196 utils.log.error(197 'failed to start process, "' + cmd.executable + '" exec not found'198 );199 bus.emit('error', code);200 process.exit();201 }202 203 // If the command failed with code 2, it may or may not be a syntax error204 // See: http://git.io/fNOAR205 // We will only assume a parse error, if the child failed quickly206 if (code === 2 && Date.now() < config.lastStarted + 500) {207 utils.log.error('process failed, unhandled exit code (2)');208 utils.log.error('');209 utils.log.error('Either the command has a syntax error,');210 utils.log.error('or it is exiting with reserved code 2.');211 utils.log.error('');212 utils.log.error('To keep nodemon running even after a code 2,');213 utils.log.error('add this to the end of your command: || exit 1');214 utils.log.error('');215 utils.log.error('Read more here: https://git.io/fNOAG');216 utils.log.error('');217 utils.log.error('nodemon will stop now so that you can fix the command.');218 utils.log.error('');219 bus.emit('error', code);220 process.exit();221 }222 223 // In case we killed the app ourselves, set the signal thusly224 if (killedAfterChange) {225 killedAfterChange = false;226 signal = config.signal;227 }228 // this is nasty, but it gives it windows support229 if (utils.isWindows && signal === 'SIGTERM') {230 signal = config.signal;231 }232 233 if (signal === config.signal || code === 0) {234 // this was a clean exit, so emit exit, rather than crash235 debug('bus.emit(exit) via ' + config.signal);236 bus.emit('exit', signal);237 238 // exit the monitor, but do it gracefully239 if (signal === config.signal) {240 return restart();241 }242 243 if (code === 0) {244 // clean exit - wait until file change to restart245 if (runCmd) {246 utils.log.status('clean exit - waiting for changes before restart');247 }248 child = null;249 }250 } else {251 bus.emit('crash');252 253 // support the old syntax of `exitcrash` - 2024-12-13254 if (options.exitcrash) {255 options.exitCrash = true;256 delete options.exitcrash;257 }258 259 if (options.exitCrash) {260 utils.log.fail('app crashed');261 if (!config.required) {262 process.exit(1);263 }264 } else {265 utils.log.fail(266 'app crashed - waiting for file changes before' + ' starting...'267 );268 child = null;269 }270 }271 272 if (config.options.restartable) {273 // stdin needs to kick in again to be able to listen to the274 // restart command275 process.stdin.resume();276 }277 });278 279 // moved the run.kill outside to handle both the cases280 // intial start281 // no start282 283 // connect stdin to the child process (options.stdin is on by default)284 if (options.stdin) {285 process.stdin.resume();286 // FIXME decide whether or not we need to decide the encoding287 // process.stdin.setEncoding('utf8');288 289 // swallow the stdin error if it happens290 // ref: https://github.com/remy/nodemon/issues/1195291 if (hasStdio) {292 child.stdin.on('error', () => {});293 process.stdin.pipe(child.stdin);294 } else {295 if (child.stdout) {296 child.stdout.pipe(process.stdout);297 } else {298 utils.log.error(299 'running an unsupported version of node ' + process.version300 );301 utils.log.error(302 'nodemon may not work as expected - ' +303 'please consider upgrading to LTS'304 );305 }306 }307 308 bus.once('exit', function () {309 if (child && process.stdin.unpipe) {310 // node > 0.8311 process.stdin.unpipe(child.stdin);312 }313 });314 }315 316 debug('start watch on: %s', config.options.watch);317 if (config.options.watch !== false) {318 watch();319 }320}321 322function waitForSubProcesses(pid, callback) {323 debug('checking ps tree for pids of ' + pid);324 psTree(pid, (err, pids) => {325 if (!pids.length) {326 return callback();327 }328 329 utils.log.status(330 `still waiting for ${pids.length} sub-process${331 pids.length > 2 ? 'es' : ''332 } to finish...`333 );334 setTimeout(() => waitForSubProcesses(pid, callback), 1000);335 });336}337 338function kill(child, signal, callback) {339 if (!callback) {340 callback = noop;341 }342 343 if (utils.isWindows) {344 const taskKill = () => {345 try {346 exec('taskkill /pid ' + child.pid + ' /T /F');347 } catch (e) {348 utils.log.error('Could not shutdown sub process cleanly');349 }350 };351 352 // We are handling a 'SIGKILL' , 'SIGUSR2' and 'SIGUSR1' POSIX signal under Windows the353 // same way it is handled on a UNIX system: We are performing354 // a hard shutdown without waiting for the process to clean-up.355 if (356 signal === 'SIGKILL' ||357 osRelease < 10 ||358 signal === 'SIGUSR2' ||359 signal === 'SIGUSR1'360 ) {361 debug('terminating process group by force: %s', child.pid);362 363 // We are using the taskkill utility to terminate the whole364 // process group ('/t') of the child ('/pid') by force ('/f').365 // We need to end all sub processes, because the 'child'366 // process in this context is actually a cmd.exe wrapper.367 taskKill();368 callback();369 return;370 }371 372 try {373 // We are using the Windows Management Instrumentation Command-line374 // (wmic.exe) to resolve the sub-child process identifier, because the375 // 'child' process in this context is actually a cmd.exe wrapper.376 // We want to send the termination signal directly to the node process.377 // The '2> nul' silences the no process found error message.378 const resultBuffer = execSync(379 `wmic process where (ParentProcessId=${child.pid}) get ProcessId 2> nul`380 );381 const result = resultBuffer.toString().match(/^[0-9]+/m);382 383 // If there is no sub-child process we fall back to the child process.384 const processId = Array.isArray(result) ? result[0] : child.pid;385 386 debug('sending kill signal SIGINT to process: %s', processId);387 388 // We are using the standalone 'windows-kill' executable to send the389 // standard POSIX signal 'SIGINT' to the node process. This fixes #1720.390 const windowsKill = path.normalize(391 `${__dirname}/../../bin/windows-kill.exe`392 );393 394 // We have to detach the 'windows-kill' execution completely from this395 // process group to avoid terminating the nodemon process itself.396 // See: https://github.com/alirdn/windows-kill#how-it-works--limitations397 //398 // Therefore we are using 'start' to create a new cmd.exe context.399 // The '/min' option hides the new terminal window and the '/wait'400 // option lets the process wait for the command to finish.401 402 execSync(403 `start "windows-kill" /min /wait "${windowsKill}" -SIGINT ${processId}`404 );405 } catch (e) {406 taskKill();407 }408 callback();409 } else {410 // we use psTree to kill the full subtree of nodemon, because when411 // spawning processes like `coffee` under the `--debug` flag, it'll spawn412 // it's own child, and that can't be killed by nodemon, so psTree gives us413 // an array of PIDs that have spawned under nodemon, and we send each the414 // configured signal (default: SIGUSR2) signal, which fixes #335415 // note that psTree also works if `ps` is missing by looking in /proc416 let sig = signal.replace('SIG', '');417 418 psTree(child.pid, function (err, pids) {419 // if ps isn't native to the OS, then we need to send the numeric value420 // for the signal during the kill, `signals` is a lookup table for that.421 if (!psTree.hasPS) {422 sig = signals[signal];423 }424 425 // the sub processes need to be killed from smallest to largest426 debug('sending kill signal to ' + pids.join(', '));427 428 child.kill(signal);429 430 pids.sort().forEach((pid) => exec(`kill -${sig} ${pid}`, noop));431 432 waitForSubProcesses(child.pid, () => {433 // finally kill the main user process434 exec(`kill -${sig} ${child.pid}`, callback);435 });436 });437 }438}439 440run.kill = function (noRestart, callback) {441 // I hate code like this :( - Remy (author of said code)442 if (typeof noRestart === 'function') {443 callback = noRestart;444 noRestart = false;445 }446 447 if (!callback) {448 callback = noop;449 }450 451 if (child !== null) {452 // if the stdin piping is on, we need to unpipe, but also close stdin on453 // the child, otherwise linux can throw EPIPE or ECONNRESET errors.454 if (run.options.stdin) {455 process.stdin.unpipe(child.stdin);456 }457 458 // For the on('exit', ...) handler above the following looks like a459 // crash, so we set the killedAfterChange flag if a restart is planned460 if (!noRestart) {461 killedAfterChange = true;462 }463 464 /* Now kill the entire subtree of processes belonging to nodemon */465 var oldPid = child.pid;466 if (child) {467 kill(child, config.signal, function () {468 // this seems to fix the 0.11.x issue with the "rs" restart command,469 // though I'm unsure why. it seems like more data is streamed in to470 // stdin after we close.471 if (child && run.options.stdin && child.stdin && oldPid === child.pid) {472 child.stdin.end();473 }474 callback();475 });476 }477 } else if (!noRestart) {478 // if there's no child, then we need to manually start the process479 // this is because as there was no child, the child.on('exit') event480 // handler doesn't exist which would normally trigger the restart.481 bus.once('start', callback);482 run.restart();483 } else {484 callback();485 }486};487 488run.restart = noop;489 490bus.on('quit', function onQuit(code) {491 if (code === undefined) {492 code = 0;493 }494 495 // remove event listener496 var exitTimer = null;497 var exit = function () {498 clearTimeout(exitTimer);499 exit = noop; // null out in case of race condition500 child = null;501 if (!config.required) {502 // Execute all other quit listeners.503 bus.listeners('quit').forEach(function (listener) {504 if (listener !== onQuit) {505 listener();506 }507 });508 process.exit(code);509 } else {510 bus.emit('exit');511 }512 };513 514 // if we're not running already, don't bother with trying to kill515 if (config.run === false) {516 return exit();517 }518 519 // immediately try to stop any polling520 config.run = false;521 522 if (child) {523 // give up waiting for the kids after 10 seconds524 exitTimer = setTimeout(exit, 10 * 1000);525 child.removeAllListeners('exit');526 child.once('exit', exit);527 528 kill(child, 'SIGINT');529 } else {530 exit();531 }532});533 534bus.on('restart', function () {535 // run.kill will send a SIGINT to the child process, which will cause it536 // to terminate, which in turn uses the 'exit' event handler to restart537 run.kill();538});539 540// remove the child file on exit541process.on('exit', function () {542 utils.log.detail('exiting');543 if (child) {544 child.kill();545 }546});547 548// because windows borks when listening for the SIG* events549if (!utils.isWindows) {550 bus.once('boot', () => {551 // usual suspect: ctrl+c exit552 process.once('SIGINT', () => bus.emit('quit', 130));553 process.once('SIGTERM', () => {554 bus.emit('quit', 143);555 if (child) {556 child.kill('SIGTERM');557 }558 });559 });560}561 562module.exports = run;563 