refactor: move watch out in favour of chokidar

Plus jscs clean ups
This commit is contained in:
Remy Sharp
2015-08-31 18:24:35 +01:00
parent 2e56d8404e
commit b0fd56f7de
26 changed files with 273 additions and 466 deletions

13
.jscsrc Normal file
View File

@@ -0,0 +1,13 @@
{
"preset": "node-style-guide",
"requireCapitalizedComments": null,
"requireSpacesInAnonymousFunctionExpression": {
"beforeOpeningCurlyBrace": true,
"beforeOpeningRoundBrace": true
},
"disallowSpacesInNamedFunctionExpression": {
"beforeOpeningRoundBrace": true
},
"excludeFiles": ["node_modules/**"],
"disallowSpacesInFunction": null
}

View File

@@ -10,5 +10,7 @@
"node": true, "node": true,
"quotmark": "single", "quotmark": "single",
"undef": true, "undef": true,
"strict": false,
"unused": true "unused": true
} }

View File

@@ -1,9 +1,10 @@
'use strict';
var parse = require('./parse'); var parse = require('./parse');
/** /**
* Converts a string to command line args, in particular * Converts a string to command line args, in particular
* groups together quoted values * groups together quoted values.
* This is a utility function to allow calling nodemon as a required
* library, but with the CLI args passed in (instead of an object).
* *
* @param {String} string * @param {String} string
* @return {Array} * @return {Array}

View File

@@ -1,4 +1,3 @@
'use strict';
/* /*
nodemon is a utility for node, and replaces the use of the executable nodemon is a utility for node, and replaces the use of the executable
@@ -15,9 +14,9 @@ nodemon can be run in a number of ways:
*/ */
var fs = require('fs'), var fs = require('fs');
path = require('path'), var path = require('path');
existsSync = fs.existsSync || path.existsSync; var existsSync = fs.existsSync || path.existsSync;
module.exports = parse; module.exports = parse;
@@ -96,19 +95,6 @@ function parse(argv) {
} }
} }
// FIXME this was commented out on 2014-12-05 due to logic being moved
// to exec.js. I know I have git to revive the code if I need to, but
// I'm going to leave it here for the short term. Next passerby: feel free
// to remove.
// -------------
// allows the user to specify a script and for nodemon to throw an exception
// *instead* of echoing out the usage and ignoring the poor user altogether,
// just because the filename (or argument) specified wasn't found.
// if (!script && args.length) {
// script = args.pop();
// }
nodemonOptions.script = script; nodemonOptions.script = script;
nodemonOptions.args = args; nodemonOptions.args = args;
@@ -216,7 +202,7 @@ function nodemonOption(options, arg, eatNext) {
function findAppScript() { function findAppScript() {
// nodemon has been run alone, so try to read the package file // nodemon has been run alone, so try to read the package file
// or try to read the index.js file // or try to read the index.js file
if (existsSync('./index.js')) { // FIXME is ./ the right location? if (existsSync('./index.js')) {
return { exec: null, script: 'index.js' }; return { exec: null, script: 'index.js' };
} }

View File

@@ -1,9 +1,8 @@
'use strict'; var utils = require('../utils');
var utils = require('../utils'), var watchable = require('./watchable');
watchable = require('./watchable'), var fs = require('fs');
fs = require('fs'), var exec = require('child_process').exec;
exec = require('child_process').exec, var checkComplete = false;
checkComplete = false;
module.exports = checkWatchSupport; module.exports = checkWatchSupport;
@@ -21,16 +20,14 @@ function checkWatchSupport(config, callback) {
} }
var ready = function () { var ready = function () {
if (checkComplete) { if (!checkComplete) {
//utils.bus.emit('config:update');
} else {
checkComplete = true; checkComplete = true;
callback(config); callback(config);
} }
}; };
var alternativeCheck = function () { var alternativeCheck = function () {
watchable.check(function(success) { watchable.check(function (success) {
// whether or not fs.watch actually works on this platform, tested and set // whether or not fs.watch actually works on this platform, tested and set
// later before starting // later before starting
config.system.useWatch = success; config.system.useWatch = success;
@@ -42,15 +39,21 @@ function checkWatchSupport(config, callback) {
// this is because it has a default ulimit of 256 - which is WAY LOW, // this is because it has a default ulimit of 256 - which is WAY LOW,
// and without asking the user to `unlimit -n <BIG-ASS-NUMBER>` it'll throw // and without asking the user to `unlimit -n <BIG-ASS-NUMBER>` it'll throw
// up all over your screen like this: http://d.pr/i/R6B8+ // up all over your screen like this: http://d.pr/i/R6B8+
// even with higher ulimit -n Mac has another problem: https://github.com/joyent/node/issues/5463 // even with higher ulimit -n Mac has another problem:
// https://github.com/joyent/node/issues/5463
// This will be fixed in 0.12, before then we default to find // This will be fixed in 0.12, before then we default to find
config.system.useFind = utils.isMac || utils.isLinux || !fs.watch; config.system.useFind = utils.isMac || utils.isLinux || !fs.watch;
var mtime = utils.isMac ? '-mtime -1s' : '-mmin -0.01'; var mtime = utils.isMac ? '-mtime -1s' : '-mmin -0.01';
if (config.system.useFind) { if (config.system.useFind) {
exec('find -L /dev/null -type f ' + mtime + ' -print', function(error) { exec('find -L /dev/null -type f ' + mtime + ' -print', function (error) {
if (error) { if (error) {
if (!fs.watch) { if (!fs.watch) {
utils.log.error('The version of node you are using combined with the version of find being used does not support watching files. Upgrade to a newer version of node, or install a version of find that supports search by seconds.'); var notice = 'The version of node you are using combined with the ' +
'version of find being used does not support watching files. ' +
'Upgrade to a newer version of node, or install a version of ' +
'find that supports search by seconds.';
utils.log.error(notice);
process.exit(1); process.exit(1);
} else { } else {
config.system.useFind = false; config.system.useFind = false;

View File

@@ -1,11 +1,24 @@
'use strict';
module.exports = command; module.exports = command;
/**
* command constructs the executable command to run in a shell including the
* user script, the command arguments.
*
* @param {Object} settings Object as:
* { execOptions: {
* exec: String,
* [script: String],
* [scriptPosition: Number],
* [execArgs: Array<string>]
* }
* }
* @return {Object} an object with the node executable and the
* arguments to the command
*/
function command(settings) { function command(settings) {
var options = settings.execOptions; var options = settings.execOptions;
var executable = options.exec, var executable = options.exec;
args = []; var args = [];
// after "executable" go the exec args (like --debug, etc) // after "executable" go the exec args (like --debug, etc)
if (options.execArgs) { if (options.execArgs) {
@@ -19,11 +32,12 @@ function command(settings) {
// after the "executable" goes the user's script // after the "executable" goes the user's script
if (options.script) { if (options.script) {
args.splice((options.scriptPosition || 0) + options.execArgs.length, 0, options.script); args.splice((options.scriptPosition || 0) +
options.execArgs.length, 0, options.script);
} }
return { return {
executable: executable, executable: executable,
args: args args: args,
}; };
} }

View File

@@ -5,8 +5,8 @@ module.exports = {
py: 'python', py: 'python',
rb: 'ruby', rb: 'ruby',
// more can be added here such as ls: lsc - but please ensure it's cross // more can be added here such as ls: lsc - but please ensure it's cross
// compatible with linux, mac and windows, or make the default.js dynamically // compatible with linux, mac and windows, or make the default.js
// append the `.cmd` for node based utilities // dynamically append the `.cmd` for node based utilities
}, },
ignore: ['.git', 'node_modules', 'bower_components', '.sass-cache'], ignore: ['.git', 'node_modules', 'bower_components', '.sass-cache'],
watch: ['*.*'], watch: ['*.*'],
@@ -16,6 +16,6 @@ module.exports = {
// 'stdout' refers to the default behaviour of a required nodemon's child, // 'stdout' refers to the default behaviour of a required nodemon's child,
// but also includes stderr. If this is false, data is still dispatched via // but also includes stderr. If this is false, data is still dispatched via
// nodemon.on('stdout/stderr') // nodemon.on('stdout/stderr')
stdout: true stdout: true,
}; };

View File

@@ -1,6 +1,5 @@
'use strict'; var path = require('path');
var path = require('path'), var utils = require('../utils');
utils = require('../utils');
module.exports = exec; module.exports = exec;
@@ -30,7 +29,8 @@ function execFromPackage() {
} }
function replace(map, str) { function replace(map, str) {
return str.replace(new RegExp('\{\{(' + Object.keys(map).join('|') + ')\}\}'), function (all, m) { var re = new RegExp('{{(' + Object.keys(map).join('|') + ')}}');
return str.replace(re, function (all, m) {
return map[m] || all || ''; return map[m] || all || '';
}); });
} }
@@ -51,10 +51,10 @@ function exec(nodemonOptions, execMap) {
execMap = {}; execMap = {};
} }
// if there's no exec found yet, then try to read it from the local package.json // if there's no exec found yet, then try to read it from the local
// this logic used to sit in the cli/parse, but actually the cli should be parsed // package.json this logic used to sit in the cli/parse, but actually the cli
// first, then the user options (via nodemon.json) then finally default down to // should be parsed first, then the user options (via nodemon.json) then
// pot shots at the directory via package.json // finally default down to pot shots at the directory via package.json
if (!nodemonOptions.exec && !nodemonOptions.script) { if (!nodemonOptions.exec && !nodemonOptions.script) {
var found = execFromPackage(); var found = execFromPackage();
if (found !== null) { if (found !== null) {
@@ -64,24 +64,26 @@ function exec(nodemonOptions, execMap) {
if (!nodemonOptions.script) { if (!nodemonOptions.script) {
nodemonOptions.script = found.script; nodemonOptions.script = found.script;
} }
if (Array.isArray(nodemonOptions.args) && nodemonOptions.scriptPosition === null) { if (Array.isArray(nodemonOptions.args) &&
nodemonOptions.scriptPosition === null) {
nodemonOptions.scriptPosition = nodemonOptions.args.length; nodemonOptions.scriptPosition = nodemonOptions.args.length;
} }
} }
} }
var options = utils.clone(nodemonOptions || {}), var options = utils.clone(nodemonOptions || {});
script = path.basename(options.script || ''), var script = path.basename(options.script || '');
scriptExt = path.extname(script).slice(1), var scriptExt = path.extname(script).slice(1);
extension = options.ext || scriptExt || 'js', var extension = options.ext || scriptExt || 'js';
execDefined = !!options.exec; var execDefined = !!options.exec;
// strip any leading periods int he extension // strip any leading periods int he extension
if (extension.indexOf('.') === 0) { if (extension.indexOf('.') === 0) {
extension = extension.slice(1); extension = extension.slice(1);
} }
// allows the user to simplify cli usage: https://github.com/remy/nodemon/issues/195 // allows the user to simplify cli usage:
// https://github.com/remy/nodemon/issues/195
// but always give preference to the user defined argument // but always give preference to the user defined argument
if (!options.exec && execMap[scriptExt] !== undefined) { if (!options.exec && execMap[scriptExt] !== undefined) {
options.exec = execMap[scriptExt]; options.exec = execMap[scriptExt];
@@ -99,9 +101,14 @@ function exec(nodemonOptions, execMap) {
options.exec = 'node'; options.exec = 'node';
} else { } else {
// allow variable substitution for {{filename}} and {{pwd}} // allow variable substitution for {{filename}} and {{pwd}}
var substitution = replace.bind(null, { filename: options.script, pwd: process.cwd() }); var substitution = replace.bind(null, {
filename: options.script,
pwd: process.cwd(),
});
var newExec = substitution(options.exec); var newExec = substitution(options.exec);
if (newExec !== options.exec && options.exec.indexOf('{{filename}}') !== -1) { if (newExec !== options.exec &&
options.exec.indexOf('{{filename}}') !== -1) {
options.script = null; options.script = null;
} }
options.exec = newExec; options.exec = newExec;
@@ -119,7 +126,8 @@ function exec(nodemonOptions, execMap) {
} }
// note: indexOf('coffee') handles both .coffee and .litcoffee // note: indexOf('coffee') handles both .coffee and .litcoffee
if (!execDefined && options.exec === 'node' && scriptExt.indexOf('coffee') !== -1) { if (!execDefined && options.exec === 'node' &&
scriptExt.indexOf('coffee') !== -1) {
options.exec = 'coffee'; options.exec = 'coffee';
// we need to get execArgs set before the script // we need to get execArgs set before the script

View File

@@ -1,18 +1,16 @@
'use strict';
/** /**
* Manages the internal config of nodemon, checking for the state of support * Manages the internal config of nodemon, checking for the state of support
* with fs.watch, how nodemon can watch files (using find or fs methods). * with fs.watch, how nodemon can watch files (using find or fs methods).
* *
* This is *not* the user's config. * This is *not* the user's config.
*/ */
var load = require('./load'), var load = require('./load');
rules = require('../rules'), var rules = require('../rules');
utils = require('../utils'), var utils = require('../utils');
command = require('./command'), var command = require('./command');
path = require('path'), var rulesToMonitor = require('../monitor/match').rulesToMonitor;
rulesToMonitor = require('../monitor/match').rulesToMonitor, var bus = utils.bus;
bus = utils.bus, var checkWatchSupport = require('./checkWatchSupport');
checkWatchSupport = require('./checkWatchSupport');
function reset() { function reset() {
rules.reset(); rules.reset();
@@ -34,7 +32,7 @@ var config = {
required: false, required: false,
dirs: [], dirs: [],
timeout: 1000, timeout: 1000,
options: {} options: {},
}; };
/** /**
@@ -57,14 +55,14 @@ config.load = function (settings, ready) {
options.watch.push('*.*'); options.watch.push('*.*');
} }
if (options.watch_interval) { config.watchInterval = options.watchInterval ||
config.watch_interval = options.watch_interval; options.watch_interval || // jshint ignore:line
} null;
var cmd = command(config.options); var cmd = command(config.options);
config.command = { config.command = {
raw: cmd, raw: cmd,
string: utils.stringify(cmd.executable, cmd.args) string: utils.stringify(cmd.executable, cmd.args),
}; };
// now run automatic checks on system adding to the config object // now run automatic checks on system adding to the config object
@@ -87,5 +85,4 @@ config.load = function (settings, ready) {
config.reset = reset; config.reset = reset;
module.exports = config; module.exports = config;

View File

@@ -1,12 +1,11 @@
'use strict'; var fs = require('fs');
var fs = require('fs'), var path = require('path');
path = require('path'), var exists = fs.exists || path.exists;
exists = fs.exists || path.exists, var utils = require('../utils');
utils = require('../utils'), var rules = require('../rules');
rules = require('../rules'), var parse = require('../rules/parse');
parse = require('../rules/parse'), var exec = require('./exec');
exec = require('./exec'), var defaults = require('./defaults');
defaults = require('./defaults');
module.exports = load; module.exports = load;
@@ -40,7 +39,7 @@ function load(settings, options, config, callback) {
scriptPosition: options.scriptPosition, scriptPosition: options.scriptPosition,
nodeArgs: options.nodeArgs, nodeArgs: options.nodeArgs,
ext: options.ext, ext: options.ext,
env: options.env env: options.env,
}, options.execMap); }, options.execMap);
// clean up values that we don't need at the top level // clean up values that we don't need at the top level
@@ -49,22 +48,6 @@ function load(settings, options, config, callback) {
delete options.args; delete options.args;
delete options.ext; delete options.ext;
/*
// if the script was discovered via package.main or package.start, it
// would have happened in the `exec` function above, except `options.script`
// doesn't get updated, when `options.execOptions.script` does actually
// hold the right value now, so we copy it across along with the
// scriptPosition as this is used in command.js to correctly construct
// the executable command
if (!options.script && options.execOptions.script) {
options.script = options.execOptions.script;
// options.scriptPosition = options.execOptions.scriptPosition;
}
*/
// copy the extension across to a user readable format
// options.ext = options.execOptions.ext.replace(/[\*\.\$]/g, '').split('|').join(' ');
if (options.quiet) { if (options.quiet) {
utils.quiet(); utils.quiet();
} }
@@ -105,7 +88,7 @@ function load(settings, options, config, callback) {
* *
* @param {Object} options nodemon user options * @param {Object} options nodemon user options
* @param {Function} success * @param {Function} success
* @param {String} filename the ignore file (.nodemonignore or nodemon-ignore) * @param {String} filename ignore file (.nodemonignore or nodemon-ignore)
* @param {Function} fail (optional) failure callback * @param {Function} fail (optional) failure callback
*/ */
function loadLegacyIgnore(options, success, filename, fail) { function loadLegacyIgnore(options, success, filename, fail) {

View File

@@ -1,10 +1,9 @@
'use strict'; var utils = require('../utils');
var utils = require('../utils'), var path = require('path');
path = require('path'), var crypto = require('crypto');
crypto = require('crypto'), var fs = require('fs');
fs = require('fs'), var watchFileName;
watchFileName, var watchFile;
watchFile;
// Attempts to see if fs.watch will work. On some platforms, it doesn't. // Attempts to see if fs.watch will work. On some platforms, it doesn't.
// See: http://nodejs.org/api/fs.html#fs_caveats // See: http://nodejs.org/api/fs.html#fs_caveats
@@ -29,14 +28,16 @@ function check(cb) {
tmpdir = '/tmp'; tmpdir = '/tmp';
} }
watchFileName = path.join(tmpdir, 'nodemonCheckFsWatch' + crypto.randomBytes(16).toString('hex')); watchFileName = path.join(tmpdir, 'nodemonCheckFsWatch' +
crypto.randomBytes(16).toString('hex'));
watchFile = fs.openSync(watchFileName, 'w'); watchFile = fs.openSync(watchFileName, 'w');
if (watchFile < 0) { if (watchFile < 0) {
utils.log.fail('Unable to write to temp directory. If you experience problems with file reloading, ensure ' + tmpdir + ' is writable.'); utils.log.fail('Unable to write to temp directory. If you experience ' +
'problems with file reloading, ensure ' + tmpdir + ' is writable.');
cb(true); cb(true);
return; return;
} }
fs.watch(watchFileName, function() { fs.watch(watchFileName, function () {
cb(true); cb(true);
}); });
@@ -56,7 +57,7 @@ function check(cb) {
// Verifies that fs.watch was not triggered and sends false to the callback // Verifies that fs.watch was not triggered and sends false to the callback
// but if the callback has already been used (changeDetected), it won't call. // but if the callback has already been used (changeDetected), it won't call.
var finish = function() { var finish = function () {
try { try {
fs.unlinkSync(watchFileName); fs.unlinkSync(watchFileName);
watchable.cb(false); watchable.cb(false);
@@ -67,7 +68,7 @@ var finish = function() {
process.on('exit', finish); process.on('exit', finish);
var watchable = module.exports = function (config, ready) { var watchable = module.exports = function (config, ready) {
check(function(success) { check(function (success) {
config.system.useWatch = success; config.system.useWatch = success;
if (changeDetected) { if (changeDetected) {
utils.bus.emit('config:update'); utils.bus.emit('config:update');

View File

@@ -1,5 +1,5 @@
var fs = require('fs'), var fs = require('fs');
path = require('path'); var path = require('path');
module.exports = help; module.exports = help;
@@ -15,7 +15,8 @@ function help(item) {
item = item.replace(/[^a-z]/gi, ''); item = item.replace(/[^a-z]/gi, '');
try { try {
var body = fs.readFileSync(path.join(__dirname, '..', '..', 'doc', 'cli', item + '.txt'), 'utf8'); var dir = path.join(__dirname, '..', '..', 'doc', 'cli', item + '.txt');
var body = fs.readFileSync(dir, 'utf8');
return body; return body;
} catch (e) { } catch (e) {
return '"' + item + '" help can\'t be found'; return '"' + item + '" help can\'t be found';

View File

@@ -1,7 +1,6 @@
var fs = require('fs'), var fs = require('fs');
utils = require('../utils'), var config = require('../config');
config = require('../config'), var path = require('path');
path = require('path');
module.exports = changedSince; module.exports = changedSince;

View File

@@ -1,9 +1,7 @@
'use strict'; var minimatch = require('minimatch');
var path = require('path');
var minimatch = require('minimatch'), var fs = require('fs');
path = require('path'), var utils = require('../utils');
fs = require('fs'),
utils = require('../utils');
module.exports = match; module.exports = match;
module.exports.rulesToMonitor = rulesToMonitor; module.exports.rulesToMonitor = rulesToMonitor;
@@ -105,10 +103,11 @@ function rulesToMonitor(watch, ignore, config) {
} }
function tryBaseDir(dir) { function tryBaseDir(dir) {
var stat;
if (/[?*\{\[]+/.test(dir)) { // if this is pattern, then try to find the base if (/[?*\{\[]+/.test(dir)) { // if this is pattern, then try to find the base
try { try {
var base = path.dirname(dir.replace(/([?*\{\[]+.*$)/, 'foo')); var base = path.dirname(dir.replace(/([?*\{\[]+.*$)/, 'foo'));
var stat = fs.statSync(base); stat = fs.statSync(base);
if (stat.isDirectory()) { if (stat.isDirectory()) {
return base; return base;
} }
@@ -117,7 +116,7 @@ function tryBaseDir(dir) {
} }
} else { } else {
try { try {
var stat = fs.statSync(dir); stat = fs.statSync(dir);
// if this path is actually a single file that exists, then just monitor // if this path is actually a single file that exists, then just monitor
// that, *specifically*. // that, *specifically*.
if (stat.isFile() || stat.isDirectory()) { if (stat.isFile() || stat.isDirectory()) {
@@ -155,7 +154,7 @@ function match(files, monitor, ext) {
if (prefix === '!') { if (prefix === '!') {
return '!**' + (prefix !== path.sep ? path.sep : '') + s.slice(1); return '!**' + (prefix !== path.sep ? path.sep : '') + s.slice(1);
} else if (s.slice(0, 2) == '..') { } else if (s.slice(0, 2) === '..') {
return path.resolve(process.cwd(), s); return path.resolve(process.cwd(), s);
} }
return '**' + (prefix !== path.sep ? path.sep : '') + s; return '**' + (prefix !== path.sep ? path.sep : '') + s;

View File

@@ -1,4 +1,3 @@
'use strict';
var fs = require('fs'); var fs = require('fs');
var config = require('../config'); var config = require('../config');
var offset = null; var offset = null;
@@ -34,8 +33,10 @@ module.exports = function () {
module.exports.pretty = function () { module.exports.pretty = function () {
var date = new Date(offset); var date = new Date(offset);
return two(date.getHours()) + 'h' + two(date.getMinutes()) + 'm' + two(date.getSeconds()) + 's'; return two(date.getHours()) + 'h' +
} two(date.getMinutes()) + 'm' +
two(date.getSeconds()) + 's';
};
function two(s) { function two(s) {
s += ''; s += '';

View File

@@ -1,18 +1,16 @@
'use strict'; var utils = require('../utils');
var utils = require('../utils'), var bus = utils.bus;
bus = utils.bus, var childProcess = require('child_process');
childProcess = require('child_process'), var spawn = childProcess.spawn;
spawn = childProcess.spawn, var exec = childProcess.exec;
exec = childProcess.exec, var watch = require('./watch');
watch = require('./watch'), var config = require('../config');
config = require('../config'), var child = null; // the actual child process we spawn
child = null, // the actual child process we spawn var killedAfterChange = false;
killedAfterChange = false, var noop = function() {};
// timeout = 1000, // check every 1 second var restart = null;
noop = function() {}, var psTree = require('ps-tree');
restart = null, var hasPS = true;
psTree = require('ps-tree'),
hasPS = true;
// discover if the OS has `ps`, and therefore can use psTree // discover if the OS has `ps`, and therefore can use psTree
exec('ps', function(error) { exec('ps', function(error) {
@@ -141,7 +139,9 @@ function run(options) {
// restart // restart
restart(); restart();
} else if (code === 0) { // clean exit - wait until file change to restart } else if (code === 0) { // clean exit - wait until file change to restart
if (runCmd) utils.log.status('clean exit - waiting for changes before restart'); if (runCmd) {
utils.log.status('clean exit - waiting for changes before restart');
}
child = null; child = null;
} }
} else { } else {
@@ -250,7 +250,7 @@ function kill(child, signal, callback) {
}))).on('close', callback); }))).on('close', callback);
}); });
} else { } else {
exec('kill -s ' + signal + ' ' + child.pid, function (error) { exec('kill -s ' + signal + ' ' + child.pid, function () {
// ignore if the process has been killed already // ignore if the process has been killed already
callback(); callback();
}); });
@@ -261,7 +261,7 @@ function kill(child, signal, callback) {
// stubbed out for now, filled in during run // stubbed out for now, filled in during run
run.kill = function (flag, callback) { run.kill = function (flag, callback) {
if (callback) { if (callback) {
callback() callback();
} }
}; };
run.restart = noop; run.restart = noop;

View File

@@ -1,251 +1,43 @@
'use strict'; module.exports = watch;
var fs = require('fs'), var chokidar = require('chokidar');
path = require('path'), var config = require('../config');
changedSince = require('./changed-since'), var path = require('path');
utils = require('../utils'), var utils = require('../utils');
bus = utils.bus, var bus = utils.bus;
match = require('./match'), var match = require('./match');
config = require('../config'), var watchers = [];
offset = require('./offset'), var debouncedBus;
childProcess = require('child_process'),
touch = require('touch'),
exec = childProcess.exec,
watched = [],
debouncedBus,
watchers = [];
var changeFunction = function () { function watch() {
utils.log.error('changeFunction called when it shouldn\'t be.'); if (watchers.length) {
}; return;
function reset() {
// manually remove all the file watchers.
// note that fs.unwatchFile doesn't really work, particularly in vagrant
// shared folders and in Travis CI...
var watcher;
while (watchers.length) {
watcher = watchers.pop();
watcher.removeAllListeners('change');
if (watcher.close) { // watchFile doesn't have this
watcher.close();
}
} }
// reset the watched directory too var dirs = [].slice.call(config.dirs);
watched.length = 0;
}
function showWatchCount() { dirs.forEach(function (dir) {
var cmds = []; var watcher = chokidar.watch(dir, {
config.dirs.forEach(function(dir) { // ignore our files, but also ignore dotfiles
cmds.push('find -L "' + dir + '" ' + ignoredFileTypesForFind(dir) + ' \\( -type f -print \\) | wc'); ignored: [config.options.ignore.re, /[\/\\]\./],
}); persistent: true,
exec(cmds.join(';'), function (error, stdout) {
if (!config.run) {
return; // we're too late
}
var total = 0;
stdout.split(/\n/).map(function (line) {
total += (line.trim().split(/\s+/)[0]||0) * 1;
}); });
utils.log.detail('watching ' + (total).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' files'); watcher.on('change', filterAndRestart);
watchers.push(watcher);
if (total > 25000) {
utils.log.fail('watching ' + (total).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') + ' files - this might cause high cpu usage. To reduce use "--watch".');
}
});
}
bus.on('config:update', function () {
reset();
if (config.system.useFind) {
// let's do a sanity check for the amount of files we'll be watching for...
showWatchCount();
// if native fs.watch doesn't work the way we want, we keep polling find
// command (mac only oddly)
changeFunction = function (lastStarted, callback) {
var cmds = [];
var delta = ((((Date.now() - config.lastStarted) * -1) + offset())/1000|0);
var mtime = utils.isMac ? '-mtime ' + delta + 's' : '-mmin ' + (delta/60);
if (delta > 0) {
// we can't use -mmin or -mtime, we need to use -newer, and best bet is
// to touch a file with our reference timestamp, and search http://superuser.com/a/416191
touch.sync('.nodemon-find-ref', { time: new Date(Date.now() + (delta * 1000)) });
mtime = '-newer .nodemon-find-ref';
}
// don't run the find when delta is 0 seconds, otherwise
// nodemon goes into a barmy restart loop
if (delta === 0) {
return callback([]);
}
config.dirs.forEach(function(dir) {
cmds.push('find -L "' + dir + '" ' + ignoredFileTypesForFind(dir) + ' \\( -type f -and ' + mtime + ' -print \\)');
});
exec(cmds.join(';'), function (error, stdout) {
var files = stdout.split(/\n/);
files.pop(); // remove blank line ending and split
if (delta > 0) {
try { fs.unlinkSync('.nodemon-find-ref'); }
catch (e) {} // swallow any errors
}
callback(files);
});
};
} else if (config.system.useWatch || config.system.useWatchFile) {
bus.once('quit', reset);
var watchFile = config.system.useWatch === false && (config.system.useWatchFile || config.options.legacyWatch),
watchMethod = watchFile ? 'watchFile' : 'watch';
changeFunction = function (lastStarted, callback) {
// recursive watch - watch each directory and it's subdirectories, etc, etc
function watch(err, dir) {
try {
if (watched.indexOf(dir) === -1 && ignoredFilter(dir)) {
var watcher_options = { persistent: false };
if (config.watch_interval) {
watcher_options.interval = config.watch_interval;
}
var watcher = fs[watchMethod](dir, watcher_options, function (event, filename) {
var filepath;
if (typeof filename === 'string') {
filepath = path.join(dir, filename || '');
} else { // was called from watchFile
filepath = dir;
}
callback([filepath]);
});
watched.push(dir);
watchers.push(watcher);
}
fs.readdir(dir, function (err, files) {
if (err) { return; }
files.forEach(function (rawfile) {
var file = path.join(dir, rawfile);
if (watched.indexOf(file) === -1 && ignoredFilter(file)) {
fs.lstat(file, function (err, stat) {
if (err || !stat) { return; }
// if we're using fs.watch, then watch directories
if (!watchFile && stat.isDirectory()) {
// recursive call to watch()
fs.realpath(file, watch);
} else {
// if we're in legacy mode, i.e. Vagrant + editing over
// shared drive, then watch the individual file
if (watchFile) {
fs.realpath(file, watch);
} else if (ignoredFilter(file)) {
watched.push(file);
}
}
});
}
});
});
} catch (e) {
if ('EMFILE' === e.code) {
utils.log.error('EMFILE: Watching too many files.');
}
// ignoring this directory, likely it's "My Music"
// or some such windows fangled stuff
}
}
showWatchCount();
config.dirs.forEach(function (dir) {
fs.realpath(dir, watch);
});
};
} else {
// changedSince, the fallback for when both the find method and fs.watch
// don't work, is not compatible with the way changeFunction works. If we
// have reached this point, changeFunction should not be called from herein
// out.
utils.log.error('No clean method to watch files with - please report\nto http://github.com/remy/nodemon/issues/new with `nodemon --dump`');
}
});
// filter ignored files
function ignoredFilter(file) {
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
var fileParts = file.split(/\\/g);
// Remove the first piece (C:)
fileParts.shift();
// Join the parts together with Unix slashes
file = '/' + fileParts.join('/');
}
return !config.options.ignore.re.test(file);
} else {
return true;
}
}
function ignoredFileTypesForFind(dir) {
// search within dir for ignored files and paths
if (!config.options.ignore.length) {
return '';
}
var paths = [];
if (dir.charAt(dir.length - 1) !== '/') {
dir += '/';
}
config.options.ignore.forEach(function (path) {
var pathIsDir = (fs.existsSync(dir + path) && fs.statSync(dir + path).isDirectory()) || (path.charAt(path.length - 1) === '/');
if (pathIsDir) {
paths.push(' -ipath "' + dir + path + '*" -prune');
}
paths.push(' -ipath "' + dir + path + '" -prune');
});
return ' \\( ' + paths.join(' -or ') + ' \\) -or ';
}
function debounce(fn, delay) {
var timer = null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}
function restartBus(matched) {
utils.log.status('restarting due to changes...');
matched.result.map(function (file) {
utils.log.detail(path.relative(process.cwd(), file));
}); });
if (config.options.verbose) { bus.on('reset', function () {
utils.log._log(''); watchers.forEach(function (watcher) {
} watcher.close();
});
bus.emit('restart', matched.result); watchers = [];
});
} }
function filterAndRestart(files) { function filterAndRestart(files) {
if (!Array.isArray(files)) {
files = [files];
}
if (files.length) { if (files.length) {
if (utils.isWindows) { if (utils.isWindows) {
// ensure the drive letter is in uppercase (c:\foo -> C:\foo) // ensure the drive letter is in uppercase (c:\foo -> C:\foo)
@@ -278,30 +70,29 @@ function filterAndRestart(files) {
} }
} }
} }
if (config.system.useFind || config.options.legacyWatch) {
if (config.run) {
setTimeout(watch, config.timeout);
}
}
} }
var watch = module.exports = function () {
// if we have useFind or useWatch (i.e. not using `find`) function restartBus(matched) {
// then called `changeFunction` which is local to this script utils.log.status('restarting due to changes...');
if ((config.system.useFind || config.system.useWatch || config.system.useWatchFile) && !config.options.legacyWatch) { matched.result.map(function (file) {
changeFunction(config.lastStarted, function (files) { utils.log.detail(path.relative(process.cwd(), file));
if (config.run) { });
filterAndRestart(files);
} if (config.options.verbose) {
}); utils.log._log('');
} else {
// Fallback for when both find and fs.watch don't work
// using the `changedSince` which is external
changedSince(config.lastStarted, function (files) {
if (config.run) {
filterAndRestart(files);
}
});
} }
};
bus.emit('restart', matched.result);
}
function debounce(fn, delay) {
var timer = null;
return function () {
var context = this, args = arguments;
clearTimeout(timer);
timer = setTimeout(function () {
fn.apply(context, args);
}, delay);
};
}

View File

@@ -1,16 +1,14 @@
'use strict'; var path = require('path');
var path = require('path'), var monitor = require('./monitor');
monitor = require('./monitor'), var cli = require('./cli');
cli = require('./cli'), var version = require('./version');
version = require('./version'), var util = require('util');
util = require('util'), var utils = require('./utils');
utils = require('./utils'), var bus = utils.bus;
rules = require('./rules'), var help = require('./help');
bus = utils.bus, var config = require('./config');
help = require('./help'), var spawn = require('./spawn');
config = require('./config'), var eventHandlers = {};
spawn = require('./spawn'),
eventHandlers = {};
// this is fairly dirty, but theoretically sound since it's part of the // this is fairly dirty, but theoretically sound since it's part of the
// stable module API // stable module API
@@ -232,6 +230,10 @@ nodemon.removeAllListeners = function (event) {
}; };
nodemon.reset = function (done) { nodemon.reset = function (done) {
bus.emit('reset', done);
};
bus.on('reset', function (done) {
monitor.run.kill(true, function () { monitor.run.kill(true, function () {
nodemon.removeAllListeners(); nodemon.removeAllListeners();
utils.reset(); utils.reset();
@@ -241,27 +243,27 @@ nodemon.reset = function (done) {
done(); done();
} }
}); });
}; });
// expose the full config // expose the full config
nodemon.config = config; nodemon.config = config;
// on exception *inside* nodemon, shutdown wrapped node app // on exception *inside* nodemon, shutdown wrapped node app
if (!config.required) { // if (!config.required) {
process.on('uncaughtException', function (err) { // process.on('uncaughtException', function (err) {
console.error('exception in nodemon killing node'); // console.error('exception in nodemon killing node');
console.error(err.stack); // console.error(err.stack);
console.error(); // console.error();
console.error('----------------------------------------------------------'); // console.error('----------------------------------------------------------');
console.error('If appropriate, please file an error with the output from:'); // console.error('If appropriate, please file an error with the output from:');
console.error('$ ' + process.argv.join(' ') + (process.argv.indexOf('--dump') === -1 ? ' --dump' : '')); // console.error('$ ' + process.argv.join(' ') + (process.argv.indexOf('--dump') === -1 ? ' --dump' : ''));
console.error('At http://github.com/remy/nodemon/issues/new'); // console.error('At http://github.com/remy/nodemon/issues/new');
console.error('----------------------------------------------------------\n'); // console.error('----------------------------------------------------------\n');
if (!config.required) { // if (!config.required) {
process.exit(1); // process.exit(1);
} // }
}); // });
} // }
module.exports = nodemon; module.exports = nodemon;

View File

@@ -3,11 +3,12 @@
var utils = require('../utils'); var utils = require('../utils');
// internal // internal
var reEscComments = /\\#/g, var reEscComments = /\\#/g;
reUnescapeComments = /\^\^/g, // note that '^^' is used in place of escaped comments // note that '^^' is used in place of escaped comments
reComments = /#.*$/, var reUnescapeComments = /\^\^/g;
reEscapeChars = /[.|\-[\]()\\]/g, var reComments = /#.*$/;
reAsterisk = /\*/g; var reEscapeChars = /[.|\-[\]()\\]/g;
var reAsterisk = /\*/g;
module.exports = add; module.exports = add;
@@ -24,13 +25,15 @@ module.exports = add;
* add(rules, 'watch', ':(\d)*\.js'); // note: string based regexp * add(rules, 'watch', ':(\d)*\.js'); // note: string based regexp
* add(rules, 'watch', /\d*\.js/); * add(rules, 'watch', /\d*\.js/);
* *
* @param {Object} rules containing `watch` and `ignore`. Also updated during execution * @param {Object} rules containing `watch` and `ignore`. Also updated during
* execution
* @param {String} which must be either "watch" or "ignore" * @param {String} which must be either "watch" or "ignore"
* @param {String|RegExp} the actual rule. * @param {String|RegExp} the actual rule.
*/ */
function add(rules, which, rule) { function add(rules, which, rule) {
if (!{ 'ignore' : 1, 'watch' : 1}[which]) { if (!{ ignore: 1, watch: 1}[which]) {
throw new Error('rules/index.js#add requires "ignore" or "watch" as the first argument'); throw new Error('rules/index.js#add requires "ignore" or "watch" as the ' +
'first argument');
} }
if (Array.isArray(rule)) { if (Array.isArray(rule)) {
@@ -71,7 +74,7 @@ function add(rules, which, rule) {
// rules[which].push(rule); // rules[which].push(rule);
} else { } else {
// rule = rule.replace(reEscapeChars, '\\$&') // rule = rule.replace(reEscapeChars, '\\$&')
// .replace(reAsterisk, '.*'); // .replace(reAsterisk, '.*');
rules[which].push(rule); rules[which].push(rule);
// compile a regexp of all the rules for this ignore or watch // compile a regexp of all the rules for this ignore or watch

View File

@@ -42,12 +42,12 @@ module.exports = {
load: load, load: load,
ignore: { ignore: {
test: add.bind(null, rules, 'ignore'), test: add.bind(null, rules, 'ignore'),
add: add.bind(null, rules, 'ignore') add: add.bind(null, rules, 'ignore'),
}, },
watch: { watch: {
test: add.bind(null, rules, 'watch'), test: add.bind(null, rules, 'watch'),
add: add.bind(null, rules, 'watch') add: add.bind(null, rules, 'watch'),
}, },
add: add.bind(null, rules), add: add.bind(null, rules),
rules: rules rules: rules,
}; };

View File

@@ -1,4 +1,3 @@
'use strict';
var utils = require('./utils'), var utils = require('./utils'),
merge = utils.merge, merge = utils.merge,
bus = utils.bus, bus = utils.bus,

View File

@@ -3,7 +3,7 @@ module.exports = clone;
// via http://stackoverflow.com/a/728694/22617 // via http://stackoverflow.com/a/728694/22617
function clone(obj) { function clone(obj) {
// Handle the 3 simple types, and null or undefined // Handle the 3 simple types, and null or undefined
if (null === obj || "object" !== typeof obj) { if (null === obj || 'object' !== typeof obj) {
return obj; return obj;
} }

View File

@@ -16,7 +16,7 @@ function strip(str) {
colour.red = '\x1B[31m'; colour.red = '\x1B[31m';
colour.yellow = '\x1B[33m'; colour.yellow = '\x1B[33m';
colour.green = '\x1B[32m'; colour.green = '\x1B[32m';
colour.black = '\x1B[39m' colour.black = '\x1B[39m';
var reStr = Object.keys(colour).map(function (key) { var reStr = Object.keys(colour).map(function (key) {
return colour[key]; return colour[key];

View File

@@ -1,9 +1,7 @@
'use strict';
var noop = function () {}, var noop = function () {},
path = require('path'), path = require('path'),
version = process.versions.node.split('.') || [null, null, null]; version = process.versions.node.split('.') || [null, null, null];
var utils = module.exports = { var utils = module.exports = {
version: { version: {
major: parseInt(version[0] || 0, 10), major: parseInt(version[0] || 0, 10),
@@ -17,11 +15,12 @@ var utils = module.exports = {
isMac: process.platform === 'darwin', isMac: process.platform === 'darwin',
isLinux: process.platform === 'linux', isLinux: process.platform === 'linux',
isRequired: (function () { isRequired: (function () {
var p = module; var p = module.parent;
while (p = p.parent) { while (p) {
if (p.filename.indexOf('bin' + path.sep + 'nodemon.js') !== -1) { if (p.filename.indexOf('bin' + path.sep + 'nodemon.js') !== -1) {
return false; return false;
} }
p = p.parent;
} }
return true; return true;
@@ -48,7 +47,10 @@ var utils = module.exports = {
this.debug = false; this.debug = false;
}, },
regexpToText: function (t) { regexpToText: function (t) {
return t.replace(/\.\*\\./g, '*.').replace(/\\{2}/g, '^^').replace(/\\/g, '').replace(/\^\^/g, '\\'); return t.replace(/\.\*\\./g, '*.')
.replace(/\\{2}/g, '^^')
.replace(/\\/g, '')
.replace(/\^\^/g, '\\');
}, },
stringify: function (exec, args) { stringify: function (exec, args) {
// serializes an executable string and array of arguments into a string // serializes an executable string and array of arguments into a string

View File

@@ -42,6 +42,8 @@
"should": "~4.0.0" "should": "~4.0.0"
}, },
"dependencies": { "dependencies": {
"anymatch": "^1.3.0",
"chokidar": "^1.0.5",
"minimatch": "~0.3.0", "minimatch": "~0.3.0",
"ps-tree": "~0.0.3", "ps-tree": "~0.0.3",
"touch": "~1.0.0", "touch": "~1.0.0",

View File

@@ -10,10 +10,10 @@
"delay": 2, "delay": 2,
"ignore": [ "ignore": [
"/vendor/*", "/vendor/*",
"/public/*" "/public/*",
"./README.md" "./README.md",
"*.css", "*.css",
"/vendor/*", "/vendor/*",
":(\d)*\.js" ":(\\d)*\\.js"
] ]
} }