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
			| 
											3 years ago
										 | 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); | ||
|  |   }; | ||
|  | } |