Repository: remy/nodemon (js)

Issue:
Can nodemon watch and restart on changes occurring to files with no extension?
I'm using express-generator from Express.js (>4.0). As already discussed in #330, it sets things up to run the app with `node ./bin/www`. How should I setup nodemon in order to trigger restarts on changes to ./bin/www.

```
$ DEBUG=test nodemon --verbose ./bin/www
13 Jan 16:41:47 - [nodemon] v1.2.1
13 Jan 16:41:47 - [nodemon] to restart at any time, enter `rs`
13 Jan 16:41:47 - [nodemon] ignoring: .git /tmp/test/node_modules/**/* bower_components .sass-cache
13 Jan 16:41:47 - [nodemon] watching: *.*
13 Jan 16:41:47 - [nodemon] watching extensions: js
13 Jan 16:41:47 - [nodemon] starting `node ./bin/www`
13 Jan 16:41:47 - [nodemon] child pid: 1516
13 Jan 16:41:47 - [nodemon] watching 806 files
```

When changing the file:

```
13 Jan 16:41:52 - [nodemon] files triggering change check: bin/www
13 Jan 16:41:52 - [nodemon] changes after filters (before/after): 1/0
```

I tryed to explicitly add the file to the watchlist with the following cmdline, but looks like it still get filtered out.

```
$ DEBUG=test nodemon -w ./bin/www --verbose ./bin/www
```


Pull request description:
feat: watch script regardless of extension
Fixes the issue where express is a js based project, but the executable
is `www`, so it misses on the match. So now nodemon will watch for
matching extensions but *also* the script the user gave.

Fixes #461

