Spaces:
Sleeping
Sleeping
| 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); | |
| }; | |
| } | |