Repository: remy/nodemon (js)

Issue:
latest release (1.14.3) can't resolve path without extension
- `nodemon -v`: __1.14.3__
- `node -v`: __8.9.0__
- Operating system/terminal environment: __Mac OSX 10.11.6 / zsh 5.2__
- Command you ran: `NODE_ENV=development ./node_modules/.bin/nodemon ./dev/server`


### Expected behaviour
`nodemon` starts the development server

### Actual behaviour
It appears to be falling back to `npm start` and recursively appending that command to the previous one. My `start` script is:
```bash
NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server
```
This works as expected running `nodemon@1.14.2`, or if I add the `.js` extension to `./dev/server`.

### Dump Output (*without extension*)
```js
{ ...
  options:
   { dump: true,
     ...
     execOptions:
      { script: undefined,
        exec: 'NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server',
        args: [ './dev/server' ],
        scriptPosition: 1,
        nodeArgs: undefined,
        execArgs: [],
        ext: 'js,json,hbs,y?(a)ml',
        env: {} },
     ... },
  ...
  command:
   { raw:
      { executable: 'NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server',
        args: [ './dev/server' ] },
     string: 'NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server ./dev/server' } }
```

### Dump Output (*with extension*)
```js
{ ...
  options:
   { dump: true,
     ...
     execOptions:
      { script: './dev/server.js',
        exec: 'node',
        args: [],
        scriptPosition: 0,
        nodeArgs: undefined,
        execArgs: [],
        ext: 'js,json,hbs,y?(a)ml',
        env: {} },
     ... },
  ...
  command:
   { raw: { executable: 'node', args: [ './dev/server.js' ] },
     string: 'node ./dev/server.js' } }
```

### Command Output
```
> NODE_ENV=development ./node_modules/.bin/nodemon ./dev/server
[nodemon] 1.14.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: **/*
[nodemon] starting `NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server ./dev/server`
[nodemon] 1.14.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: **/*
[nodemon] starting `NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server --require babel-register ./dev/server ./dev/server`
[nodemon] 1.14.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: **/*
[nodemon] starting `NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server --require babel-register ./dev/server --require babel-register ./dev/server ./dev/server`
[nodemon] 1.14.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: **/*
[nodemon] starting `NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server --require babel-register ./dev/server --require babel-register ./dev/server --require babel-register ./dev/server ./dev/server`
[nodemon] 1.14.3
[nodemon] to restart at any time, enter `rs`
[nodemon] watching: **/*
[nodemon] starting `NODE_ENV=${NODE_ENV:-development} nodemon --require babel-register ./dev/server --require babel-register ./dev/server --require babel-register ./dev/server --require babel-register ./dev/server --require babel-register ./dev/server ./dev/server`
```


Pull request:
fix: support implicit filename extension
Fixes #1193

Patch:
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 32d9248..fbd5222 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -15,4 +15,4 @@
 
 ---
 
-If applicable, please append the `--dump` flag on your command and include the output here **ensuring to remove any sensitive/personal details or tokens**.
+If applicable, please append the `--dump` flag on your command and include the output here **ensuring to remove any sensitive/personal details or tokens.
diff --git a/README.md b/README.md
index 3a5e05c..d070258 100644
--- a/README.md
+++ b/README.md
@@ -112,7 +112,7 @@ Simply specify the config in the same format as you would for a config file but
   "...": "... other standard package.json values",
   "nodemonConfig": {
     "ignore": ["test/*", "docs/*"],
-"delay": "2500"
+    "delay": "2500"
   }
 }
 ```
@@ -324,12 +324,6 @@ Nodemon is not perfect, and CLI arguments has sprawled beyond where I'm complete
 
 See the [FAQ](https://github.com/remy/nodemon/blob/master/faq.md) and please add your own questions if you think they would help others.
 
-## Contributors
-
-This project exists thanks to all the people who [contribute](https://github.com/remy/nodemon/blob/master/.github/CONTRIBUTING.md).
-[![nodemon contributors](https://opencollective.com/nodemon/contributors.svg?width=890)](https://opencollective.com/nodemon#backer)
-
-
 ## Backers
 
 Thank you to all [our backers](https://opencollective.com/nodemon#backer)! 🙏
diff --git a/faq.md b/faq.md
index ecb1d90..efbd349 100644
--- a/faq.md
+++ b/faq.md
@@ -26,6 +26,16 @@ $ nodemon app.js -- -L -opt2 -opt3
 
 nodemon will ignore all script arguments after `--` and pass them to your script.
 
+# Error: "process failed, unhandled exit code (2)"
+
+Nodemon will look for exit signals from the child process it runs. When the exit code is `2`, nodemon throws an error. Typically this is because the arguments are bad for the executing program, but it can also be due other reasons.
+
+For example, mocha@3.x will exit with `2` on failing tests. To handle the exit code in a way that nodemon can consume, manually exit the process, i.e.:
+
+```bash
+nodemon -x 'mocha test/bad.test.js || exit 1'
+```
+
 # Can't install nodemon: permission issue
 
 You may need to install nodemon using `sudo` (which isn't recommended, but I understand it's unavoidable in some environemnts). If the install fails with this appearing in the npm error log, then you need the following workaround.
@@ -103,7 +113,7 @@ A workaround is to make sure that `node` binary exists in the `PATH`:
 sudo ln -s /usr/bin/nodejs /usr/local/bin/node
 ```
 
