166 lines
4.3 KiB
JavaScript
166 lines
4.3 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
module.exports = wrap
|
||
|
wrap.runMain = runMain
|
||
|
|
||
|
const Module = require('module')
|
||
|
const fs = require('fs')
|
||
|
const cp = require('child_process')
|
||
|
const ChildProcess = cp.ChildProcess
|
||
|
const assert = require('assert')
|
||
|
const crypto = require('crypto')
|
||
|
const IS_WINDOWS = require('is-windows')()
|
||
|
const makeDir = require('make-dir')
|
||
|
const rimraf = require('rimraf')
|
||
|
const path = require('path')
|
||
|
const signalExit = require('signal-exit')
|
||
|
const {IS_DEBUG, debug} = require("./lib/debug")
|
||
|
const munge = require("./lib/munge")
|
||
|
const homedir = require("./lib/homedir")
|
||
|
|
||
|
const shebang = process.platform === 'os390' ?
|
||
|
'#!/bin/env ' : '#!'
|
||
|
|
||
|
const shim = shebang + process.execPath + '\n' +
|
||
|
fs.readFileSync(path.join(__dirname, 'shim.js'))
|
||
|
|
||
|
function wrap(argv, env, workingDir) {
|
||
|
const spawnSyncBinding = process.binding('spawn_sync')
|
||
|
|
||
|
// if we're passed in the working dir, then it means that setup
|
||
|
// was already done, so no need.
|
||
|
const doSetup = !workingDir
|
||
|
if (doSetup) {
|
||
|
workingDir = setup(argv, env)
|
||
|
}
|
||
|
const spawn = ChildProcess.prototype.spawn
|
||
|
const spawnSync = spawnSyncBinding.spawn
|
||
|
|
||
|
function unwrap() {
|
||
|
if (doSetup && !IS_DEBUG) {
|
||
|
rimraf.sync(workingDir)
|
||
|
}
|
||
|
ChildProcess.prototype.spawn = spawn
|
||
|
spawnSyncBinding.spawn = spawnSync
|
||
|
}
|
||
|
|
||
|
spawnSyncBinding.spawn = wrappedSpawnFunction(spawnSync, workingDir)
|
||
|
ChildProcess.prototype.spawn = wrappedSpawnFunction(spawn, workingDir)
|
||
|
|
||
|
return unwrap
|
||
|
}
|
||
|
|
||
|
function wrappedSpawnFunction (fn, workingDir) {
|
||
|
return wrappedSpawn
|
||
|
|
||
|
function wrappedSpawn (options) {
|
||
|
const mungedOptions = munge(workingDir, options)
|
||
|
debug('WRAPPED', mungedOptions)
|
||
|
return fn.call(this, mungedOptions)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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 an array')
|
||
|
} else {
|
||
|
argv = []
|
||
|
}
|
||
|
|
||
|
if (env) {
|
||
|
assert(typeof env === 'object', 'env must be an object')
|
||
|
} else {
|
||
|
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.
|
||
|
const execArgv = []
|
||
|
for (let i = 0; i < argv.length; i++) {
|
||
|
if (argv[i].startsWith('-')) {
|
||
|
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)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const key = process.pid + '-' + crypto.randomBytes(6).toString('hex')
|
||
|
let workingDir = path.resolve(homedir, `.node-spawn-wrap-${key}`)
|
||
|
|
||
|
const settings = JSON.stringify({
|
||
|
module: __filename,
|
||
|
deps: {
|
||
|
foregroundChild: require.resolve('foreground-child'),
|
||
|
signalExit: require.resolve('signal-exit'),
|
||
|
debug: require.resolve('./lib/debug')
|
||
|
},
|
||
|
isWindows: IS_WINDOWS,
|
||
|
key,
|
||
|
workingDir,
|
||
|
argv,
|
||
|
execArgv,
|
||
|
env,
|
||
|
root: process.pid
|
||
|
}, null, 2) + '\n'
|
||
|
|
||
|
if (!IS_DEBUG) {
|
||
|
signalExit(() => rimraf.sync(workingDir))
|
||
|
}
|
||
|
|
||
|
makeDir.sync(workingDir)
|
||
|
workingDir = fs.realpathSync(workingDir)
|
||
|
if (IS_WINDOWS) {
|
||
|
const cmdShim =
|
||
|
'@echo off\r\n' +
|
||
|
'SETLOCAL\r\n' +
|
||
|
'CALL :find_dp0\r\n' +
|
||
|
'SET PATHEXT=%PATHEXT:;.JS;=;%\r\n' +
|
||
|
'"' + process.execPath + '" "%dp0%node" %*\r\n' +
|
||
|
'EXIT /b %errorlevel%\r\n'+
|
||
|
':find_dp0\r\n' +
|
||
|
'SET dp0=%~dp0\r\n' +
|
||
|
'EXIT /b\r\n'
|
||
|
|
||
|
fs.writeFileSync(path.join(workingDir, 'node.cmd'), cmdShim)
|
||
|
fs.chmodSync(path.join(workingDir, 'node.cmd'), '0755')
|
||
|
}
|
||
|
fs.writeFileSync(path.join(workingDir, 'node'), shim)
|
||
|
fs.chmodSync(path.join(workingDir, 'node'), '0755')
|
||
|
const cmdname = path.basename(process.execPath).replace(/\.exe$/i, '')
|
||
|
if (cmdname !== 'node') {
|
||
|
fs.writeFileSync(path.join(workingDir, cmdname), shim)
|
||
|
fs.chmodSync(path.join(workingDir, cmdname), '0755')
|
||
|
}
|
||
|
fs.writeFileSync(path.join(workingDir, 'settings.json'), settings)
|
||
|
|
||
|
return workingDir
|
||
|
}
|
||
|
|
||
|
function runMain () {
|
||
|
process.argv.splice(1, 1)
|
||
|
process.argv[1] = path.resolve(process.argv[1])
|
||
|
delete require.cache[process.argv[1]]
|
||
|
Module.runMain()
|
||
|
}
|