Note that this can't handle the situation where npm is used to run `node
./bin/www` as nodemon can't split out a package script command.

Patch:
diff --git a/Dockerfile b/Dockerfile
deleted file mode 100644
index 990c12e..0000000
--- a/Dockerfile
+++ /dev/null
@@ -1,19 +0,0 @@
-FROM ubuntu:12.04
-MAINTAINER Remy Sharp <remy@leftlogic.com>
-
-RUN rm /bin/sh && ln -s /bin/bash /bin/sh
-RUN apt-get update && apt-get install curl npm -y
-
-ENV NVM_DIR /usr/local/nvm
-ENV NODE_VERSION 4
-ENV NVM_VERSION 0.26.1
-ENV TRAVIS TRUE
-
-# # Install nvm with node and npm
-RUN curl https://raw.githubusercontent.com/creationix/nvm/v$NVM_VERSION/install.sh | bash \
-    && source $NVM_DIR/nvm.sh \
-    && nvm install 0.10 \
-    && nvm install 0.12 \
-    && nvm install 4 \
-    && nvm alias default $NODE_VERSION \
-    && nvm use default
diff --git a/lib/config/defaults.js b/lib/config/defaults.js
index 622c71b..9a55b21 100644
--- a/lib/config/defaults.js
+++ b/lib/config/defaults.js
@@ -16,6 +16,7 @@ module.exports = {
   stdin: true,
   runOnChangeOnly: false,
   verbose: false,
+  signal: 'SIGUSR2',
   // '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
   // nodemon.on('stdout/stderr')
diff --git a/lib/config/exec.js b/lib/config/exec.js
index de0cd30..09c15c0 100644
--- a/lib/config/exec.js
+++ b/lib/config/exec.js
@@ -1,7 +1,10 @@
-var path = require('path');
-var utils = require('../utils');
+const path = require('path');
+const fs = require('fs');
+const existsSync = fs.existsSync;
+const utils = require('../utils');
 
 module.exports = exec;
+module.exports.expandScript = expandScript;
 
 /**
  * Reads the cwd/package.json file and looks to see if it can load a script
@@ -36,6 +39,24 @@ function replace(map, str) {
   });
 }
 
+function expandScript(script, ext) {
+  if (!ext) {
+    ext = '.js';
+  }
+  if (script.indexOf(ext) !== -1) {
+    return script;
+  }
+
+  if (existsSync(path.resolve(script))) {
+    return script;
+  }
+
+  if (existsSync(path.resolve(script + ext))) {
+    return script + ext;
+  }
+
+  return script;
+}
 
 /**
  * Discovers all the options required to run the script
@@ -74,6 +95,7 @@ function exec(nodemonOptions, execMap) {
 
   var options = utils.clone(nodemonOptions || {});
   var script = path.basename(options.script || '');
+
   var scriptExt = path.extname(script).slice(1);
   var extension = options.ext || (scriptExt ? scriptExt + ',json' : 'js,json');
   var execDefined = !!options.exec;
@@ -163,6 +185,11 @@ function exec(nodemonOptions, execMap) {
 
   options.ext = extension;
 
+  if (options.script) {
+    options.script = expandScript(options.script, '.' +
+      extension.split(',')[0]);
+  }
+
   options.env = {};
   // make sure it's an object (and since we don't have )
   if (({}).toString.apply(nodemonOptions.env) === '[object Object]') {
diff --git a/lib/config/index.js b/lib/config/index.js
index 12b6b65..aae238e 100644
--- a/lib/config/index.js
+++ b/lib/config/index.js
@@ -31,7 +31,6 @@ var config = {
   dirs: [],
   timeout: 1000,
   options: {},
-  signal: 'SIGUSR2',
 };
 
 /**
@@ -59,7 +58,7 @@ config.load = function (settings, ready) {
     }
 
     config.watchInterval = options.watchInterval || null;
-    if (options['signal']) { // jshint ignore:line
+    if (options.signal) {
       config.signal = options.signal;
     }
 
diff --git a/lib/config/load.js b/lib/config/load.js
index 73ae275..06ec801 100644
--- a/lib/config/load.js
+++ b/lib/config/load.js
@@ -22,11 +22,11 @@ function findAppScript() {
 
 /**
  * Load the nodemon config, first reading the global root/nodemon.json, then
- * the local nodemon.json to the exec and then overwritting using any user
+ * the local nodemon.json to the exec and then overwriting using any user
  * specified settings (i.e. from the cli)
  *
  * @param  {Object} settings user defined settings
- * @param  {Function} ready    callback that recieves complete config
+ * @param  {Function} ready    callback that receives complete config
  */
 function load(settings, options, config, callback) {
   config.loaded = [];
diff --git a/lib/monitor/watch.js b/lib/monitor/watch.js
index 68220b5..09c334d 100644
--- a/lib/monitor/watch.js
+++ b/lib/monitor/watch.js
@@ -24,7 +24,6 @@ function resetWatchers() {
   watchers = [];
 }
 
-
 function watch() {
   if (watchers.length) {
     debug('early exit on watch, still watching (%s)', watchers.length);
@@ -141,6 +140,22 @@ function filterAndRestart(files) {
       undefsafe(config, 'options.execOptions.ext')
     );
 
+    // if there's no matches, then test to see if the changed file is the
+    // running script, if so, let's allow a restart
+    const script = path.resolve(config.options.execOptions.script);
+    if (matched.result.length === 0 && script) {
+      const length = script.length;
+      files.find(file => {
+        if (file.substr(-length, length) === script) {
+          matched = {
+            result: [ file ],
+            total: 1,
+          }
+          return true;
+        }
+      })
+    }
+
     utils.log.detail('changes after filters (before/after): ' +
       [files.length, matched.result.length].join('/'));
 
diff --git a/lib/nodemon.js b/lib/nodemon.js
index 9298232..5e5f4e8 100644
--- a/lib/nodemon.js
+++ b/lib/nodemon.js
@@ -135,21 +135,23 @@ function nodemon(settings) {
         config.options.restartable + '`');
     }
 