-Alternatively the `--exec nodejs` option can be used. 
+Alternatively the `--exec nodejs` option can be used.
 
 Fedora and Ubuntu pakage node as nodejs, because node.dpkg is
 
diff --git a/lib/config/command.js b/lib/config/command.js
index 2ce4634..9839b5c 100644
--- a/lib/config/command.js
+++ b/lib/config/command.js
@@ -40,4 +40,4 @@ function command(settings) {
     executable: executable,
     args: args,
   };
-}
\ No newline at end of file
+}
diff --git a/lib/config/exec.js b/lib/config/exec.js
index 852186f..3a4f7f7 100644
--- a/lib/config/exec.js
+++ b/lib/config/exec.js
@@ -73,28 +73,43 @@ function exec(nodemonOptions, execMap) {
     execMap = {};
   }
 
+  var options = utils.clone(nodemonOptions || {});
+  var script;
+
+  // if there's no script passed, try to get it from the first argument
+  if (!options.script && (options.args || []).length) {
+    script = expandScript(options.args[0],
+      options.ext && ('.' + (options.ext || 'js').split(',')[0]));
+
+    // if the script was found, shift it off our args
+    if (script !== options.args[0]) {
+      options.script = script;
+      options.args.shift();
+    }
+  }
+
   // if there's no exec found yet, then try to read it from the local
   // package.json this logic used to sit in the cli/parse, but actually the cli
   // should be parsed first, then the user options (via nodemon.json) then
   // finally default down to pot shots at the directory via package.json
