Merge pull request #242 from remy/fix/241

Fixes for #241
This commit is contained in:
Remy Sharp
2014-01-05 07:53:58 -08:00
30 changed files with 641 additions and 182 deletions

View File

@@ -4,6 +4,7 @@ WIP - but here's the TL;DR
## Issues
- Please include the output from `nodemon --dump` for diagnosis
- If there's a script that nodemon is having trouble with or is causing nodemon to throw exceptions, please include it in your filed issue to allow me to replicate the issue.
## Sending pull requests

View File

@@ -73,12 +73,14 @@ A config file can take any of the command line arguments as JSON key values, for
The above `nodemon.json` file might be my global config so that I have support for ruby files and processing files, and I can simply run `nodemon demo.pde` and nodemon will automatically know how to run the script even though out of the box support for processing scripts.
Note that `ignore` and `watch` values are *merged* with your CLI arguments, rather than overwritten.
A further example of options can be seen in [sample-nodemon.md]()
*This section needs better documentation, but for now you can also see `nodemon --help config` ([also here]())*.
## Using nodemon as a module
Please see [doc/requireable.md](doc/requireable.md)
## Running non-node scripts
nodemon can also be used to execute and monitor other programs. nodemon will read the file extension of the script being run and monitor that extension instead of .js if there's no .nodemonignore:

View File

@@ -12,6 +12,5 @@ if (notifier.update) {
}
var options = cli.parse(process.argv);
options.restartable = 'rs';
nodemon(options);

3
doc/cli/usage.txt Normal file
View File

@@ -0,0 +1,3 @@
Usage: nodemon [nodemon options] [script.js] [args]
See "nodemon --help" for more.

34
doc/rules.md Normal file
View File

@@ -0,0 +1,34 @@
# Rules
Given a nodemon.json file that contains:
```json
{
"ignore": ["*.coffee"],
"watch": ["server/*.coffee", "test/"]
}
```
Then nodemon detects changes, but what causes nodemon to restart? The ignored files or the watched files? Which wins?
```js
var files = ['server/foo.coffee', 'server/app.js'];
// afterIgnore = ['server/app.js'] now since foo.coffee matches *.coffee
var afterIgnore = files.filter(applyIgnore);
// watch = ['server/foo.coffee'] as it's under the watch
var watched = files.filter(applyWatch);
```
What about:
```js
var files = ['test/app.js', 'test/app.coffee'];
// afterIgnore = ['test/app.js'] now since test/app.coffee matches *.coffee
var afterIgnore = files.filter(applyIgnore);
// watch.length = 2 as watch implies test/*.*
var watched = files.filter(applyWatch);
```

View File