-    var none = function (v) {
-      return v;
-    };
+    if (!config.required) {
+      const restartSignal = config.options.signal === 'SIGUSR2' ? 'SIGHUP' : 'SIGUSR2';
+      process.on(restartSignal, nodemon.restart);
+      utils.log.detail((config.options.restartable ? 'or ' : '') + 'send ' +
+        restartSignal + ' to ' + process.pid + ' to restart');
+    }
 
     utils.log.detail('ignoring: ' + config.options.monitor.map(function (rule) {
       return rule.slice(0, 1) === '!' ? rule.slice(1) : false;
-    }).filter(none).join(' '));
+    }).filter(Boolean).join(' '));
 
     utils.log.info('watching: ' + config.options.monitor.map(function (rule) {
       return rule.slice(0, 1) !== '!' ? rule : false;
-    }).filter(none).join(' '));
+    }).filter(Boolean).join(' '));
 
     utils.log.detail('watching extensions: ' + config.options.execOptions.ext);
 
-
     if (config.options.dump) {
       utils.log._log('log', '--------------');
       utils.log._log('log', 'node: ' + process.version);
@@ -164,33 +166,35 @@ function nodemon(settings) {
         process.exit();
       }
 
-    } else {
-      config.run = true;
+      return;
+    }
 
-      if (config.options.stdout === false) {
-        nodemon.on('start', function () {
-          nodemon.stdout = bus.stdout;
-          nodemon.stderr = bus.stderr;
+    config.run = true;
 
-          bus.emit('readable');
-        });
-      }
+    if (config.options.stdout === false) {
+      nodemon.on('start', function () {
+        nodemon.stdout = bus.stdout;
+        nodemon.stderr = bus.stderr;
 
-      if (config.options.events && Object.keys(config.options.events).length) {
-        Object.keys(config.options.events).forEach(function (key) {
-          utils.log.detail('bind ' + key + ' -> `' +
-            config.options.events[key] + '`');
-          nodemon.on(key, function () {
-            if (config.options && config.options.events) {
-              spawn(config.options.events[key], config,
-                [].slice.apply(arguments));
-            }
-          });
-        });
-      }
+        bus.emit('readable');
+      });
+    }
 
-      monitor.run(config.options);
+    if (config.options.events && Object.keys(config.options.events).length) {
+      Object.keys(config.options.events).forEach(function (key) {
+        utils.log.detail('bind ' + key + ' -> `' +
+          config.options.events[key] + '`');
+        nodemon.on(key, function () {
+          if (config.options && config.options.events) {
+            spawn(config.options.events[key], config,
+              [].slice.apply(arguments));
+          }
+        });
+      });
     }
+
+    monitor.run(config.options);
+
   });
 
   return nodemon;
diff --git a/lib/rules/add.js b/lib/rules/add.js
index d2e1827..de85bb7 100644
--- a/lib/rules/add.js
+++ b/lib/rules/add.js
@@ -13,7 +13,7 @@ var reAsterisk = /\*/g;
 module.exports = add;
 
 /**
- * Coverts file patterns or regular expressions to nodemon
+ * Converts file patterns or regular expressions to nodemon
  * compatible RegExp matching rules. Note: the `rules` argument
  * object is modified to include the new rule and new RegExp
  *
@@ -86,4 +86,4 @@ function add(rules, which, rule) {
     // used for the directory matching
     rules[which].re = new RegExp(re);
   }
-}
\ No newline at end of file
+}
diff --git a/package.json b/package.json
index cfa3736..660ed7f 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "test": "npm run lint && npm run spec",
     "spec": "for FILE in test/**/*.test.js; do echo $FILE; TEST=1 mocha --exit --timeout 30000 $FILE; if [ $? -ne 0 ]; then exit 1; fi; sleep 1; done",
     "postspec": "npm run clean",
-    "clean": "rm -rf test/fixtures/test*.js",
+    "clean": "rm -rf test/fixtures/test*.js test/fixtures/test*.md",
     "web": "node web",
     "semantic-release": "semantic-release pre && npm publish && semantic-release post",
     "postinstall": "node -e \"console.log('\\u001b[32mLove nodemon? You can now support the project via the open collective:\\u001b[22m\\u001b[39m\\n > \\u001b[96m\\u001b[1mhttps://opencollective.com/nodemon/donate\\u001b[0m\\n')\""


