diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 264ef5a..4f97cac 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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 diff --git a/README.md b/README.md index b133f12..9c7e86b 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/bin/nodemon.js b/bin/nodemon.js index 01b3060..3867539 100755 --- a/bin/nodemon.js +++ b/bin/nodemon.js @@ -12,6 +12,5 @@ if (notifier.update) { } var options = cli.parse(process.argv); -options.restartable = 'rs'; nodemon(options); \ No newline at end of file diff --git a/doc/cli/usage.txt b/doc/cli/usage.txt new file mode 100644 index 0000000..bca98b5 --- /dev/null +++ b/doc/cli/usage.txt @@ -0,0 +1,3 @@ + Usage: nodemon [nodemon options] [script.js] [args] + + See "nodemon --help" for more. diff --git a/doc/rules.md b/doc/rules.md new file mode 100644 index 0000000..da9cbdc --- /dev/null +++ b/doc/rules.md @@ -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); +``` diff --git a/lib/config/defaults.js b/lib/config/defaults.js index 543247f..df24f9a 100644 --- a/lib/config/defaults.js +++ b/lib/config/defaults.js @@ -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 }; \ No newline at end of file diff --git a/lib/config/exec.js b/lib/config/exec.js index b9d3f4a..ab6fb88 100644 --- a/lib/config/exec.js +++ b/lib/config/exec.js @@ -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; diff --git a/lib/config/index.js b/lib/config/index.js index 67760e2..e022293 100644 --- a/lib/config/index.js +++ b/lib/config/index.js @@ -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; diff --git a/lib/config/load.js b/lib/config/load.js index 2662197..69884e7 100644 --- a/lib/config/load.js +++ b/lib/config/load.js @@ -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 diff --git a/lib/monitor/match.js b/lib/monitor/match.js new file mode 100644 index 0000000..16a43cf --- /dev/null +++ b/lib/monitor/match.js @@ -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 + }; +} \ No newline at end of file diff --git a/lib/monitor/run.js b/lib/monitor/run.js index b913bad..23da12d 100644 --- a/lib/monitor/run.js +++ b/lib/monitor/run.js @@ -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 () { diff --git a/lib/monitor/watch.js b/lib/monitor/watch.js index cb91a65..a290c05 100644 --- a/lib/monitor/watch.js +++ b/lib/monitor/watch.js @@ -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); + } }); } }; \ No newline at end of file diff --git a/lib/nodemon.js b/lib/nodemon.js index 83be052..2b84c16 100644 --- a/lib/nodemon.js +++ b/lib/nodemon.js @@ -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; diff --git a/lib/rules/add.js b/lib/rules/add.js index 0b835af..24bc53f 100644 --- a/lib/rules/add.js +++ b/lib/rules/add.js @@ -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 : 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); + } } \ No newline at end of file diff --git a/lib/rules/index.js b/lib/rules/index.js index a609329..f926387 100644 --- a/lib/rules/index.js +++ b/lib/rules/index.js @@ -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: { diff --git a/lib/utils/bus.js b/lib/utils/bus.js index f818d9a..68a31c1 100644 --- a/lib/utils/bus.js +++ b/lib/utils/bus.js @@ -1,3 +1,4 @@ +'use strict'; var events = require('events'), util = require('util'); diff --git a/lib/utils/index.js b/lib/utils/index.js index c6c3fdf..6850195 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -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, '\\'); } }; diff --git a/lib/utils/log.js b/lib/utils/log.js index 31fc91c..e2eb1e5 100644 --- a/lib/utils/log.js +++ b/lib/utils/log.js @@ -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 { diff --git a/package.json b/package.json index 284293b..8366c98 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/test/cli/exec.test.js b/test/cli/exec.test.js index a1dabe3..922d7cc 100644 --- a/test/cli/exec.test.js +++ b/test/cli/exec.test.js @@ -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); }); }); \ No newline at end of file diff --git a/test/config/load.test.js b/test/config/load.test.js index 8b75106..ebd74a1 100644 --- a/test/config/load.test.js +++ b/test/config/load.test.js @@ -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(); }); }); diff --git a/test/fixtures/legacy/.nodemonignore b/test/fixtures/legacy/.nodemonignore index ac5e5be..dbb953f 100644 --- a/test/fixtures/legacy/.nodemonignore +++ b/test/fixtures/legacy/.nodemonignore @@ -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 \ No newline at end of file +:(\d)*\.js # ignore javascript files with only digits in their name +app.json # any app.json file \ No newline at end of file diff --git a/test/fork/change-detect.test.js b/test/fork/change-detect.test.js index b739fda..c9363ae 100644 --- a/test/fork/change-detect.test.js +++ b/test/fork/change-detect.test.js @@ -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); diff --git a/test/fork/watch-restart.test.js b/test/fork/watch-restart.test.js index 1ce9020..9701d52 100644 --- a/test/fork/watch-restart.test.js +++ b/test/fork/watch-restart.test.js @@ -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); } } diff --git a/test/lib/require.test.js b/test/lib/require.test.js index e3cf2fe..ea34d27 100644 --- a/test/lib/require.test.js +++ b/test/lib/require.test.js @@ -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(); }); }); diff --git a/test/monitor/match.test.js b/test/monitor/match.test.js new file mode 100644 index 0000000..69d11c8 --- /dev/null +++ b/test/monitor/match.test.js @@ -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(); + }); + }); +}); \ No newline at end of file diff --git a/test/monitor/run.test.js b/test/monitor/run.test.js index bf172fa..1af7fa0 100644 --- a/test/monitor/run.test.js +++ b/test/monitor/run.test.js @@ -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 () { }); }); -}); \ No newline at end of file +}); diff --git a/test/monitor/watch-restart.test.js b/test/monitor/watch-restart.test.js index df1b3c6..c7cac8b 100644 --- a/test/monitor/watch-restart.test.js +++ b/test/monitor/watch-restart.test.js @@ -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); + }); + }); \ No newline at end of file diff --git a/test/rules/index.test.js b/test/rules/index.test.js index a642c43..95e5a78 100644 --- a/test/rules/index.test.js +++ b/test/rules/index.test.js @@ -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(); }); }); diff --git a/test/utils/log.test.js b/test/utils/log.test.js index e38b327..90ac24e 100644 --- a/test/utils/log.test.js +++ b/test/utils/log.test.js @@ -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); + // }); }); \ No newline at end of file