-  if (!nodemonOptions.exec && !nodemonOptions.script) {
+  if (!options.exec && !options.script) {
     var found = execFromPackage();
     if (found !== null) {
       if (found.exec) {
-        nodemonOptions.exec = found.exec;
+        options.exec = found.exec;
       }
-      if (!nodemonOptions.script) {
-        nodemonOptions.script = found.script;
+      if (!options.script) {
+        options.script = found.script;
       }
-      if (Array.isArray(nodemonOptions.args) &&
-        nodemonOptions.scriptPosition === null) {
-        nodemonOptions.scriptPosition = nodemonOptions.args.length;
+      if (Array.isArray(options.args) &&
+        options.scriptPosition === null) {
+        options.scriptPosition = options.args.length;
       }
     }
   }
 
-  var options = utils.clone(nodemonOptions || {});
-  var script = path.basename(options.script || '');
+  // var options = utils.clone(nodemonOptions || {});
+  script = path.basename(options.script || '');
 
   var scriptExt = path.extname(script).slice(1);
 
diff --git a/lib/config/load.js b/lib/config/load.js
index 06ec801..c4ce775 100644
--- a/lib/config/load.js
+++ b/lib/config/load.js
@@ -9,6 +9,7 @@ var exec = require('./exec');
 var defaults = require('./defaults');
 
 module.exports = load;
+module.exports.mutateExecOptions = mutateExecOptions;
 
 var existsSync = fs.existsSync || path.existsSync;
 
@@ -72,23 +73,7 @@ function load(settings, options, config, callback) {
         }
       }
 
-      // work out the execOptions based on the final config we have
-      options.execOptions = exec({
-        script: options.script,
-        exec: options.exec,
-        args: options.args,
-        scriptPosition: options.scriptPosition,
-        nodeArgs: options.nodeArgs,
-        execArgs: options.execArgs,
-        ext: options.ext,
-        env: options.env,
-      }, options.execMap);
-
-      // clean up values that we don't need at the top level
-      delete options.scriptPosition;
-      delete options.script;
-      delete options.args;
-      delete options.ext;
+      mutateExecOptions(options);
 
       if (options.quiet) {
         utils.quiet();
@@ -229,3 +214,25 @@ function loadPackageJSON(config, ready) {
     ready(settings.nodemonConfig || {});
   });
 }
+
+function mutateExecOptions(options) {
+  // work out the execOptions based on the final config we have
+  options.execOptions = exec({
+    script: options.script,
+    exec: options.exec,
+    args: options.args,
+    scriptPosition: options.scriptPosition,
+    nodeArgs: options.nodeArgs,
+    execArgs: options.execArgs,
+    ext: options.ext,
+    env: options.env,
+  }, options.execMap);
+
+  // clean up values that we don't need at the top level
+  delete options.scriptPosition;
+  delete options.script;
+  delete options.args;
+  delete options.ext;
+
+  return options;
+}
diff --git a/lib/monitor/run.js b/lib/monitor/run.js
index b6b0e30..cc14913 100644
--- a/lib/monitor/run.js
+++ b/lib/monitor/run.js
@@ -143,8 +143,7 @@ function run(options) {
 
     if (code === 2) {
       // something wrong with parsed command
-      utils.log.error('failed to start process, possible issue with exec ' +
-        'arguments');
+      utils.log.error('process failed, unhandled exit code (2)');
       bus.emit('error', code);
       process.exit();
     }
diff --git a/lib/nodemon.js b/lib/nodemon.js
index c2f8973..c71e2e6 100644
--- a/lib/nodemon.js
+++ b/lib/nodemon.js
@@ -138,6 +138,9 @@ function nodemon(settings) {
     if (!config.required) {
       const restartSignal = config.options.signal === 'SIGUSR2' ? 'SIGHUP' : 'SIGUSR2';
       process.on(restartSignal, nodemon.restart);
+      utils.bus.on('error', () => {
+        utils.log.fail((new Error().stack));
+      });
       utils.log.detail((config.options.restartable ? 'or ' : '') + 'send ' +
         restartSignal + ' to ' + process.pid + ' to restart');
     }
diff --git a/package.json b/package.json
index 158eab6..d96deab 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
     "clean": "rm -rf test/fixtures/test*.js test/fixtures/test*.md",
     "web": "node web",
     "semantic-release": "semantic-release pre && npm publish && semantic-release post",
+    "prepush": "npm run lint",
     "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')\""
   },
   "devDependencies": {


Test patch:
diff --git a/test/cli/parse.test.js b/test/cli/parse.test.js
index 15e16d7..636feb5 100644
--- a/test/cli/parse.test.js
+++ b/test/cli/parse.test.js
@@ -7,6 +7,8 @@ var cli = require('../../lib/cli/'),
   command = require('../../lib/config/command'),
   utils = require('../../lib/utils');
 
+const mutateExecOptions = require('../../lib/config/load').mutateExecOptions;
+
 function asCLI(cmd) {
   return ('node nodemon ' + (cmd || '')).trim();
 }
@@ -26,6 +28,7 @@ function parse(cmd) {
   });
 
   return parsed;
+    return mutateExecOptions(cli.parse(cmd));
 }
 
 function commandToString(command) {
@@ -97,18 +100,6 @@ describe('nodemon CLI parser', function () {
     assert(cmd === 'node --harmony app.js', 'command is ' + cmd);
   });
 
-  // it('should put the script at the end if found in package.scripts.start', function () {
-  //   var pwd = process.cwd();
-  //   process.chdir('test/fixtures/packages/start'); // allows us to load text/fixtures/package.json
-  //   var settings = parse(asCLI('--harmony')),
-  //       cmd = commandToString(command(settings));
-
-  //   process.chdir(pwd);
-  //   console.log(settings, cmd);
-
-  //   assert(cmd === 'node --harmony app.js', 'command is ' + cmd);
-  // });
-
   it('should support default express4 format', function () {
     var pwd = process.cwd();
     process.chdir('test/fixtures/packages/express4'); // allows us to load text/fixtures/package.json
@@ -274,6 +265,27 @@ describe('nodemon respects custom "ext" and "execMap"', function () {
   });
 });
 
+describe('nodemon should support implicit extensions', () => {
+  it('should expand script to script.js', () => {
+    const cwd = process.cwd();
+    process.chdir('test/fixtures/');
+    const settings = parse(asCLI('env'));
+    process.chdir(cwd);
+    var cmd = commandToString(command(settings));
+    assert.equal(cmd, 'node env.js', 'implicit extension added');
+  });
+
+  it('should support non-js', () => {
+    const cwd = process.cwd();
+    process.chdir('test/fixtures/');
+    const settings = parse(asCLI('hello --ext py'));
+    process.chdir(cwd);
+    var cmd = commandToString(command(settings));
+    assert.equal(cmd, 'node hello.py', 'implicit extension added');
+  });
+
+});
+
 describe('nodemon should slurp properly', () => {
   it('should read quotes as a single entity', () => {
     const settings = parse(asCLI('notindex.js -- -b "hello - world"'));