1 | var fork = require('child_process').fork
|
2 | var chokidar = require('chokidar')
|
3 | var ipc = require('./ipc')
|
4 | var resolveMain = require('./resolveMain')
|
5 | var compiler = require('./compiler')
|
6 | var fs = require('fs')
|
7 | var tsNodeVersion = require('ts-node').VERSION
|
8 | var tsVersion = require('typescript').version
|
9 | var kill = require('tree-kill')
|
10 | var readline = require('readline')
|
11 |
|
12 | module.exports = function (script, scriptArgs, nodeArgs, opts) {
|
13 | if (typeof script !== 'string' || script.length === 0) {
|
14 | throw new TypeError('`script` must be a string')
|
15 | }
|
16 |
|
17 | if (!Array.isArray(scriptArgs)) {
|
18 | throw new TypeError('`scriptArgs` must be an array')
|
19 | }
|
20 |
|
21 | if (!Array.isArray(nodeArgs)) {
|
22 | throw new TypeError('`nodeArgs` must be an array')
|
23 | }
|
24 |
|
25 |
|
26 | var child
|
27 |
|
28 | var wrapper = resolveMain(__dirname + '/wrap.js')
|
29 | var main = resolveMain(script)
|
30 | var cfg = require('./cfg')(main, opts)
|
31 | var log = require('./log')(cfg)
|
32 | var notify = require('./notify')(cfg, log)
|
33 | opts.log = log
|
34 | compiler.init(opts)
|
35 |
|
36 | compiler.notify = notify
|
37 | compiler.stop = stop
|
38 |
|
39 | if (cfg.dedupe) process.env.NODE_DEV_PRELOAD = __dirname + '/dedupe'
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 | function initWatcher() {
|
49 | var watcher = chokidar.watch([], {
|
50 | usePolling: opts.poll,
|
51 | interval: parseInt(opts.interval) || undefined,
|
52 | })
|
53 | watcher.on('change', restart)
|
54 |
|
55 | watcher.on('fallback', function (limit) {
|
56 | log.warn(
|
57 | 'node-dev ran out of file handles after watching %s files.',
|
58 | limit
|
59 | )
|
60 | log.warn('Falling back to polling which uses more CPU.')
|
61 | log.info('Run ulimit -n 10000 to increase the file descriptor limit.')
|
62 | if (cfg.deps) log.info('... or add `--no-deps` to use less file handles.')
|
63 | })
|
64 | return watcher
|
65 | }
|
66 | var watcher = initWatcher()
|
67 |
|
68 | var starting = false
|
69 |
|
70 |
|
71 | if (opts.rs !== false) {
|
72 | const rl = readline.createInterface({
|
73 | input: process.stdin,
|
74 | output: process.stdout,
|
75 | terminal: false,
|
76 | })
|
77 | rl.on('line', function (line) {
|
78 | if (line.trim() === 'rs') {
|
79 | restart('', true)
|
80 | }
|
81 | })
|
82 | }
|
83 |
|
84 | |
85 |
|
86 |
|
87 | var compileReqWatcher
|
88 | function start() {
|
89 | console.log(
|
90 | 'Using ts-node version',
|
91 | tsNodeVersion + ', typescript version',
|
92 | tsVersion
|
93 | )
|
94 | for (let watched of (opts.watch || '').split(',')) {
|
95 | if (watched) watcher.add(watched)
|
96 | }
|
97 | var cmd = nodeArgs.concat(wrapper, script, scriptArgs)
|
98 | var childHookPath = compiler.getChildHookPath()
|
99 | cmd = (opts.priorNodeArgs || []).concat(['-r', childHookPath]).concat(cmd)
|
100 | log.debug('Starting child process %s', cmd.join(' '))
|
101 | child = fork(cmd[0], cmd.slice(1), {
|
102 | cwd: process.cwd(),
|
103 | env: process.env,
|
104 | })
|
105 | starting = false
|
106 |
|
107 | if (compileReqWatcher) {
|
108 | compileReqWatcher.close()
|
109 | }
|
110 | compileReqWatcher = chokidar.watch([], {
|
111 | usePolling: opts.poll,
|
112 | interval: parseInt(opts.interval) || undefined,
|
113 | })
|
114 | var currentCompilePath
|
115 | fs.writeFileSync(compiler.getCompileReqFilePath(), '')
|
116 | compileReqWatcher.add(compiler.getCompileReqFilePath())
|
117 | compileReqWatcher.on('change', function (file) {
|
118 | fs.readFile(file, 'utf-8', function (err, data) {
|
119 | if (err) {
|
120 | log.error('Error reading compile request file', err)
|
121 | return
|
122 | }
|
123 | var split = data.split('\n')
|
124 | var compile = split[0]
|
125 | var compiledPath = split[1]
|
126 | if (currentCompilePath == compiledPath) return
|
127 | currentCompilePath = compiledPath
|
128 |
|
129 | if (compiledPath) {
|
130 | compiler.compile({
|
131 | compile: compile,
|
132 | compiledPath: compiledPath,
|
133 | })
|
134 | }
|
135 | })
|
136 | })
|
137 | child.on('message', function (message) {
|
138 | if (!message.compiledPath || currentCompilePath === message.compiledPath)
|
139 | return
|
140 | currentCompilePath = message.compiledPath
|
141 | compiler.compile(message)
|
142 | })
|
143 |
|
144 | child.on('exit', function (code) {
|
145 | log.debug('Child exited with code %s', code)
|
146 | if (!child) return
|
147 | if (!child.respawn) process.exit(code)
|
148 | child = undefined
|
149 | })
|
150 |
|
151 | if (cfg.respawn) {
|
152 | child.respawn = true
|
153 | }
|
154 |
|
155 | if (compiler.tsConfigPath) {
|
156 | watcher.add(compiler.tsConfigPath)
|
157 | }
|
158 |
|
159 |
|
160 | ipc.on(child, 'required', function (m) {
|
161 | var isIgnored =
|
162 | cfg.ignore.some(isPrefixOf(m.required)) ||
|
163 | cfg.ignore.some(isRegExpMatch(m.required))
|
164 |
|
165 | if (!isIgnored && (cfg.deps === -1 || getLevel(m.required) <= cfg.deps)) {
|
166 | log.debug(m.required, 'added to watcher')
|
167 | watcher.add(m.required)
|
168 | }
|
169 | })
|
170 |
|
171 |
|
172 | ipc.on(child, 'error', function (m) {
|
173 | log.debug('Child error')
|
174 | notify(m.error, m.message, 'error')
|
175 | stop(m.willTerminate)
|
176 | })
|
177 | compiler.writeReadyFile()
|
178 | }
|
179 | const killChild = () => {
|
180 | if (!child) return
|
181 | log.debug('Sending SIGTERM kill to child pid', child.pid)
|
182 | if (opts['tree-kill']) {
|
183 | log.debug('Using tree-kill')
|
184 | kill(child.pid)
|
185 | } else {
|
186 | child.kill('SIGTERM')
|
187 | }
|
188 | }
|
189 | function stop(willTerminate) {
|
190 | if (!child || child.stopping) {
|
191 | return
|
192 | }
|
193 | child.stopping = true
|
194 | child.respawn = true
|
195 | if (child.connected === undefined || child.connected === true) {
|
196 | log.debug('Disconnecting from child')
|
197 | child.disconnect()
|
198 |
|
199 | killChild()
|
200 |
|
201 | }
|
202 | }
|
203 |
|
204 | function restart(file, isManualRestart) {
|
205 | if (file === compiler.tsConfigPath) {
|
206 | notify('Reinitializing TS compilation')
|
207 | compiler.init(opts)
|
208 | }
|
209 | compiler.clearErrorCompile()
|
210 |
|
211 | if (cfg.clear) process.stdout.write('\033[2J\033[H')
|
212 | if (isManualRestart === true) {
|
213 | notify('Restarting', 'manual restart from user')
|
214 | } else {
|
215 | notify('Restarting', file + ' has been modified')
|
216 | }
|
217 | compiler.compileChanged(file)
|
218 | if (starting) {
|
219 | log.debug('Already starting')
|
220 | return
|
221 | }
|
222 | log.debug('Removing all watchers from files')
|
223 |
|
224 |
|
225 | watcher.close()
|
226 | watcher = initWatcher()
|
227 | starting = true
|
228 | if (child) {
|
229 | log.debug('Child is still running, restart upon exit')
|
230 | child.on('exit', start)
|
231 | stop()
|
232 | } else {
|
233 | log.debug('Child is already stopped, probably due to a previous error')
|
234 | start()
|
235 | }
|
236 | }
|
237 | compiler.restart = restart
|
238 |
|
239 |
|
240 | process.on('SIGTERM', function () {
|
241 | log.debug('Process got SIGTERM')
|
242 | killChild()
|
243 | if (opts['restart-terminated']) {
|
244 | var timeout = opts['restart-terminated'] || 0
|
245 | log.info('Restarting terminated in ' + timeout + ' seconds')
|
246 | setTimeout(() => {
|
247 | start()
|
248 | }, timeout)
|
249 | } else {
|
250 | process.exit(0)
|
251 | }
|
252 | })
|
253 |
|
254 | start()
|
255 | }
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 | function getLevel(mod) {
|
263 | var p = getPrefix(mod)
|
264 | return p.split('node_modules').length - 1
|
265 | }
|
266 |
|
267 |
|
268 |
|
269 |
|
270 |
|
271 | function getPrefix(mod) {
|
272 | var n = 'node_modules'
|
273 | var i = mod.lastIndexOf(n)
|
274 | return ~i ? mod.slice(0, i + n.length) : ''
|
275 | }
|
276 |
|
277 | function isPrefixOf(value) {
|
278 | return function (prefix) {
|
279 | return value.indexOf(prefix) === 0
|
280 | }
|
281 | }
|
282 |
|
283 | function isRegExpMatch(value) {
|
284 | return function (regExp) {
|
285 | return new RegExp(regExp).test(value)
|
286 | }
|
287 | }
|