Skip to content

Commit

Permalink
BREAKING CHANGE: node-dev now only tracks files that were loaded via …
Browse files Browse the repository at this point in the history
…require(). Therefore applications have to load the new node-dev module.
  • Loading branch information
fgnass committed Apr 4, 2011
1 parent d28b6a0 commit 5cb4dc0
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 182 deletions.
28 changes: 17 additions & 11 deletions README.md
@@ -1,13 +1,26 @@
About
=====

Node-dev is a supervisor for Node.js 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](http://growl.info/about.php).
Node-dev is a supervisor for Node.js that automatically restarts the node process when a script is modified.

The output written to stderr is captured and scanned for stack-traces. If an error is detected, node-dev displays the error message as [Growl notification](http://growl.info/about.php) and waits until one of the files referenced in the stack-trace is modified again, before it tries to re-span the child-proccess.

![Screenshot](http://cloud.github.com/downloads/fgnass/fgnass.github.com/node-dev.png)

Important
=========

Since version 0.1.0 node-dev instruments node's `require()` function to keep track of the files to watch. This way hot-reloading also works with modules that live outside of your project's directory. Therefore you have to make your scripts _node-dev aware_ by requiring the `node-dev` module (see usage instructions below).

Usage
=====

Insert `require('node-dev')` __at the top__ of your __main script__ file. Then run your script using the `node-dev` binary (instead of `node`).

All command-line arguments are passed on to the child-process: `node-dev —debug app.js foo` will start the child-process with `--debug` and pass `foo` as first argument. Hence your app will be debugged and not the supervisor itself.

The node-dev module does nothing, if the process was not spawned by the `node-dev` binary, hence it's safe to require it even in production environments.

Installation
============

Expand All @@ -18,10 +31,3 @@ The node-dev supervisor can be installed via [npm](http://github.com/isaacs/npm)
This will add the `node-dev` executable to your PATH.

In order to use Growl notifications [growlnotify](http://growl.info/extras.php#growlnotify) (form the Extras folder on the Growl disk image) must be installed on your system.

Usage
=====

Instead of running `node script.js`, type `node-dev script.js` instead.

All command-line arguments are passed on to the child-process: `node-dev —debug app.js foo` will start the child-process with `--debug` and pass `foo` as first argument. Hence your app will be debugged and not the supervisor itself.
128 changes: 128 additions & 0 deletions bin/node-dev
@@ -0,0 +1,128 @@
#! /usr/bin/env node
/*
* Node.js supervisor that spawns a node child-process and restarts
* it when the worker commits suicide. In order to work, your main
* script must require('node-dev') before loading any other modules.
*
* Author: Felix Gnass [fgnass at neteye dot de]
* License: MIT
* See http://github.com/fgnass/node-dev
*/
var sys = require('sys'),
fs = require('fs'),
path = require('path'),
child_process = require('child_process'),
util = require('util'),
server = null,
error = '',
files,
args = [].concat(process.argv),
cmd = args.shift();

args.shift();

process.stdin.on('data', function (chunk) {
if (server) {
server.stdin.write('data: ' + chunk);
}
});

process.on('SIGINT', function() {
server.kill('SIGHUP');
process.exit(0);
});

/**
* Logs a message to the console. The level is displayed in ANSI colors,
* either bright red in case of an error or green otherwise.
*/
function log(msg, level) {
var csi = level == 'error' ? '1;31' : '32';
sys.log('[\x1B[' + csi + 'm' + level.toUpperCase() + '\x1B[0m] ' + msg);
}

/**
* Displays a message as Growl notification.
* Requires http://growl.info/extras.php#growlnotify
*/
function notify(msg, title, level) {
level = level || 'info';
log(title || msg, level);
child_process.spawn('growlnotify', [
'-m', msg,
'--image', __dirname + '/../icons/node_' + level + '.png',
title || 'node.js']);
}

function start(title, msg) {
/** Spawn a node child-process */
server = child_process.spawn(cmd, args, {customFds: [-1, 1, -1]});
notify(msg || 'Started', title);
server.on('exit', function (code) {
if (code == 101) {
/** Worker committed suicide */
start('Restarting', 'Restarting');
}
else if (files) {
/** Worker exited due to an error */
files.forEach(function(file) {
fs.watchFile(file, function(cur, prev) {
if (+cur.mtime !== +prev.mtime) {
/** Stop watching the files */
files.forEach(function(file) {
fs.unwatchFile(file);
});
files = null;
/** Resume */
start("Resuming");
}
});
});
}
});

/** Scan stderr for stack-traces */
server.stderr.on('data', function(data) {
var s = data.toString(), stack, src, m, file, line, col;

error += s;

stack = s.match(/^(.+): (.*)\n\s+at.+\((.*?):(\d+):(\d+)/m);
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[3];
line = stack[4];
col = stack[5];
}
notify(stack[2] + '\n @' + file + ',' + line + ':' + col, stack[1], 'error');

/** Extract all file paths from the stack-trace */
files = [];
(error.match(/\(\/.*?:\d+:\d+\)/mg) || []).forEach(function(line) {
files.push(line.match(/(\/.*?):/)[1]);
});
error = '';
}
sys.print(data);
});
}

if (args.length > 0) {
start();
}
else {
sys.print('Usage: node-dev [options] script.js [arguments]\n');
}
49 changes: 49 additions & 0 deletions lib/index.js
@@ -0,0 +1,49 @@
/**
* Hooks into `require()` to watch for modifications of the required
* file. If a modification is detected, the process exits with code
* `101`.
* If the process wasn't spawned by the `node-dev` binary the module
* does nothing, hence it's safe require it even in production.
*/

/**
* Module dependencies.
*/
var fs = require('fs');

/**
* Watches the given module's filename.
*/
function watch(module) {
if (!module.loaded) {
fs.watchFile(module.filename, {interval : 500}, function(cur, prev) {
if (cur && +cur.mtime !== +prev.mtime) {
process.exit(101);
}
});
}
}

/**
* Test if the process was started as `node-dev`. Will only work in
* bash-like shells that set `$_`.
*/
if (/node-dev$/.test(process.env._)) {

/**
* Watch this module's ancestors.
*/
var m = module;
while ((m = m.parent)) {
watch(m);
}

/**
* Hook into `require`.
*/
var requireJs = require.extensions['.js'];
require.extensions['.js'] = function(module, filename) {
watch(module);
requireJs(module, filename);
};
}
168 changes: 0 additions & 168 deletions node-dev

This file was deleted.

0 comments on commit 5cb4dc0

Please sign in to comment.