Repository: fgnass/node-dev (js)

Issue:
 Cannot read property 'stack' of null
```
/home/haggen/.../node_modules/node-dev/lib/wrap.js:38
  console.error(err.stack || err);
                    ^

TypeError: Cannot read property 'stack' of null
    at process.<anonymous> (/home/haggen/.../node_modules/node-dev/lib/wrap.js:38:21)
    at process.emit (events.js:315:20)
    at process._fatalException (internal/process/execution.js:165:25)
```

See [`wrap.js` line 38](https://github.com/fgnass/node-dev/blob/624665a57dedb70c4942abee1a83b48c301a5fc2/lib/wrap.js#L38).

Just found out my part of the issue: I was throwing `null` somewhere, as you've probably imagined. I suppose it still would be nice to check `err` before trying to access `err.stack`.

Pull request description:
v5.1.0 / 2020-07-28
- [wrap.js] Improve uncaughtException handling to turn non-errors into errors (Fixes #231)
- [ipc.js] Declare `NODE_DEV` as a variable
- [ipc.js] Inline single line function only used twice
- [tests] Filenames should be snake-case

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51fdd72..3a2f6d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,12 @@
 # node-dev
 
+## v5.1.0 / 2020-07-28
+
+- [wrap.js] Improve uncaughtException handling to turn non-errors into errors (Fixes #231)
+- [ipc.js] Declare `NODE_DEV` as a variable
+- [ipc.js] Inline single line function only used twice
+- [tests] Filenames should be snake-case
+
 ## v5.0.0 / 2020-07-08
 
 - Remove `--all-deps` and `--no-deps` CLI options, use `--deps=-1` or `--deps=0` respectively
diff --git a/lib/ipc.js b/lib/ipc.js
index c6267b8..b4e5ae6 100644
--- a/lib/ipc.js
+++ b/lib/ipc.js
@@ -1,22 +1,17 @@
-/**
- * Checks if the given message is an internal node-dev message.
- */
-function isNodeDevMessage(m) {
-  return m.cmd === 'NODE_DEV';
-}
+const NODE_DEV = 'NODE_DEV';
 
 /**
  * Sends a message to the given process.
  */
 exports.send = function (m, dest) {
-  m.cmd = 'NODE_DEV';
+  m.cmd = NODE_DEV;
   if (!dest) dest = process;
   if (dest.send) dest.send(m);
 };
 
 exports.on = function (proc, type, cb) {
   function handleMessage(m) {
-    if (isNodeDevMessage(m) && type in m) cb(m);
+    if (m.cmd === NODE_DEV && type in m) cb(m);
   }
   proc.on('internalMessage', handleMessage);
   proc.on('message', handleMessage);
@@ -25,7 +20,7 @@ exports.on = function (proc, type, cb) {
 exports.relay = function (src, dest) {
   if (!dest) dest = process;
   function relayMessage(m) {
-    if (isNodeDevMessage(m)) dest.send(m);
+    if (m.cmd === NODE_DEV) dest.send(m);
   }
   src.on('internalMessage', relayMessage);
   src.on('message', relayMessage);
diff --git a/lib/wrap.js b/lib/wrap.js
index 274eeca..43c1649 100755
--- a/lib/wrap.js
+++ b/lib/wrap.js
@@ -35,15 +35,16 @@ if (fork) {
 
 // Error handler that displays a notification and logs the stack to stderr:
 process.on('uncaughtException', function (err) {
-  console.error(err.stack || err);
+  // Sometimes uncaught exceptions are not errors
+  const { message, name, stack } = err instanceof Error ? err : new Error(`uncaughtException ${err}`);
+
+  console.error(stack);
+
   // If there's a custom uncaughtException handler expect it to terminate
   // the process.
-  const hasCustomHandler = process.listeners('uncaughtException').length > 1;
-  ipc.send({
-    error: err.name || 'Error',
-    message: err.message,
-    willTerminate: hasCustomHandler
-  });
+  const willTerminate = process.listeners('uncaughtException').length > 1;
+
+  ipc.send({ error: name, message, willTerminate });
 });
 
 // Hook into require() and notify the parent process about required files
diff --git a/package.json b/package.json
index 7cd47d0..d7a05df 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "node-dev",
-  "version": "5.0.0",
+  "version": "5.1.0",
   "description": "Restarts your app when files are modified",
   "keywords": [
     "restart",


Test patch:
diff --git a/test/fixture/.node-dev.json b/test/fixture/.node-dev.json
index 8c72d65..a8e9faf 100644
--- a/test/fixture/.node-dev.json
+++ b/test/fixture/.node-dev.json
@@ -1,7 +1,7 @@
 {
   "notify": false,
   "ignore": [
-    "./ignoredModule.js"
+    "./ignored-module.js"
   ],
   "extensions": {
     "coffee": "coffeescript/register",
diff --git a/test/fixture/catchNoSuchModule.js b/test/fixture/catch-no-such-module.js
similarity index 100%
rename from test/fixture/catchNoSuchModule.js
rename to test/fixture/catch-no-such-module.js
diff --git a/test/fixture/error-null.js b/test/fixture/error-null.js
new file mode 100644
index 0000000..54659f7
--- /dev/null
+++ b/test/fixture/error-null.js
@@ -0,0 +1,4 @@
+require('./message');
+
+// eslint-disable-next-line no-throw-literal
+throw null;
diff --git a/test/fixture/ignoredModule.js b/test/fixture/ignored-module.js
similarity index 100%
rename from test/fixture/ignoredModule.js
rename to test/fixture/ignored-module.js
diff --git a/test/fixture/noSuchModule.js b/test/fixture/no-such-module.js
similarity index 100%
rename from test/fixture/noSuchModule.js
rename to test/fixture/no-such-module.js
diff --git a/test/fixture/server.js b/test/fixture/server.js
index 29acf39..0d0d158 100644
--- a/test/fixture/server.js
+++ b/test/fixture/server.js
@@ -2,7 +2,7 @@ var http = require('http');
 var message = require('./message');
 
 // Changes to this module should not cause a server restart:
-require('./ignoredModule');
+require('./ignored-module');
 
 var server = http.createServer(function (req, res) {
   res.writeHead(200, { 'Content-Type': 'text/plain' });
diff --git a/test/fixture/uncaughtExceptionHandler.js b/test/fixture/uncaught-exception-handler.js
similarity index 100%
rename from test/fixture/uncaughtExceptionHandler.js
rename to test/fixture/uncaught-exception-handler.js
diff --git a/test/spawn.js b/test/spawn.js
index 7f75c7f..128a85f 100644
--- a/test/spawn.js
+++ b/test/spawn.js
@@ -40,15 +40,28 @@ tap.test('should handle errors', function (t) {
   });
 });
 
+tap.test('should handle null errors', function (t) {
+  spawn('error-null.js', function (out) {
+    if (out.match(/ERROR/)) {
+      touchFile();
+      return function (out2) {
+        if (out2.match(/Restarting/)) {
+          return { exit: t.end.bind(t) };
+        }
+      };
+    }
+  });
+});
+
 tap.test('should watch if no such module', function (t) {
-  spawn('noSuchModule.js', function (out) {
+  spawn('no-such-module.js', function (out) {
     t.like(out, /ERROR/);
     return { exit: t.end.bind(t) };
   });
 });
 
 tap.test('should run async code un uncaughtException handlers', function (t) {
-  spawn('uncaughtExceptionHandler.js', function (out) {
+  spawn('uncaught-exception-handler.js', function (out) {
     if (out.match(/ERROR/)) {
       return function (out2) {
         if (out2.match(/async \[?ReferenceError/)) {
@@ -60,7 +73,7 @@ tap.test('should run async code un uncaughtException handlers', function (t) {
 });
 
 tap.test('should ignore caught errors', function (t) {
-  spawn('catchNoSuchModule.js', function (out) {
+  spawn('catch-no-such-module.js', function (out) {
     t.like(out, /Caught/);
     return { exit: t.end.bind(t) };
   });