run-script-pkg.js 2.9 KB

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