UNPKG

6.1 kBJavaScriptView Raw
1module.exports.watch = watch;
2module.exports.resetWatchers = resetWatchers;
3
4var debug = require('debug')('nodemon:watch');
5var debugRoot = require('debug')('nodemon');
6var chokidar = require('chokidar');
7var undefsafe = require('undefsafe');
8var config = require('../config');
9var path = require('path');
10var utils = require('../utils');
11var bus = utils.bus;
12var match = require('./match');
13var watchers = [];
14var debouncedBus;
15
16bus.on('reset', resetWatchers);
17
18function resetWatchers() {
19 debugRoot('resetting watchers');
20 watchers.forEach(function (watcher) {
21 watcher.close();
22 });
23 watchers = [];
24}
25
26function watch() {
27 if (watchers.length) {
28 debug('early exit on watch, still watching (%s)', watchers.length);
29 return;
30 }
31
32 var dirs = [].slice.call(config.dirs);
33
34 debugRoot('start watch on: %s', dirs.join(', '));
35 const rootIgnored = config.options.ignore;
36 debugRoot('ignored', rootIgnored);
37
38 var watchedFiles = [];
39
40 const promise = new Promise(function (resolve) {
41 const dotFilePattern = /[/\\]\./;
42 var ignored = match.rulesToMonitor(
43 [], // not needed
44 Array.from(rootIgnored),
45 config
46 ).map(pattern => pattern.slice(1));
47
48 const addDotFile = dirs.filter(dir => dir.match(dotFilePattern));
49
50 // don't ignore dotfiles if explicitly watched.
51 if (addDotFile.length === 0) {
52 ignored.push(dotFilePattern);
53 }
54
55 var watchOptions = {
56 ignorePermissionErrors: true,
57 ignored: ignored,
58 persistent: true,
59 usePolling: config.options.legacyWatch || false,
60 interval: config.options.pollingInterval,
61 };
62
63 if (utils.isWindows) {
64 watchOptions.disableGlobbing = true;
65 }
66
67 if (process.env.TEST) {
68 watchOptions.useFsEvents = false;
69 }
70
71 var watcher = chokidar.watch(
72 dirs,
73 Object.assign({}, watchOptions, config.watchOptions || {})
74 );
75
76 watcher.ready = false;
77
78 var total = 0;
79
80 watcher.on('change', filterAndRestart);
81 watcher.on('add', function (file) {
82 if (watcher.ready) {
83 return filterAndRestart(file);
84 }
85
86 watchedFiles.push(file);
87 bus.emit('watching', file);
88 debug('watching dir: %s', file);
89 });
90 watcher.on('ready', function () {
91 watchedFiles = Array.from(new Set(watchedFiles)); // ensure no dupes
92 total = watchedFiles.length;
93 watcher.ready = true;
94 resolve(total);
95 debugRoot('watch is complete');
96 });
97
98 watcher.on('error', function (error) {
99 if (error.code === 'EINVAL') {
100 utils.log.error(
101 'Internal watch failed. Likely cause: too many ' +
102 'files being watched (perhaps from the root of a drive?\n' +
103 'See https://github.com/paulmillr/chokidar/issues/229 for details'
104 );
105 } else {
106 utils.log.error('Internal watch failed: ' + error.message);
107 process.exit(1);
108 }
109 });
110
111 watchers.push(watcher);
112 });
113
114 return promise.catch(e => {
115 // this is a core error and it should break nodemon - so I have to break
116 // out of a promise using the setTimeout
117 setTimeout(() => {
118 throw e;
119 });
120 }).then(function () {
121 utils.log.detail(`watching ${watchedFiles.length} file${
122 watchedFiles.length === 1 ? '' : 's'}`);
123 return watchedFiles;
124 });
125}
126
127function filterAndRestart(files) {
128 if (!Array.isArray(files)) {
129 files = [files];
130 }
131
132 if (files.length) {
133 var cwd = process.cwd();
134 if (this.options && this.options.cwd) {
135 cwd = this.options.cwd;
136 }
137
138 utils.log.detail(
139 'files triggering change check: ' +
140 files
141 .map(file => {
142 const res = path.relative(cwd, file);
143 return res;
144 })
145 .join(', ')
146 );
147
148 // make sure the path is right and drop an empty
149 // filenames (sometimes on windows)
150 files = files.filter(Boolean).map(file => {
151 return path.relative(process.cwd(), path.relative(cwd, file));
152 });
153
154 if (utils.isWindows) {
155 // ensure the drive letter is in uppercase (c:\foo -> C:\foo)
156 files = files.map(f => {
157 if (f.indexOf(':') === -1) { return f; }
158 return f[0].toUpperCase() + f.slice(1);
159 });
160 }
161
162
163 debug('filterAndRestart on', files);
164
165 var matched = match(
166 files,
167 config.options.monitor,
168 undefsafe(config, 'options.execOptions.ext')
169 );
170
171 debug('matched?', JSON.stringify(matched));
172
173 // if there's no matches, then test to see if the changed file is the
174 // running script, if so, let's allow a restart
175 if (config.options.execOptions.script) {
176 const script = path.resolve(config.options.execOptions.script);
177 if (matched.result.length === 0 && script) {
178 const length = script.length;
179 files.find(file => {
180 if (file.substr(-length, length) === script) {
181 matched = {
182 result: [file],
183 total: 1,
184 };
185 return true;
186 }
187 });
188 }
189 }
190
191 utils.log.detail(
192 'changes after filters (before/after): ' +
193 [files.length, matched.result.length].join('/')
194 );
195
196 // reset the last check so we're only looking at recently modified files
197 config.lastStarted = Date.now();
198
199 if (matched.result.length) {
200 if (config.options.delay > 0) {
201 utils.log.detail('delaying restart for ' + config.options.delay + 'ms');
202 if (debouncedBus === undefined) {
203 debouncedBus = debounce(restartBus, config.options.delay);
204 }
205 debouncedBus(matched);
206 } else {
207 return restartBus(matched);
208 }
209 }
210 }
211}
212
213function restartBus(matched) {
214 utils.log.status('restarting due to changes...');
215 matched.result.map(file => {
216 utils.log.detail(path.relative(process.cwd(), file));
217 });
218
219 if (config.options.verbose) {
220 utils.log._log('');
221 }
222
223 bus.emit('restart', matched.result);
224}
225
226function debounce(fn, delay) {
227 var timer = null;
228 return function () {
229 const context = this;
230 const args = arguments;
231 clearTimeout(timer);
232 timer = setTimeout(() =>fn.apply(context, args), delay);
233 };
234}