Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

chokidar

Package Overview
Dependencies
Maintainers
2
Versions
106
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

chokidar - npm Package Compare versions

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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc