aboutsummaryrefslogtreecommitdiff
path: root/node_modules/spawn-wrap/index.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/spawn-wrap/index.js')
-rw-r--r--node_modules/spawn-wrap/index.js398
1 files changed, 255 insertions, 143 deletions
diff --git a/node_modules/spawn-wrap/index.js b/node_modules/spawn-wrap/index.js
index a25cedb10..a64995bbf 100644
--- a/node_modules/spawn-wrap/index.js
+++ b/node_modules/spawn-wrap/index.js
@@ -12,10 +12,16 @@ var rimraf = require('rimraf')
var path = require('path')
var signalExit = require('signal-exit')
var homedir = require('os-homedir')() + '/.node-spawn-wrap-'
-var winRebase = require('./lib/win-rebase')
var which = require('which')
+var util = require('util')
-var cmdname = path.basename(process.execPath, '.exe')
+var doDebug = process.env.SPAWN_WRAP_DEBUG === '1'
+var debug = doDebug ? function () {
+ var message = util.format.apply(util, arguments).trim()
+ var pref = 'SW ' + process.pid + ': '
+ message = pref + message.split('\n').join('\n' + pref)
+ process.stderr.write(message + '\n')
+} : function () {}
var shim = '#!' + process.execPath + '\n' +
fs.readFileSync(__dirname + '/shim.js')
@@ -52,12 +58,12 @@ function wrap (argv, env, workingDir) {
}
function unwrap () {
- if (doSetup) {
+ if (doSetup && !doDebug) {
rimraf.sync(workingDir)
}
ChildProcess.prototype.spawn = spawn
if (spawnSyncBinding) {
- spawnSync = spawnSyncBinding.spawn
+ spawnSyncBinding.spawn = spawnSync
}
}
@@ -73,141 +79,252 @@ function wrappedSpawnFunction (fn, workingDir) {
return wrappedSpawn
function wrappedSpawn (options) {
- var pathEnv
- var cmdi, c, re, match, exe
-
- // handle case where node/iojs is exec'd
- // this doesn't handle EVERYTHING, but just the most common
- // case of doing `exec(process.execPath + ' file.js')
- var file = path.basename(options.file, '.exe')
- if (file === 'dash' ||
- file === 'sh' ||
- file === 'bash' ||
- file === 'zsh') {
- cmdi = options.args.indexOf('-c')
- if (cmdi !== -1) {
- c = options.args[cmdi + 1]
- re = /^\s*((?:[^\= ]*\=[^\=\s]*\s*)*)([^\s]+|"[^"]+"|'[^']+')/
- match = c.match(re)
- if (match) {
- exe = path.basename(match[2])
- // Strip a trailing quote from the basename if it matches a
- // leading quote.
- var quote = match[2].charAt(0)
- if ((quote === '"' || quote === '\'') && quote === exe.slice(-1)) {
- exe = exe.slice(0, -1)
- }
-
- if (exe === 'iojs' ||
- exe === 'node' ||
- exe === cmdname) {
- c = c.replace(re, '$1 ' + workingDir + '/node')
- options.args[cmdi + 1] = c
- } else if (exe === 'npm' && !isWindows) {
- var npmPath = whichOrUndefined('npm')
-
- if (npmPath) {
- c = c.replace(re, '$1 ' + workingDir + '/node ' + npmPath)
- options.args[cmdi + 1] = c
- }
- }
- }
- }
- } else if (isWindows && (
- file === path.basename(process.env.comspec || '') ||
- file === 'cmd'
- )) {
- cmdi = options.args.indexOf('/c')
- if (cmdi !== -1) {
- options.args[cmdi + 1] = winRebase(options.args[cmdi + 1],
- workingDir + '/node.cmd',
- whichOrUndefined)
- }
- } else if (file === 'node' || file === 'iojs' || cmdname === file) {
- // make sure it has a main script.
- // otherwise, just let it through.
- var a = 0
- var hasMain = false
- for (var a = 1; !hasMain && a < options.args.length; a++) {
- switch (options.args[a]) {
- case '-i':
- case '--interactive':
- case '--eval':
- case '-e':
- case '-pe':
- hasMain = false
- a = options.args.length
- continue
-
- case '-r':
- case '--require':
- a += 1
- continue
-
- default:
- if (options.args[a].match(/^-/)) {
- continue
- } else {
- hasMain = true
- a = options.args.length
- break
- }
- }
- }
+ munge(workingDir, options)
+ debug('WRAPPED', options)
+ return fn.call(this, options)
+ }
+}
- if (hasMain) {
- options.file = workingDir + '/' + file
- options.args[0] = workingDir + '/' + file
- }
+function isSh (file) {
+ return file === 'dash' ||
+ file === 'sh' ||
+ file === 'bash' ||
+ file === 'zsh'
+}
- } else {
- try {
- var resolved = which.sync(options.file)
- } catch (er) {}
- if (resolved) {
- var shebang = fs.readFileSync(resolved, 'utf8')
- var match = shebang.match(/^#!([^\r\n]+)/)
- if (match) {
- var shebangbin = match[1].split(' ')[0]
- var maybeNode = path.basename(shebangbin)
- if (maybeNode === 'node' || maybeNode === 'iojs' || cmdname === maybeNode) {
- file = options.file = shebangbin
- options.args = [shebangbin, workingDir + '/' + maybeNode]
- .concat(resolved)
- .concat(match[1].split(' ').slice(1))
- .concat(options.args.slice(1))
- }
- }
- }
+function mungeSh (workingDir, options) {
+ var cmdi = options.args.indexOf('-c')
+ if (cmdi === -1)
+ return // no -c argument
+
+ var c = options.args[cmdi + 1]
+ var re = /^\s*((?:[^\= ]*\=[^\=\s]*)*[\s]*)([^\s]+|"[^"]+"|'[^']+')( .*)?$/
+ var match = c.match(re)
+ if (!match)
+ return // not a command invocation. weird but possible
+
+ var command = match[2]
+ // strip quotes off the command
+ var quote = command.charAt(0)
+ if ((quote === '"' || quote === '\'') && quote === command.slice(-1)) {
+ command = command.slice(1, -1)
+ }
+ var exe = path.basename(command)
+
+ if (isNode(exe)) {
+ options.originalNode = command
+ c = match[1] + match[2] + ' "' + workingDir + '/node" ' + match[3]
+ options.args[cmdi + 1] = c
+ } else if (exe === 'npm' && !isWindows) {
+ // XXX this will exhibit weird behavior when using /path/to/npm,
+ // if some other npm is first in the path.
+ var npmPath = whichOrUndefined('npm')
+
+ if (npmPath) {
+ c = c.replace(re, '$1 "' + workingDir + '/node" "' + npmPath + '" $3')
+ options.args[cmdi + 1] = c
+ debug('npm munge!', c)
}
+ }
+}
- for (var i = 0; i < options.envPairs.length; i++) {
- var ep = options.envPairs[i]
- if (ep.match(pathRe)) {
- pathEnv = ep.substr(5)
- var k = ep.substr(0, 5)
- options.envPairs[i] = k + workingDir + colon + pathEnv
- }
- }
- if (!pathEnv) {
- options.envPairs.push((isWindows ? 'Path=' : 'PATH=') + workingDir)
+function isCmd (file) {
+ var comspec = path.basename(process.env.comspec || '').replace(/\.exe$/i, '')
+ return isWindows && (file === comspec || /^cmd(\.exe|\.EXE)?$/.test(file))
+}
+
+function mungeCmd (workingDir, options) {
+ var cmdi = options.args.indexOf('/c')
+ if (cmdi === -1)
+ return
+
+ var re = /^\s*("*)([^"]*?\b(?:node|iojs)(?:\.exe|\.EXE)?)("*)( .*)?$/
+ var npmre = /^\s*("*)([^"]*?\b(?:npm))("*)( |$)/
+ var path_ = require('path')
+ if (path_.win32)
+ path_ = path_.win32
+
+ var command = options.args[cmdi + 1]
+ if (!command)
+ return
+
+ var m = command.match(re)
+ var replace
+ if (m) {
+ options.originalNode = m[2]
+ replace = m[1] + workingDir + '/node.cmd' + m[3] + m[4]
+ options.args[cmdi + 1] = m[1] + m[2] + m[3] +
+ ' "' + workingDir + '\\node"' + m[4]
+ } else {
+ // XXX probably not a good idea to rewrite to the first npm in the
+ // path if it's a full path to npm. And if it's not a full path to
+ // npm, then the dirname will not work properly!
+ m = command.match(npmre)
+ if (!m)
+ return
+
+ var npmPath = whichOrUndefined('npm') || 'npm'
+ npmPath = path_.dirname(npmPath) + '\\node_modules\\npm\\bin\\npm-cli.js'
+ replace = m[1] + workingDir + '/node.cmd' +
+ ' "' + npmPath + '"' +
+ m[3] + m[4]
+ options.args[cmdi + 1] = command.replace(npmre, replace)
+ }
+}
+
+function isNode (file) {
+ var cmdname = path.basename(process.execPath).replace(/\.exe$/i, '')
+ return file === 'node' || file === 'iojs' || cmdname === file
+}
+
+function mungeNode (workingDir, options) {
+ options.originalNode = options.file
+ var command = path.basename(options.file).replace(/\.exe$/i, '')
+ // make sure it has a main script.
+ // otherwise, just let it through.
+ var a = 0
+ var hasMain = false
+ var mainIndex = 1
+ for (var a = 1; !hasMain && a < options.args.length; a++) {
+ switch (options.args[a]) {
+ case '-p':
+ case '-i':
+ case '--interactive':
+ case '--eval':
+ case '-e':
+ case '-pe':
+ hasMain = false
+ a = options.args.length
+ continue
+
+ case '-r':
+ case '--require':
+ a += 1
+ continue
+
+ default:
+ if (options.args[a].match(/^-/)) {
+ continue
+ } else {
+ hasMain = true
+ mainIndex = a
+ a = options.args.length
+ break
+ }
}
+ }
+
+ if (hasMain) {
+ var replace = workingDir + '/' + command
+ options.args.splice(mainIndex, 0, replace)
+ }
- if (file === 'npm' && !isWindows) {
- var npmPath = whichOrUndefined('npm')
+ // If the file is just something like 'node' then that'll
+ // resolve to our shim, and so to prevent double-shimming, we need
+ // to resolve that here first.
+ // This also handles the case where there's not a main file, like
+ // `node -e 'program'`, where we want to avoid the shim entirely.
+ if (options.file === options.basename) {
+ var realNode = whichOrUndefined(options.file) || process.execPath
+ options.file = options.args[0] = realNode
+ }
- if (npmPath) {
- options.args[0] = npmPath
+ debug('mungeNode after', options.file, options.args)
+}
- options.file = workingDir + '/node'
- options.args.unshift(workingDir + '/node')
- }
+function mungeShebang (workingDir, options) {
+ try {
+ var resolved = which.sync(options.file)
+ } catch (er) {
+ // nothing to do if we can't resolve
+ // Most likely: file doesn't exist or is not executable.
+ // Let exec pass through, probably will fail, oh well.
+ return
+ }
+
+ var shebang = fs.readFileSync(resolved, 'utf8')
+ var match = shebang.match(/^#!([^\r\n]+)/)
+ if (!match)
+ return // not a shebang script, probably a binary
+
+ var shebangbin = match[1].split(' ')[0]
+ var maybeNode = path.basename(shebangbin)
+ if (!isNode(maybeNode))
+ return // not a node shebang, leave untouched
+
+ options.originalNode = shebangbin
+ options.basename = maybeNode
+ options.file = shebangbin
+ options.args = [shebangbin, workingDir + '/' + maybeNode]
+ .concat(resolved)
+ .concat(match[1].split(' ').slice(1))
+ .concat(options.args.slice(1))
+}
+
+function mungeEnv (workingDir, options) {
+ var pathEnv
+ for (var i = 0; i < options.envPairs.length; i++) {
+ var ep = options.envPairs[i]
+ if (ep.match(pathRe)) {
+ pathEnv = ep.substr(5)
+ var k = ep.substr(0, 5)
+ options.envPairs[i] = k + workingDir + colon + pathEnv
}
+ }
+ if (!pathEnv) {
+ options.envPairs.push((isWindows ? 'Path=' : 'PATH=') + workingDir)
+ }
+ if (options.originalNode) {
+ var key = path.basename(workingDir).substr('.node-spawn-wrap-'.length)
+ options.envPairs.push('SW_ORIG_' + key + '=' + options.originalNode)
+ }
- if (isWindows) fixWindowsBins(workingDir, options)
+ if (process.env.SPAWN_WRAP_DEBUG === '1')
+ options.envPairs.push('SPAWN_WRAP_DEBUG=1')
+}
- return fn.call(this, options)
+function isnpm (file) {
+ // XXX is this even possible/necessary?
+ // wouldn't npm just be detected as a node shebang?
+ return file === 'npm' && !isWindows
+}
+
+function mungenpm (workingDir, options) {
+ debug('munge npm')
+ // XXX weird effects of replacing a specific npm with a global one
+ var npmPath = whichOrUndefined('npm')
+
+ if (npmPath) {
+ options.args[0] = npmPath
+
+ options.file = workingDir + '/node'
+ options.args.unshift(workingDir + '/node')
+ }
+}
+
+function munge (workingDir, options) {
+ options.basename = path.basename(options.file).replace(/\.exe$/i, '')
+
+ // XXX: dry this
+ if (isSh(options.basename)) {
+ mungeSh(workingDir, options)
+ } else if (isCmd(options.basename)) {
+ mungeCmd(workingDir, options)
+ } else if (isNode(options.basename)) {
+ mungeNode(workingDir, options)
+ } else if (isnpm(options.basename)) {
+ // XXX unnecessary? on non-windows, npm is just another shebang
+ mungenpm(workingDir, options)
+ } else {
+ mungeShebang(workingDir, options)
}
+
+ // now the options are munged into shape.
+ // whether we changed something or not, we still update the PATH
+ // so that if a script somewhere calls `node foo`, it gets our
+ // wrapper instead.
+
+ mungeEnv(workingDir, options)
}
function whichOrUndefined (executable) {
@@ -218,18 +335,6 @@ function whichOrUndefined (executable) {
return path
}
-// by default Windows will reference the full
-// path to the node.exe or iojs.exe as the bin,
-// we should instead point spawn() at our .cmd shim.
-function fixWindowsBins (workingDir, options) {
- var re = /.*\b(node|iojs)(\.exe)?$/
- if (options.file.match(re)) {
- options.file = process.execPath
- var shim = workingDir + '/node'
- options.args.splice(0, 1, options.file, workingDir + '/node')
- }
-}
-
function setup (argv, env) {
if (argv && typeof argv === 'object' && !env && !Array.isArray(argv)) {
env = argv
@@ -252,6 +357,8 @@ function setup (argv, env) {
env = {}
}
+ debug('setup argv=%j env=%j', argv, env)
+
// For stuff like --use_strict or --harmony, we need to inject
// the argument *before* the wrap-main.
var execArgv = []
@@ -273,23 +380,26 @@ function setup (argv, env) {
}
}
+ var key = process.pid + '-' + crypto.randomBytes(6).toString('hex')
+ var workingDir = homedir + key
+
var settings = JSON.stringify({
module: __filename,
deps: {
foregroundChild: require.resolve('foreground-child'),
signalExit: require.resolve('signal-exit'),
},
+ key: key,
+ workingDir: workingDir,
argv: argv,
execArgv: execArgv,
env: env,
root: process.pid
}, null, 2) + '\n'
- var workingDir = homedir + process.pid + '-' +
- crypto.randomBytes(6).toString('hex')
-
signalExit(function () {
- rimraf.sync(workingDir)
+ if (!doDebug)
+ rimraf.sync(workingDir)
})
mkdirp.sync(workingDir)
@@ -310,6 +420,7 @@ function setup (argv, env) {
fs.chmodSync(workingDir + '/node', '0755')
fs.writeFileSync(workingDir + '/iojs', shim)
fs.chmodSync(workingDir + '/iojs', '0755')
+ var cmdname = path.basename(process.execPath).replace(/\.exe$/i, '')
if (cmdname !== 'iojs' && cmdname !== 'node') {
fs.writeFileSync(workingDir + '/' + cmdname, shim)
fs.chmodSync(workingDir + '/' + cmdname, '0755')
@@ -322,5 +433,6 @@ function setup (argv, env) {
function runMain () {
process.argv.splice(1, 1)
process.argv[1] = path.resolve(process.argv[1])
+ delete require.cache[process.argv[1]]
Module.runMain()
}