Comparing version 3.0.0 to 3.0.1
347
index.js
@@ -6,3 +6,2 @@ 'use strict'; | ||
const readdirp = require('readdirp'); | ||
const asyncEach = require('async-each'); | ||
const anymatch = require('anymatch'); | ||
@@ -13,2 +12,3 @@ const globParent = require('glob-parent'); | ||
const normalizePath = require('normalize-path'); | ||
const { promisify } = require('util'); | ||
@@ -18,2 +18,5 @@ const NodeFsHandler = require('./lib/nodefs-handler'); | ||
const stat = promisify(fs.stat); | ||
const readdir = promisify(fs.readdir); | ||
/** | ||
@@ -59,2 +62,3 @@ * @typedef {String} Path | ||
const DOUBLE_SLASH = /\/\//; | ||
const SLASH_OR_BACK_SLASH = /[\/\\]/; | ||
const BRACE_START = '{'; | ||
@@ -64,4 +68,7 @@ const BANG = '!'; | ||
const TWO_DOTS = '..'; | ||
const GLOBSTAR = '**'; | ||
const SLASH_GLOBSTAR = '/**'; | ||
const DOT_RE = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/; | ||
const REPLACER_RE = /^\.[\/\\]/; | ||
const STRING_TYPE = 'string'; | ||
const EMPTY_FN = () => {}; | ||
@@ -82,6 +89,18 @@ | ||
const normalizeIgnored = (cwd = '') => (path) => { | ||
if (typeof path !== 'string') return path; | ||
if (typeof path !== STRING_TYPE) return path; | ||
return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path)); | ||
}; | ||
const getAbsolutePath = (path, cwd) => { | ||
if (sysPath.isAbsolute(path)) { | ||
return path; | ||
} else if (path[0] === BANG) { | ||
return BANG + sysPath.join(cwd, path.substring(1)); | ||
} else { | ||
return sysPath.join(cwd, path); | ||
} | ||
}; | ||
const undef = (opts, key) => opts[key] === undefined; | ||
/** | ||
@@ -103,6 +122,8 @@ * Directory entry. | ||
} | ||
add(item) { | ||
if (item !== ONE_DOT && item !== TWO_DOTS) this.items.add(item); | ||
} | ||
remove(item) { | ||
async remove(item) { | ||
this.items.delete(item); | ||
@@ -112,7 +133,10 @@ | ||
const dir = this.path; | ||
fs.readdir(dir, err => { | ||
if (err) this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir)); | ||
}); | ||
try { | ||
await readdir(dir); | ||
} catch (err) { | ||
this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir)); | ||
} | ||
} | ||
} | ||
has(item) { | ||
@@ -130,2 +154,77 @@ return this.items.has(item); | ||
class WatchHelper { | ||
constructor(path, watchPath, follow, fsw) { | ||
this.fsw = fsw; | ||
this.path = path = path.replace(REPLACER_RE, ''); | ||
this.watchPath = watchPath; | ||
this.fullWatchPath = sysPath.resolve(watchPath); | ||
this.hasGlob = watchPath !== path; | ||
this.globSymlink = this.hasGlob && follow ? null : false; | ||
this.globFilter = this.hasGlob ? anymatch(path) : false; | ||
this.dirParts = this.getDirParts(path); | ||
this.dirParts.forEach((parts) => { | ||
if (parts.length > 1) parts.pop(); | ||
}); | ||
this.followSymlinks = follow; | ||
this.statMethod = follow ? 'stat' : 'lstat'; | ||
} | ||
checkGlobSymlink(entry) { | ||
// only need to resolve once | ||
// first entry should always have entry.parentDir === '' | ||
if (this.globSymlink == null) { | ||
this.globSymlink = entry.fullParentDir === this.fullWatchPath ? | ||
false : {realPath: entry.fullParentDir, linkPath: this.fullWatchPath}; | ||
} | ||
if (this.globSymlink) { | ||
return entry.fullPath.replace(this.globSymlink.realPath, this.globSymlink.linkPath); | ||
} | ||
return entry.fullPath; | ||
} | ||
entryPath(entry) { | ||
return sysPath.join(this.watchPath, | ||
sysPath.relative(this.watchPath, this.checkGlobSymlink(entry)) | ||
); | ||
} | ||
filterPath(entry) { | ||
const {stats} = entry; | ||
if (stats && stats.isSymbolicLink()) return this.filterDir(entry); | ||
const resolvedPath = this.entryPath(entry); | ||
const matchesGlob = this.hasGlob ? this.globFilter(resolvedPath) : true; | ||
return matchesGlob && | ||
this.fsw._isntIgnored(resolvedPath, stats) && | ||
this.fsw._hasReadPermissions(stats); | ||
} | ||
getDirParts(path) { | ||
if (!this.hasGlob) return []; | ||
const parts = []; | ||
const expandedPath = path.includes(BRACE_START) | ||
? braces.expand(path) | ||
: [path]; | ||
expandedPath.forEach((path) => { | ||
parts.push(sysPath.relative(this.watchPath, path).split(SLASH_OR_BACK_SLASH)); | ||
}); | ||
return parts; | ||
} | ||
filterDir(entry) { | ||
if (this.hasGlob) { | ||
const entryParts = this.getDirParts(this.checkGlobSymlink(entry)); | ||
let globstar = false; | ||
this.unmatchedGlob = !this.dirParts.some((parts) => { | ||
return parts.every((part, i) => { | ||
if (part === GLOBSTAR) globstar = true; | ||
return globstar || !entryParts[0][i] || anymatch(part, entryParts[0][i]); | ||
}); | ||
}); | ||
} | ||
return !this.unmatchedGlob && this.fsw._isntIgnored(this.entryPath(entry), entry.stats); | ||
} | ||
} | ||
/** | ||
@@ -163,15 +262,14 @@ * Watches files & directories for changes. Emitted events: | ||
const undef = (key) => opts[key] === undefined; | ||
// Set up default options. | ||
if (undef('persistent')) opts.persistent = true; | ||
if (undef('ignoreInitial')) opts.ignoreInitial = false; | ||
if (undef('ignorePermissionErrors')) opts.ignorePermissionErrors = false; | ||
if (undef('interval')) opts.interval = 100; | ||
if (undef('binaryInterval')) opts.binaryInterval = 300; | ||
if (undef('disableGlobbing')) opts.disableGlobbing = false; | ||
if (undef(opts, 'persistent')) opts.persistent = true; | ||
if (undef(opts, 'ignoreInitial')) opts.ignoreInitial = false; | ||
if (undef(opts, 'ignorePermissionErrors')) opts.ignorePermissionErrors = false; | ||
if (undef(opts, 'interval')) opts.interval = 100; | ||
if (undef(opts, 'binaryInterval')) opts.binaryInterval = 300; | ||
if (undef(opts, 'disableGlobbing')) opts.disableGlobbing = false; | ||
opts.enableBinaryInterval = opts.binaryInterval !== opts.interval; | ||
// Enable fsevents on OS X when polling isn't explicitly enabled. | ||
if (undef('useFsEvents')) opts.useFsEvents = !opts.usePolling; | ||
if (undef(opts, 'useFsEvents')) opts.useFsEvents = !opts.usePolling; | ||
@@ -184,3 +282,3 @@ // If we can't use fsevents, ensure the options reflect it's disabled. | ||
// Other platforms use non-polling fs_watch. | ||
if (undef('usePolling') && !opts.useFsEvents) { | ||
if (undef(opts, 'usePolling') && !opts.useFsEvents) { | ||
opts.usePolling = process.platform === 'darwin'; | ||
@@ -209,8 +307,8 @@ } | ||
// Editor atomic write normalization enabled by default with fs.watch | ||
if (undef('atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents; | ||
if (undef(opts, 'atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents; | ||
if (opts.atomic) this._pendingUnlinks = new Map(); | ||
if (undef('followSymlinks')) opts.followSymlinks = true; | ||
if (undef(opts, 'followSymlinks')) opts.followSymlinks = true; | ||
if (undef('awaitWriteFinish')) opts.awaitWriteFinish = false; | ||
if (undef(opts, 'awaitWriteFinish')) opts.awaitWriteFinish = false; | ||
if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {}; | ||
@@ -232,3 +330,3 @@ const awf = opts.awaitWriteFinish; | ||
// use process.nextTick to allow time for listener to be bound | ||
process.nextTick(this.emit.bind(this, 'ready')); | ||
process.nextTick(() => this.emit('ready')); | ||
} | ||
@@ -254,2 +352,12 @@ }; | ||
_normalizePaths(paths_) { | ||
const paths = flatten(arrify(paths_)); | ||
if (!paths.every(p => typeof p === STRING_TYPE)) { | ||
throw new TypeError('Non-string provided as watch path: ' + paths); | ||
} | ||
return paths; | ||
} | ||
/** | ||
@@ -262,5 +370,4 @@ * Adds paths to be watched on an existing FSWatcher instance | ||
*/ | ||
add(paths_, _origAdd, _internal) { | ||
const disableGlobbing = this.options.disableGlobbing; | ||
const cwd = this.options.cwd; | ||
async add(paths_, _origAdd, _internal) { | ||
const {cwd, disableGlobbing} = this.options; | ||
this.closed = false; | ||
@@ -271,26 +378,17 @@ | ||
*/ | ||
let paths = flatten(arrify(paths_)); | ||
let paths = this._normalizePaths(paths_); | ||
if (!paths.every(p => typeof p === 'string')) { | ||
throw new TypeError('Non-string provided as watch path: ' + paths); | ||
if (cwd) { | ||
paths = paths.map((path) => { | ||
const absPath = getAbsolutePath(path, cwd); | ||
// Check `path` instead of `absPath` because the cwd portion can't be a glob | ||
if (disableGlobbing || !isGlob(path)) { | ||
return absPath; | ||
} else { | ||
return normalizePath(absPath); | ||
} | ||
}); | ||
} | ||
if (cwd) paths = paths.map((path) => { | ||
let absPath; | ||
if (sysPath.isAbsolute(path)) { | ||
absPath = path; | ||
} else if (path[0] === BANG) { | ||
absPath = BANG + sysPath.join(cwd, path.substring(1)); | ||
} else { | ||
absPath = sysPath.join(cwd, path); | ||
} | ||
// Check `path` instead of `absPath` because the cwd portion can't be a glob | ||
if (disableGlobbing || !isGlob(path)) { | ||
return absPath; | ||
} else { | ||
return normalizePath(absPath); | ||
} | ||
}); | ||
// set aside negated glob strings | ||
@@ -304,3 +402,3 @@ paths = paths.filter((path) => { | ||
this._ignoredPaths.delete(path); | ||
this._ignoredPaths.delete(path + '/**'); | ||
this._ignoredPaths.delete(path + SLASH_GLOBSTAR); | ||
@@ -322,12 +420,12 @@ // reset the cached userIgnored anymatch fn | ||
this._readyCount += paths.length; | ||
asyncEach(paths, (path, next) => { | ||
this._nodeFsHandler._addToNodeFs(path, !_internal, 0, 0, _origAdd, (err, res) => { | ||
const results = await Promise.all( | ||
paths.map(async path => { | ||
const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, 0, 0, _origAdd); | ||
if (res) this._emitReady(); | ||
next(err, res); | ||
}); | ||
}, (error, results) => { | ||
results.forEach((item) => { | ||
if (!item || this.closed) return; | ||
this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item)); | ||
}); | ||
return res; | ||
}) | ||
); | ||
results.forEach((item) => { | ||
if (!item || this.closed) return; | ||
this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item)); | ||
}); | ||
@@ -347,2 +445,3 @@ } | ||
paths = flatten(arrify(paths)); | ||
const cwd = this.options.cwd; | ||
@@ -352,3 +451,2 @@ paths.forEach((path) => { | ||
if (!sysPath.isAbsolute(path) && !this._closers.has(path)) { | ||
const cwd = this.options.cwd; | ||
if (cwd) path = sysPath.join(cwd, path); | ||
@@ -362,3 +460,3 @@ path = sysPath.resolve(path); | ||
if (this._watched.has(path)) { | ||
this._ignoredPaths.add(path + '/**'); | ||
this._ignoredPaths.add(path + SLASH_GLOBSTAR); | ||
} | ||
@@ -408,2 +506,7 @@ | ||
emitWithAll(event, args) { | ||
this.emit(...args); | ||
if (event !== 'error') this.emit(...['all'].concat(args)); | ||
} | ||
// Common helpers | ||
@@ -422,3 +525,5 @@ // -------------- | ||
*/ | ||
_emit(event, path, val1, val2, val3) { | ||
async _emit(event, path, val1, val2, val3) { | ||
if (this.closed) return; | ||
const opts = this.options; | ||
@@ -444,4 +549,4 @@ if (opts.cwd) path = sysPath.relative(opts.cwd, path); | ||
this._pendingUnlinks.forEach((entry, path) => { | ||
this.emit.apply(this, entry); | ||
this.emit.apply(this, ['all'].concat(entry)); | ||
this.emit(...entry); | ||
this.emit(...['all', ...entry]); | ||
this._pendingUnlinks.delete(path); | ||
@@ -457,7 +562,2 @@ }); | ||
const emitEvent = () => { | ||
this.emit.apply(this, args); | ||
if (event !== 'error') this.emit.apply(this, ['all'].concat(args)); | ||
}; | ||
if (awf && (event === 'add' || event === 'change') && this._readyEmitted) { | ||
@@ -468,3 +568,3 @@ const awfEmit = (err, stats) => { | ||
args[1] = err; | ||
emitEvent(); | ||
this.emitWithAll(event, args); | ||
} else if (stats) { | ||
@@ -477,3 +577,3 @@ // if stats doesn't exist the file must have been deleted | ||
} | ||
emitEvent(); | ||
this.emitWithAll(event, args); | ||
} | ||
@@ -495,11 +595,11 @@ }; | ||
const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path; | ||
fs.stat(fullPath, (error, stats) => { | ||
try { | ||
const stats = await stat(fullPath); | ||
// Suppress event when fs_stat fails, to avoid sending undefined 'stat' | ||
if (error || !stats) return; | ||
if (!stats) return; | ||
args.push(stats); | ||
emitEvent(); | ||
}); | ||
this.emitWithAll(event, args); | ||
} catch (err) {} | ||
} else { | ||
emitEvent(); | ||
this.emitWithAll(event, args); | ||
} | ||
@@ -604,4 +704,5 @@ | ||
timeoutHandler = setTimeout( | ||
awaitWriteFinish.bind(this, curStat), | ||
this.options.awaitWriteFinish.pollInterval | ||
awaitWriteFinish, | ||
this.options.awaitWriteFinish.pollInterval, | ||
curStat | ||
); | ||
@@ -622,3 +723,3 @@ } | ||
timeoutHandler = setTimeout( | ||
awaitWriteFinish.bind(this), | ||
awaitWriteFinish, | ||
this.options.awaitWriteFinish.pollInterval | ||
@@ -645,6 +746,6 @@ ); | ||
const ignored = ign && ign.map(normalizeIgnored(cwd)); | ||
const ignored = ign && ign.map(normalizeIgnored(cwd)); | ||
const paths = arrify(ignored) | ||
.filter((path) => typeof path === 'string' && !isGlob(path)) | ||
.map((path) => path + '/**'); | ||
.filter((path) => typeof path === STRING_TYPE && !isGlob(path)) | ||
.map((path) => path + SLASH_GLOBSTAR); | ||
this._userIgnored = anymatch( | ||
@@ -669,90 +770,9 @@ this._getGlobIgnored() | ||
* @param {Number=} depth at any depth > 0, this isn't a glob | ||
* @returns {WatchHelpers} object containing helpers for this path | ||
* @returns {WatchHelper} object containing helpers for this path | ||
*/ | ||
_getWatchHelpers(path, depth) { | ||
path = path.replace(REPLACER_RE, ''); | ||
const watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path); | ||
const fullWatchPath = sysPath.resolve(watchPath); | ||
const hasGlob = watchPath !== path; | ||
const globFilter = hasGlob ? anymatch(path) : false; | ||
const follow = this.options.followSymlinks; | ||
/** @type {any} */ | ||
let globSymlink = hasGlob && follow ? null : false; | ||
const checkGlobSymlink = (entry) => { | ||
// only need to resolve once | ||
// first entry should always have entry.parentDir === '' | ||
if (globSymlink == null) { | ||
globSymlink = entry.fullParentDir === fullWatchPath ? false : { | ||
realPath: entry.fullParentDir, | ||
linkPath: fullWatchPath | ||
}; | ||
} | ||
if (globSymlink) { | ||
return entry.fullPath.replace(globSymlink.realPath, globSymlink.linkPath); | ||
} | ||
return entry.fullPath; | ||
}; | ||
const entryPath = (entry) => { | ||
return sysPath.join(watchPath, | ||
sysPath.relative(watchPath, checkGlobSymlink(entry)) | ||
); | ||
}; | ||
const filterPath = (entry) => { | ||
const {stats} = entry; | ||
if (stats && stats.isSymbolicLink()) return filterDir(entry); | ||
const resolvedPath = entryPath(entry); | ||
const matchesGlob = hasGlob ? globFilter(resolvedPath) : true; | ||
return matchesGlob && | ||
this._isntIgnored(resolvedPath, stats) && | ||
this._hasReadPermissions(stats); | ||
}; | ||
const getDirParts = (path) => { | ||
if (!hasGlob) return []; | ||
const parts = []; | ||
const expandedPath = path.includes(BRACE_START) | ||
? braces.expand(path) | ||
: [path]; | ||
expandedPath.forEach((path) => { | ||
parts.push(sysPath.relative(watchPath, path).split(/[\/\\]/)); | ||
}); | ||
return parts; | ||
}; | ||
const dirParts = getDirParts(path); | ||
dirParts.forEach((parts) => { | ||
if (parts.length > 1) parts.pop(); | ||
}); | ||
let unmatchedGlob; | ||
const filterDir = (entry) => { | ||
if (hasGlob) { | ||
const entryParts = getDirParts(checkGlobSymlink(entry)); | ||
let globstar = false; | ||
unmatchedGlob = !dirParts.some((parts) => { | ||
return parts.every((part, i) => { | ||
if (part === '**') globstar = true; | ||
return globstar || !entryParts[0][i] || anymatch(part, entryParts[0][i]); | ||
}); | ||
}); | ||
} | ||
return !unmatchedGlob && this._isntIgnored(entryPath(entry), entry.stats); | ||
}; | ||
return { | ||
followSymlinks: follow, | ||
statMethod: follow ? 'stat' : 'lstat', | ||
path: path, | ||
watchPath: watchPath, | ||
entryPath: entryPath, | ||
hasGlob: hasGlob, | ||
globFilter: globFilter, | ||
filterPath: filterPath, | ||
filterDir: filterDir | ||
}; | ||
return new WatchHelper(path, watchPath, follow, this); | ||
} | ||
@@ -907,3 +927,8 @@ | ||
*/ | ||
const watch = (paths, options) => new FSWatcher(options).add(paths); | ||
const watch = (paths, options) => { | ||
const watcher = new FSWatcher(options); | ||
watcher.add(watcher._normalizePaths(paths)); | ||
return watcher; | ||
}; | ||
exports.watch = watch; |
@@ -5,2 +5,4 @@ 'use strict'; | ||
const sysPath = require('path'); | ||
const { promisify } = require('util'); | ||
let fsevents; | ||
@@ -23,4 +25,14 @@ try { fsevents = require('fsevents'); } catch (error) { | ||
const EMPTY_FN = () => {}; | ||
const Option = (key, value) => isNaN(value) ? {} : {[key]: value}; | ||
const stat = promisify(fs.stat); | ||
const open = promisify(fs.open); | ||
const close = promisify(fs.close); | ||
const lstat = promisify(fs.lstat); | ||
const realpath = promisify(fs.realpath); | ||
const statMethods = { stat, lstat }; | ||
/** | ||
@@ -87,3 +99,4 @@ * @typedef {String} Path | ||
const hasSymlink = resolvedPath !== realPath; | ||
function filteredListener(fullPath, flags, info) { | ||
const filteredListener = (fullPath, flags, info) => { | ||
if (hasSymlink) fullPath = fullPath.replace(realPath, resolvedPath); | ||
@@ -94,17 +107,17 @@ if ( | ||
) listener(fullPath, flags, info); | ||
} | ||
}; | ||
// check if there is already a watcher on a parent path | ||
// modifies `watchPath` to the parent path when it finds a match | ||
const watchedParent = () => { | ||
for (const watchedPath of FSEventsWatchers.keys()) { | ||
if (realPath.indexOf(sysPath.resolve(watchedPath) + sysPath.sep) === 0) { | ||
watchPath = watchedPath; | ||
cont = FSEventsWatchers.get(watchPath); | ||
return true; | ||
} | ||
let watchedParent = false; | ||
for (const watchedPath of FSEventsWatchers.keys()) { | ||
if (realPath.indexOf(sysPath.resolve(watchedPath) + sysPath.sep) === 0) { | ||
watchPath = watchedPath; | ||
cont = FSEventsWatchers.get(watchPath); | ||
watchedParent = true; | ||
break; | ||
} | ||
}; | ||
} | ||
if (cont || watchedParent()) { | ||
if (cont || watchedParent) { | ||
cont.listeners.add(filteredListener); | ||
@@ -129,3 +142,3 @@ } else { | ||
// instance if there are no more listeners left | ||
return function close() { | ||
return () => { | ||
const wl = cont.listeners; | ||
@@ -182,3 +195,68 @@ | ||
} | ||
checkIgnored(path, stats) { | ||
const ipaths = this.fsw._ignoredPaths; | ||
if (this.fsw._isIgnored(path, stats)) { | ||
ipaths.add(path); | ||
if (stats && stats.isDirectory()) { | ||
ipaths.add(path + '/**/*'); | ||
} | ||
return true; | ||
} else { | ||
ipaths.delete(path); | ||
ipaths.delete(path + '/**/*'); | ||
} | ||
} | ||
addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts) { | ||
const event = watchedDir.has(item) ? 'change' : 'add'; | ||
this.handleEvent(event, path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} | ||
async checkFd(path, fullPath, realPath, parent, watchedDir, item, info, opts) { | ||
try { | ||
const fd = await open(path, 'r'); | ||
await close(fd); | ||
this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} catch (error) { | ||
if (error.code !== 'EACCES') { | ||
this.handleEvent('unlink', path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} else { | ||
this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} | ||
} | ||
} | ||
handleEvent(event, path, fullPath, realPath, parent, watchedDir, item, info, opts) { | ||
if (this.fsw.closed || this.checkIgnored(path)) return; | ||
if (event === 'unlink') { | ||
// suppress unlink events on never before seen files | ||
if (info.type === 'directory' || watchedDir.has(item)) { | ||
this.fsw._remove(parent, item); | ||
} | ||
} else { | ||
if (event === 'add') { | ||
// track new directories | ||
if (info.type === 'directory') this.fsw._getWatchedDir(path); | ||
if (info.type === 'symlink' && opts.followSymlinks) { | ||
// push symlinks back to the top of the stack to get handled | ||
const curDepth = opts.depth === undefined ? | ||
undefined : depth(fullPath, realPath) + 1; | ||
return this._addToFsEvents(path, false, true, curDepth); | ||
} else { | ||
// track new paths | ||
// (other than symlinks being followed, which will be tracked soon) | ||
this.fsw._getWatchedDir(parent).add(item); | ||
} | ||
} | ||
/** | ||
* @type {'add'|'addDir'|'unlink'|'unlinkDir'} | ||
*/ | ||
const eventName = info.type === 'directory' ? event + 'Dir' : event; | ||
this.fsw._emit(eventName, path); | ||
if (eventName === 'addDir') this._addToFsEvents(path, false, true); | ||
} | ||
} | ||
/** | ||
@@ -196,3 +274,3 @@ * Handle symlinks encountered during directory scan | ||
const opts = this.fsw.options; | ||
const watchCallback = (fullPath, flags, info) => { | ||
const watchCallback = async (fullPath, flags, info) => { | ||
if ( | ||
@@ -212,80 +290,18 @@ opts.depth !== undefined && | ||
); | ||
const checkIgnored = (stats) => { | ||
const ipaths = this.fsw._ignoredPaths; | ||
if (this.fsw._isIgnored(path, stats)) { | ||
ipaths.add(path); | ||
if (stats && stats.isDirectory()) { | ||
ipaths.add(path + '/**/*'); | ||
} | ||
return true; | ||
} else { | ||
ipaths.delete(path); | ||
ipaths.delete(path + '/**/*'); | ||
} | ||
}; | ||
const handleEvent = (event) => { | ||
if (checkIgnored()) return; | ||
if (event === 'unlink') { | ||
// suppress unlink events on never before seen files | ||
if (info.type === 'directory' || watchedDir.has(item)) { | ||
this.fsw._remove(parent, item); | ||
} | ||
} else { | ||
if (event === 'add') { | ||
// track new directories | ||
if (info.type === 'directory') this.fsw._getWatchedDir(path); | ||
if (info.type === 'symlink' && opts.followSymlinks) { | ||
// push symlinks back to the top of the stack to get handled | ||
const curDepth = opts.depth === undefined ? | ||
undefined : depth(fullPath, realPath) + 1; | ||
return this._addToFsEvents(path, false, true, curDepth); | ||
} else { | ||
// track new paths | ||
// (other than symlinks being followed, which will be tracked soon) | ||
this.fsw._getWatchedDir(parent).add(item); | ||
} | ||
} | ||
/** | ||
* @type {'add'|'addDir'|'unlink'|'unlinkDir'} | ||
*/ | ||
const eventName = info.type === 'directory' ? event + 'Dir' : event; | ||
this.fsw._emit(eventName, path); | ||
if (eventName === 'addDir') this._addToFsEvents(path, false, true); | ||
} | ||
}; | ||
function addOrChange() { | ||
handleEvent(watchedDir.has(item) ? 'change' : 'add'); | ||
} | ||
function checkFd() { | ||
fs.open(path, 'r', function opened(error, fd) { | ||
if (error) { | ||
if (error.code !== 'EACCES') { | ||
handleEvent('unlink'); | ||
} else { | ||
addOrChange(); | ||
} | ||
} else { | ||
fs.close(fd, function closed(err) { | ||
if (err && err.code !== 'EACCES') { | ||
handleEvent('unlink'); | ||
} else { | ||
addOrChange(); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
// correct for wrong events emitted | ||
if (wrongEventFlags.has(flags) || info.event === 'unknown') { | ||
if (typeof opts.ignored === 'function') { | ||
fs.stat(path, (error, stats) => { | ||
if (checkIgnored(stats)) return; | ||
stats ? addOrChange() : handleEvent('unlink'); | ||
}); | ||
let stats; | ||
try { | ||
stats = await stat(path); | ||
} catch (error) {} | ||
if (this.checkIgnored(path, stats)) return; | ||
if (stats) { | ||
this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} else { | ||
this.handleEvent('unlink', path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} | ||
} else { | ||
checkFd(); | ||
this.checkFd(path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} | ||
@@ -296,6 +312,6 @@ } else { | ||
case 'modified': | ||
return addOrChange(); | ||
return this.addOrChange(path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
case 'deleted': | ||
case 'moved': | ||
return checkFd(); | ||
return this.checkFd(path, fullPath, realPath, parent, watchedDir, item, info, opts); | ||
} | ||
@@ -324,5 +340,5 @@ } | ||
*/ | ||
_handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { | ||
async _handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) { | ||
// don't follow the same symlink more than once | ||
if (this.fsw._symlinkPaths.has(fullPath)) return; | ||
if (this.fsw.closed || this.fsw._symlinkPaths.has(fullPath)) return; | ||
@@ -332,4 +348,5 @@ this.fsw._symlinkPaths.set(fullPath, true); | ||
fs.realpath(linkPath, (error, linkTarget) => { | ||
if (this.fsw._handleError(error) || this.fsw._isIgnored(linkTarget)) { | ||
try { | ||
const linkTarget = await realpath(linkPath); | ||
if (this.fsw._isIgnored(linkTarget)) { | ||
return this.fsw._emitReady(); | ||
@@ -352,6 +369,42 @@ } | ||
}, false, curDepth); | ||
}); | ||
} catch(error) { | ||
if (this.fsw._handleError(error)) { | ||
return this.fsw._emitReady(); | ||
} | ||
} | ||
} | ||
/** | ||
* | ||
* @param {Path} newPath | ||
* @param {fs.Stats} stats | ||
*/ | ||
emitAdd(newPath, stats, processPath, opts, forceAdd) { | ||
const pp = processPath(newPath); | ||
const isDir = stats.isDirectory(); | ||
const dirObj = this.fsw._getWatchedDir(sysPath.dirname(pp)); | ||
const base = sysPath.basename(pp); | ||
// ensure empty dirs get tracked | ||
if (isDir) this.fsw._getWatchedDir(pp); | ||
if (dirObj.has(base)) return; | ||
dirObj.add(base); | ||
if (!opts.ignoreInitial || forceAdd === true) { | ||
this.fsw._emit(isDir ? 'addDir' : 'add', pp, stats); | ||
} | ||
} | ||
initWatch(realPath, path, wh, processPath) { | ||
if (this.fsw.closed) return; | ||
const closer = this._watchWithFsEvents( | ||
wh.watchPath, | ||
sysPath.resolve(realPath || wh.watchPath), | ||
processPath, | ||
wh.globFilter | ||
); | ||
this.fsw._addPathCloser(path, closer); | ||
} | ||
/** | ||
* Handle added path with fsevents | ||
@@ -364,45 +417,23 @@ * @param {String} path file/dir path or glob pattern | ||
*/ | ||
_addToFsEvents(path, transform, forceAdd, priorDepth) { | ||
async _addToFsEvents(path, transform, forceAdd, priorDepth) { | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
const opts = this.fsw.options; | ||
const processPath = typeof transform === 'function' ? transform : (val => val); | ||
/** | ||
* | ||
* @param {Path} newPath | ||
* @param {fs.Stats} stats | ||
*/ | ||
const emitAdd = (newPath, stats) => { | ||
const pp = processPath(newPath); | ||
const isDir = stats.isDirectory(); | ||
const dirObj = this.fsw._getWatchedDir(sysPath.dirname(pp)); | ||
const base = sysPath.basename(pp); | ||
// ensure empty dirs get tracked | ||
if (isDir) this.fsw._getWatchedDir(pp); | ||
if (dirObj.has(base)) return; | ||
dirObj.add(base); | ||
if (!opts.ignoreInitial || forceAdd === true) { | ||
this.fsw._emit(isDir ? 'addDir' : 'add', pp, stats); | ||
} | ||
}; | ||
const wh = this.fsw._getWatchHelpers(path); | ||
// evaluate what is at the path we're being asked to watch | ||
fs[wh.statMethod](wh.watchPath, | ||
/** | ||
* @param {Error} error | ||
* @param {fs.Stats} stats | ||
*/ | ||
(error, stats) => { | ||
if (this.fsw._handleError(error) || this.fsw._isIgnored(wh.watchPath, stats)) { | ||
this.fsw._emitReady(); | ||
return this.fsw._emitReady(); | ||
try { | ||
const stats = await statMethods[wh.statMethod](wh.watchPath); | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
if (this.fsw._isIgnored(wh.watchPath, stats)) { | ||
throw null; | ||
} | ||
if (stats.isDirectory()) { | ||
// emit addDir unless this is a glob parent | ||
if (!wh.globFilter) emitAdd(processPath(path), stats); | ||
if (!wh.globFilter) this.emitAdd(processPath(path), stats, processPath, opts, forceAdd); | ||
@@ -414,7 +445,10 @@ // don't recurse further if it would exceed depth setting | ||
this.fsw._readdirp(wh.watchPath, { | ||
fileFilter: wh.filterPath, | ||
directoryFilter: wh.filterDir, | ||
fileFilter: entry => wh.filterPath(entry), | ||
directoryFilter: entry => wh.filterDir(entry), | ||
...Option("depth", opts.depth - (priorDepth || 0)) | ||
}).on('data', (entry) => { | ||
// need to check filterPath on dirs b/c filterDir is less restrictive | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
if (entry.stats.isDirectory() && !wh.filterPath(entry)) return; | ||
@@ -433,30 +467,29 @@ | ||
} else { | ||
emitAdd(joinedPath, entry.stats); | ||
this.emitAdd(joinedPath, entry.stats, processPath, opts, forceAdd); | ||
} | ||
}).on('error', () => {/* Ignore readdirp errors */}).on('end', () => { | ||
}).on('error', EMPTY_FN).on('end', () => { | ||
this.fsw._emitReady(); | ||
}); | ||
} else { | ||
emitAdd(wh.watchPath, stats); | ||
this.emitAdd(wh.watchPath, stats, processPath, opts, forceAdd); | ||
this.fsw._emitReady(); | ||
} | ||
}); | ||
} catch (error) { | ||
if (!error || this.fsw._handleError(error)) { | ||
// TODO: Strange thing: "should not choke on an ignored watch path" will be failed without 2 ready calls -__- | ||
this.fsw._emitReady(); | ||
this.fsw._emitReady(); | ||
} | ||
} | ||
if (opts.persistent && forceAdd !== true) { | ||
const initWatch = (error, realPath) => { | ||
if (this.fsw.closed) return; | ||
const closer = this._watchWithFsEvents( | ||
wh.watchPath, | ||
sysPath.resolve(realPath || wh.watchPath), | ||
processPath, | ||
wh.globFilter | ||
); | ||
this.fsw._addPathCloser(path, closer); | ||
}; | ||
if (typeof transform === 'function') { | ||
// realpath has already been resolved | ||
initWatch(); | ||
this.initWatch(null, path, wh, processPath); | ||
} else { | ||
fs.realpath(wh.watchPath, initWatch); | ||
let realPath; | ||
try { | ||
realPath = await realpath(wh.watchPath); | ||
} catch (e) {} | ||
this.initWatch(realPath, path, wh, processPath); | ||
} | ||
@@ -463,0 +496,0 @@ } |
@@ -6,3 +6,12 @@ 'use strict'; | ||
const isBinaryPath = require('is-binary-path'); | ||
const { promisify } = require('util'); | ||
const open = promisify(fs.open); | ||
const stat = promisify(fs.stat); | ||
const lstat = promisify(fs.lstat); | ||
const close = promisify(fs.close); | ||
const realpath = promisify(fs.realpath); | ||
const statMethods = { lstat, stat }; | ||
/** | ||
@@ -31,2 +40,3 @@ * @typedef {String} Path | ||
const emptyFn = () => {}; | ||
const FUNCTIONS = ['listeners', 'errHandlers', 'rawEmitters']; | ||
@@ -114,11 +124,11 @@ /** | ||
const broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers'); | ||
watcher.on('error', (error) => { | ||
watcher.on('error', async (error) => { | ||
cont.watcherUnusable = true; // documented since Node 10.4.1 | ||
// Workaround for https://github.com/joyent/node/issues/4337 | ||
if (process.platform === 'win32' && error.code === 'EPERM') { | ||
fs.open(path, 'r', (err, fd) => { | ||
if (!err) fs.close(fd, (err) => { | ||
if (!err) broadcastErr(error); | ||
}); | ||
}); | ||
try { | ||
const fd = await open(path, 'r'); | ||
await close(fd); | ||
broadcastErr(error); | ||
} catch (err) {} | ||
} else { | ||
@@ -140,3 +150,3 @@ broadcastErr(error); | ||
// instance if there are no more listeners left | ||
return function close() { | ||
return () => { | ||
cont.listeners.delete(listener); | ||
@@ -151,3 +161,3 @@ cont.errHandlers.delete(errHandler); | ||
FsWatchInstances.delete(fullPath); | ||
['listeners', 'errHandlers', 'rawEmitters'].forEach(key => { | ||
FUNCTIONS.forEach(key => { | ||
const set = cont[key]; | ||
@@ -223,3 +233,3 @@ set.clear(); | ||
// instance if there are no more listeners left. | ||
return function close() { | ||
return () => { | ||
cont.listeners.delete(listener); | ||
@@ -293,3 +303,6 @@ cont.rawEmitters.delete(rawEmitter); | ||
*/ | ||
_handleFile(file, stats, initialAdd, callback) { | ||
_handleFile(file, stats, initialAdd) { | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
const dirname = sysPath.dirname(file); | ||
@@ -302,22 +315,24 @@ const basename = sysPath.basename(file); | ||
// if the file is already being watched, do nothing | ||
if (parent.has(basename)) return callback(); | ||
if (parent.has(basename)) return; | ||
// kick off the watcher | ||
const closer = this._watchWithNodeFs(file, (path, newStats) => { | ||
const closer = this._watchWithNodeFs(file, async (path, newStats) => { | ||
if (!this.fsw._throttle('watch', file, 5)) return; | ||
if (!newStats || newStats && newStats.mtimeMs === 0) { | ||
fs.stat(file, (error, newStats) => { | ||
try { | ||
const newStats = await stat(file); | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
// Check that change event was not fired because of changed only accessTime. | ||
const at = newStats.atimeMs; | ||
const mt = newStats.mtimeMs; | ||
if (!at || at <= mt || mt !== prevStats.mtimeMs) { | ||
this.fsw._emit('change', file, newStats); | ||
} | ||
prevStats = newStats; | ||
} catch (error) { | ||
// Fix issues where mtime is null but file is still present | ||
if (error) { | ||
this.fsw._remove(dirname, basename); | ||
} else { | ||
// Check that change event was not fired because of changed only accessTime. | ||
const at = newStats.atimeMs; | ||
const mt = newStats.mtimeMs; | ||
if (!at || at <= mt || mt !== prevStats.mtimeMs) { | ||
this.fsw._emit('change', file, newStats); | ||
} | ||
prevStats = newStats; | ||
} | ||
}); | ||
this.fsw._remove(dirname, basename); | ||
} | ||
// add is about to be emitted if file not already tracked in parent | ||
@@ -341,3 +356,2 @@ } else if (parent.has(basename)) { | ||
if (callback) callback(); | ||
return closer; | ||
@@ -354,3 +368,6 @@ } | ||
*/ | ||
_handleSymlink(entry, directory, path, item) { | ||
async _handleSymlink(entry, directory, path, item) { | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
const full = entry.fullPath; | ||
@@ -362,15 +379,14 @@ const dir = this.fsw._getWatchedDir(directory); | ||
this.fsw._incrReadyCount(); | ||
fs.realpath(path, (error, linkPath) => { | ||
if (dir.has(item)) { | ||
if (this.fsw._symlinkPaths.get(full) !== linkPath) { | ||
this.fsw._symlinkPaths.set(full, linkPath); | ||
this.fsw._emit('change', path, entry.stats); | ||
} | ||
} else { | ||
dir.add(item); | ||
const linkPath = await realpath(path); | ||
if (dir.has(item)) { | ||
if (this.fsw._symlinkPaths.get(full) !== linkPath) { | ||
this.fsw._symlinkPaths.set(full, linkPath); | ||
this.fsw._emit('add', path, entry.stats); | ||
this.fsw._emit('change', path, entry.stats); | ||
} | ||
this.fsw._emitReady(); | ||
}); | ||
} else { | ||
dir.add(item); | ||
this.fsw._symlinkPaths.set(full, linkPath); | ||
this.fsw._emit('add', path, entry.stats); | ||
} | ||
this.fsw._emitReady(); | ||
return true; | ||
@@ -387,64 +403,47 @@ } | ||
/** | ||
* Read directory to add / remove files from `@watched` list and re-read it on change. | ||
* @param {String} dir fs path | ||
* @param {fs.Stats} stats | ||
* @param {Boolean} initialAdd | ||
* @param {Number} depth relative to user-supplied path | ||
* @param {String} target child path targetted for watch | ||
* @param {Object} wh Common watch helpers for this path | ||
* @param {Function} callback Called when dir scan is done | ||
* @returns {Function} closer for the watcher instance. | ||
*/ | ||
_handleDir(dir, stats, initialAdd, depth, target, wh, realpath, callback) { | ||
const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir)); | ||
const tracked = parentDir.has(sysPath.basename(dir)); | ||
if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) { | ||
if (!wh.hasGlob || wh.globFilter(dir)) this.fsw._emit('addDir', dir, stats); | ||
_handleRead(directory, initialAdd, wh, target, dir, depth, throttler) { | ||
// Normalize the directory name on Windows | ||
directory = sysPath.join(directory, ''); | ||
if (!wh.hasGlob) { | ||
// throttler = this.fsw._throttle('readdir', directory, 1000); | ||
// if (!throttler) return; | ||
} | ||
// ensure dir is tracked (harmless if redundant) | ||
parentDir.add(sysPath.basename(dir)); | ||
this.fsw._getWatchedDir(dir); | ||
let throttler; | ||
const previous = this.fsw._getWatchedDir(wh.path); | ||
const current = new Set(); | ||
const read = (directory, initialAdd, done) => { | ||
// Normalize the directory name on Windows | ||
directory = sysPath.join(directory, ''); | ||
if (!wh.hasGlob) { | ||
// throttler = this.fsw._throttle('readdir', directory, 1000); | ||
// if (!throttler) return; | ||
const stream = this.fsw._readdirp(directory, { | ||
fileFilter: entry => wh.filterPath(entry), | ||
directoryFilter: entry => wh.filterDir(entry), | ||
depth: 0, | ||
}).on('data', async (entry) => { | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
const item = entry.path; | ||
let path = sysPath.join(directory, item); | ||
current.add(item); | ||
const previous = this.fsw._getWatchedDir(wh.path); | ||
const current = new Set(); | ||
if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path, item)) | ||
return; | ||
this.fsw._readdirp(directory, { | ||
fileFilter: wh.filterPath, | ||
directoryFilter: wh.filterDir, | ||
depth: 0, | ||
}).on('data', (entry) => { | ||
const item = entry.path; | ||
let path = sysPath.join(directory, item); | ||
current.add(item); | ||
// Files that present in current directory snapshot | ||
// but absent in previous are added to watch list and | ||
// emit `add` event. | ||
if (item === target || !target && !previous.has(item)) { | ||
this.fsw._incrReadyCount(); | ||
if (entry.stats.isSymbolicLink() && | ||
this._handleSymlink(entry, directory, path, item)) return; | ||
// ensure relativeness of path is preserved in case of watcher reuse | ||
path = sysPath.join(dir, sysPath.relative(dir, path)); | ||
// Files that present in current directory snapshot | ||
// but absent in previous are added to watch list and | ||
// emit `add` event. | ||
if (item === target || !target && !previous.has(item)) { | ||
this.fsw._incrReadyCount(); | ||
// ensure relativeness of path is preserved in case of watcher reuse | ||
path = sysPath.join(dir, sysPath.relative(dir, path)); | ||
this._addToNodeFs(path, initialAdd, wh, depth + 1); | ||
} | ||
}).on('end', () => { | ||
this._addToNodeFs(path, initialAdd, wh, depth + 1); | ||
} | ||
}).on('error', this._boundHandleError); | ||
return new Promise(res => | ||
stream | ||
.on('end', () => { | ||
const wasThrottled = throttler ? throttler.clear() : false; | ||
if (done) done(); | ||
res(); | ||
@@ -454,4 +453,4 @@ // Files that absent in current directory snapshot | ||
// and are removed from @watched[directory]. | ||
previous.getChildren().filter((item) => { | ||
return item !== directory && | ||
previous.getChildren().filter((item) => | ||
item !== directory && | ||
!current.has(item) && | ||
@@ -463,4 +462,4 @@ // in case of intersecting globs; | ||
fullPath: sysPath.resolve(directory, item) | ||
})); | ||
}).forEach((item) => { | ||
})) | ||
).forEach((item) => { | ||
this.fsw._remove(directory, item); | ||
@@ -470,6 +469,31 @@ }); | ||
// one more time for any missed in case changes came in extremely quickly | ||
if (wasThrottled) read(directory, false); | ||
}).on('error', this._boundHandleError); | ||
}; | ||
if (wasThrottled) this._handleRead(directory, false, wh, target, dir, depth, throttler); | ||
}) | ||
); | ||
} | ||
/** | ||
* Read directory to add / remove files from `@watched` list and re-read it on change. | ||
* @param {String} dir fs path | ||
* @param {fs.Stats} stats | ||
* @param {Boolean} initialAdd | ||
* @param {Number} depth relative to user-supplied path | ||
* @param {String} target child path targetted for watch | ||
* @param {Object} wh Common watch helpers for this path | ||
* @param {Function} callback Called when dir scan is done | ||
* @returns {Function} closer for the watcher instance. | ||
*/ | ||
async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath) { | ||
const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir)); | ||
const tracked = parentDir.has(sysPath.basename(dir)); | ||
if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) { | ||
if (!wh.hasGlob || wh.globFilter(dir)) this.fsw._emit('addDir', dir, stats); | ||
} | ||
// ensure dir is tracked (harmless if redundant) | ||
parentDir.add(sysPath.basename(dir)); | ||
this.fsw._getWatchedDir(dir); | ||
let throttler; | ||
let closer; | ||
@@ -479,3 +503,6 @@ | ||
if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath)) { | ||
if (!target) read(dir, initialAdd, callback); | ||
if (!target) { | ||
await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler); | ||
} | ||
closer = this._watchWithNodeFs(dir, (dirPath, stats) => { | ||
@@ -485,6 +512,4 @@ // if current directory is removed, do nothing | ||
read(dirPath, false); | ||
this._handleRead(dirPath, false, wh, target, dir, depth, throttler); | ||
}); | ||
} else { | ||
callback(); | ||
} | ||
@@ -505,57 +530,57 @@ return closer; | ||
*/ | ||
_addToNodeFs(path, initialAdd, priorWh, depth, target, callback) { | ||
if (!callback) callback = Function.prototype; | ||
async _addToNodeFs(path, initialAdd, priorWh, depth, target) { | ||
const ready = this.fsw._emitReady; | ||
if (this.fsw._isIgnored(path) || this.fsw.closed) { | ||
ready(); | ||
return callback(null, false); | ||
return false; | ||
} | ||
const wh = this.fsw._getWatchHelpers(path, depth); | ||
let wh = this.fsw._getWatchHelpers(path, depth); | ||
if (!wh.hasGlob && priorWh) { | ||
wh.hasGlob = priorWh.hasGlob; | ||
wh.globFilter = priorWh.globFilter; | ||
wh.filterPath = priorWh.filterPath; | ||
wh.filterDir = priorWh.filterDir; | ||
wh.filterPath = entry => priorWh.filterPath(entry); | ||
wh.filterDir = entry => priorWh.filterDir(entry); | ||
} | ||
// evaluate what is at the path we're being asked to watch | ||
fs[wh.statMethod](wh.watchPath, (error, stats) => { | ||
if (this.fsw._handleError(error)) return callback(null, path); | ||
try { | ||
const stats = await statMethods[wh.statMethod](wh.watchPath); | ||
if (this.fsw.closed) { | ||
return; | ||
} | ||
if (this.fsw._isIgnored(wh.watchPath, stats)) { | ||
ready(); | ||
return callback(null, false); | ||
return false; | ||
} | ||
const initDir = (dir, target, realpath) => { | ||
return this._handleDir(dir, stats, initialAdd, depth, target, wh, realpath, ready); | ||
}; | ||
const targetPath = path.includes("*") || path.includes("{") ? path : await realpath(path); | ||
let closer; | ||
if (stats.isDirectory()) { | ||
closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath); | ||
// preserve this symlink's target path | ||
if (path !== targetPath && targetPath !== undefined) { | ||
this.fsw._symlinkPaths.set(targetPath, true); | ||
} | ||
} else if (stats.isSymbolicLink()) { | ||
const parent = sysPath.dirname(wh.watchPath); | ||
this.fsw._getWatchedDir(parent).add(wh.watchPath); | ||
this.fsw._emit('add', wh.watchPath, stats); | ||
closer = await this._handleDir(parent, stats, initialAdd, depth, path, wh, targetPath); | ||
fs.realpath(path, (error, targetPath) => { | ||
let closer; | ||
if (stats.isDirectory()) { | ||
closer = initDir(wh.watchPath, target, targetPath); | ||
// preserve this symlink's target path | ||
if (path !== targetPath && targetPath !== undefined) { | ||
this.fsw._symlinkPaths.set(targetPath, true); | ||
} | ||
} else if (stats.isSymbolicLink()) { | ||
const parent = sysPath.dirname(wh.watchPath); | ||
this.fsw._getWatchedDir(parent).add(wh.watchPath); | ||
this.fsw._emit('add', wh.watchPath, stats); | ||
closer = initDir(parent, path, targetPath); | ||
// preserve this symlink's target path | ||
if (targetPath !== undefined) { | ||
this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath); | ||
} | ||
ready(); | ||
} else { | ||
closer = this._handleFile(wh.watchPath, stats, initialAdd, ready); | ||
// preserve this symlink's target path | ||
if (targetPath !== undefined) { | ||
this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath); | ||
} | ||
} else { | ||
closer = this._handleFile(wh.watchPath, stats, initialAdd); | ||
} | ||
ready(); | ||
this.fsw._addPathCloser(path, closer); | ||
callback(null, false); | ||
}); | ||
}); | ||
this.fsw._addPathCloser(path, closer); | ||
return false; | ||
} catch (error) { | ||
if (this.fsw._handleError(error)) return path; | ||
} | ||
} | ||
@@ -562,0 +587,0 @@ |
{ | ||
"name": "chokidar", | ||
"description": "A neat wrapper around node.js fs.watch / fs.watchFile / fsevents.", | ||
"version": "3.0.0", | ||
"version": "3.0.1", | ||
"homepage": "https://github.com/paulmillr/chokidar", | ||
@@ -22,3 +22,3 @@ "author": "Paul Miller (https://paulmillr.com)", | ||
"normalize-path": "^3.0.0", | ||
"readdirp": "^3.0.1" | ||
"readdirp": "^3.0.2" | ||
}, | ||
@@ -25,0 +25,0 @@ "optionalDependencies": { |
@@ -274,3 +274,3 @@ # Chokidar [![Weekly downloads](https://img.shields.io/npm/dw/chokidar.svg)](https://github.com/paulmillr/chokidar) [![Yearly downloads](https://img.shields.io/npm/dy/chokidar.svg)](https://github.com/paulmillr/chokidar) | ||
For more detailed changelog, see `.github/full_changelog.md` | ||
For more detailed changelog, see [`.github/full_changelog.md`](.github/full_changelog.md). | ||
@@ -277,0 +277,0 @@ - v3 (Apr 30, 2019): Massive CPU & RAM consumption improvements. 17x package & deps size reduction. Node 8+-only |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
81090
1876
0
2
Updatedreaddirp@^3.0.2