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