diff --git a/bin/ts-node-dev b/bin/ts-node-dev old mode 100644 new mode 100755 diff --git a/lib/index.js b/lib/index.js index ff1a995..d6596f2 100755 --- a/lib/index.js +++ b/lib/index.js @@ -8,6 +8,8 @@ var tsNodeVersion = require('ts-node').VERSION var tsVersion = require('typescript').version var kill = require('tree-kill') var readline = require('readline') +var path = require('path') +var chokidar = require('chokidar') module.exports = function(script, scriptArgs, nodeArgs, opts) { if (typeof script !== 'string' || script.length === 0) { @@ -38,6 +40,7 @@ module.exports = function(script, scriptArgs, nodeArgs, opts) { // Run ./dedupe.js as preload script if (cfg.dedupe) process.env.NODE_DEV_PRELOAD = __dirname + '/dedupe' + var chokidarWatches = [] var watcher = filewatcher({ forcePolling: opts.poll, interval: parseInt(opts.interval), @@ -150,7 +153,14 @@ module.exports = function(script, scriptArgs, nodeArgs, opts) { // Upon errors, display a notification and tell the child to exit. ipc.on(child, 'error', function(m) { - log.debug('Child error') + log.debug('Child error', m) + var lastRequire = m.lastRequire + if (m.code === 'MODULE_NOT_FOUND' && lastRequire && lastRequire.path.startsWith('.')) { + var pathNoExt = path.normalize(path.join(path.dirname(lastRequire.filename), lastRequire.path)) + var watch = chokidar.watch([pathNoExt + '.ts', pathNoExt + '.tsx']) + watch.on('all', (arg, file) => restart(file, false)) + chokidarWatches.push(watch) + } notify(m.error, m.message, 'error') stop(m.willTerminate) }) @@ -199,6 +209,8 @@ module.exports = function(script, scriptArgs, nodeArgs, opts) { return } log.debug('Removing all watchers from files') + chokidarWatches.forEach(nw => nw.close()) + chokidarWatches = [] watcher.removeAll() starting = true if (child) { diff --git a/lib/wrap.js b/lib/wrap.js index 486e40f..988efd2 100755 --- a/lib/wrap.js +++ b/lib/wrap.js @@ -1,3 +1,4 @@ +var Module = require('module') var path = require('path'); var childProcess = require('child_process'); var fork = childProcess.fork; @@ -21,6 +22,15 @@ if (process.env.NODE_DEV_PRELOAD) { require(process.env.NODE_DEV_PRELOAD); } +// We hook on the require call to keep track of the last required files in case there's a +// MODULE_NOT_FOUND error for file watching +var lastRequire = null; +var origRequire = Module.prototype.require; +Module.prototype.require = function (requirePath) { + lastRequire = { path: requirePath, filename: this.filename }; + return origRequire.apply(this, arguments); +}; + // Listen SIGTERM and exit unless there is another listener process.on('SIGTERM', function () { if (process.listeners('SIGTERM').length === 1) process.exit(0); @@ -45,13 +55,16 @@ process.on('uncaughtException', function (err) { // If there's a custom uncaughtException handler expect it to terminate // the process. var hasCustomHandler = process.listeners('uncaughtException').length > 1; - var isTsError = err.message && /TypeScript/.test(err.message) - if (!hasCustomHandler && !isTsError) { + var isTsError = err.message && /TypeScript/.test(err.message); + if (!hasCustomHandler && !isTsError) { console.error(err.stack || err); - } + } + ipc.send({ error: isTsError ? '' : err.name || 'Error', message: err.message, + code: err.code, + lastRequire: lastRequire, willTerminate: hasCustomHandler }); }); diff --git a/package.json b/package.json index 1b117e2..955e4d0 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "test-docker": "docker run --rm -v ${PWD}:/app mhart/alpine-node:8.7.0 sh -c 'cd app && node ./bin/ts-node-dev --cache-directory .ts-node test/ts/big'" }, "dependencies": { + "chokidar": "^3.1.0", "dateformat": "~1.0.4-1.2.3", "dynamic-dedupe": "^0.3.0", "filewatcher": "~3.0.0", @@ -50,7 +51,7 @@ "mkdirp": "^0.5.1", "node-notifier": "^5.4.0", "resolve": "^1.0.0", - "rimraf": "^2.6.1", + "rimraf": "^2.7.1", "source-map-support": "^0.5.12", "tree-kill": "^1.2.1", "ts-node": "*",