UNPKG

deadpool

Version:

Spawn node.js programs with the speed of thought

132 lines (116 loc) 3.66 kB
const fs = require('fs'); const inherits = require('util').inherits; const EventEmitter = require('events').EventEmitter; const minimist = require('minimist'); const through = require('through2'); const has = require('has'); const bl = require('bl'); const msgpack = require('msgpack5')(); function defaultmoduleLoader(name) { try { return require(name); } catch (err) { return null; } } function writer(fileno) { return through(function(data, enc, cb) { cb(null, msgpack.encode([fileno, enc ? new Buffer(data, enc) : data])); }, function(cb) { this.push(msgpack.encode([fileno, null])); setImmediate(cb); }); } function Process(args, cwd, stdin, stdout, stderr, connection) { this.args = args; this.stdin = stdin; this.stdout = stdout; this.stderr = stderr; this.cwd = cwd; this.exit = exit; stdout.pipe(writer(1)).pipe(connection, {end: false}); stderr.pipe(writer(2)).pipe(connection, {end: false}); function exit(status) { setImmediate(function() { // ensure stdout/stderr won't send any more data stdout.unpipe(); stderr.unpipe(); var timeout; setImmediate(function() { // send the status through the connection connection.write(msgpack.encode([3, status || 0])); // wait up to 2 seconds for the remote to close it. timeout = setTimeout(function() { console.error('remote failed ot close the connection in time'); connection.end(); }, 2000); }); connection.on('close', function() { console.error('closing connection'); clearTimeout(timeout); }); }); } } inherits(Process, EventEmitter); module.exports = function deadpool(moduleLoader) { if (!moduleLoader) moduleLoader = defaultmoduleLoader; var modules = {}; return function connectionHandler(connection) { var stdinEof = false; connection.on('error', function(err) { console.error('connection error: ' + err.code); }); connection .pipe(msgpack.decoder()) .pipe(through.obj(function(msg, enc, cb) { if (has(this, 'process')) { if (msg[0] === 0) { if (msg[1] === null) { this.process.stdin.end(); stdinEof = true; } else if (!stdinEof) { this.process.stdin.write(msg[1], null, cb); } else { console.error('received stdin data after EOF'); } } else if (msg[0] === 4) { this.process.emit(msg[1].toString()) } return; } var name = msg.argv[0]; // load the command constructor if (!has(modules, name)) { var module = moduleLoader(name); if (typeof module === 'function' || typeof module === 'object') { modules[name] = module; } } this.process = new Process( minimist(msg.argv.slice(1)), // parse command-line options msg.cwd || process.cwd(), through(), through(), through(), connection ); if (!has(modules, name)) { connection.write(msgpack.encode([5, 'unknown module "' + name + '"'])); process.exit(1); } else { // run the module passing the process proxy instance modules[name](this.process); } cb(); }, function(cb) { if (has(this, 'process')) { if (!stdinEof) this.process.stdin.end(); this.process.stdout.unpipe(); this.process.stderr.unpipe(); process.emit('close'); } cb(); })); } }