Skip to content

Instantly share code, notes, and snippets.

@fgnass
Created October 5, 2010 15:41
Show Gist options
  • Select an option

  • Save fgnass/611749 to your computer and use it in GitHub Desktop.

Select an option

Save fgnass/611749 to your computer and use it in GitHub Desktop.
Supervisor for node.js
/**
* Node.js supervisor that spawns a node child-process and restarts it when changes are detected
* in the filesystem. If the child-process exits due to an error, the supervisor waits for another
* modification before it attempts to re-spawn it. The output written to stderr is captured and
* scanned for stack-traces. If an error is detected it is displayed as Growl notification.
*/
var fs = require('fs'),
sys = require('sys'),
path = require('path'),
http = require('http'),
child_process = require('child_process'),
server = null,
error = '',
files = [],
args = [].concat(process.argv),
cmd = args.shift();
args.shift();
/**
* Spawns a node child-process.
*/
function start(title, msg) {
watchFiles();
server = child_process.spawn(cmd, args);
notify(msg || 'Started', title);
server.addListener('exit', function (code, signal) {
server = null;
});
// Print data written to stdout
server.stdout.addListener('data', function(data) {
process.stdout.write(data);
});
// Data sent to stderr is scanned for stack-traces ...
server.stderr.addListener('data', function(data) {
var s = new String(data), stack, src, m, file, line, col;
sys.print(data);
error += s;
stack = s.match(/^(.+)\n\s+at.+\((.*?):(\d+):(\d+)/);
if (stack) {
// file:line
// source-code
// ^^^^^
// ErrorType: Message
src = error.match(/^\s*(.+):(\d+)\n(.*)\n(\s*)\^/);
if (src && !src[3].match(/throw/)) {
file = src[1];
line = src[2];
col = src[4].length;
}
else {
// No source-code or error was rethrown
file = stack[2];
line = stack[3];
col = stack[4];
}
notify(file + ',' + line + ':' + col, stack[1], 'error');
error = '';
}
});
}
/**
* Watches all .js files and restarts the server if a modification is detected.
*/
function watchFiles() {
files.forEach(fs.unwatchFile);
files = [];
walk(__dirname, function(file) {
files.push(file);
fs.watchFile(file, {interval : 500}, function(curr, prev) {
if (curr.mtime.valueOf() != prev.mtime.valueOf() || curr.ctime.valueOf() != prev.ctime.valueOf()) {
if (server) {
server.addListener('exit', function (code, signal) {
if (signal) {
start('Restarting', 'File modified: ' + file);
}
});
server.kill();
}
else {
start();
}
}
});
});
}
function walk(filename, callback) {
fs.stat(filename, function(err, stats) {
if(stats.isFile() && filename.match(/\.js$/)) {
callback(filename);
}
else if(stats.isDirectory()) {
fs.readdir(filename, function(err, files) {
files.forEach(function(f) {
walk(filename + '/' + f, callback);
});
});
}
});
}
/**
* Displays a message as Growl notification.
* Requires http://growl.info/extras.php#growlnotify
*/
function notify(msg, title, icon) {
icons[icon || 'green'].get(function(path) {
child_process.spawn('growlnotify', [
'-m', msg,
'--image', path,
title || 'node.js']);
});
}
var icons = {
green: new Gist('6a64dc22047197dd840a92a08fa89517a73bbdd9', 'node_green.png'),
error: new Gist('84497b6c3fff14273cd39bc860a29b364188919c', 'node_red.png')
};
function Gist(sha, name) {
var file = path.join(process.env.TMPDIR, name);
this.get = function(callback) {
path.exists(file, function(exists) {
if (exists) {
callback(file);
}
else {
download(callback);
}
});
};
function download(callback) {
var fd = fs.openSync(file, 'w');
var gist = http.createClient(80, 'gist.github.com');
var request = gist.request('GET', '/raw/611749/' + sha + '/' + name, {
host: 'gist.github.com'
});
request.end();
request.on('response', function (response) {
response.on('data', function (chunk) {
fs.writeSync(fd, chunk, 0, chunk.length);
});
response.on('end', function () {
fs.closeSync(fd);
callback(file);
});
});
}
}
start();
@fgnass
Copy link
Author

fgnass commented Oct 6, 2010

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment