1 | module.exports.watch = watch;
|
2 | module.exports.resetWatchers = resetWatchers;
|
3 |
|
4 | var debug = require('debug')('nodemon:watch');
|
5 | var debugRoot = require('debug')('nodemon');
|
6 | var chokidar = require('chokidar');
|
7 | var undefsafe = require('undefsafe');
|
8 | var config = require('../config');
|
9 | var path = require('path');
|
10 | var utils = require('../utils');
|
11 | var bus = utils.bus;
|
12 | var match = require('./match');
|
13 | var watchers = [];
|
14 | var debouncedBus;
|
15 |
|
16 | bus.on('reset', resetWatchers);
|
17 |
|
18 | function resetWatchers() {
|
19 | debugRoot('resetting watchers');
|
20 | watchers.forEach(function (watcher) {
|
21 | watcher.close();
|
22 | });
|
23 | watchers = [];
|
24 | }
|
25 |
|
26 | function 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 | [],
|
44 | Array.from(rootIgnored),
|
45 | config
|
46 | ).map(pattern => pattern.slice(1));
|
47 |
|
48 | const addDotFile = dirs.filter(dir => dir.match(dotFilePattern));
|
49 |
|
50 |
|
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));
|
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 |
|
116 |
|
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 |
|
127 | function 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 |
|
149 |
|
150 | files = files.filter(Boolean).map(file => {
|
151 | return path.relative(process.cwd(), path.relative(cwd, file));
|
152 | });
|
153 |
|
154 | if (utils.isWindows) {
|
155 |
|
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 |
|
174 |
|
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 |
|
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 |
|
213 | function 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 |
|
226 | function 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 | }
|