Test patch:
diff --git a/test/cli/exec.test.js b/test/cli/exec.test.js
index 871b96b..d66ea34 100644
--- a/test/cli/exec.test.js
+++ b/test/cli/exec.test.js
@@ -1,9 +1,11 @@
 'use strict';
 /*global describe:true, it: true */
-var exec = require('../../lib/config/exec'),
-    command = require('../../lib/config/command'),
-    assert = require('assert'),
-    utils = require('../../lib/utils');
+const path = require('path');
+const exec = require('../../lib/config/exec');
+const expandScript = exec.expandScript;
+const command = require('../../lib/config/command');
+const assert = require('assert');
+const utils = require('../../lib/utils');
 
 function toCmd(options) {
   var cmd = command({
@@ -17,7 +19,46 @@ function toCmd(options) {
   };
 }
 
+describe('expandScript', () => {
+  var pwd = process.cwd();
+
+  afterEach(function () {
+    process.chdir(pwd);
+  });
+
+  beforeEach(function () {
+    // move to the fixtures directory to allow for config loading
+    process.chdir(path.resolve(pwd, 'test/fixtures'));
+  });
+
+  it('should expand app.js', () => {
+    const script = expandScript('app');
+    assert.equal(script, 'app.js', script);
+  })
+
+  it('should expand hello.py', () => {
+    const script = expandScript('hello', '.py');
+    assert.equal(script, 'hello.py', script);
+  })
+
+  it('should ignore foo.js', () => {
+    const script = expandScript('foo', '.js');
+    assert.equal(script, 'foo', script);
+  })
+});
+
 describe('nodemon exec', function () {
+  var pwd = process.cwd();
+
+  afterEach(function () {
+    process.chdir(pwd);
+  });
+
+  beforeEach(function () {
+    // move to the fixtures directory to allow for config loading
+    process.chdir(path.resolve(pwd, 'test/fixtures'));
+  });
+
   it('should default to node', function () {
     var options = exec({ script: 'index.js' });
     var cmd = toCmd(options);
@@ -136,4 +177,28 @@ describe('nodemon exec', function () {
     assert(options.ext.indexOf('js') !== -1);
     assert(options.ext.indexOf('jade') !== -1);
   });
+
+  it('should expand app to app.js', function () {
+    var options = exec({ script: 'app' });
+    var cmd = toCmd(options);
+    assert(cmd.string === 'node app.js', cmd.string);
+  });
+
+  it('should expand based on custom extensions to hello.py', function () {
+    var options = exec({ script: 'hello', ext: '.py', exec: 'python' });
+    var cmd = toCmd(options);
+    assert(cmd.string === 'python hello.py', cmd.string);
+  });
+
+  it('should expand based on custom extensions to app.js (js,jsx,mjs)', function () {
+    var options = exec({ script: 'app', ext: 'js,jsx,mjs' });
+    var cmd = toCmd(options);
+    assert(cmd.string === 'node app.js', cmd.string);
+  });
+
+  it('should not expand index to non-existant index.js', function () {
+    var options = exec({ script: 'index' });
+    var cmd = toCmd(options);
+    assert(cmd.string === 'node index', cmd.string);
+  });
 });
diff --git a/test/cli/parse.test.js b/test/cli/parse.test.js
index b0e1fd2..b01b862 100644
--- a/test/cli/parse.test.js
+++ b/test/cli/parse.test.js
@@ -162,7 +162,7 @@ describe('nodemon CLI parser', function () {
 
   it('should support stand alone `nodemon` command', function () {
     var settings = parse(asCLI(''));
-    assert(settings.execOptions.script === pkg.main);
+    assert(settings.execOptions.script === pkg.main + '.js', `${settings.execOptions.script} === ${pkg.main}`);
   });
 
   it('should put --debug in the right place with coffescript', function () {