Files
meteor/tools/shell-client.js
Avital Oliver 128fb7ce55 Make meteor shell not crash when piped from another command.
This commit makes possible but but doesn't fully solve the problem of
using `meteor shell` within shell scripts. See #5055 for more
details.

Closes #5056
2015-09-08 19:34:53 -07:00

176 lines
4.2 KiB
JavaScript

var assert = require("assert");
var fs = require("fs");
var net = require("net");
var eachline = require("eachline");
var chalk = require("chalk");
var EOL = require("os").EOL;
var server = require('./static-assets/server/shell-server.js');
var EXITING_MESSAGE = server.EXITING_MESSAGE;
var getInfoFile = server.getInfoFile;
// Invoked by the process running `meteor shell` to attempt to connect to
// the server via the socket file.
exports.connect = function connect(shellDir) {
new Client(shellDir).connect();
};
function Client(shellDir) {
var self = this;
assert.ok(self instanceof Client);
self.shellDir = shellDir;
self.exitOnClose = false;
self.firstTimeConnecting = true;
self.connected = false;
self.reconnectCount = 0;
}
var Cp = Client.prototype;
Cp.reconnect = function reconnect(delay) {
var self = this;
// Display the "Server unavailable" warning only on the third attempt
// to reconnect, so it doesn't get shown for successful reconnects.
if (++self.reconnectCount === 3) {
console.error(chalk.yellow(
"Server unavailable (waiting to reconnect)"
));
}
if (!self.reconnectTimer) {
self.reconnectTimer = setTimeout(function() {
delete self.reconnectTimer;
self.connect();
}, delay || 100);
}
};
Cp.connect = function connect() {
var self = this;
var infoFile = getInfoFile(self.shellDir);
fs.readFile(infoFile, "utf8", function(err, json) {
if (err) {
return self.reconnect();
}
try {
var info = JSON.parse(json);
} catch (err) {
return self.reconnect();
}
if (info.status !== "enabled") {
if (self.firstTimeConnecting) {
return self.reconnect();
}
if (info.reason) {
console.error(info.reason);
}
console.error(EXITING_MESSAGE);
process.exit(0);
}
self.setUpSocket(
net.connect(info.port, "127.0.0.1"),
info.key
);
});
};
Cp.setUpSocket = function setUpSocket(sock, key) {
var self = this;
self.sock = sock;
// Put STDIN into "flowing mode":
// http://nodejs.org/api/stream.html#stream_compatibility_with_older_node_versions
process.stdin.resume();
function onConnect() {
self.firstTimeConnecting = false;
self.reconnectCount = 0;
self.connected = true;
// Sending a JSON-stringified options object (even just an empty
// object) over the socket is required to start the REPL session.
sock.write(JSON.stringify({
terminal: ! process.env.EMACS,
key: key
}));
process.stderr.write(shellBanner());
process.stdin.pipe(sock);
if (process.stdin.setRawMode) { // https://github.com/joyent/node/issues/8204
process.stdin.setRawMode(true);
}
}
function onClose() {
tearDown();
// If we received the special EXITING_MESSAGE just before the socket
// closed, then exit the shell instead of reconnecting.
if (self.exitOnClose) {
process.exit(0);
} else {
self.reconnect();
}
}
function onError(err) {
tearDown();
self.reconnect();
}
function tearDown() {
self.connected = false;
if (process.stdin.setRawMode) { // https://github.com/joyent/node/issues/8204
process.stdin.setRawMode(false);
}
process.stdin.unpipe(sock);
sock.unpipe(process.stdout);
sock.removeListener("connect", onConnect);
sock.removeListener("close", onClose);
sock.removeListener("error", onError);
sock.end();
}
sock.pipe(process.stdout);
eachline(sock, "utf8", function(line) {
self.exitOnClose = line.indexOf(EXITING_MESSAGE) >= 0;
});
sock.on("connect", onConnect);
sock.on("close", onClose);
sock.on("error", onError);
};
function shellBanner() {
var bannerLines = [
"",
"Welcome to the server-side interactive shell!"
];
if (! process.env.EMACS) {
// Tab completion sadly does not work in Emacs.
bannerLines.push(
"",
"Tab completion is enabled for global variables."
);
}
bannerLines.push(
"",
"Type .reload to restart the server and the shell.",
"Type .exit to disconnect from the server and leave the shell.",
"Type .help for additional help.",
EOL
);
return chalk.green(bannerLines.join(EOL));
}