@@ -1,5 +1,6 @@
// default options for config.options
module.exports = {
restartable: 'rs',
execMap: {
py: 'python',
rb: 'ruby'
@@ -7,8 +8,8 @@ module.exports = {
// compatible with linux, mac and windows, or make the default.js dynamically
// append the `.cmd` for node based utilities
},
watch: [],
ignore: [],
ignore: ['.git/', 'node_modules/**/node_modules/'],
watch: ['*.*'],
stdin: true,
verbose: false
};

View File

@@ -22,11 +22,16 @@ function exec(nodemonOptions, execMap) {
var options = utils.clone(nodemonOptions || {}),
script = path.basename(options.script || ''),
scriptExt = path.extname(script),
extension = options.ext || scriptExt || '.js';
extension = options.ext || scriptExt || 'js';
// strip any leading periods int he extension
if (extension.indexOf('.') === 0) {
extension = extension.slice(1);
}
// allows the user to simplify cli usage: https://github.com/remy/nodemon/issues/195
if (execMap[extension.slice(1)] !== undefined) {
options.exec = execMap[extension.slice(1)];
if (execMap[extension] !== undefined) {
options.exec = execMap[extension];
}
if (options.exec === undefined) {
@@ -71,7 +76,7 @@ function exec(nodemonOptions, execMap) {
if (options.exec === 'coffee') {
// don't override user specified extension tracking
if (!options.ext) {
extension = '.coffee|.litcoffee|.js';
extension = 'coffee litcoffee js';
}
// because windows can't find 'coffee', it needs the real file 'coffee.cmd'
@@ -94,13 +99,13 @@ function exec(nodemonOptions, execMap) {
.replace(/,/g, '|') // convert commas to pipes
.split('|') // split on those pipes
.map(function (item) {
return '.' + item.replace(/^[\*\.]+/, ''); // remove "*."
}).join('$|'); // return regexp string like: .js$|.jade$
return item.replace(/^[\*\.]+/, ''); // remove "*."
}).join(','); // return regexp string like: .js$|.jade$
}
// this final part ensures both multiple extension and
// single extensions work
extension += '$';
// extension += '$';
options.ext = extension;

View File

@@ -6,22 +6,30 @@
* This is *not* the user's config.
*/
var load = require('./load'),
bus = require('../utils/bus'),
rules = require('../rules'),
utils = require('../utils'),
fs = require('fs'),
path = require('path'),
existsSync = fs.existsSync || path.existsSync,
bus = utils.bus,
checkWatchSupport = require('./checkWatchSupport');
function reset() {
rules.reset();
config.dirs = [];
config.options = { ignore: [], watch: [] };
config.lastStarted = 0;
config.loaded = [];
}
var config = {
run: false,
system: {
// TODO decide whether we need both of these
noWatch: false,
watchWorks: false,
},
required: false,
dirs: [],
ignoring: [],
timeout: 1000,
options: {}
};
@@ -36,26 +44,64 @@ var config = {
* @param {Function} ready callback fired once the config is loaded
*/
config.load = function (settings, ready) {
reset();
var config = this;
load(settings, config.options, config, function (options) {
config.options = options;
if (options.execOptions.ext) {
rules.watch.add(new RegExp(options.execOptions.ext));
if (options.watch && options.watch.length) {
options.monitor = utils.clone(options.watch);
}
// read directories to monitor
options.watch.forEach(function (dir) {
dir = path.resolve(dir);
if (existsSync(config.dir)) {
config.dirs.push(path.resolve(config.dir));
}
});
if (options.ignore) {
[].push.apply(options.monitor, (options.ignore || []).map(function (rule) {
return '!' + rule;
}));
}
// if (options.execOptions.ext) {
// options.execOptions.ext.split(',').forEach(function (ext) {
// options.monitor.push(ext);
// });
// }
// delete options.watch;
// delete options.ignore;
var cwd = process.cwd();
if (config.dirs.length === 0) {
config.dirs.unshift(process.cwd());
config.dirs.unshift(cwd);
}
// next check if the monitored paths are actual directories
// or just patterns - and expand the rule to include *.*
options.monitor = options.monitor.map(function (rule) {
var not = rule.slice(0, 1) === '!';
if (rule.slice(-1) === '/') {
// just slap on a * anyway
return rule + '*';
}
if (not) {
rule = rule.slice(1);
}
var dir = path.resolve(cwd, rule);
try {
var stat = fs.statSync(dir);
if (stat.isDirectory()) {
if (rule.slice(-1) !== '/') {
rule += '/';
}
rule += '*';
}
} catch (e) {}
return (not ? '!' : '') + rule;
});
// now run automatic checks on system adding to the config object
checkWatchSupport(config, function (config) {
bus.emit('config:update', config);
@@ -64,5 +110,7 @@ config.load = function (settings, ready) {
});
};
config.reset = reset;
module.exports = config;

View File

@@ -60,7 +60,7 @@ function load(settings, options, config, callback) {
// if we didn't pick up a nodemon.json file & there's no cli ignores
// then try loading an old style .nodemonignore file
if (config.loaded.length === 0 && options.ignore.length === 0) {
if (config.loaded.length === 0) {
// TODO decide whether this is just confusing...
var legacy = loadLegacyIgnore.bind(null, options, ready);
@@ -129,6 +129,10 @@ function loadFile(options, config, dir, ready) {
ready = function () {};
}
if (!dir) {
return callback({});
}
var callback = function (settings) {
// prefer the local nodemon.json and fill in missing items using
// the global options

77
lib/monitor/match.js Normal file
View File

@@ -0,0 +1,77 @@
'use strict';
var minimatch = require('minimatch'),
path = require('path'),
utils = require('../utils');
module.exports = match;
function match(files, monitor, ext) {
// sort the rules by highest specificity (based on number of slashes)
// TODO actually check separator rules work on windows
var rules = monitor.sort(function (a, b) {
var r = b.split(path.sep).length - a.split(path.sep).length;
if (r === 0) {
return b.length - a.length;
}
return r;
}).map(function (s) {
var prefix = s.slice(0, 1),
sep = path.sep;
if (prefix === '!') {
return '!**' + (s.slice(0, 1) !== '/' ? '/' : '') + s.slice(1);
}
return '**' + (s.slice(0, 1) !== '/' ? '/' : '') + s;
});
var good = [],
ignored = 0,
watched = 0;
files.forEach(function (file) {
var matched = false;
for (var i = 0; i < rules.length; i++) {
if (rules[i].slice(0, 1) === '!') {
if (!minimatch(file, rules[i])) {
ignored++;
matched = true;
break;
}
} else {
if (minimatch(file, rules[i])) {
watched++;
utils.log.detail('matched rule: ' + rules[i]);
good.push(file);
matched = true;
break;
}
}
}
if (!matched) {
ignored++;
}
});
// finally check the good files against the extensions that we're monitoring
if (ext) {
if (ext.indexOf(',') === -1) {
ext = '**/*.' + ext;
} else {
ext = '**/*.{' + ext + '}';
}
// console.log('before', good, ext);
good = good.filter(function(file) {
return minimatch(file, ext);
});
} // else assume *.*
return {
result: good,
ignored: ignored,
watched: watched,
total: files.length
};
}

View File

@@ -162,12 +162,23 @@ bus.on('quit', function () {
}
};
// if we're not running already, don't bother with trying to kill
if (config.run === false) {
return exit();
}
// immediately try to stop any polling
config.run = false;
if (child) {
child.removeAllListeners('exit');
child.on('exit', exit);
child.kill('SIGINT');
// give up waiting for the kids after 10 seconds
setTimeout(exit, 10 * 1000);
} else {
exit();
}
setTimeout(exit, 10 * 1000); // give up waiting for the kids after 10 seconds
});
bus.on('restart', function () {

View File

@@ -4,6 +4,7 @@ var fs = require('fs'),
changedSince = require('./changed-since'),
utils = require('../utils'),
bus = utils.bus,
match = require('./match'),
config = require('../config'),
childProcess = require('child_process'),
exec = childProcess.exec,
@@ -102,7 +103,7 @@ bus.on('config:update', function () {
// filter ignored files
function ignoredFilter(file) {
if (config.options.ignore.length) {
if (config.options.ignore.length && config.options.ignore.re) {
// If we are in a Windows machine
if (utils.isWindows) {
// Break up the file by slashes
@@ -122,35 +123,29 @@ function ignoredFilter(file) {
}
function filterAndRestart(files) {
var data = {};
if (files.length) {
data.all = files.length;
utils.log.detail('files trigggering change check: ' + files.join('\n'));
var cwd = process.cwd();
utils.log.detail('files triggering change check: ' + files.map(function (file) {
return path.relative(cwd, file);
}).join(', '));
files = files.filter(ignoredFilter);
data.ignore = files.length;
if (config.options.watch.length) {
files = files.filter(function (file) {
return config.options.watch.re.test(file);
});
if (!config.options.execOptions) {
console.log(config);
}
var matched = match(files, config.options.monitor, config.options.execOptions.ext);
data.watch = files.length;
utils.log.detail('changes after filters (pre/ignored/valid): ' + [data.all, data.ignore, data.watch].join('/'));
utils.log.detail('changes after filters (before/after): ' + [files.length, matched.result.length].join('/'));
// reset the last check so we're only looking at recently modified files
config.lastStarted = Date.now();
if (files.length) {
if (matched.result.length) {
if (restartTimer !== null) {
clearTimeout(restartTimer);
}
restartTimer = setTimeout(function () {
utils.log.status('restarting due to changes...');
files.forEach(function (file) {
matched.result.forEach(function (file) {
utils.log.detail(path.relative(process.cwd(), file));
});
if (config.options.verbose) {
@@ -165,22 +160,28 @@ function filterAndRestart(files) {
}
if (config.system.noWatch || config.options.forceLegacyWatch) {
setTimeout(monitor, config.timeout);
if (config.run) {
setTimeout(watch, config.timeout);
}
}
}
var monitor = module.exports = function () {
var watch = module.exports = function () {
// if we have noWatch or watchWorks (i.e. not using `find`)
// then called `changeFunction` which is local to this script
if ((config.system.noWatch || config.system.watchWorks) && !config.options.forceLegacyWatch) {
changeFunction(config.lastStarted, function (files) {
filterAndRestart(files);
if (config.run) {
filterAndRestart(files);
}
});
} else {
// Fallback for when both find and fs.watch don't work
// using the `changedSince` which is external
changedSince(config.lastStarted, function (files) {
filterAndRestart(files);
if (config.run) {
filterAndRestart(files);
}
});
}
};

View File

@@ -4,6 +4,7 @@ var monitor = require('./monitor'),
version = require('./version'),
util = require('util'),
utils = require('./utils'),
rules = require('./rules'),
bus = utils.bus,
help = require('./help'),
config = require('./config'),
@@ -14,21 +15,15 @@ var monitor = require('./monitor'),
config.required = utils.isRequired;
function nodemon(settings) {
nodemon.removeAllListners();
utils.reset();
// set the debug flag as early as possible to get all the detailed logging
if (settings.verbose) {
utils.debug = true;
}
nodemon.reset();
if (typeof settings === 'string') {
settings = cli.parse(settings);
}
// by default, ignore .git and node_modules
if (!settings.ignore) {
settings.ignore = ['.git', 'node_modules/**/node_modules'];
// set the debug flag as early as possible to get all the detailed logging
if (settings.verbose) {
utils.debug = true;
}
if (settings.help) {
@@ -45,10 +40,18 @@ function nodemon(settings) {
}
}
// always echo out the current version
utils.log.info(version);
config.load(settings, function (config) {
if (!config.options.script) {
if (!config.required) {
console.log(help('usage'));
process.exit();
}
return;
}
// always echo out the current version
utils.log.info(version);
// echo out notices about running state
if (config.options.restartable) {
// allow nodemon to restart when the use types 'rs\n'
@@ -93,15 +96,20 @@ function nodemon(settings) {
utils.log.info('to restart at any time, enter `' + config.options.restartable + '`');
}
config.dirs.forEach(function (dir) {
utils.log.info('watching: ' + dir);
});
var none = function (v) {
return v;
};
utils.log.detail('ignoring: ' + config.options.monitor.map(function (rule) {
return rule.slice(0, 1) === '!' ? rule.slice(1) : false;
}).filter(none).join(' '));
utils.log.info('watching: ' + config.options.monitor.map(function (rule) {
return rule.slice(0, 1) !== '!' ? rule : false;
}).filter(none).join(' '));
utils.log.detail('watching extensions: ' + config.options.ext);
config.options.ignore.forEach(function (pattern) {
utils.log.detail('ignoring: ' + pattern);
});
if (config.options.dump) {
utils.log._log('log', '--------------');
@@ -118,6 +126,7 @@ function nodemon(settings) {
}
}
config.run = true;
monitor.run(config.options);
});
@@ -141,7 +150,7 @@ nodemon.once = function (event, handler) {
if (!eventHandlers[event]) { eventHandlers[event] = []; }
eventHandlers[event].push(handler);
bus.once(event, function () {
eventHandlers[event].splice(eventHandlers[event].indexOf(handler), 0);
eventHandlers[event].splice(eventHandlers[event].indexOf(handler), 1);
handler.apply(this, arguments);
});
return nodemon;
@@ -159,7 +168,7 @@ nodemon.removeAllListners = function (event) {
}).forEach(function (event) {
eventHandlers[event].forEach(function (handler) {
bus.removeListener(event, handler);
eventHandlers[event].splice(eventHandlers[event].indexOf(handler), 0);
eventHandlers[event].splice(eventHandlers[event].indexOf(handler), 1);
});
// delete eventHandlers[event];
});
@@ -167,21 +176,30 @@ nodemon.removeAllListners = function (event) {
return nodemon;
};
nodemon.reset = function () {
// console.trace();
nodemon.removeAllListners();
utils.reset();
rules.reset();
config.run = false;
};
// expose the full config
nodemon.config = config;
// on exception *inside* nodemon, shutdown wrapped node app
process.on('uncaughtException', function (err) {
console.error('exception in nodemon killing node');
console.error(err.stack);
console.error();
console.error('If appropriate, please file an error: http://github.com/remy/nodemon/issues/new\n');
if (!config.required) {
process.exit(1);
}
});
if (!config.required) {
process.on('uncaughtException', function (err) {
console.error('exception in nodemon killing node');
console.error(err.stack);
console.error();
console.error('If appropriate, please file an error: http://github.com/remy/nodemon/issues/new\n');
if (!config.required) {
process.exit(1);
}
});
}
module.exports = nodemon;

View File

@@ -1,3 +1,7 @@
'use strict';
var utils = require('../utils');
// internal
var reEscComments = /\\#/g,
reUnescapeComments = /\^\^/g, // note that '^^' is used in place of escaped comments
@@ -39,7 +43,9 @@ function add(rules, which, rule) {
// support the rule being a RegExp, but reformat it to
// the custom :<regexp> format that we're working with.
if (rule instanceof RegExp) {
rule = ':' + rule.toString().replace(/^\/(.*?)\/$/g, '$1');
// rule = ':' + rule.toString().replace(/^\/(.*?)\/$/g, '$1');
utils.log.error('RegExp format no longer supported, but globs are.');
return;
}
// remove comments and trim lines
@@ -54,6 +60,7 @@ function add(rules, which, rule) {
if (typeof rule === 'string' && rule.substring(0, 1) === ':') {
rule = rule.substring(1);
utils.log.error('RegExp no longer supported: ' + rule);
regexp = true;
} else if (rule.length === 0) {
// blank line (or it was a comment)
@@ -61,14 +68,19 @@ function add(rules, which, rule) {
}
if (regexp) {
rules[which].push(rule);
// rules[which].push(rule);
} else {
rule = rule.replace(reEscapeChars, '\\$&')
.replace(reAsterisk, '.*');
// rule = rule.replace(reEscapeChars, '\\$&')
// .replace(reAsterisk, '.*');
rules[which].push(rule);
}
// compile a regexp of all the rules for this ignore or watch
var re = rules[which].map(function (rule) {
return rule.replace(reEscapeChars, '\\$&')
.replace(reAsterisk, '.*');
}).join('|');
// compile a regexp of all the rules for this ignore or watch
rules[which].re = new RegExp(rules[which].join('|'));
// used for the directory matching
rules[which].re = new RegExp(re);
}
}

View File

@@ -36,6 +36,8 @@ function load(filename, callback) {
module.exports = {
reset: function () { // just used for testing
rules.ignore.length = rules.watch.length = 0;
delete rules.ignore.re;
delete rules.watch.re;
},
load: load,
ignore: {

View File

@@ -1,3 +1,4 @@
'use strict';
var events = require('events'),
util = require('util');

View File

@@ -22,17 +22,20 @@ var utils = module.exports = {
// nukes the logging
if (!this.debug) {
Object.keys(this.log).forEach(function (method) {
// this.log[method] = noop;
this.log[method] = noop;
}.bind(this));
}
},
reset: function () {
if (!this.debug) {
Object.keys(this.log).forEach(function (method) {
// delete this.log[method];
delete this.log[method];
}.bind(this));
}
this.debug = false;
},
regexpToText: function (t) {
return t.replace(/\.\*\\./g, '*.').replace(/\\{2}/g, '^^').replace(/\\/g, '').replace(/\^\^/g, '\\');
}
};

View File

@@ -51,7 +51,7 @@ Logger.prototype.required = function (val) {
Logger.prototype.debug = false;
Logger.prototype._log = function (type, msg) {
if (required) {
bus.emit('log', { type: type, message: msg, colour: msg });
bus.emit('log', { type: type, message: msg || '', colour: msg || '' });
} else if (type === 'error') {
util.error(msg);
} else {

View File

@@ -31,8 +31,8 @@
],
"main": "./lib/nodemon",
"scripts": {
"coverage": "istanbul cover _mocha -- --timeout 15000 --ui bdd --reporter list test/**/*.test.js",
"test": "node_modules/mocha/bin/_mocha --timeout 15000 --ui bdd --reporter list test/**/*.test.js",
"coverage": "istanbul cover _mocha -- --timeout 20000 --ui bdd --reporter list test/**/*.test.js",
"test": "node_modules/mocha/bin/_mocha --timeout 20000 --ui bdd test/**/*.test.js",
"web": "node web"
},
"devDependencies": {
@@ -43,6 +43,7 @@
"touch": "0.0.2"
},
"dependencies": {
"update-notifier": "~0.1.7"
"update-notifier": "~0.1.7",
"minimatch": "~0.2.14"
}
}

View File

@@ -7,16 +7,16 @@ var exec = require('../../lib/config/exec'),
describe('nodemon exec', function () {
it('should default to node', function () {
var options = exec({ script: 'index.js' });
assert(options.exec === 'node');
assert(options.ext === '.js$');
assert(options.exec === 'node', 'exec is node');
assert(options.ext === 'js');
});
it('should support --debug', function () {
var options = exec({ script: 'app.js', nodeArgs: [ '--debug' ]});
assert(options.exec === 'node');
assert(options.execArgs.indexOf('--debug') !== -1);
assert(options.ext.indexOf('.js') !== -1);
assert(options.exec === 'node', 'exec is node');
assert(options.execArgs.indexOf('--debug') !== -1, '--debug is in the execArgs');
assert(options.ext.indexOf('js') !== -1, 'extension watched is .js');
});
it('should support --debug=XXXX', function () {
@@ -24,7 +24,7 @@ describe('nodemon exec', function () {
assert(options.exec === 'node');
assert(options.execArgs.indexOf('--debug=9999') !== -1);
assert(options.ext.indexOf('.js') !== -1);
assert(options.ext.indexOf('js') !== -1);
});
it('should support multiple extensions', function () {
@@ -58,7 +58,7 @@ describe('nodemon exec', function () {
it('should use coffeescript on .coffee', function () {
var options = exec({ script: 'index.coffee' });
assert(options.exec.indexOf('coffee') === 0, 'using coffeescript to execute');
assert(options.ext.indexOf('.coffee') !== -1);
assert(options.ext.indexOf('coffee') !== -1);
});
it('should support coffeescript in debug mode', function () {
@@ -66,14 +66,14 @@ describe('nodemon exec', function () {
assert(options.exec.indexOf('coffee') === 0, 'using coffeescript to execute');
assert(options.execArgs.indexOf('--debug') !== -1);
assert(options.ext.indexOf('.coffee') !== -1);
assert(options.ext.indexOf('coffee') !== -1);
});
it('should support custom execs', function () {
var options = exec({ script: 'app.py', exec: 'python'});
assert(options.exec === 'python');
assert(options.ext.indexOf('.py') !== -1);
assert(options.ext.indexOf('py') !== -1);
});
it('should support custom executables with arguments', function () {
@@ -81,7 +81,7 @@ describe('nodemon exec', function () {
assert(options.exec === 'python');
assert(options.execArgs.indexOf('--debug') !== -1);
assert(options.ext.indexOf('.py') !== -1);
assert(options.ext.indexOf('py') !== -1);
});
});

View File

@@ -1,10 +1,12 @@
'use strict';
/*global describe:true, it: true, afterEach: true, beforeEach: true */
/*global describe:true, it: true, afterEach: true, beforeEach: true, after:true */
var load = require('../../lib/config/load'),
path = require('path'),
testUtils = require('../utils'),
utils = require('../../lib/utils'),
rules = require('../../lib/rules'),
exec = require('../../lib/config/exec'),
nodemon = require('../../lib/nodemon'),
assert = require('assert');
describe('config load', function () {
@@ -16,13 +18,19 @@ describe('config load', function () {
utils.home = oldhome;
});
after(function (done) {
// clean up just in case.
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
});
function removeRegExp(options) {
delete options.watch.re;
delete options.ignore.re;
}
utils.quiet();
beforeEach(function () {
// move to the fixtures directory to allow for config loading
process.chdir(path.resolve(pwd, 'test/fixtures'));
@@ -31,6 +39,30 @@ describe('config load', function () {
rules.reset();
});
it('should remove ignore defaults if user provides their own', function (done) {
nodemon({
script: testUtils.appjs,
verbose: true
}).on('log', function (event) {
// console.log(event.colour);
}).on('start', function () {
assert.ok(nodemon.config.options.ignore.indexOf('one') !== -1, 'Contains "one" path');
assert.ok(nodemon.config.options.ignore.indexOf('three') !== -1, 'Contains "three" path');
// note: we use the escaped format: \\.git
assert.ok(nodemon.config.options.ignore.indexOf('\\.git') === -1, 'nodemon is not ignoring (default) .git');
nodemon.on('exit', function () {
nodemon.reset();
done();
});
setTimeout(function () {
nodemon.emit('quit');
}, 1000);
});
});
it('should support old .nodemonignore', function (done) {
// prevents our test from finding the nodemon.json files
process.chdir(path.resolve(pwd, 'test/fixtures/legacy'));
@@ -41,8 +73,7 @@ describe('config load', function () {
options = {};
load(settings, options, config, function (config) {
removeRegExp(config);
assert(config.ignore.length > 0, 'no ignore rules found');
assert(config.ignore.length === 5, '5 rules found: ' + config.ignore);
done();
});
});
@@ -53,8 +84,7 @@ describe('config load', function () {
settings = { quiet: true },
options = {};
load(settings, options, config, function (config) {
removeRegExp(config);
assert(config.verbose);
assert(config.verbose, 'we are verbose');
// ensure global mapping works too
var options = exec({ script: 'template.jade' }, config.execMap);
@@ -71,8 +101,9 @@ describe('config load', function () {
options = {};
load(settings, options, config, function (config) {
removeRegExp(config);
assert.deepEqual(config.ignore, ['one', 'three']);
assert.deepEqual(config.watch, ['four']);
assert.ok(config.ignore.indexOf('one') !== -1, 'ignore contains "one": ' + config.ignore);
assert.ok(config.ignore.indexOf('three') !== -1, 'ignore contains "three": ' + config.ignore);
assert.deepEqual(config.watch, ['four'], 'watch is "four": ' + config.watch);
done();
});
});
@@ -83,8 +114,8 @@ describe('config load', function () {
options = {};
load(settings, options, config, function (config) {
removeRegExp(config);
assert.deepEqual(config.ignore, ['one']);
assert.deepEqual(config.watch, ['one']);
assert.deepEqual(config.ignore, ['one'], 'ignore is "one": ' + config.ignore);
assert.deepEqual(config.watch, ['one'], 'watch is "one": ' + config.watch);
done();
});
});

View File

@@ -4,4 +4,5 @@
/public/* # static files
./README.md # a specific file
*.css # ignore any CSS files too
:(\d)*\.js # monitor javascript files with only digits in their name
:(\d)*\.js # ignore javascript files with only digits in their name
app.json # any app.json file

View File

@@ -10,7 +10,7 @@ var utils = require('../utils'),
cleanup = utils.cleanup,
run = utils.run;
describe('nodemon simply running', function () {
describe('nodemon fork simply running', function () {
it('should start', function (done) {
var p = run(appjs, {
output: function (data) {
@@ -28,7 +28,7 @@ describe('nodemon simply running', function () {
});
describe('nodemon monitor', function () {
describe('nodemon fork monitor', function () {
var complete = function (p, done, err) {
p.once('exit', function () {
done(err);

View File

@@ -1,7 +1,6 @@
'use strict';
/*global describe:true, it: true, after: true */
var nodemon = require('../../lib/'),
assert = require('assert'),
var assert = require('assert'),
fs = require('fs'),
utils = require('../utils'),
colour = require('../../lib/utils/colour'),
@@ -20,9 +19,6 @@ describe('nodemon fork child restart', function () {
after(function () {
fs.unlink(tmpjs);
fs.unlink(tmpmd);
// clean up just in case.
nodemon.emit('quit');
nodemon.removeAllListners();
});
it('should happen when monitoring a single extension', function (done) {
@@ -42,8 +38,6 @@ describe('nodemon fork child restart', function () {
}, 1000);
} else if (event.type === 'restart') {
assert(true, 'nodemon restarted');
nodemon.emit('quit');
nodemon.removeAllListners();
cleanup(p, done);
}
});
@@ -64,8 +58,6 @@ describe('nodemon fork child restart', function () {
var changes = msg.slice(-5).split('/');
var restartedOn = changes.pop();
assert(restartedOn === '1', 'nodemon restarted on a single file change');
nodemon.emit('quit');
nodemon.removeAllListners();
cleanup(p, done);
}
}

View File

@@ -7,9 +7,11 @@ var nodemon = require('../../lib/'),
appjs = path.resolve(__dirname, '..', 'fixtures', 'app.js');
describe('require-able', function () {
afterEach(function (){
nodemon.emit('quit');
nodemon.removeAllListners();
afterEach(function (done) {
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
});
it('should know nodemon has been required', function () {
@@ -28,7 +30,7 @@ describe('require-able', function () {
nodemon.emit('quit');
}).on('quit', function () {
assert(restarted, 'nodemon restarted and quit properly');
nodemon.removeAllListners();
nodemon.reset();
done();
}).on('log', function (event) {
// console.log(event.message);
@@ -47,8 +49,8 @@ describe('require-able', function () {
nodemon.emit('quit');
}).on('quit', function () {
assert(restarted);
nodemon.reset();
// unbind events for testing again
nodemon.removeAllListners();
done();
});
});

152
test/monitor/match.test.js Normal file
View File

@@ -0,0 +1,152 @@
'use strict';
/*global describe:true, it: true */
var assert = require('assert'),
match = require('../../lib/monitor/match'),
config = require('../../lib/config'),
path = require('path'),
nodemonUtils = require('../../lib/utils'),
utils = require('../utils');
describe('match', function () {
var monitor = [
'!.git',
'!node_modules/*',
'!public/*',
'!npm-debug.log',
'!node_modules/*',
'views/server/*.coffee',
'!*.coffee',
];
it('should return based on number of slashes in monitor rules', function () {
var files = [ 'views/server/remy.coffee', 'random.coffee', '/User/remy/app/server/foo.coffee' ];
var results = match(files, monitor); // ignoring extension support
assert(results.result.length === 1, 'expecting 1 file in good');
});
it('should apply *.js to any js file', function () {
var files = [utils.appjs];
var result = match(files, ['*.*'], 'js');
assert.deepEqual(result.result, files, 'file returned from match, matches');
assert(result.ignored === 0, 'no files were ignored');
assert(result.watched === files.length, 'a single file was matched');
});
it('should ignore .coffee if watching *.js', function () {
var files = [utils.appcoffee];
var result = match(files, ['*.*'], 'js');
assert.deepEqual(result.result, [], 'no results returned');
});
it('should match .coffee if watching *.js & *.coffee', function (done) {
config.load({
ext: 'js coffee'
}, function (config) {
var files = [utils.appcoffee];
var result = match(files, config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, files, 'coffee file matched');
assert(result.ignored === 0, '0 files ignored');
done();
});
});
it('should ignore nodemon default rules', function (done) {
config.load({ ext: '*.js' }, function (config) {
var files = [utils.appjs, path.join(__dirname, '/.git/foo.js')];
var result = match(files, config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, files.slice(0, 1), 'first file matched');
assert(result.ignored === 1, '.git file was ignored');
assert(result.watched === 1, 'a single file was matched');
done();
});
});
it('should ignore directories', function (done) {
config.load({
ext: 'js',
ignore: 'test/fixtures'
}, function (config) {
var files = [utils.appjs];
var result = match(files, config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, [], 'should be no files matched');
done();
});
});
it('should check all directories by default', function (done) {
config.load({
ext: 'js'
}, function (config) {
var files = [utils.appjs];
var result = match(files, config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, files, 'results should match');
done();
});
});
it('should support old .nodemonignore', function (done) {
// prevents our test from finding the nodemon.json files
var pwd = process.cwd(),
old = nodemonUtils.home;
process.chdir(path.resolve(pwd, 'test/fixtures/legacy'));
nodemonUtils.home = path.resolve(pwd, 'test/fixtures/legacy');
// will load the legacy file format
config.load({ script: utils.appjs, ext: 'js json' }, function (config) {
var files = [utils.appjs];
var result = match(files, config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, files, 'allows app.js: ' + result.result);
files = [path.resolve(pwd, 'test/fixtures/app.json')];
result = match(files, config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, [], 'nothing matched' + result.result);
process.chdir(pwd);
nodemonUtils.home = old;
done();
});
});
it('should be specific about directories', function (done) {
config.load({
ext: 'js md jade',
watch: ['lib']
}, function (config) {
var files = [utils.appjs];
var result = match(files, config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, [], 'no results');
done();
});
});
it('should not match coffee when monitoring just js', function (done) {
config.load({
script: utils.appjs
}, function (config) {
var result = match([utils.appcoffee], config.options.monitor, config.options.execOptions.ext);
assert.deepEqual(result.result, [], 'no results');
done();
});
});
});

View File

@@ -10,11 +10,13 @@ var nodemon = require('../../lib/'),
describe('when nodemon runs', function () {
var tmp = path.resolve('test/fixtures/test' + crypto.randomBytes(16).toString('hex') + '.js');
after(function () {
after(function (done) {
fs.unlink(tmp);
// clean up just in case.
nodemon.emit('quit');
nodemon.removeAllListners();
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
});
it('should wait when the script crashes', function (done) {
@@ -28,9 +30,10 @@ describe('when nodemon runs', function () {
}, 1000);
}).on('restart', function () {
assert(true, 'nodemon restarted');
nodemon.emit('quit');
nodemon.removeAllListners();
done();
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
});
});
@@ -47,9 +50,10 @@ describe('when nodemon runs', function () {
}, 500);
}).on('restart', function () {
assert(true, 'nodemon restarted');
nodemon.emit('quit');
nodemon.removeAllListners();
done();
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
});
});
@@ -71,8 +75,7 @@ describe('when nodemon runs', function () {
assert(false, 'detected crashed state');
}).on('exit', function () {
assert(true, 'quit correctly');
nodemon.emit('quit');
nodemon.removeAllListners();
nodemon.reset();
done();
setTimeout(function () {
@@ -82,4 +85,4 @@ describe('when nodemon runs', function () {
});
});
});
});

View File

@@ -10,38 +10,48 @@ var nodemon = require('../../lib/'),
crypto = require('crypto'),
baseFilename = 'test/fixtures/test' + crypto.randomBytes(16).toString('hex');
describe('nodemon child restart', function () {
describe('nodemon monitor child restart', function () {
var tmpjs = path.resolve(baseFilename + '.js'),
tmpmd = path.resolve(baseFilename + '.md');
function write(both) {
fs.writeFileSync(tmpjs, 'true;');
if (both) {
fs.writeFileSync(tmpmd, '# true');
}
}
after(function (done) {
fs.unlink(tmpjs);
fs.unlink(tmpmd);
// clean up just in case.
bus.once('exit', done);
nodemon.emit('quit');
nodemon.removeAllListners();
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
});
it('should happen when monitoring a single extension', function (done) {
fs.writeFileSync(tmpjs, 'true;');
write();
nodemon({ script: tmpjs, verbose: true, ext: 'js' }).on('start', function () {
setTimeout(function () {
touch.sync(tmpjs);
}, 1000);
}).on('restart', function () {
assert(true, 'nodemon restarted');
bus.once('exit', done);
nodemon.emit('quit');
nodemon.removeAllListners();
});
setTimeout(function () {
nodemon({ script: tmpjs, verbose: true, ext: 'js' }).on('start', function () {
setTimeout(function () {
touch.sync(tmpjs);
}, 1500);
}).on('restart', function () {
assert(true, 'nodemon restarted');
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
});
}, 2000);
});
it('should happen when monitoring multiple extensions', function (done) {
write(true);
setTimeout(function () {
fs.writeFileSync(tmpjs, 'true;');
fs.writeFileSync(tmpmd, '# true');
nodemon({
script: tmpjs,
@@ -50,7 +60,7 @@ describe('nodemon child restart', function () {
}).on('start', function () {
setTimeout(function () {
touch.sync(tmpmd);
}, 1000);
}, 1500);
}).on('log', function (event) {
// console.log(event.message);
var msg = event.message;
@@ -58,11 +68,41 @@ describe('nodemon child restart', function () {
var changes = msg.trim().slice(-5).split('/');
var restartedOn = changes.pop();
assert(restartedOn === '1', 'nodemon restarted on a single file change');
bus.once('exit', done);
nodemon.emit('quit');
nodemon.removeAllListners();
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
}
});
}, 2000);
});
it('should restart when watching directory', function (done) {
write(true);
setTimeout(function () {
nodemon({
script: tmpjs,
verbose: true,
ext: 'js md',
watch: ['test/fixtures/']
}).on('start', function () {
setTimeout(function () {
touch.sync(tmpmd);
}, 1000);
}).on('log', function (event) {
var msg = event.message;
if (utils.match(msg, 'changes after filters')) {
var changes = msg.trim().slice(-5).split('/');
var restartedOn = changes.pop();
assert(restartedOn === '1', 'nodemon restarted when watched directory');
nodemon.once('exit', function () {
nodemon.reset();
done();
}).emit('quit');
}
});
}, 2000);
});
});

View File

@@ -1,6 +1,7 @@
'use strict';
/*global describe:true, it: true, beforeEach: true */
var fs = require('fs'),
nodemon = require('../../lib/nodemon'),
rules = require('../../lib/rules'),
assert = require('assert');
@@ -22,12 +23,26 @@ describe('nodemon rules', function () {
};
beforeEach(function () {
rules.reset();
nodemon.reset();
});
it('should be resetable', function (done) {
nodemon.reset();
rules.load('./test/fixtures/simple.json', function () {
nodemon.reset();
rules.load('./test/fixtures/comments', function (error, rules) {
assert.deepEqual(rules, { watch: [], ignore: [] }, 'rules are empty: ' + JSON.stringify(rules));
done();
});
});
});
it('should read json', function (done) {
rules.load('./test/fixtures/simple.json', function (error, rules) {
assert(rules.ignore.re.test('/public/anything'), 'ignores public directory');
assert(typeof rules === 'object', 'rules file is parsed');
done();
});
});
@@ -48,9 +63,9 @@ describe('nodemon rules', function () {
});
});
it('should read regular expressions', function (done) {
it('should ignore regular expressions', function (done) {
rules.load(fixtures.regexp.path, function (error, rules) {
assert.equal(rules.ignore.re.test('nodemon.js'), true);
assert.deepEqual(rules, { 'watch': [], 'ignore': [] }, 'rules are empty');
done();
});
});

View File

@@ -28,22 +28,22 @@ describe('logger', function () {
});
});
it('should not log detail if debug is off', function (done) {
logger.debug = false;
// it('should not log detail if debug is off', function (done) {
// logger.debug = false;
function handler() {
assert(false, 'logged a message when we should not have done');
bus.removeListener('log', handler);
done();
}
// function handler() {
// assert(false, 'logged a message when we should not have done');
// bus.removeListener('log', handler);
// done();
// }
bus.addListener('log', handler);
// bus.addListener('log', handler);
logger.detail('detail');
// logger.detail('detail');
setTimeout(function () {
bus.removeListener('log', handler);
done();
}, 500);
});
// setTimeout(function () {
// bus.removeListener('log', handler);
// done();
// }, 500);
// });
});