Skip to content
This repository was archived by the owner on Dec 19, 2025. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.vagrant
node_modules
*.log
.ts-node
.ts-node
.idea
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ tsnd --respawn server.ts
- `--debounce` - Debounce file change events (ms, non-polling mode)
- `--clear` (`--cls`) Will clear screen on restart
- `--watch` - Explicitly add files or folders to watch and restart on change (list separated by commas)
- `--notifyLevel` - `error` or `info` (default). The level for which OS level notifications should be sent

**Caveats and points of notice:**

Expand Down
1 change: 1 addition & 0 deletions bin/ts-node-dev
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ var opts = minimist(devArgs, {
'ignore-watch',
'interval',
'debounce',
'notifyLevel',
'watch'
],
alias: {
Expand Down
2 changes: 2 additions & 0 deletions lib/cfg.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = function (main, opts) {
if (opts.respawn) c.respawn = true;
if (opts.notify === false) c.notify = false;
if (opts.clear || opts.cls) c.clear = true;
if (opts.notifyLevel) c.notifyLevel = opts.notifyLevel;
}

var ignoreWatch = ([]).concat(opts && opts['ignore-watch'] || []).concat(c.ignore || []);
Expand All @@ -37,6 +38,7 @@ module.exports = function (main, opts) {
vm: c.vm !== false,
fork: c.fork !== false,
notify: c.notify !== false,
notifyLevel: c.notifyLevel || 'DEBUG',
deps: c.deps,
timestamp: c.timestamp || (c.timestamp !== false && 'HH:MM:ss'),
clear: !!(c.clear),
Expand Down
11 changes: 10 additions & 1 deletion lib/child-require-hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var fs = require('fs')
var getCompiledPath = require('./get-compiled-path')
var sep = require('path').sep
var join = require('path').join
var notify = require(join(__dirname, './notify'))
var execSync = require('child_process').execSync
var compilationId
var timeThreshold = 0
Expand All @@ -12,6 +13,7 @@ var ignore = [/node_modules/]
var readyFile
var execCheck = false
var sourceMapSupportPath
var cfg = {}

var checkFileScript = join(__dirname, 'check-file-exists.js')

Expand Down Expand Up @@ -112,9 +114,16 @@ if (readyFile) {
}
}

process.on('uncaughtException', err => {
notify(cfg, null, err)('Error', err.message || err.stack, 'error')
process.exitCode = 1
process.kill(process.pid, 'SIGTERM')
})

process.on('SIGTERM', function() {
// This is to make sure debuggers (e.g. Chrome) close
console.log('Child got SIGTERM, exiting.')
process.exit()
process.kill(process.pid, 'SIGINT')
})

module.exports.registerExtensions = registerExtensions
8 changes: 8 additions & 0 deletions lib/compiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ var compilationInstanceStamp = Math.random()
var compiler = {
allowJs: false,
tsConfigPath: '',
cfg: {},
setConfig(cfg) {
this.cfg = cfg
},
getCompilationId: function() {
return compilationInstanceStamp
},
Expand Down Expand Up @@ -113,6 +117,10 @@ var compiler = {
/__dirname/,
'"' + __dirname.replace(/\\/g, '/') + '"'
)
fileData = fileData.replace(
'var cfg = {}',
'var cfg = ' + JSON.stringify(this.cfg)
)
fs.writeFileSync(compiler.getChildHookPath(), fileData)
},
init: function(options) {
Expand Down
20 changes: 17 additions & 3 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ var fs = require('fs')
var tsNodeVersion = require('ts-node').VERSION
var tsVersion = require('typescript').version
var kill = require('tree-kill')
var path = require('path')
var chokidar = require('chokidar')

module.exports = function(script, scriptArgs, nodeArgs, opts) {
if (typeof script !== 'string' || script.length === 0) {
Expand All @@ -30,21 +32,23 @@ module.exports = function(script, scriptArgs, nodeArgs, opts) {
var log = require('./log')(cfg)
var notify = require('./notify')(cfg, log)
opts.log = log
compiler.setConfig(cfg)
compiler.init(opts)

compiler.notify = notify
compiler.stop = stop
// 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),
debounce: parseInt(opts.debounce),
recursive: true
})
var starting = false
watcher.on('change', function(file) {
function onFileChanged(file) {
if (file === compiler.tsConfigPath) {
notify('Reinitializing TS compilation')
compiler.init(opts)
Expand All @@ -61,6 +65,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) {
Expand All @@ -71,7 +77,8 @@ module.exports = function(script, scriptArgs, nodeArgs, opts) {
log.debug('Child is already stopped, probably due to a previous error')
start()
}
})
}
watcher.on('change', onFileChanged)

watcher.on('fallback', function(limit) {
log.warn('node-dev ran out of file handles after watching %s files.', limit)
Expand Down Expand Up @@ -161,7 +168,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) => onFileChanged(file))
chokidarWatches.push(watch)
}
notify(m.error, m.message, 'error')
stop(m.willTerminate)
})
Expand Down
15 changes: 13 additions & 2 deletions lib/notify.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@ function icon(level) {
return path.resolve(__dirname, '../icons/node_' + level + '.png');
}

function levelToInt(strLevel) {
if (!strLevel) return 0;
switch (strLevel.toLowerCase()) {
case 'info': return 1;
case 'error': return 3;
default: return 0;
}
}

/**
* Displays a desktop notification and writes a message to the console.
*/
module.exports = function (cfg, log) {
return function (title, msg, level) {
level = level || 'info';
log([title, msg].filter(_ => _).join(': '), level);
if (cfg.notify) {
if (log) {
log([title, msg].filter(_ => _).join(': '), level);
}
if (cfg.notify && levelToInt(cfg.notifyLevel) <= levelToInt(level)) {
notifier.notify({
title: title || 'node.js',
icon: icon(level),
Expand Down
19 changes: 16 additions & 3 deletions lib/wrap.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
var Module = require('module')
var path = require('path');
var childProcess = require('child_process');
var fork = childProcess.fork;
Expand All @@ -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);
Expand All @@ -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
});
});
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@
"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",
"minimist": "^1.1.3",
"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": "*",
Expand Down