diff options
Diffstat (limited to 'node_modules/spawn-wrap/index.js')
-rw-r--r-- | node_modules/spawn-wrap/index.js | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/node_modules/spawn-wrap/index.js b/node_modules/spawn-wrap/index.js new file mode 100644 index 000000000..a25cedb10 --- /dev/null +++ b/node_modules/spawn-wrap/index.js @@ -0,0 +1,326 @@ +module.exports = wrap +wrap.runMain = runMain + +var Module = require('module') +var fs = require('fs') +var cp = require('child_process') +var ChildProcess = cp.ChildProcess +var assert = require('assert') +var crypto = require('crypto') +var mkdirp = require('mkdirp') +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 cmdname = path.basename(process.execPath, '.exe') + +var shim = '#!' + process.execPath + '\n' + + fs.readFileSync(__dirname + '/shim.js') + +var isWindows = require('./lib/is-windows')() + +var pathRe = /^PATH=/ +if (isWindows) pathRe = /^PATH=/i + +var colon = isWindows ? ';' : ':' + +function wrap (argv, env, workingDir) { + if (!ChildProcess) { + var child = cp.spawn(process.execPath, []) + ChildProcess = child.constructor + child.kill('SIGKILL') + } + + // spawn_sync available since Node v0.11 + var spawnSyncBinding, spawnSync + try { + spawnSyncBinding = process.binding('spawn_sync') + } catch (e) {} + + // if we're passed in the working dir, then it means that setup + // was already done, so no need. + var doSetup = !workingDir + if (doSetup) { + workingDir = setup(argv, env) + } + var spawn = ChildProcess.prototype.spawn + if (spawnSyncBinding) { + spawnSync = spawnSyncBinding.spawn + } + + function unwrap () { + if (doSetup) { + rimraf.sync(workingDir) + } + ChildProcess.prototype.spawn = spawn + if (spawnSyncBinding) { + spawnSync = spawnSyncBinding.spawn + } + } + + if (spawnSyncBinding) { + spawnSyncBinding.spawn = wrappedSpawnFunction(spawnSync, workingDir) + } + ChildProcess.prototype.spawn = wrappedSpawnFunction(spawn, workingDir) + + return unwrap +} + +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 + } + } + } + + if (hasMain) { + options.file = workingDir + '/' + file + options.args[0] = workingDir + '/' + file + } + + } 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)) + } + } + } + } + + 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 (file === 'npm' && !isWindows) { + var npmPath = whichOrUndefined('npm') + + if (npmPath) { + options.args[0] = npmPath + + options.file = workingDir + '/node' + options.args.unshift(workingDir + '/node') + } + } + + if (isWindows) fixWindowsBins(workingDir, options) + + return fn.call(this, options) + } +} + +function whichOrUndefined (executable) { + var path + try { + path = which.sync(executable) + } catch (er) {} + 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 + argv = [] + } + + if (!argv && !env) { + throw new Error('at least one of "argv" and "env" required') + } + + if (argv) { + assert(Array.isArray(argv), 'argv must be array') + } else { + argv = [] + } + + if (env) { + assert(typeof env === 'object', 'env must be an object') + } else { + env = {} + } + + // For stuff like --use_strict or --harmony, we need to inject + // the argument *before* the wrap-main. + var execArgv = [] + for (var i = 0; i < argv.length; i++) { + if (argv[i].match(/^-/)) { + execArgv.push(argv[i]) + if (argv[i] === '-r' || argv[i] === '--require') { + execArgv.push(argv[++i]) + } + } else { + break + } + } + if (execArgv.length) { + if (execArgv.length === argv.length) { + argv.length = 0 + } else { + argv = argv.slice(execArgv.length) + } + } + + var settings = JSON.stringify({ + module: __filename, + deps: { + foregroundChild: require.resolve('foreground-child'), + signalExit: require.resolve('signal-exit'), + }, + 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) + }) + + mkdirp.sync(workingDir) + workingDir = fs.realpathSync(workingDir) + if (isWindows) { + var cmdShim = + '@echo off\r\n' + + 'SETLOCAL\r\n' + + 'SET PATHEXT=%PATHEXT:;.JS;=;%\r\n' + + '"' + process.execPath + '"' + ' "%~dp0\\.\\node" %*\r\n' + + fs.writeFileSync(workingDir + '/node.cmd', cmdShim) + fs.chmodSync(workingDir + '/node.cmd', '0755') + fs.writeFileSync(workingDir + '/iojs.cmd', cmdShim) + fs.chmodSync(workingDir + '/iojs.cmd', '0755') + } + fs.writeFileSync(workingDir + '/node', shim) + fs.chmodSync(workingDir + '/node', '0755') + fs.writeFileSync(workingDir + '/iojs', shim) + fs.chmodSync(workingDir + '/iojs', '0755') + if (cmdname !== 'iojs' && cmdname !== 'node') { + fs.writeFileSync(workingDir + '/' + cmdname, shim) + fs.chmodSync(workingDir + '/' + cmdname, '0755') + } + fs.writeFileSync(workingDir + '/settings.json', settings) + + return workingDir +} + +function runMain () { + process.argv.splice(1, 1) + process.argv[1] = path.resolve(process.argv[1]) + Module.runMain() +} |