deadpool
Version:
Spawn node.js programs with the speed of thought
132 lines (116 loc) • 3.66 kB
JavaScript
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();
}));
}
}