You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							240 lines
						
					
					
						
							6.3 KiB
						
					
					
				
			
		
		
	
	
							240 lines
						
					
					
						
							6.3 KiB
						
					
					
				| module.exports.watch = watch;
 | |
| module.exports.resetWatchers = resetWatchers;
 | |
| 
 | |
| var debug = require('debug')('nodemon:watch');
 | |
| var debugRoot = require('debug')('nodemon');
 | |
| var chokidar = require('chokidar');
 | |
| var undefsafe = require('undefsafe');
 | |
| var config = require('../config');
 | |
| var path = require('path');
 | |
| var utils = require('../utils');
 | |
| var bus = utils.bus;
 | |
| var match = require('./match');
 | |
| var watchers = [];
 | |
| var debouncedBus;
 | |
| 
 | |
| bus.on('reset', resetWatchers);
 | |
| 
 | |
| function resetWatchers() {
 | |
|   debugRoot('resetting watchers');
 | |
|   watchers.forEach(function (watcher) {
 | |
|     watcher.close();
 | |
|   });
 | |
|   watchers = [];
 | |
| }
 | |
| 
 | |
| function watch() {
 | |
|   if (watchers.length) {
 | |
|     debug('early exit on watch, still watching (%s)', watchers.length);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   var dirs = [].slice.call(config.dirs);
 | |
| 
 | |
|   debugRoot('start watch on: %s', dirs.join(', '));
 | |
|   const rootIgnored = config.options.ignore;
 | |
|   debugRoot('ignored', rootIgnored);
 | |
| 
 | |
|   var watchedFiles = [];
 | |
| 
 | |
|   const promise = new Promise(function (resolve) {
 | |
|     const dotFilePattern = /[/\\]\./;
 | |
|     var ignored = match.rulesToMonitor(
 | |
|       [], // not needed
 | |
|       Array.from(rootIgnored),
 | |
|       config
 | |
|     ).map(pattern => pattern.slice(1));
 | |
| 
 | |
|     const addDotFile = dirs.filter(dir => dir.match(dotFilePattern));
 | |
| 
 | |
|     // don't ignore dotfiles if explicitly watched.
 | |
|     if (addDotFile.length === 0) {
 | |
|       ignored.push(dotFilePattern);
 | |
|     }
 | |
| 
 | |
|     var watchOptions = {
 | |
|       ignorePermissionErrors: true,
 | |
|       ignored: ignored,
 | |
|       persistent: true,
 | |
|       usePolling: config.options.legacyWatch || false,
 | |
|       interval: config.options.pollingInterval,
 | |
|       // note to future developer: I've gone back and forth on adding `cwd`
 | |
|       // to the props and in some cases it fixes bugs but typically it causes
 | |
|       // bugs elsewhere (since nodemon is used is so many ways). the final
 | |
|       // decision is to *not* use it at all and work around it
 | |
|       // cwd: ...
 | |
|     };
 | |
| 
 | |
|     if (utils.isWindows) {
 | |
|       watchOptions.disableGlobbing = true;
 | |
|     }
 | |
| 
 | |
|     if (process.env.TEST) {
 | |
|       watchOptions.useFsEvents = false;
 | |
|     }
 | |
| 
 | |
|     var watcher = chokidar.watch(
 | |
|       dirs,
 | |
|       Object.assign({}, watchOptions, config.options.watchOptions || {})
 | |
|     );
 | |
| 
 | |
|     watcher.ready = false;
 | |
| 
 | |
|     var total = 0;
 | |
| 
 | |
|     watcher.on('change', filterAndRestart);
 | |
|     watcher.on('add', function (file) {
 | |
|       if (watcher.ready) {
 | |
|         return filterAndRestart(file);
 | |
|       }
 | |
| 
 | |
|       watchedFiles.push(file);
 | |
|       bus.emit('watching', file);
 | |
|       debug('chokidar watching: %s', file);
 | |
|     });
 | |
|     watcher.on('ready', function () {
 | |
|       watchedFiles = Array.from(new Set(watchedFiles)); // ensure no dupes
 | |
|       total = watchedFiles.length;
 | |
|       watcher.ready = true;
 | |
|       resolve(total);
 | |
|       debugRoot('watch is complete');
 | |
|     });
 | |
| 
 | |
|     watcher.on('error', function (error) {
 | |
|       if (error.code === 'EINVAL') {
 | |
|         utils.log.error(
 | |
|           'Internal watch failed. Likely cause: too many ' +
 | |
|           'files being watched (perhaps from the root of a drive?\n' +
 | |
|           'See https://github.com/paulmillr/chokidar/issues/229 for details'
 | |
|         );
 | |
|       } else {
 | |
|         utils.log.error('Internal watch failed: ' + error.message);
 | |
|         process.exit(1);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     watchers.push(watcher);
 | |
|   });
 | |
| 
 | |
|   return promise.catch(e => {
 | |
|     // this is a core error and it should break nodemon - so I have to break
 | |
|     // out of a promise using the setTimeout
 | |
|     setTimeout(() => {
 | |
|       throw e;
 | |
|     });
 | |
|   }).then(function () {
 | |
|     utils.log.detail(`watching ${watchedFiles.length} file${
 | |
|       watchedFiles.length === 1 ? '' : 's'}`);
 | |
|     return watchedFiles;
 | |
|   });
 | |
| }
 | |
| 
 | |
| function filterAndRestart(files) {
 | |
|   if (!Array.isArray(files)) {
 | |
|     files = [files];
 | |
|   }
 | |
| 
 | |
|   if (files.length) {
 | |
|     var cwd = process.cwd();
 | |
|     if (this.options && this.options.cwd) {
 | |
|       cwd = this.options.cwd;
 | |
|     }
 | |
| 
 | |
|     utils.log.detail(
 | |
|       'files triggering change check: ' +
 | |
|       files
 | |
|         .map(file => {
 | |
|           const res = path.relative(cwd, file);
 | |
|           return res;
 | |
|         })
 | |
|         .join(', ')
 | |
|     );
 | |
| 
 | |
|     // make sure the path is right and drop an empty
 | |
|     // filenames (sometimes on windows)
 | |
|     files = files.filter(Boolean).map(file => {
 | |
|       return path.relative(process.cwd(), path.relative(cwd, file));
 | |
|     });
 | |
| 
 | |
|     if (utils.isWindows) {
 | |
|       // ensure the drive letter is in uppercase (c:\foo -> C:\foo)
 | |
|       files = files.map(f => {
 | |
|         if (f.indexOf(':') === -1) { return f; }
 | |
|         return f[0].toUpperCase() + f.slice(1);
 | |
|       });
 | |
|     }
 | |
| 
 | |
| 
 | |
|     debug('filterAndRestart on', files);
 | |
| 
 | |
|     var matched = match(
 | |
|       files,
 | |
|       config.options.monitor,
 | |
|       undefsafe(config, 'options.execOptions.ext')
 | |
|     );
 | |
| 
 | |
|     debug('matched?', JSON.stringify(matched));
 | |
| 
 | |
|     // if there's no matches, then test to see if the changed file is the
 | |
|     // running script, if so, let's allow a restart
 | |
|     if (config.options.execOptions && config.options.execOptions.script) {
 | |
|       const script = path.resolve(config.options.execOptions.script);
 | |
|       if (matched.result.length === 0 && script) {
 | |
|         const length = script.length;
 | |
|         files.find(file => {
 | |
|           if (file.substr(-length, length) === script) {
 | |
|             matched = {
 | |
|               result: [file],
 | |
|               total: 1,
 | |
|             };
 | |
|             return true;
 | |
|           }
 | |
|         });
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     utils.log.detail(
 | |
|       'changes after filters (before/after): ' +
 | |
|       [files.length, matched.result.length].join('/')
 | |
|     );
 | |
| 
 | |
|     // reset the last check so we're only looking at recently modified files
 | |
|     config.lastStarted = Date.now();
 | |
| 
 | |
|     if (matched.result.length) {
 | |
|       if (config.options.delay > 0) {
 | |
|         utils.log.detail('delaying restart for ' + config.options.delay + 'ms');
 | |
|         if (debouncedBus === undefined) {
 | |
|           debouncedBus = debounce(restartBus, config.options.delay);
 | |
|         }
 | |
|         debouncedBus(matched);
 | |
|       } else {
 | |
|         return restartBus(matched);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| function restartBus(matched) {
 | |
|   utils.log.status('restarting due to changes...');
 | |
|   matched.result.map(file => {
 | |
|     utils.log.detail(path.relative(process.cwd(), file));
 | |
|   });
 | |
| 
 | |
|   if (config.options.verbose) {
 | |
|     utils.log._log('');
 | |
|   }
 | |
| 
 | |
|   bus.emit('restart', matched.result);
 | |
| }
 | |
| 
 | |
| function debounce(fn, delay) {
 | |
|   var timer = null;
 | |
|   return function () {
 | |
|     const context = this;
 | |
|     const args = arguments;
 | |
|     clearTimeout(timer);
 | |
|     timer = setTimeout(() =>fn.apply(context, args), delay);
 | |
|   };
 | |
| }
 |