run-script-pkg.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. const makeSpawnArgs = require('./make-spawn-args.js')
  2. const promiseSpawn = require('@npmcli/promise-spawn')
  3. const packageEnvs = require('./package-envs.js')
  4. const { isNodeGypPackage, defaultGypInstallScript } = require('@npmcli/node-gyp')
  5. const signalManager = require('./signal-manager.js')
  6. const isServerPackage = require('./is-server-package.js')
  7. const runScriptPkg = async options => {
  8. const {
  9. args = [],
  10. binPaths = false,
  11. env = {},
  12. event,
  13. nodeGyp,
  14. path,
  15. pkg,
  16. scriptShell,
  17. // how long to wait for a process.kill signal
  18. // only exposed here so that we can make the test go a bit faster.
  19. signalTimeout = 500,
  20. stdio = 'pipe',
  21. stdioString,
  22. } = options
  23. const { scripts = {}, gypfile } = pkg
  24. let cmd = null
  25. if (options.cmd) {
  26. cmd = options.cmd
  27. } else if (pkg.scripts && pkg.scripts[event]) {
  28. cmd = pkg.scripts[event]
  29. } else if (
  30. // If there is no preinstall or install script, default to rebuilding node-gyp packages.
  31. event === 'install' &&
  32. !scripts.install &&
  33. !scripts.preinstall &&
  34. gypfile !== false &&
  35. await isNodeGypPackage(path)
  36. ) {
  37. cmd = defaultGypInstallScript
  38. } else if (event === 'start' && await isServerPackage(path)) {
  39. cmd = 'node server.js'
  40. }
  41. if (!cmd) {
  42. return { code: 0, signal: null }
  43. }
  44. let inputEnd = () => {}
  45. if (stdio === 'inherit') {
  46. let banner
  47. if (pkg._id) {
  48. banner = `\n> ${pkg._id} ${event}\n`
  49. } else {
  50. banner = `\n> ${event}\n`
  51. }
  52. banner += `> ${cmd.trim().replace(/\n/g, '\n> ')}`
  53. if (args.length) {
  54. banner += ` ${args.join(' ')}`
  55. }
  56. banner += '\n'
  57. const { output, input } = require('proc-log')
  58. output.standard(banner)
  59. inputEnd = input.start()
  60. }
  61. const [spawnShell, spawnArgs, spawnOpts] = makeSpawnArgs({
  62. args,
  63. binPaths,
  64. cmd,
  65. env: { ...env, ...packageEnvs(pkg) },
  66. event,
  67. nodeGyp,
  68. path,
  69. scriptShell,
  70. stdio,
  71. stdioString,
  72. })
  73. const p = promiseSpawn(spawnShell, spawnArgs, spawnOpts, {
  74. event,
  75. script: cmd,
  76. pkgid: pkg._id,
  77. path,
  78. })
  79. if (stdio === 'inherit') {
  80. signalManager.add(p.process)
  81. }
  82. if (p.stdin) {
  83. p.stdin.end()
  84. }
  85. return p.catch(er => {
  86. const { signal } = er
  87. // coverage disabled because win32 never emits signals
  88. /* istanbul ignore next */
  89. if (stdio === 'inherit' && signal) {
  90. // by the time we reach here, the child has already exited. we send the
  91. // signal back to ourselves again so that npm will exit with the same
  92. // status as the child
  93. process.kill(process.pid, signal)
  94. // just in case we don't die, reject after 500ms
  95. // this also keeps the node process open long enough to actually
  96. // get the signal, rather than terminating gracefully.
  97. return new Promise((res, rej) => setTimeout(() => rej(er), signalTimeout))
  98. } else {
  99. throw er
  100. }
  101. }).finally(inputEnd)
  102. }
  103. module.exports = runScriptPkg