CoolFace
Apppublic

opusdev/vector-similarity-api

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes
run.js563 linesDownload Raw Back to monitor
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