Skip to content
This repository has been archived by the owner on Jul 6, 2019. It is now read-only.

Commit

Permalink
fix(spawn): spawn child processes with node without relying on the sh…
Browse files Browse the repository at this point in the history
…ebang. (#174)
  • Loading branch information
jdalton authored and zkat committed Apr 13, 2018
1 parent 11d9fe0 commit cba97bb
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 11 deletions.
27 changes: 16 additions & 11 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ function installPackages (specs, prefix, opts) {
module.exports._execCommand = execCommand
function execCommand (_existing, argv) {
return findNodeScript(_existing, argv).then(existing => {
const argvCmdOpts = argv.cmdOpts || []
if (existing && !argv.alwaysSpawn && !argv.nodeArg && !argv.shell && existing !== process.argv[1]) {
const Module = require('module')
// let it take over the process. This means we can skip node startup!
Expand All @@ -263,31 +264,35 @@ function execCommand (_existing, argv) {
process.argv = [
process.argv[0], // Current node binary
existing // node script path. `runMain()` will set this as the new main
].concat(argv.cmdOpts) // options for the cmd itself
].concat(argvCmdOpts) // options for the cmd itself
Module.runMain() // ✨MAGIC✨. Sorry-not-sorry
} else if (!existing && argv.nodeArg && argv.nodeArg.length) {
throw new Error(Y()`ERROR: --node-arg/-n can only be used on packages with node scripts.`)
} else {
let cmd = existing
let opts = argv
if (existing && argv.nodeArg && argv.nodeArg.length) {
let cmdOpts = argvCmdOpts
if (existing) {
cmd = process.argv[0]
if (process.platform === 'win32') {
cmd = child.escapeArg(cmd, true)
}
// If we know we're running a run script and we got a --node-arg,
// we need to fudge things a bit to get them working right.
let nargs = argv.nodeArg
if (typeof nargs === 'string') {
nargs = [nargs]
cmdOpts = argv.nodeArg
if (cmdOpts) {
cmdOpts = Array.isArray(cmdOpts) ? cmdOpts : [cmdOpts]
} else {
cmdOpts = []
}
// It's valid for a single arg to be a string of multiple
// space-separated node args.
// Example: `$ npx -n '--inspect --harmony --debug' ...`
nargs = nargs.reduce((acc, arg) => {
cmdOpts = cmdOpts.reduce((acc, arg) => {
return acc.concat(arg.split(/\s+/))
}, [])
cmd = child.escapeArg(process.argv[0], true)
opts = Object.assign({}, argv, {
cmdOpts: nargs.concat([existing], argv.cmdOpts || [])
})
cmdOpts = cmdOpts.concat(existing, argvCmdOpts)
}
const opts = Object.assign({}, argv, { cmdOpts })
return child.runCommand(cmd, opts).catch(err => {
if (err.isOperational && err.exitCode) {
// At this point, we want to treat errors from the child as if
Expand Down
33 changes: 33 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,39 @@ const NPM_PATH = path.resolve(__dirname, '..', 'node_modules', 'npm', 'bin', 'np

const NPX_ESC = isWindows ? child.escapeArg(NPX_PATH) : NPX_PATH

test('npx --always-spawn', t => {
return child.spawn('node', [
NPX_ESC, '--always-spawn', 'echo-cli', 'hewwo'
], {stdio: 'pipe'}).then(res => {
t.equal(res.stdout.trim(), 'hewwo')
})
})

test('npx --always-spawn resolves promise after command is executed', t => {
const _runCommand = child.runCommand
const parsed = main.parseArgs([
process.argv[0],
'[fake arg]',
'--always-spawn',
'echo-cli',
'hewwo'
], NPM_PATH)
child.runCommand = (command, opts) => {
child.runCommand = _runCommand
return Promise.resolve([command, opts])
}
return main(parsed)
.then(args => {
const command = args[0]
const opts = args[1]
t.ok(command.includes('node'), 'node executes the command')
t.equal(opts.alwaysSpawn, true, 'set opts.alwaysSpawn')
t.equal(opts.command, 'echo-cli', 'set opts.command')
t.ok(opts.cmdOpts[0].includes('echo-cli'), 'set opts.cmdOpts[0]')
t.equal(opts.cmdOpts[1], 'hewwo', 'set opts.cmdOpts[1]')
})
})

test('npx --shell-auto-fallback', t => {
return child.spawn('node', [
NPX_ESC, '--shell-auto-fallback', 'zsh'
Expand Down

0 comments on commit cba97bb

Please sign in to comment.