Socket
Socket
Sign inDemoInstall

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 2.1.5 to 3.0.0

LICENSE

1063

index.js
'use strict';
var EventEmitter = require('events').EventEmitter;
var fs = require('fs');
var sysPath = require('path');
var asyncEach = require('async-each');
var anymatch = require('anymatch');
var globParent = require('glob-parent');
var isGlob = require('is-glob');
var isAbsolute = require('path-is-absolute');
var inherits = require('inherits');
var braces = require('braces');
var normalizePath = require('normalize-path');
var upath = require('upath');
const EventEmitter = require('events').EventEmitter;
const fs = require('fs');
const sysPath = require('path');
const readdirp = require('readdirp');
const asyncEach = require('async-each');
const anymatch = require('anymatch');
const globParent = require('glob-parent');
const isGlob = require('is-glob');
const braces = require('braces');
const normalizePath = require('normalize-path');
var NodeFsHandler = require('./lib/nodefs-handler');
var FsEventsHandler = require('./lib/fsevents-handler');
const NodeFsHandler = require('./lib/nodefs-handler');
const FsEventsHandler = require('./lib/fsevents-handler');
var arrify = function(value) {
if (value == null) return [];
return Array.isArray(value) ? value : [value];
};
/**
* @typedef {String} Path
* @typedef {'all'|'add'|'addDir'|'change'|'unlink'|'unlinkDir'|'raw'|'error'|'ready'} EventName
* @typedef {'readdir'|'watch'|'add'|'remove'|'change'} ThrottleType
*/
var flatten = function(list, result) {
if (result == null) result = [];
list.forEach(function(item) {
/**
*
* @typedef {Object} WatchHelpers
* @property {Boolean} followSymlinks
* @property {'stat'|'lstat'} statMethod
* @property {Path} path
* @property {Path} watchPath
* @property {Function} entryPath
* @property {Boolean} hasGlob
* @property {Object} globFilter
* @property {Function} filterPath
* @property {Function} filterDir
*/
/**
* @param {String|Array<String>} value
*/
const arrify = (value = []) => Array.isArray(value) ? value : [value];
const flatten = (list, result = []) => {
list.forEach(item => {
if (Array.isArray(item)) {

@@ -35,43 +52,106 @@ flatten(item, result);

// Little isString util for use in Array#every.
var isString = function(thing) {
return typeof thing === 'string';
// Optimize RAM usage.
const BACK_SLASH = /\\/g;
const SLASH = '/';
const DOUBLE_SLASH = /\/\//;
const BRACE_START = '{';
const BANG = '!';
const ONE_DOT = '.';
const TWO_DOTS = '..';
const DOT_RE = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/;
const REPLACER_RE = /^\.[\/\\]/;
const EMPTY_FN = () => {};
const toUnix = (string) => {
let str = string.replace(BACK_SLASH, SLASH);
while (str.match(DOUBLE_SLASH)) {
str = str.replace(DOUBLE_SLASH, SLASH);
}
return str;
};
// Public: Main class.
// Watches files & directories for changes.
//
// * _opts - object, chokidar options hash
//
// Emitted events:
// `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
//
// Examples
//
// var watcher = new FSWatcher()
// .add(directories)
// .on('add', path => console.log('File', path, 'was added'))
// .on('change', path => console.log('File', path, 'was changed'))
// .on('unlink', path => console.log('File', path, 'was removed'))
// .on('all', (event, path) => console.log(path, ' emitted ', event))
//
function FSWatcher(_opts) {
EventEmitter.call(this);
var opts = {};
// in case _opts that is passed in is a frozen object
if (_opts) for (var opt in _opts) opts[opt] = _opts[opt];
this._watched = Object.create(null);
this._closers = Object.create(null);
this._ignoredPaths = Object.create(null);
Object.defineProperty(this, '_globIgnored', {
get: function() { return Object.keys(this._ignoredPaths); }
});
this.closed = false;
this._throttled = Object.create(null);
this._symlinkPaths = Object.create(null);
// Our version of upath.normalize
// TODO: this is not equal to path-normalize module - investigate why
const normalizePathToUnix = (path) => toUnix(sysPath.normalize(toUnix(path)));
function undef(key) {
return opts[key] === undefined;
const normalizeIgnored = (cwd = '') => (path) => {
if (typeof path !== 'string') return path;
return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path));
};
/**
* Directory entry.
* @property {Path} path
* @property {Set<Path>} items
*/
class DirEntry {
/**
* @param {Path} dir
* @param {Function} removeWatcher
*/
constructor(dir, removeWatcher) {
this.path = dir;
this._removeWatcher = removeWatcher;
/** @type {Set<Path>} */
this.items = new Set();
}
add(item) {
if (item !== ONE_DOT && item !== TWO_DOTS) this.items.add(item);
}
remove(item) {
this.items.delete(item);
if (!this.items.size) {
const dir = this.path;
fs.readdir(dir, err => {
if (err) this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir));
});
}
}
has(item) {
return this.items.has(item);
}
/**
* @returns {Array<String>}
*/
getChildren() {
return Array.from(this.items.values());
}
}
/**
* Watches files & directories for changes. Emitted events:
* `add`, `addDir`, `change`, `unlink`, `unlinkDir`, `all`, `error`
*
* new FSWatcher()
* .add(directories)
* .on('add', path => log('File', path, 'was added'))
*/
class FSWatcher extends EventEmitter {
// Not indenting methods for history sake; for now.
constructor(_opts) {
super();
const opts = {};
if (_opts) Object.assign(opts, _opts); // for frozen objects
/** @type {Map<String, DirEntry>} */
this._watched = new Map();
/** @type {Map<String, Array>} */
this._closers = new Map();
/** @type {Set<String>} */
this._ignoredPaths = new Set();
/** @type {Map<ThrottleType, Map>} */
this._throttled = new Map();
/** @type {Map<Path, String|Boolean>} */
this._symlinkPaths = new Map();
this._streams = new Set();
this.closed = false;
const undef = (key) => opts[key] === undefined;
// Set up default options.

@@ -84,3 +164,3 @@ if (undef('persistent')) opts.persistent = true;

if (undef('disableGlobbing')) opts.disableGlobbing = false;
this.enableBinaryInterval = opts.binaryInterval !== opts.interval;
opts.enableBinaryInterval = opts.binaryInterval !== opts.interval;

@@ -91,6 +171,7 @@ // Enable fsevents on OS X when polling isn't explicitly enabled.

// If we can't use fsevents, ensure the options reflect it's disabled.
if (!FsEventsHandler.canUse()) opts.useFsEvents = false;
const canUseFsEvents = FsEventsHandler.canUse();
if (!canUseFsEvents) opts.useFsEvents = false;
// Use polling on Mac if not using fsevents.
// Other platforms use non-polling fs.watch.
// Other platforms use non-polling fs_watch.
if (undef('usePolling') && !opts.useFsEvents) {

@@ -102,5 +183,5 @@ opts.usePolling = process.platform === 'darwin';

// instances of chokidar, regardless of usage/dependency depth)
var envPoll = process.env.CHOKIDAR_USEPOLLING;
const envPoll = process.env.CHOKIDAR_USEPOLLING;
if (envPoll !== undefined) {
var envLower = envPoll.toLowerCase();
const envLower = envPoll.toLowerCase();

@@ -112,6 +193,6 @@ if (envLower === 'false' || envLower === '0') {

} else {
opts.usePolling = !!envLower
opts.usePolling = !!envLower;
}
}
var envInterval = process.env.CHOKIDAR_INTERVAL;
const envInterval = process.env.CHOKIDAR_INTERVAL;
if (envInterval) {

@@ -123,3 +204,3 @@ opts.interval = parseInt(envInterval);

if (undef('atomic')) opts.atomic = !opts.usePolling && !opts.useFsEvents;
if (opts.atomic) this._pendingUnlinks = Object.create(null);
if (opts.atomic) this._pendingUnlinks = new Map();

@@ -130,19 +211,15 @@ if (undef('followSymlinks')) opts.followSymlinks = true;

if (opts.awaitWriteFinish === true) opts.awaitWriteFinish = {};
var awf = opts.awaitWriteFinish;
const awf = opts.awaitWriteFinish;
if (awf) {
if (!awf.stabilityThreshold) awf.stabilityThreshold = 2000;
if (!awf.pollInterval) awf.pollInterval = 100;
this._pendingWrites = Object.create(null);
this._pendingWrites = new Map();
}
if (opts.ignored) opts.ignored = arrify(opts.ignored);
this._isntIgnored = function(path, stat) {
return !this._isIgnored(path, stat);
}.bind(this);
var readyCalls = 0;
this._emitReady = function() {
if (++readyCalls >= this._readyCount) {
this._emitReady = Function.prototype;
let readyCalls = 0;
this._emitReady = () => {
readyCalls++;
if (readyCalls >= this._readyCount) {
this._emitReady = EMPTY_FN;
this._readyEmitted = true;

@@ -152,6 +229,14 @@ // use process.nextTick to allow time for listener to be bound

}
}.bind(this);
};
this._emitRaw = (...args) => this.emit('raw', ...args);
this._readyEmitted = false;
this.options = opts;
// Initialize with proper watcher.
if (opts.useFsEvents) {
this._fsEventsHandler = new FsEventsHandler(this);
} else {
this._nodeFsHandler = new NodeFsHandler(this);
}
// You’re frozen when your heart’s not open.

@@ -161,18 +246,169 @@ Object.freeze(opts);

inherits(FSWatcher, EventEmitter);
// Public methods
// --------------
/**
* Adds paths to be watched on an existing FSWatcher instance
* @param {Path|Array<Path>} paths_
* @param {String=} _origAdd private; for handling non-existent paths to be watched
* @param {Boolean=} _internal private; indicates a non-user add
* @returns {FSWatcher} for chaining
*/
add(paths_, _origAdd, _internal) {
const disableGlobbing = this.options.disableGlobbing;
const cwd = this.options.cwd;
this.closed = false;
/**
* @type {Array<String>}
*/
let paths = flatten(arrify(paths_));
if (!paths.every(p => typeof p === 'string')) {
throw new TypeError('Non-string provided as watch path: ' + paths);
}
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
paths = paths.filter((path) => {
if (path[0] === BANG) {
this._ignoredPaths.add(path.substring(1));
return false;
} else {
// if a path is being added that was previously ignored, stop ignoring it
this._ignoredPaths.delete(path);
this._ignoredPaths.delete(path + '/**');
// reset the cached userIgnored anymatch fn
// to make ignoredPaths changes effective
this._userIgnored = null;
return true;
}
});
if (this.options.useFsEvents && this._fsEventsHandler) {
if (!this._readyCount) this._readyCount = paths.length;
if (this.options.persistent) this._readyCount *= 2;
paths.forEach((path) => this._fsEventsHandler._addToFsEvents(path));
} else {
if (!this._readyCount) this._readyCount = 0;
this._readyCount += paths.length;
asyncEach(paths, (path, next) => {
this._nodeFsHandler._addToNodeFs(path, !_internal, 0, 0, _origAdd, (err, res) => {
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 this;
}
/**
* Close watchers or start ignoring events from specified paths.
* @param {Path|Array<Path>} paths - string or array of strings, file/directory paths and/or globs
* @returns {FSWatcher} for chaining
*/
unwatch(paths) {
if (this.closed) return this;
paths = flatten(arrify(paths));
paths.forEach((path) => {
// convert to absolute path unless relative path already matches
if (!sysPath.isAbsolute(path) && !this._closers.has(path)) {
const cwd = this.options.cwd;
if (cwd) path = sysPath.join(cwd, path);
path = sysPath.resolve(path);
}
this._closePath(path);
this._ignoredPaths.add(path);
if (this._watched.has(path)) {
this._ignoredPaths.add(path + '/**');
}
// reset the cached userIgnored anymatch fn
// to make ignoredPaths changes effective
this._userIgnored = null;
});
return this;
}
/**
* Close watchers and remove all listeners from watched paths.
* @returns {FSWatcher} for chaining.
*/
close() {
if (this.closed) return this;
this.closed = true;
// Memory management.
this._closers.forEach(closerList => closerList.forEach(closer => closer()));
this._closers.clear();
this._watched.clear();
this._streams.forEach(stream => stream.destroy());
this._streams.clear();
this._symlinkPaths.clear();
this._throttled.clear();
this.removeAllListeners();
return this;
}
/**
* Expose list of watched paths
* @returns {Object} for chaining
*/
getWatched() {
const watchList = {};
this._watched.forEach((entry, dir) => {
const key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir;
watchList[key || ONE_DOT] = entry.getChildren().sort();
});
return watchList;
}
// Common helpers
// --------------
// Private method: Normalize and emit events
//
// * event - string, type of event
// * path - string, file or directory path
// * val[1..3] - arguments to be passed with event
//
// Returns the error if defined, otherwise the value of the
// FSWatcher instance's `closed` flag
FSWatcher.prototype._emit = function(event, path, val1, val2, val3) {
if (this.options.cwd) path = sysPath.relative(this.options.cwd, path);
var args = [event, path];
/**
* Normalize and emit events.
* Calling _emit DOES NOT MEAN emit() would be called!
* @param {EventName} event Type of event
* @param {Path} path File or directory path
* @param {*=} val1 arguments to be passed with event
* @param {*=} val2
* @param {*=} val3
* @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
*/
_emit(event, path, val1, val2, val3) {
const opts = this.options;
if (opts.cwd) path = sysPath.relative(opts.cwd, path);
/** @type Array<any> */
const args = [event, path];
if (val3 !== undefined) args.push(val1, val2, val3);

@@ -182,34 +418,33 @@ else if (val2 !== undefined) args.push(val1, val2);

var awf = this.options.awaitWriteFinish;
if (awf && this._pendingWrites[path]) {
this._pendingWrites[path].lastChange = new Date();
const awf = opts.awaitWriteFinish;
let pw;
if (awf && (pw = this._pendingWrites.get(path))) {
pw.lastChange = new Date();
return this;
}
if (this.options.atomic) {
if (opts.atomic) {
if (event === 'unlink') {
this._pendingUnlinks[path] = args;
setTimeout(function() {
Object.keys(this._pendingUnlinks).forEach(function(path) {
this.emit.apply(this, this._pendingUnlinks[path]);
this.emit.apply(this, ['all'].concat(this._pendingUnlinks[path]));
delete this._pendingUnlinks[path];
}.bind(this));
}.bind(this), typeof this.options.atomic === "number"
? this.options.atomic
: 100);
this._pendingUnlinks.set(path, args);
setTimeout(() => {
this._pendingUnlinks.forEach((entry, path) => {
this.emit.apply(this, entry);
this.emit.apply(this, ['all'].concat(entry));
this._pendingUnlinks.delete(path);
});
}, typeof opts.atomic === "number" ? opts.atomic : 100);
return this;
} else if (event === 'add' && this._pendingUnlinks[path]) {
} else if (event === 'add' && this._pendingUnlinks.has(path)) {
event = args[0] = 'change';
delete this._pendingUnlinks[path];
this._pendingUnlinks.delete(path);
}
}
var emitEvent = function() {
const emitEvent = () => {
this.emit.apply(this, args);
if (event !== 'error') this.emit.apply(this, ['all'].concat(args));
}.bind(this);
};
if (awf && (event === 'add' || event === 'change') && this._readyEmitted) {
var awfEmit = function(err, stats) {
const awfEmit = (err, stats) => {
if (err) {

@@ -235,12 +470,12 @@ event = args[0] = 'error';

if (event === 'change') {
if (!this._throttle('change', path, 50)) return this;
const isThrottled = !this._throttle('change', path, 50);
if (isThrottled) return this;
}
if (
this.options.alwaysStat && val1 === undefined &&
if (opts.alwaysStat && val1 === undefined &&
(event === 'add' || event === 'addDir' || event === 'change')
) {
var fullPath = this.options.cwd ? sysPath.join(this.options.cwd, path) : path;
fs.stat(fullPath, function(error, stats) {
// Suppress event when fs.stat fails, to avoid sending undefined 'stat'
const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path;
fs.stat(fullPath, (error, stats) => {
// Suppress event when fs_stat fails, to avoid sending undefined 'stat'
if (error || !stats) return;

@@ -256,69 +491,81 @@

return this;
};
}
// Private method: Common handler for errors
//
// * error - object, Error instance
//
// Returns the error if defined, otherwise the value of the
// FSWatcher instance's `closed` flag
FSWatcher.prototype._handleError = function(error) {
var code = error && error.code;
var ipe = this.options.ignorePermissionErrors;
if (error &&
code !== 'ENOENT' &&
code !== 'ENOTDIR' &&
(!ipe || (code !== 'EPERM' && code !== 'EACCES'))
) this.emit('error', error);
/**
* Common handler for errors
* @param {Error} error
* @returns {Error|Boolean} The error if defined, otherwise the value of the FSWatcher instance's `closed` flag
*/
_handleError(error) {
const code = error && error.code;
if (error && code !== 'ENOENT' && code !== 'ENOTDIR' &&
(!this.options.ignorePermissionErrors || (code !== 'EPERM' && code !== 'EACCES'))
) {
this.emit('error', error);
}
return error || this.closed;
};
}
// Private method: Helper utility for throttling
//
// * action - string, type of action being throttled
// * path - string, path being acted upon
// * timeout - int, duration of time to suppress duplicate actions
//
// Returns throttle tracking object or false if action should be suppressed
FSWatcher.prototype._throttle = function(action, path, timeout) {
if (!(action in this._throttled)) {
this._throttled[action] = Object.create(null);
/**
* Helper utility for throttling
* @param {ThrottleType} actionType type being throttled
* @param {Path} path being acted upon
* @param {Number} timeout duration of time to suppress duplicate actions
* @returns {Object|false} tracking object or false if action should be suppressed
*/
_throttle(actionType, path, timeout) {
if (!this._throttled.has(actionType)) {
this._throttled.set(actionType, new Map());
}
var throttled = this._throttled[action];
if (path in throttled) {
throttled[path].count++;
/** @type {Map<Path, Object>} */
const action = this._throttled.get(actionType);
/** @type {Object} */
const actionPath = action.get(path);
if (actionPath) {
actionPath.count++;
return false;
}
function clear() {
var count = throttled[path] ? throttled[path].count : 0;
delete throttled[path];
let timeoutObject;
const clear = () => {
const item = action.get(path);
const count = item ? item.count : 0;
action.delete(path);
clearTimeout(timeoutObject);
if (item) clearTimeout(item.timeoutObject);
return count;
}
var timeoutObject = setTimeout(clear, timeout);
throttled[path] = {timeoutObject: timeoutObject, clear: clear, count: 0};
return throttled[path];
};
};
timeoutObject = setTimeout(clear, timeout);
const thr = {timeoutObject: timeoutObject, clear: clear, count: 0};
action.set(path, thr);
return thr;
}
// Private method: Awaits write operation to finish
//
// * path - string, path being acted upon
// * threshold - int, time in milliseconds a file size must be fixed before
// acknowledging write operation is finished
// * awfEmit - function, to be called when ready for event to be emitted
// Polls a newly created file for size variations. When files size does not
// change for 'threshold' milliseconds calls callback.
FSWatcher.prototype._awaitWriteFinish = function(path, threshold, event, awfEmit) {
var timeoutHandler;
_incrReadyCount() {
return this._readyCount++;
}
var fullPath = path;
if (this.options.cwd && !isAbsolute(path)) {
/**
* Awaits write operation to finish.
* Polls a newly created file for size variations. When files size does not change for 'threshold' milliseconds calls callback.
* @param {Path} path being acted upon
* @param {Number} threshold Time in milliseconds a file size must be fixed before acknowledging write OP is finished
* @param {EventName} event
* @param {Function} awfEmit Callback to be called when ready for event to be emitted.
*/
_awaitWriteFinish(path, threshold, event, awfEmit) {
let timeoutHandler;
let fullPath = path;
if (this.options.cwd && !sysPath.isAbsolute(path)) {
fullPath = sysPath.join(this.options.cwd, path);
}
var now = new Date();
const now = new Date();
var awaitWriteFinish = (function (prevStat) {
fs.stat(fullPath, function(err, curStat) {
if (err || !(path in this._pendingWrites)) {
const awaitWriteFinish = (prevStat) => {
fs.stat(fullPath, (err, curStat) => {
if (err || !this._pendingWrites.has(path)) {
if (err && err.code !== 'ENOENT') awfEmit(err);

@@ -328,10 +575,12 @@ return;

var now = new Date();
const now = Number(new Date());
if (prevStat && curStat.size != prevStat.size) {
this._pendingWrites[path].lastChange = now;
this._pendingWrites.get(path).lastChange = now;
}
const pw = this._pendingWrites.get(path);
const df = now - pw.lastChange;
if (now - this._pendingWrites[path].lastChange >= threshold) {
delete this._pendingWrites[path];
if (df >= threshold) {
this._pendingWrites.delete(path);
awfEmit(null, curStat);

@@ -344,14 +593,14 @@ } else {

}
}.bind(this));
}.bind(this));
});
};
if (!(path in this._pendingWrites)) {
this._pendingWrites[path] = {
if (!this._pendingWrites.has(path)) {
this._pendingWrites.set(path, {
lastChange: now,
cancelWait: function() {
delete this._pendingWrites[path];
cancelWait: () => {
this._pendingWrites.delete(path);
clearTimeout(timeoutHandler);
return event;
}.bind(this)
};
}
});
timeoutHandler = setTimeout(

@@ -362,31 +611,29 @@ awaitWriteFinish.bind(this),

}
};
}
// Private method: Determines whether user has asked to ignore this path
//
// * path - string, path to file or directory
// * stats - object, result of fs.stat
//
// Returns boolean
var dotRe = /\..*\.(sw[px])$|\~$|\.subl.*\.tmp/;
FSWatcher.prototype._isIgnored = function(path, stats) {
if (this.options.atomic && dotRe.test(path)) return true;
_getGlobIgnored() {
return Array.from(this._ignoredPaths.values());
}
/**
* Determines whether user has asked to ignore this path.
* @param {Path} path filepath or dir
* @param {fs.Stats=} stats result of fs.stat
* @returns {Boolean}
*/
_isIgnored(path, stats) {
if (this.options.atomic && DOT_RE.test(path)) return true;
if (!this._userIgnored) {
var cwd = this.options.cwd;
var ignored = this.options.ignored;
if (cwd && ignored) {
ignored = ignored.map(function (path) {
if (typeof path !== 'string') return path;
return upath.normalize(isAbsolute(path) ? path : sysPath.join(cwd, path));
});
}
var paths = arrify(ignored)
.filter(function(path) {
return typeof path === 'string' && !isGlob(path);
}).map(function(path) {
return path + '/**';
});
const cwd = this.options.cwd;
const ign = this.options.ignored;
const ignored = ign && ign.map(normalizeIgnored(cwd));
const paths = arrify(ignored)
.filter((path) => typeof path === 'string' && !isGlob(path))
.map((path) => path + '/**');
this._userIgnored = anymatch(
this._globIgnored.concat(ignored).concat(paths)
this._getGlobIgnored()
.map(normalizeIgnored(cwd))
.concat(ignored)
.concat(paths)
);

@@ -396,22 +643,25 @@ }

return this._userIgnored([path, stats]);
};
}
// Private method: Provides a set of common helpers and properties relating to
// symlink and glob handling
//
// * path - string, file, directory, or glob pattern being watched
// * depth - int, at any depth > 0, this isn't a glob
//
// Returns object containing helpers for this path
var replacerRe = /^\.[\/\\]/;
FSWatcher.prototype._getWatchHelpers = function(path, depth) {
path = path.replace(replacerRe, '');
var watchPath = depth || this.options.disableGlobbing || !isGlob(path) ? path : globParent(path);
var fullWatchPath = sysPath.resolve(watchPath);
var hasGlob = watchPath !== path;
var globFilter = hasGlob ? anymatch(path) : false;
var follow = this.options.followSymlinks;
var globSymlink = hasGlob && follow ? null : false;
_isntIgnored(path, stat) {
return !this._isIgnored(path, stat);
}
var checkGlobSymlink = function(entry) {
/**
* Provides a set of common helpers and properties relating to symlink and glob handling.
* @param {Path} path file, directory, or glob pattern being watched
* @param {Number=} depth at any depth > 0, this isn't a glob
* @returns {WatchHelpers} 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

@@ -433,3 +683,3 @@ // first entry should always have entry.parentDir === ''

var entryPath = function(entry) {
const entryPath = (entry) => {
return sysPath.join(watchPath,

@@ -440,16 +690,19 @@ sysPath.relative(watchPath, checkGlobSymlink(entry))

var filterPath = function(entry) {
if (entry.stat && entry.stat.isSymbolicLink()) return filterDir(entry);
var resolvedPath = entryPath(entry);
return (!hasGlob || globFilter(resolvedPath)) &&
this._isntIgnored(resolvedPath, entry.stat) &&
(this.options.ignorePermissionErrors ||
this._hasReadPermissions(entry.stat));
}.bind(this);
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);
};
var getDirParts = function(path) {
if (!hasGlob) return false;
var parts = [];
var expandedPath = braces.expand(path);
expandedPath.forEach(function(path) {
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(/[\/\\]/));

@@ -460,16 +713,14 @@ });

var dirParts = getDirParts(path);
if (dirParts) {
dirParts.forEach(function(parts) {
if (parts.length > 1) parts.pop();
});
}
var unmatchedGlob;
const dirParts = getDirParts(path);
dirParts.forEach((parts) => {
if (parts.length > 1) parts.pop();
});
let unmatchedGlob;
var filterDir = function(entry) {
const filterDir = (entry) => {
if (hasGlob) {
var entryParts = getDirParts(checkGlobSymlink(entry));
var globstar = false;
unmatchedGlob = !dirParts.some(function(parts) {
return parts.every(function(part, i) {
const entryParts = getDirParts(checkGlobSymlink(entry));
let globstar = false;
unmatchedGlob = !dirParts.some((parts) => {
return parts.every((part, i) => {
if (part === '**') globstar = true;

@@ -480,4 +731,4 @@ return globstar || !entryParts[0][i] || anymatch(part, entryParts[0][i]);

}
return !unmatchedGlob && this._isntIgnored(entryPath(entry), entry.stat);
}.bind(this);
return !unmatchedGlob && this._isntIgnored(entryPath(entry), entry.stats);
};

@@ -495,3 +746,3 @@ return {

};
};
}

@@ -501,28 +752,13 @@ // Directory helpers

// Private method: Provides directory tracking objects
//
// * directory - string, path of the directory
//
// Returns the directory's tracking object
FSWatcher.prototype._getWatchedDir = function(directory) {
var dir = sysPath.resolve(directory);
var watcherRemove = this._remove.bind(this);
if (!(dir in this._watched)) this._watched[dir] = {
_items: Object.create(null),
add: function(item) {
if (item !== '.' && item !== '..') this._items[item] = true;
},
remove: function(item) {
delete this._items[item];
if (!this.children().length) {
fs.readdir(dir, function(err) {
if (err) watcherRemove(sysPath.dirname(dir), sysPath.basename(dir));
});
}
},
has: function(item) {return item in this._items;},
children: function() {return Object.keys(this._items);}
};
return this._watched[dir];
};
/**
* Provides directory tracking objects
* @param {String} directory path of the directory
* @returns {DirEntry} the directory's tracking object
*/
_getWatchedDir(directory) {
if (!this._boundRemove) this._boundRemove = this._remove.bind(this);
const dir = sysPath.resolve(directory);
if (!this._watched.has(dir)) this._watched.set(dir, new DirEntry(dir, this._boundRemove));
return this._watched.get(dir);
}

@@ -532,27 +768,31 @@ // File helpers

// Private method: Check for read permissions
// Based on this answer on SO: http://stackoverflow.com/a/11781404/1358405
//
// * stats - object, result of fs.stat
//
// Returns boolean
FSWatcher.prototype._hasReadPermissions = function(stats) {
return Boolean(4 & parseInt(((stats && stats.mode) & 0x1ff).toString(8)[0], 10));
};
/**
* Check for read permissions.
* Based on this answer on SO: http://stackoverflow.com/a/11781404/1358405
* @param {fs.Stats} stats - object, result of fs_stat
* @returns {Boolean} indicates whether the file can be read
*/
_hasReadPermissions(stats) {
if (this.options.ignorePermissionErrors) return true;
// Private method: Handles emitting unlink events for
// files and directories, and via recursion, for
// files and directories within directories that are unlinked
//
// * directory - string, directory within which the following item is located
// * item - string, base path of item/directory
//
// Returns nothing
FSWatcher.prototype._remove = function(directory, item) {
const st = (stats && stats.mode) & 0o777;
const it = parseInt(st.toString(8)[0], 10);
return Boolean(4 & it);
}
/**
* Handles emitting unlink events for
* files and directories, and via recursion, for
* files and directories within directories that are unlinked
* @param {String} directory within which the following item is located
* @param {String} item base path of item/directory
* @returns {void}
*/
_remove(directory, item) {
// if what is being deleted is a directory, get that directory's paths
// for recursive deleting and cleaning of watched object
// if it is not a directory, nestedDirectoryChildren will be empty array
var path = sysPath.join(directory, item);
var fullPath = sysPath.resolve(path);
var isDirectory = this._watched[path] || this._watched[fullPath];
const path = sysPath.join(directory, item);
const fullPath = sysPath.resolve(path);
const isDirectory = this._watched.has(path) || this._watched.has(fullPath);

@@ -564,4 +804,3 @@ // prevent duplicate handling in case of arriving here nearly simultaneously

// if the only watched file is removed, watch for its return
var watchedDirs = Object.keys(this._watched);
if (!isDirectory && !this.options.useFsEvents && watchedDirs.length === 1) {
if (!isDirectory && !this.options.useFsEvents && this._watched.size === 1) {
this.add(directory, item, true);

@@ -572,19 +811,18 @@ }

// so we got to do the directory check beforehand
var nestedDirectoryChildren = this._getWatchedDir(path).children();
const wp = this._getWatchedDir(path);
const nestedDirectoryChildren = wp.getChildren();
// Recursively remove children directories / files.
nestedDirectoryChildren.forEach(function(nestedItem) {
this._remove(path, nestedItem);
}, this);
nestedDirectoryChildren.forEach(nested => this._remove(path, nested));
// Check if item was on the watched list and remove it
var parent = this._getWatchedDir(directory);
var wasTracked = parent.has(item);
const parent = this._getWatchedDir(directory);
const wasTracked = parent.has(item);
parent.remove(item);
// If we wait for this file to be fully written, cancel the wait.
var relPath = path;
let relPath = path;
if (this.options.cwd) relPath = sysPath.relative(this.options.cwd, path);
if (this.options.awaitWriteFinish && this._pendingWrites[relPath]) {
var event = this._pendingWrites[relPath].cancelWait();
if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
const event = this._pendingWrites.get(relPath).cancelWait();
if (event === 'add') return;

@@ -595,5 +833,5 @@ }

// or a bogus entry to a file, in either case we have to remove it
delete this._watched[path];
delete this._watched[fullPath];
var eventName = isDirectory ? 'unlinkDir' : 'unlink';
this._watched.delete(path);
this._watched.delete(fullPath);
const eventName = isDirectory ? 'unlinkDir' : 'unlink';
if (wasTracked && !this._isIgnored(path)) this._emit(eventName, path);

@@ -605,166 +843,61 @@

}
};
}
FSWatcher.prototype._closePath = function(path) {
if (!this._closers[path]) return;
this._closers[path]();
delete this._closers[path];
this._getWatchedDir(sysPath.dirname(path)).remove(sysPath.basename(path));
/**
*
* @param {Path} path
*/
_closePath(path) {
let closers = this._closers.get(path);
if (!closers) return;
closers.forEach(closer => closer());
this._closers.delete(path);
closers = [];
const dir = sysPath.dirname(path);
this._getWatchedDir(dir).remove(sysPath.basename(path));
}
// Public method: Adds paths to be watched on an existing FSWatcher instance
// * paths - string or array of strings, file/directory paths and/or globs
// * _origAdd - private boolean, for handling non-existent paths to be watched
// * _internal - private boolean, indicates a non-user add
// Returns an instance of FSWatcher for chaining.
FSWatcher.prototype.add = function(paths, _origAdd, _internal) {
var disableGlobbing = this.options.disableGlobbing;
var cwd = this.options.cwd;
this.closed = false;
paths = flatten(arrify(paths));
if (!paths.every(isString)) {
throw new TypeError('Non-string provided as watch path: ' + paths);
/**
*
* @param {Path} path
* @param {Function} closer
*/
_addPathCloser(path, closer) {
if (!closer) return;
let list = this._closers.get(path);
if (!list) {
list = [];
this._closers.set(path, list);
}
list.push(closer);
}
if (cwd) paths = paths.map(function(path) {
var absPath;
if (isAbsolute(path)) {
absPath = path;
} else if (path[0] === '!') {
absPath = '!' + 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);
}
_readdirp(root, opts) {
const options = Object.assign({type: 'all', alwaysStat: true, lstat: true}, opts);
let stream = readdirp(root, options);
this._streams.add(stream);
stream.once('close', () => {
stream = null;
});
// set aside negated glob strings
paths = paths.filter(function(path) {
if (path[0] === '!') {
this._ignoredPaths[path.substring(1)] = true;
} else {
// if a path is being added that was previously ignored, stop ignoring it
delete this._ignoredPaths[path];
delete this._ignoredPaths[path + '/**'];
// reset the cached userIgnored anymatch fn
// to make ignoredPaths changes effective
this._userIgnored = null;
return true;
stream.once('end', () => {
if (stream) {
this._streams.delete(stream);
stream = null;
}
}, this);
if (this.options.useFsEvents && FsEventsHandler.canUse()) {
if (!this._readyCount) this._readyCount = paths.length;
if (this.options.persistent) this._readyCount *= 2;
paths.forEach(this._addToFsEvents, this);
} else {
if (!this._readyCount) this._readyCount = 0;
this._readyCount += paths.length;
asyncEach(paths, function(path, next) {
this._addToNodeFs(path, !_internal, 0, 0, _origAdd, function(err, res) {
if (res) this._emitReady();
next(err, res);
}.bind(this));
}.bind(this), function(error, results) {
results.forEach(function(item) {
if (!item || this.closed) return;
this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item));
}, this);
}.bind(this));
}
return this;
};
// Public method: Close watchers or start ignoring events from specified paths.
// * paths - string or array of strings, file/directory paths and/or globs
// Returns instance of FSWatcher for chaining.
FSWatcher.prototype.unwatch = function(paths) {
if (this.closed) return this;
paths = flatten(arrify(paths));
paths.forEach(function(path) {
// convert to absolute path unless relative path already matches
if (!isAbsolute(path) && !this._closers[path]) {
if (this.options.cwd) path = sysPath.join(this.options.cwd, path);
path = sysPath.resolve(path);
}
this._closePath(path);
this._ignoredPaths[path] = true;
if (path in this._watched) {
this._ignoredPaths[path + '/**'] = true;
}
// reset the cached userIgnored anymatch fn
// to make ignoredPaths changes effective
this._userIgnored = null;
}, this);
return this;
};
// Public method: Close watchers and remove all listeners from watched paths.
// Returns instance of FSWatcher for chaining.
FSWatcher.prototype.close = function() {
if (this.closed) return this;
this.closed = true;
Object.keys(this._closers).forEach(function(watchPath) {
this._closers[watchPath]();
delete this._closers[watchPath];
}, this);
this._watched = Object.create(null);
this.removeAllListeners();
return this;
};
// Public method: Expose list of watched paths
// Returns object w/ dir paths as keys and arrays of contained paths as values.
FSWatcher.prototype.getWatched = function() {
var watchList = {};
Object.keys(this._watched).forEach(function(dir) {
var key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir;
watchList[key || '.'] = Object.keys(this._watched[dir]._items).sort();
}.bind(this));
return watchList;
};
// Attach watch handler prototype methods
function importHandler(handler) {
Object.keys(handler.prototype).forEach(function(method) {
FSWatcher.prototype[method] = handler.prototype[method];
});
return stream;
}
importHandler(NodeFsHandler);
if (FsEventsHandler.canUse()) importHandler(FsEventsHandler);
}
// Export FSWatcher class
exports.FSWatcher = FSWatcher;
// Public function: Instantiates watcher with paths to be tracked.
// * paths - string or array of strings, file/directory paths and/or globs
// * options - object, chokidar options
// Returns an instance of FSWatcher for chaining.
exports.watch = function(paths, options) {
return new FSWatcher(options).add(paths);
};
/**
* Instantiates watcher with paths to be tracked.
* @param {String|Array<String>} paths file/directory paths and/or globs
* @param {Object=} options chokidar opts
* @returns an instance of FSWatcher for chaining.
*/
const watch = (paths, options) => new FSWatcher(options).add(paths);
exports.watch = watch;
'use strict';
var fs = require('fs');
var sysPath = require('path');
var readdirp = require('readdirp');
var fsevents;
const fs = require('fs');
const sysPath = require('path');
let fsevents;
try { fsevents = require('fsevents'); } catch (error) {
if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error)
if (process.env.CHOKIDAR_PRINT_FSEVENTS_REQUIRE_ERROR) console.error(error);
}
// fsevents instance helper functions
if (fsevents) {
// TODO: real check
let mtch = process.version.match(/v(\d+)\.(\d+)/);
if (mtch && mtch[1] && mtch[2]) {
let maj = parseInt(mtch[1]);
let min = parseInt(mtch[2]);
if (maj === 8 && min < 16) {
fsevents = null;
}
}
}
// object to hold per-process fsevents instances
// (may be shared across chokidar FSWatcher instances)
var FSEventsWatchers = Object.create(null);
const Option = (key, value) => isNaN(value) ? {} : {[key]: value};
// Threshold of duplicate path prefixes at which to start
// consolidating going forward
var consolidateThreshhold = 10;
/**
* @typedef {String} Path
*/
// Private function: Instantiates the fsevents interface
/**
* @typedef {Object} FsEventsWatchContainer
* @property {Set<Function>} listeners
* @property {Function} rawEmitter
* @property {{stop: Function}} watcher
*/
// * path - string, path to be watched
// * callback - function, called when fsevents is bound and ready
// fsevents instance helper functions
/**
* Object to hold per-process fsevents instances (may be shared across chokidar FSWatcher instances)
* @type {Map<Path,FsEventsWatchContainer>}
*/
const FSEventsWatchers = new Map();
// Returns new fsevents instance
function createFSEventsInstance(path, callback) {
return (new fsevents(path)).on('fsevent', callback).start();
}
// Threshold of duplicate path prefixes at which to start
// consolidating going forward
const consolidateThreshhold = 10;
// Private function: Instantiates the fsevents interface or binds listeners
// to an existing one covering the same file tree
const wrongEventFlags = new Set([
69888, 70400, 71424, 72704, 73472, 131328, 131840, 262912
]);
// * path - string, path to be watched
// * realPath - string, real path (in case of symlinks)
// * listener - function, called when fsevents emits events
// * rawEmitter - function, passes data to listeners of the 'raw' event
/**
* Instantiates the fsevents interface
* @param {Path} path path to be watched
* @param {Function} callback called when fsevents is bound and ready
* @returns {{stop: Function}} new fsevents instance
*/
const createFSEventsInstance = (path, callback) => {
const stop = fsevents.watch(path, callback);
return {stop};
};
// Returns close function
/**
* Instantiates the fsevents interface or binds listeners to an existing one covering
* the same file tree.
* @param {Path} path - to be watched
* @param {Path} realPath - real path for symlinks
* @param {Function} listener - called when fsevents emits events
* @param {Function} rawEmitter - passes data to listeners of the 'raw' event
* @returns {Function} closer
*/
function setFSEventsListener(path, realPath, listener, rawEmitter) {
var watchPath = sysPath.extname(path) ? sysPath.dirname(path) : path;
var watchContainer;
var parentPath = sysPath.dirname(watchPath);
let watchPath = sysPath.extname(path) ? sysPath.dirname(path) : path;
const parentPath = sysPath.dirname(watchPath);
let cont = FSEventsWatchers.get(watchPath);

@@ -53,4 +83,4 @@ // If we've accumulated a substantial number of paths that

var resolvedPath = sysPath.resolve(path);
var hasSymlink = resolvedPath !== realPath;
const resolvedPath = sysPath.resolve(path);
const hasSymlink = resolvedPath !== realPath;
function filteredListener(fullPath, flags, info) {

@@ -66,31 +96,29 @@ if (hasSymlink) fullPath = fullPath.replace(realPath, resolvedPath);

// modifies `watchPath` to the parent path when it finds a match
function watchedParent() {
return Object.keys(FSEventsWatchers).some(function(watchedPath) {
// condition is met when indexOf returns 0
if (!realPath.indexOf(sysPath.resolve(watchedPath) + sysPath.sep)) {
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;
}
});
}
}
};
if (watchPath in FSEventsWatchers || watchedParent()) {
watchContainer = FSEventsWatchers[watchPath];
watchContainer.listeners.push(filteredListener);
if (cont || watchedParent()) {
cont.listeners.add(filteredListener);
} else {
watchContainer = FSEventsWatchers[watchPath] = {
listeners: [filteredListener],
rawEmitters: [rawEmitter],
watcher: createFSEventsInstance(watchPath, function(fullPath, flags) {
var info = fsevents.getInfo(fullPath, flags);
watchContainer.listeners.forEach(function(listener) {
listener(fullPath, flags, info);
cont = {
listeners: new Set([filteredListener]),
rawEmitter: rawEmitter,
watcher: createFSEventsInstance(watchPath, (fullPath, flags) => {
const info = fsevents.getInfo(fullPath, flags);
cont.listeners.forEach(list => {
list(fullPath, flags, info);
});
watchContainer.rawEmitters.forEach(function(emitter) {
emitter(info.event, fullPath, info);
});
cont.rawEmitter(info.event, fullPath, info);
})
};
FSEventsWatchers.set(watchPath, cont);
}
var listenerIndex = watchContainer.listeners.length - 1;

@@ -100,7 +128,11 @@ // removes this instance's listeners and closes the underlying fsevents

return function close() {
delete watchContainer.listeners[listenerIndex];
delete watchContainer.rawEmitters[listenerIndex];
if (!Object.keys(watchContainer.listeners).length) {
watchContainer.watcher.stop();
delete FSEventsWatchers[watchPath];
const wl = cont.listeners;
wl.delete(filteredListener);
if (!wl.size) {
FSEventsWatchers.delete(watchPath);
cont.watcher.stop();
cont.rawEmitter = cont.watcher = null;
Object.freeze(cont);
Object.freeze(cont.listeners);
}

@@ -112,8 +144,5 @@ };

// parent watcher
function couldConsolidate(path) {
var keys = Object.keys(FSEventsWatchers);
var count = 0;
for (var i = 0, len = keys.length; i < len; ++i) {
var watchPath = keys[i];
const couldConsolidate = (path) => {
let count = 0;
for (const watchPath of FSEventsWatchers.keys()) {
if (watchPath.indexOf(path) === 0) {

@@ -128,37 +157,45 @@ count++;

return false;
}
};
// returns boolean indicating whether fsevents can be used
function canUse() {
return fsevents && Object.keys(FSEventsWatchers).length < 128;
}
const canUse = () => fsevents && FSEventsWatchers.size < 128;
// determines subdirectory traversal levels from root to path
function depth(path, root) {
var i = 0;
const depth = (path, root) => {
let i = 0;
while (!path.indexOf(root) && (path = sysPath.dirname(path)) !== root) i++;
return i;
}
};
// fake constructor for attaching fsevents-specific prototype methods that
// will be copied to FSWatcher's prototype
function FsEventsHandler() {}
/**
* @mixin
*/
class FsEventsHandler {
// Private method: Handle symlinks encountered during directory scan
/**
* @param {FSWatcher} fsW
*/
constructor(fsW) {
const FSWatcher = require('../index').FSWatcher;
this.fsw = fsW;
}
// * watchPath - string, file/dir path to be watched with fsevents
// * realPath - string, real path (in case of symlinks)
// * transform - function, path transformer
// * globFilter - function, path filter in case a glob pattern was provided
/**
* Handle symlinks encountered during directory scan
* @param {String} watchPath - file/dir path to be watched with fsevents
* @param {String} realPath - real path (in case of symlinks)
* @param {Function} transform - path transformer
* @param {Function} globFilter - path filter in case a glob pattern was provided
* @returns {Function} closer for the watcher instance
*/
_watchWithFsEvents(watchPath, realPath, transform, globFilter) {
// Returns close function for the watcher instance
FsEventsHandler.prototype._watchWithFsEvents =
function(watchPath, realPath, transform, globFilter) {
if (this._isIgnored(watchPath)) return;
var watchCallback = function(fullPath, flags, info) {
if (this.fsw._isIgnored(watchPath)) return;
const opts = this.fsw.options;
const watchCallback = (fullPath, flags, info) => {
if (
this.options.depth !== undefined &&
depth(fullPath, realPath) > this.options.depth
opts.depth !== undefined &&
depth(fullPath, realPath) > opts.depth
) return;
var path = transform(sysPath.join(
const path = transform(sysPath.join(
watchPath, sysPath.relative(watchPath, fullPath)

@@ -168,21 +205,22 @@ ));

// ensure directories are tracked
var parent = sysPath.dirname(path);
var item = sysPath.basename(path);
var watchedDir = this._getWatchedDir(
const parent = sysPath.dirname(path);
const item = sysPath.basename(path);
const watchedDir = this.fsw._getWatchedDir(
info.type === 'directory' ? path : parent
);
var checkIgnored = function(stats) {
if (this._isIgnored(path, stats)) {
this._ignoredPaths[path] = true;
const checkIgnored = (stats) => {
const ipaths = this.fsw._ignoredPaths;
if (this.fsw._isIgnored(path, stats)) {
ipaths.add(path);
if (stats && stats.isDirectory()) {
this._ignoredPaths[path + '/**/*'] = true;
ipaths.add(path + '/**/*');
}
return true;
} else {
delete this._ignoredPaths[path];
delete this._ignoredPaths[path + '/**/*'];
ipaths.delete(path);
ipaths.delete(path + '/**/*');
}
}.bind(this);
};
var handleEvent = function(event) {
const handleEvent = (event) => {
if (checkIgnored()) return;

@@ -193,3 +231,3 @@

if (info.type === 'directory' || watchedDir.has(item)) {
this._remove(parent, item);
this.fsw._remove(parent, item);
}

@@ -199,7 +237,7 @@ } else {

// track new directories
if (info.type === 'directory') this._getWatchedDir(path);
if (info.type === 'directory') this.fsw._getWatchedDir(path);
if (info.type === 'symlink' && this.options.followSymlinks) {
if (info.type === 'symlink' && opts.followSymlinks) {
// push symlinks back to the top of the stack to get handled
var curDepth = this.options.depth === undefined ?
const curDepth = opts.depth === undefined ?
undefined : depth(fullPath, realPath) + 1;

@@ -210,10 +248,13 @@ return this._addToFsEvents(path, false, true, curDepth);

// (other than symlinks being followed, which will be tracked soon)
this._getWatchedDir(parent).add(item);
this.fsw._getWatchedDir(parent).add(item);
}
}
var eventName = info.type === 'directory' ? event + 'Dir' : event;
this._emit(eventName, path);
/**
* @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);
}
}.bind(this);
};

@@ -224,10 +265,16 @@ function addOrChange() {

function checkFd() {
fs.open(path, 'r', function(error, fd) {
fs.open(path, 'r', function opened(error, fd) {
if (error) {
error.code !== 'EACCES' ?
handleEvent('unlink') : addOrChange();
if (error.code !== 'EACCES') {
handleEvent('unlink');
} else {
addOrChange();
}
} else {
fs.close(fd, function(err) {
err && err.code !== 'EACCES' ?
handleEvent('unlink') : addOrChange();
fs.close(fd, function closed(err) {
if (err && err.code !== 'EACCES') {
handleEvent('unlink');
} else {
addOrChange();
}
});

@@ -238,8 +285,5 @@ }

// correct for wrong events emitted
var wrongEventFlags = [
69888, 70400, 71424, 72704, 73472, 131328, 131840, 262912
];
if (wrongEventFlags.indexOf(flags) !== -1 || info.event === 'unknown') {
if (typeof this.options.ignored === 'function') {
fs.stat(path, function(error, stats) {
if (wrongEventFlags.has(flags) || info.event === 'unknown') {
if (typeof opts.ignored === 'function') {
fs.stat(path, (error, stats) => {
if (checkIgnored(stats)) return;

@@ -261,43 +305,42 @@ stats ? addOrChange() : handleEvent('unlink');

}
}.bind(this);
};
var closer = setFSEventsListener(
const closer = setFSEventsListener(
watchPath,
realPath,
watchCallback,
this.emit.bind(this, 'raw')
this.fsw._emitRaw
);
this._emitReady();
this.fsw._emitReady();
return closer;
};
}
// Private method: Handle symlinks encountered during directory scan
// * linkPath - string, path to symlink
// * fullPath - string, absolute path to the symlink
// * transform - function, pre-existing path transformer
// * curDepth - int, level of subdirectories traversed to where symlink is
// Returns nothing
FsEventsHandler.prototype._handleFsEventsSymlink =
function(linkPath, fullPath, transform, curDepth) {
/**
* Handle symlinks encountered during directory scan
* @param {String} linkPath path to symlink
* @param {String} fullPath absolute path to the symlink
* @param {Function} transform pre-existing path transformer
* @param {Number} curDepth level of subdirectories traversed to where symlink is
* @returns {void}
*/
_handleFsEventsSymlink(linkPath, fullPath, transform, curDepth) {
// don't follow the same symlink more than once
if (this._symlinkPaths[fullPath]) return;
else this._symlinkPaths[fullPath] = true;
if (this.fsw._symlinkPaths.has(fullPath)) return;
this._readyCount++;
this.fsw._symlinkPaths.set(fullPath, true);
this.fsw._incrReadyCount();
fs.realpath(linkPath, function(error, linkTarget) {
if (this._handleError(error) || this._isIgnored(linkTarget)) {
return this._emitReady();
fs.realpath(linkPath, (error, linkTarget) => {
if (this.fsw._handleError(error) || this.fsw._isIgnored(linkTarget)) {
return this.fsw._emitReady();
}
this._readyCount++;
this.fsw._incrReadyCount();
// add the linkTarget for watching with a wrapper for transform
// that causes emitted paths to incorporate the link's path
this._addToFsEvents(linkTarget || linkPath, function(path) {
var dotSlash = '.' + sysPath.sep;
var aliasedPath = linkPath;
this._addToFsEvents(linkTarget || linkPath, (path) => {
const dotSlash = '.' + sysPath.sep;
let aliasedPath = linkPath;
if (linkTarget && linkTarget !== dotSlash) {

@@ -310,44 +353,51 @@ aliasedPath = path.replace(linkTarget, linkPath);

}, false, curDepth);
}.bind(this));
};
});
}
// Private method: Handle added path with fsevents
/**
* Handle added path with fsevents
* @param {String} path file/dir path or glob pattern
* @param {Function|Boolean=} transform converts working path to what the user expects
* @param {Boolean=} forceAdd ensure add is emitted
* @param {Number=} priorDepth Level of subdirectories already traversed.
* @returns {void}
*/
_addToFsEvents(path, transform, forceAdd, priorDepth) {
const opts = this.fsw.options;
const processPath = typeof transform === 'function' ? transform : (val => val);
// * path - string, file/directory path or glob pattern
// * transform - function, converts working path to what the user expects
// * forceAdd - boolean, ensure add is emitted
// * priorDepth - int, level of subdirectories already traversed
/**
*
* @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);
// Returns nothing
FsEventsHandler.prototype._addToFsEvents =
function(path, transform, forceAdd, priorDepth) {
// applies transform if provided, otherwise returns same value
var processPath = typeof transform === 'function' ?
transform : function(val) { return val; };
var emitAdd = function(newPath, stats) {
var pp = processPath(newPath);
var isDir = stats.isDirectory();
var dirObj = this._getWatchedDir(sysPath.dirname(pp));
var base = sysPath.basename(pp);
// ensure empty dirs get tracked
if (isDir) this._getWatchedDir(pp);
if (isDir) this.fsw._getWatchedDir(pp);
if (dirObj.has(base)) return;
dirObj.add(base);
if (!this.options.ignoreInitial || forceAdd === true) {
this._emit(isDir ? 'addDir' : 'add', pp, stats);
if (!opts.ignoreInitial || forceAdd === true) {
this.fsw._emit(isDir ? 'addDir' : 'add', pp, stats);
}
}.bind(this);
};
var wh = this._getWatchHelpers(path);
const wh = this.fsw._getWatchHelpers(path);
// evaluate what is at the path we're being asked to watch
fs[wh.statMethod](wh.watchPath, function(error, stats) {
if (this._handleError(error) || this._isIgnored(wh.watchPath, stats)) {
this._emitReady();
return this._emitReady();
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();
}

@@ -360,23 +410,20 @@

// don't recurse further if it would exceed depth setting
if (priorDepth && priorDepth > this.options.depth) return;
if (priorDepth && priorDepth > opts.depth) return;
// scan the contents of the dir
readdirp({
root: wh.watchPath,
entryType: 'all',
this.fsw._readdirp(wh.watchPath, {
fileFilter: wh.filterPath,
directoryFilter: wh.filterDir,
lstat: true,
depth: this.options.depth - (priorDepth || 0)
}).on('data', function(entry) {
...Option("depth", opts.depth - (priorDepth || 0))
}).on('data', (entry) => {
// need to check filterPath on dirs b/c filterDir is less restrictive
if (entry.stat.isDirectory() && !wh.filterPath(entry)) return;
if (entry.stats.isDirectory() && !wh.filterPath(entry)) return;
var joinedPath = sysPath.join(wh.watchPath, entry.path);
var fullPath = entry.fullPath;
const joinedPath = sysPath.join(wh.watchPath, entry.path);
const fullPath = entry.fullPath;
if (wh.followSymlinks && entry.stat.isSymbolicLink()) {
if (wh.followSymlinks && entry.stats.isSymbolicLink()) {
// preserve the current depth here since it can't be derived from
// real paths past the symlink
var curDepth = this.options.depth === undefined ?
const curDepth = opts.depth === undefined ?
undefined : depth(joinedPath, sysPath.resolve(wh.watchPath)) + 1;

@@ -386,17 +433,17 @@

} else {
emitAdd(joinedPath, entry.stat);
emitAdd(joinedPath, entry.stats);
}
}.bind(this)).on('error', function() {
// Ignore readdirp errors
}).on('end', this._emitReady);
}).on('error', () => {/* Ignore readdirp errors */}).on('end', () => {
this.fsw._emitReady();
});
} else {
emitAdd(wh.watchPath, stats);
this._emitReady();
this.fsw._emitReady();
}
}.bind(this));
});
if (this.options.persistent && forceAdd !== true) {
var initWatch = function(error, realPath) {
if (this.closed) return;
var closer = this._watchWithFsEvents(
if (opts.persistent && forceAdd !== true) {
const initWatch = (error, realPath) => {
if (this.fsw.closed) return;
const closer = this._watchWithFsEvents(
wh.watchPath,

@@ -407,4 +454,4 @@ sysPath.resolve(realPath || wh.watchPath),

);
if (closer) this._closers[path] = closer;
}.bind(this);
this.fsw._addPathCloser(path, closer);
};

@@ -418,5 +465,7 @@ if (typeof transform === 'function') {

}
};
}
}
module.exports = FsEventsHandler;
module.exports.canUse = canUse;
'use strict';
var fs = require('fs');
var sysPath = require('path');
var readdirp = require('readdirp');
var isBinaryPath = require('is-binary-path');
const fs = require('fs');
const sysPath = require('path');
const isBinaryPath = require('is-binary-path');
// fs.watch helpers
/**
* @typedef {String} Path
*/
// object to hold per-process fs.watch instances
// fs_watch helpers
// object to hold per-process fs_watch instances
// (may be shared across chokidar FSWatcher instances)
var FsWatchInstances = Object.create(null);
/**
* @typedef {Object} FsWatchContainer
* @property {Set} listeners
* @property {Set} errHandlers
* @property {Set} rawEmitters
* @property {fs.FSWatcher=} watcher
* @property {Boolean=} watcherUnusable
*/
// Private function: Instantiates the fs.watch interface
/**
* @type {Map<String,FsWatchContainer>}
*/
const FsWatchInstances = new Map();
const emptyFn = () => {};
// * path - string, path to be watched
// * options - object, options to be passed to fs.watch
// * listener - function, main event handler
// * errHandler - function, handler which emits info about errors
// * emitRaw - function, handler which emits raw event data
// Returns new fsevents instance
/**
* Instantiates the fs_watch interface
* @param {String} path to be watched
* @param {Object} options to be passed to fs_watch
* @param {Function} listener main event handler
* @param {Function} errHandler emits info about errors
* @param {Function} emitRaw emits raw event data
* @returns {fs.FSWatcher} new fsevents instance
*/
function createFsWatchInstance(path, options, listener, errHandler, emitRaw) {
var handleEvent = function(rawEvent, evPath) {
const handleEvent = (rawEvent, evPath) => {
listener(path);

@@ -44,32 +60,34 @@ emitRaw(rawEvent, evPath, {watchedPath: path});

// Private function: Helper for passing fs.watch event data to a
// collection of listeners
// * fullPath - string, absolute path bound to the fs.watch instance
// * type - string, listener type
// * val[1..3] - arguments to be passed to listeners
// Returns nothing
function fsWatchBroadcast(fullPath, type, val1, val2, val3) {
if (!FsWatchInstances[fullPath]) return;
FsWatchInstances[fullPath][type].forEach(function(listener) {
/**
* Helper for passing fs_watch event data to a collection of listeners
* @param {Path} fullPath absolute path bound to fs_watch instance
* @param {String} type listener type
* @param {*=} val1 arguments to be passed to listeners
* @param {*=} val2
* @param {*=} val3
*/
const fsWatchBroadcast = (fullPath, type, val1, val2, val3) => {
const cont = FsWatchInstances.get(fullPath);
if (!cont) return;
cont[type].forEach((listener) => {
listener(val1, val2, val3);
});
}
};
// Private function: Instantiates the fs.watch interface or binds listeners
// to an existing one covering the same file system entry
/**
* Instantiates the fs_watch interface or binds listeners
* to an existing one covering the same file system entry
* @param {String} path
* @param {String} fullPath absolute path
* @param {Object} options to be passed to fs_watch
* @param {Object} handlers container for event listener functions
*/
const setFsWatchListener = (path, fullPath, options, handlers) => {
const listener = handlers.listener;
const errHandler = handlers.errHandler;
const rawEmitter = handlers.rawEmitter;
let cont = FsWatchInstances.get(fullPath);
// * path - string, path to be watched
// * fullPath - string, absolute path
// * options - object, options to be passed to fs.watch
// * handlers - object, container for event listener functions
// Returns close function
function setFsWatchListener(path, fullPath, options, handlers) {
var listener = handlers.listener;
var errHandler = handlers.errHandler;
var rawEmitter = handlers.rawEmitter;
var container = FsWatchInstances[fullPath];
var watcher;
/** @type {fs.FSWatcher=} */
let watcher;
if (!options.persistent) {

@@ -81,3 +99,7 @@ watcher = createFsWatchInstance(

}
if (!container) {
if (cont) {
cont.listeners.add(listener);
cont.errHandlers.add(errHandler);
cont.rawEmitters.add(rawEmitter);
} else {
watcher = createFsWatchInstance(

@@ -91,9 +113,9 @@ path,

if (!watcher) return;
var broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers');
watcher.on('error', function(error) {
container.watcherUnusable = true; // documented since Node 10.4.1
const broadcastErr = fsWatchBroadcast.bind(null, fullPath, 'errHandlers');
watcher.on('error', (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', function(err, fd) {
if (!err) fs.close(fd, function(err) {
fs.open(path, 'r', (err, fd) => {
if (!err) fs.close(fd, (err) => {
if (!err) broadcastErr(error);

@@ -106,57 +128,59 @@ });

});
container = FsWatchInstances[fullPath] = {
listeners: [listener],
errHandlers: [errHandler],
rawEmitters: [rawEmitter],
cont = {
listeners: new Set([listener]),
errHandlers: new Set([errHandler]),
rawEmitters: new Set([rawEmitter]),
watcher: watcher
};
} else {
container.listeners.push(listener);
container.errHandlers.push(errHandler);
container.rawEmitters.push(rawEmitter);
FsWatchInstances.set(fullPath, cont);
}
var listenerIndex = container.listeners.length - 1;
// const index = cont.listeners.indexOf(listener);
// removes this instance's listeners and closes the underlying fs.watch
// removes this instance's listeners and closes the underlying fs_watch
// instance if there are no more listeners left
return function close() {
delete container.listeners[listenerIndex];
delete container.errHandlers[listenerIndex];
delete container.rawEmitters[listenerIndex];
if (!Object.keys(container.listeners).length) {
if (!container.watcherUnusable) { // check to protect against issue #730
container.watcher.close();
}
delete FsWatchInstances[fullPath];
cont.listeners.delete(listener);
cont.errHandlers.delete(errHandler);
cont.rawEmitters.delete(rawEmitter);
if (!cont.listeners.size) {
// Check to protect against issue gh-730.
// if (cont.watcherUnusable) {
cont.watcher.close();
// }
FsWatchInstances.delete(fullPath);
['listeners', 'errHandlers', 'rawEmitters'].forEach(key => {
const set = cont[key];
set.clear();
Object.freeze(set);
});
cont.watcher = null;
Object.freeze(cont);
}
};
}
};
// fs.watchFile helpers
// fs_watchFile helpers
// object to hold per-process fs.watchFile instances
// object to hold per-process fs_watchFile instances
// (may be shared across chokidar FSWatcher instances)
var FsWatchFileInstances = Object.create(null);
const FsWatchFileInstances = new Map();
// Private function: Instantiates the fs.watchFile interface or binds listeners
// to an existing one covering the same file system entry
/**
* Instantiates the fs_watchFile interface or binds listeners
* to an existing one covering the same file system entry
* @param {String} path to be watched
* @param {String} fullPath absolute path
* @param {Object} options options to be passed to fs_watchFile
* @param {Object} handlers container for event listener functions
* @returns {Function} closer
*/
const setFsWatchFileListener = (path, fullPath, options, handlers) => {
const listener = handlers.listener;
const rawEmitter = handlers.rawEmitter;
let cont = FsWatchFileInstances.get(fullPath);
let listeners = new Set();
let rawEmitters = new Set();
// * path - string, path to be watched
// * fullPath - string, absolute path
// * options - object, options to be passed to fs.watchFile
// * handlers - object, container for event listener functions
// Returns close function
function setFsWatchFileListener(path, fullPath, options, handlers) {
var listener = handlers.listener;
var rawEmitter = handlers.rawEmitter;
var container = FsWatchFileInstances[fullPath];
var listeners = [];
var rawEmitters = [];
if (
container && (
container.options.persistent < options.persistent ||
container.options.interval > options.interval
)
) {
const copts = cont && cont.options;
if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
// "Upgrade" the watcher to persistence or a quicker interval.

@@ -166,71 +190,84 @@ // This creates some unlikely edge case issues if the user mixes

// doesn't seem worthwhile for the added complexity.
listeners = container.listeners;
rawEmitters = container.rawEmitters;
listeners = cont.listeners;
rawEmitters = cont.rawEmitters;
fs.unwatchFile(fullPath);
container = false;
cont = null;
}
if (!container) {
listeners.push(listener);
rawEmitters.push(rawEmitter);
container = FsWatchFileInstances[fullPath] = {
if (cont) {
cont.listeners.add(listener);
cont.rawEmitters.add(rawEmitter);
} else {
listeners.add(listener);
rawEmitters.add(rawEmitter);
cont = {
listeners: listeners,
rawEmitters: rawEmitters,
options: options,
watcher: fs.watchFile(fullPath, options, function(curr, prev) {
container.rawEmitters.forEach(function(rawEmitter) {
watcher: fs.watchFile(fullPath, options, (curr, prev) => {
cont.rawEmitters.forEach((rawEmitter) => {
rawEmitter('change', fullPath, {curr: curr, prev: prev});
});
var currmtime = curr.mtime.getTime();
if (curr.size !== prev.size || currmtime > prev.mtime.getTime() || currmtime === 0) {
container.listeners.forEach(function(listener) {
listener(path, curr);
});
const currmtime = curr.mtimeMs;
if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
cont.listeners.forEach((listener) => listener(path, curr));
}
})
};
} else {
container.listeners.push(listener);
container.rawEmitters.push(rawEmitter);
FsWatchFileInstances.set(fullPath, cont);
}
var listenerIndex = container.listeners.length - 1;
// const index = cont.listeners.indexOf(listener);
// removes this instance's listeners and closes the underlying fs.watchFile
// instance if there are no more listeners left
// Removes this instance's listeners and closes the underlying fs_watchFile
// instance if there are no more listeners left.
return function close() {
delete container.listeners[listenerIndex];
delete container.rawEmitters[listenerIndex];
if (!Object.keys(container.listeners).length) {
cont.listeners.delete(listener);
cont.rawEmitters.delete(rawEmitter);
if (!cont.listeners.size) {
FsWatchFileInstances.delete(fullPath);
fs.unwatchFile(fullPath);
delete FsWatchFileInstances[fullPath];
cont.options = cont.watcher = null;
Object.freeze(cont);
Object.freeze(cont.listeners);
}
};
}
};
// fake constructor for attaching nodefs-specific prototype methods that
// will be copied to FSWatcher's prototype
function NodeFsHandler() {}
/**
* @mixin
*/
class NodeFsHandler {
// Private method: Watch file for changes with fs.watchFile or fs.watch.
/**
* @param {FSWatcher} fsW
*/
constructor(fsW) {
const FSWatcher = require('../index').FSWatcher;
this.fsw = fsW;
this._boundHandleError = (error) => fsW._handleError(error);
}
// * path - string, path to file or directory.
// * listener - function, to be executed on fs change.
// Returns close function for the watcher instance
NodeFsHandler.prototype._watchWithNodeFs =
function(path, listener) {
var directory = sysPath.dirname(path);
var basename = sysPath.basename(path);
var parent = this._getWatchedDir(directory);
/**
* Watch file for changes with fs_watchFile or fs_watch.
* @param {String} path to file or dir
* @param {Function} listener on fs change
* @returns {Function} closer for the watcher instance
*/
_watchWithNodeFs(path, listener) {
const opts = this.fsw.options;
const directory = sysPath.dirname(path);
const basename = sysPath.basename(path);
const parent = this.fsw._getWatchedDir(directory);
parent.add(basename);
var absolutePath = sysPath.resolve(path);
var options = {persistent: this.options.persistent};
if (!listener) listener = Function.prototype; // empty function
const absolutePath = sysPath.resolve(path);
const options = {persistent: opts.persistent};
if (!listener) listener = emptyFn;
var closer;
if (this.options.usePolling) {
options.interval = this.enableBinaryInterval && isBinaryPath(basename) ?
this.options.binaryInterval : this.options.interval;
let closer;
if (opts.usePolling) {
options.interval = opts.enableBinaryInterval && isBinaryPath(basename) ?
opts.binaryInterval : opts.interval;
closer = setFsWatchFileListener(path, absolutePath, options, {
listener: listener,
rawEmitter: this.emit.bind(this, 'raw')
rawEmitter: this.fsw._emitRaw
});

@@ -240,24 +277,23 @@ } else {

listener: listener,
errHandler: this._handleError.bind(this),
rawEmitter: this.emit.bind(this, 'raw')
errHandler: this._boundHandleError,
rawEmitter: this.fsw._emitRaw
});
}
return closer;
};
}
// Private method: Watch a file and emit add event if warranted
// * file - string, the file's path
// * stats - object, result of fs.stat
// * initialAdd - boolean, was the file added at watch instantiation?
// * callback - function, called when done processing as a newly seen file
// Returns close function for the watcher instance
NodeFsHandler.prototype._handleFile =
function(file, stats, initialAdd, callback) {
var dirname = sysPath.dirname(file);
var basename = sysPath.basename(file);
var parent = this._getWatchedDir(dirname);
/**
* Watch a file and emit add event if warranted.
* @param {Path} file Path
* @param {fs.Stats} stats result of fs_stat
* @param {Boolean} initialAdd was the file added at watch instantiation?
* @param {Function} callback When done processing as a newly seen file
* @returns {Function} closer for the watcher instance
*/
_handleFile(file, stats, initialAdd, callback) {
const dirname = sysPath.dirname(file);
const basename = sysPath.basename(file);
const parent = this.fsw._getWatchedDir(dirname);
// stats is always present
var prevStats = stats;
let prevStats = stats;

@@ -268,35 +304,35 @@ // if the file is already being watched, do nothing

// kick off the watcher
var closer = this._watchWithNodeFs(file, function(path, newStats) {
if (!this._throttle('watch', file, 5)) return;
if (!newStats || newStats && newStats.mtime.getTime() === 0) {
fs.stat(file, function(error, newStats) {
const closer = this._watchWithNodeFs(file, (path, newStats) => {
if (!this.fsw._throttle('watch', file, 5)) return;
if (!newStats || newStats && newStats.mtimeMs === 0) {
fs.stat(file, (error, newStats) => {
// Fix issues where mtime is null but file is still present
if (error) {
this._remove(dirname, basename);
this.fsw._remove(dirname, basename);
} else {
// Check that change event was not fired because of changed only accessTime.
var at = newStats.atime.getTime();
var mt = newStats.mtime.getTime();
if (!at || at <= mt || mt !== prevStats.mtime.getTime()) {
this._emit('change', file, newStats);
const at = newStats.atimeMs;
const mt = newStats.mtimeMs;
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
this.fsw._emit('change', file, newStats);
}
prevStats = newStats;
}
}.bind(this));
});
// add is about to be emitted if file not already tracked in parent
} else if (parent.has(basename)) {
// Check that change event was not fired because of changed only accessTime.
var at = newStats.atime.getTime();
var mt = newStats.mtime.getTime();
if (!at || at <= mt || mt !== prevStats.mtime.getTime()) {
this._emit('change', file, newStats);
const at = newStats.atimeMs;
const mt = newStats.mtimeMs;
if (!at || at <= mt || mt !== prevStats.mtimeMs) {
this.fsw._emit('change', file, newStats);
}
prevStats = newStats;
}
}.bind(this));
});
// emit an add event if we're supposed to
if (!(initialAdd && this.options.ignoreInitial)) {
if (!this._throttle('add', file, 0)) return;
this._emit('add', file, stats);
if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
if (!this.fsw._throttle('add', file, 0)) return;
this.fsw._emit('add', file, stats);
}

@@ -306,33 +342,32 @@

return closer;
};
}
// Private method: Handle symlinks encountered while reading a dir
/**
* Handle symlinks encountered while reading a dir.
* @param {Object} entry returned by readdirp
* @param {String} directory path of dir being read
* @param {String} path of this item
* @param {String} item basename of this item
* @returns {Boolean} true if no more processing is needed for this entry.
*/
_handleSymlink(entry, directory, path, item) {
const full = entry.fullPath;
const dir = this.fsw._getWatchedDir(directory);
// * entry - object, entry object returned by readdirp
// * directory - string, path of the directory being read
// * path - string, path of this item
// * item - string, basename of this item
// Returns true if no more processing is needed for this entry.
NodeFsHandler.prototype._handleSymlink =
function(entry, directory, path, item) {
var full = entry.fullPath;
var dir = this._getWatchedDir(directory);
if (!this.options.followSymlinks) {
if (!this.fsw.options.followSymlinks) {
// watch symlink directly (don't follow) and detect changes
this._readyCount++;
fs.realpath(path, function(error, linkPath) {
this.fsw._incrReadyCount();
fs.realpath(path, (error, linkPath) => {
if (dir.has(item)) {
if (this._symlinkPaths[full] !== linkPath) {
this._symlinkPaths[full] = linkPath;
this._emit('change', path, entry.stat);
if (this.fsw._symlinkPaths.get(full) !== linkPath) {
this.fsw._symlinkPaths.set(full, linkPath);
this.fsw._emit('change', path, entry.stats);
}
} else {
dir.add(item);
this._symlinkPaths[full] = linkPath;
this._emit('add', path, entry.stat);
this.fsw._symlinkPaths.set(full, linkPath);
this.fsw._emit('add', path, entry.stats);
}
this._emitReady();
}.bind(this));
this.fsw._emitReady();
});
return true;

@@ -342,24 +377,25 @@ }

// don't follow the same symlink more than once
if (this._symlinkPaths[full]) return true;
else this._symlinkPaths[full] = true;
};
if (this.fsw._symlinkPaths.has(full)) {
return true;
} else {
this.fsw._symlinkPaths.set(full, true);
}
}
// Private method: Read directory to add / remove files from `@watched` list
// and re-read it on change.
// * dir - string, fs path.
// * stats - object, result of fs.stat
// * initialAdd - boolean, was the file added at watch instantiation?
// * depth - int, depth relative to user-supplied path
// * target - string, child path actually targeted for watch
// * wh - object, common watch helpers for this path
// * callback - function, called when dir scan is complete
// Returns close function for the watcher instance
NodeFsHandler.prototype._handleDir =
function(dir, stats, initialAdd, depth, target, wh, callback) {
var parentDir = this._getWatchedDir(sysPath.dirname(dir));
var tracked = parentDir.has(sysPath.basename(dir));
if (!(initialAdd && this.options.ignoreInitial) && !target && !tracked) {
if (!wh.hasGlob || wh.globFilter(dir)) this._emit('addDir', dir, stats);
/**
* 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);
}

@@ -369,5 +405,6 @@

parentDir.add(sysPath.basename(dir));
this._getWatchedDir(dir);
this.fsw._getWatchedDir(dir);
let throttler;
var read = function(directory, initialAdd, done) {
const read = (directory, initialAdd, done) => {
// Normalize the directory name on Windows

@@ -377,22 +414,19 @@ directory = sysPath.join(directory, '');

if (!wh.hasGlob) {
var throttler = this._throttle('readdir', directory, 1000);
if (!throttler) return;
// throttler = this.fsw._throttle('readdir', directory, 1000);
// if (!throttler) return;
}
var previous = this._getWatchedDir(wh.path);
var current = [];
const previous = this.fsw._getWatchedDir(wh.path);
const current = new Set();
readdirp({
root: directory,
entryType: 'all',
this.fsw._readdirp(directory, {
fileFilter: wh.filterPath,
directoryFilter: wh.filterDir,
depth: 0,
lstat: true
}).on('data', function(entry) {
var item = entry.path;
var path = sysPath.join(directory, item);
current.push(item);
}).on('data', (entry) => {
const item = entry.path;
let path = sysPath.join(directory, item);
current.add(item);
if (entry.stat.isSymbolicLink() &&
if (entry.stats.isSymbolicLink() &&
this._handleSymlink(entry, directory, path, item)) return;

@@ -404,3 +438,3 @@

if (item === target || !target && !previous.has(item)) {
this._readyCount++;
this.fsw._incrReadyCount();

@@ -412,4 +446,5 @@ // ensure relativeness of path is preserved in case of watcher reuse

}
}.bind(this)).on('end', function() {
var wasThrottled = throttler ? throttler.clear() : false;
}).on('end', () => {
const wasThrottled = throttler ? throttler.clear() : false;
if (done) done();

@@ -420,5 +455,5 @@

// and are removed from @watched[directory].
previous.children().filter(function(item) {
previous.getChildren().filter((item) => {
return item !== directory &&
current.indexOf(item) === -1 &&
!current.has(item) &&
// in case of intersecting globs;

@@ -430,18 +465,19 @@ // a path may have been filtered out of this readdir, but

}));
}).forEach(function(item) {
this._remove(directory, item);
}, this);
}).forEach((item) => {
this.fsw._remove(directory, item);
});
// one more time for any missed in case changes came in extremely quickly
if (wasThrottled) read(directory, false);
}.bind(this)).on('error', this._handleError.bind(this));
}.bind(this);
}).on('error', this._boundHandleError);
};
var closer;
let closer;
if (this.options.depth == null || depth <= this.options.depth) {
const oDepth = this.fsw.options.depth;
if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath)) {
if (!target) read(dir, initialAdd, callback);
closer = this._watchWithNodeFs(dir, function(dirPath, stats) {
closer = this._watchWithNodeFs(dir, (dirPath, stats) => {
// if current directory is removed, do nothing
if (stats && stats.mtime.getTime() === 0) return;
if (stats && stats.mtimeMs === 0) return;

@@ -454,19 +490,19 @@ read(dirPath, false);

return closer;
};
}
// Private method: Handle added file, directory, or glob pattern.
// Delegates call to _handleFile / _handleDir after checks.
// * path - string, path to file or directory.
// * initialAdd - boolean, was the file added at watch instantiation?
// * depth - int, depth relative to user-supplied path
// * target - string, child path actually targeted for watch
// * callback - function, indicates whether the path was found or not
// Returns nothing
NodeFsHandler.prototype._addToNodeFs =
function(path, initialAdd, priorWh, depth, target, callback) {
/**
* Handle added file, directory, or glob pattern.
* Delegates call to _handleFile / _handleDir after checks.
* @param {String} path to file or ir
* @param {Boolean} initialAdd was the file added at watch instantiation?
* @param {Object} priorWh depth relative to user-supplied path
* @param {Number} depth Child path actually targetted for watch
* @param {String=} target Child path actually targeted for watch
* @param {Function=} callback Indicates whetehr the path was found or not
* @returns {void}
*/
_addToNodeFs(path, initialAdd, priorWh, depth, target, callback) {
if (!callback) callback = Function.prototype;
var ready = this._emitReady;
if (this._isIgnored(path) || this.closed) {
const ready = this.fsw._emitReady;
if (this.fsw._isIgnored(path) || this.fsw.closed) {
ready();

@@ -476,3 +512,3 @@ return callback(null, false);

var wh = this._getWatchHelpers(path, depth);
const wh = this.fsw._getWatchHelpers(path, depth);
if (!wh.hasGlob && priorWh) {

@@ -486,5 +522,5 @@ wh.hasGlob = priorWh.hasGlob;

// evaluate what is at the path we're being asked to watch
fs[wh.statMethod](wh.watchPath, function(error, stats) {
if (this._handleError(error)) return callback(null, path);
if (this._isIgnored(wh.watchPath, stats)) {
fs[wh.statMethod](wh.watchPath, (error, stats) => {
if (this.fsw._handleError(error)) return callback(null, path);
if (this.fsw._isIgnored(wh.watchPath, stats)) {
ready();

@@ -494,29 +530,37 @@ return callback(null, false);

var initDir = function(dir, target) {
return this._handleDir(dir, stats, initialAdd, depth, target, wh, ready);
}.bind(this);
const initDir = (dir, target, realpath) => {
return this._handleDir(dir, stats, initialAdd, depth, target, wh, realpath, ready);
};
var closer;
if (stats.isDirectory()) {
closer = initDir(wh.watchPath, target);
} else if (stats.isSymbolicLink()) {
var parent = sysPath.dirname(wh.watchPath);
this._getWatchedDir(parent).add(wh.watchPath);
this._emit('add', wh.watchPath, stats);
closer = initDir(parent, path);
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
fs.realpath(path, function(error, targetPath) {
this._symlinkPaths[sysPath.resolve(path)] = targetPath;
// preserve this symlink's target path
if (targetPath !== undefined) {
this.fsw._symlinkPaths.set(sysPath.resolve(path), targetPath);
}
ready();
}.bind(this));
} else {
closer = this._handleFile(wh.watchPath, stats, initialAdd, ready);
}
} else {
closer = this._handleFile(wh.watchPath, stats, initialAdd, ready);
}
if (closer) this._closers[path] = closer;
callback(null, false);
}.bind(this));
};
this.fsw._addPathCloser(path, closer);
callback(null, false);
});
});
}
}
module.exports = NodeFsHandler;
{
"name": "chokidar",
"description": "A neat wrapper around node.js fs.watch / fs.watchFile / fsevents.",
"version": "2.1.5",
"keywords": [
"fs",
"watch",
"watchFile",
"watcher",
"watching",
"file",
"fsevents"
"version": "3.0.0",
"homepage": "https://github.com/paulmillr/chokidar",
"author": "Paul Miller (https://paulmillr.com)",
"contributors": [
"Paul Miller (https://paulmillr.com)",
"Elan Shanker"
],
"types": "./types/index.d.ts",
"homepage": "https://github.com/paulmillr/chokidar",
"author": "Paul Miller (https://paulmillr.com), Elan Shanker",
"engines": {
"node": ">= 8"
},
"dependencies": {
"anymatch": "^3.0.1",
"async-each": "^1.0.3",
"braces": "^3.0.2",
"glob-parent": "^5.0.0",
"is-binary-path": "^2.1.0",
"is-glob": "^4.0.1",
"normalize-path": "^3.0.0",
"readdirp": "^3.0.1"
},
"optionalDependencies": {
"fsevents": "^2.0.6"
},
"devDependencies": {
"@types/node": "^11.13.4",
"chai": "^4.2.0",
"coveralls": "^3.0.1",
"dtslint": "0.4.1",
"jshint": "^2.10.1",
"mocha": "^6.1.3",
"nyc": "^14.0.0",
"rimraf": "^2.4.3",
"sinon": "^7.3.1",
"sinon-chai": "^3.3.0",
"upath": "^1.1.2"
},
"files": [
"index.js",
"lib/",
"types/index.d.ts"
],
"repository": {

@@ -27,38 +55,28 @@ "type": "git",

"test": "nyc mocha --exit",
"mocha": "mocha",
"lint": "jshint index.js lib",
"coveralls": "nyc report --reporter=text-lcov | coveralls",
"dtslint": "dtslint types"
},
"files": [
"index.js",
"lib/",
"types/index.d.ts"
"keywords": [
"fs",
"watch",
"watchFile",
"watcher",
"watching",
"file",
"fsevents"
],
"dependencies": {
"anymatch": "^2.0.0",
"async-each": "^1.0.1",
"braces": "^2.3.2",
"glob-parent": "^3.1.0",
"inherits": "^2.0.3",
"is-binary-path": "^1.0.0",
"is-glob": "^4.0.0",
"normalize-path": "^3.0.0",
"path-is-absolute": "^1.0.0",
"readdirp": "^2.2.1",
"upath": "^1.1.1"
},
"optionalDependencies": {
"fsevents": "^1.2.7"
},
"devDependencies": {
"@types/node": "^11.9.4",
"chai": "^3.2.0",
"coveralls": "^3.0.1",
"dtslint": "0.4.1",
"graceful-fs": "4.1.4",
"mocha": "^5.2.0",
"nyc": "^11.8.0",
"rimraf": "^2.4.3",
"sinon": "^1.10.3",
"sinon-chai": "^2.6.0"
"types": "./types/index.d.ts",
"jshintConfig": {
"node": true,
"curly": false,
"bitwise": false,
"mocha": true,
"expr": true,
"esversion": 8,
"predef": [
"toString"
]
}
}

@@ -1,2 +0,2 @@

# 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) [![Mac/Linux Build Status](https://img.shields.io/travis/paulmillr/chokidar/master.svg?label=Mac%20OSX%20%26%20Linux)](https://travis-ci.org/paulmillr/chokidar) [![Windows Build status](https://img.shields.io/appveyor/ci/paulmillr/chokidar/master.svg?label=Windows)](https://ci.appveyor.com/project/paulmillr/chokidar/branch/master) [![Coverage Status](https://coveralls.io/repos/paulmillr/chokidar/badge.svg)](https://coveralls.io/r/paulmillr/chokidar)
# 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)

@@ -14,3 +14,2 @@ > A neat wrapper around node.js fs.watch / fs.watchFile / FSEvents.

* Emits most changes as `rename`.
* Has [a lot of other issues](https://github.com/nodejs/node/search?q=fs.watch&type=Issues)
* Does not provide an easy way to recursively watch file trees.

@@ -27,2 +26,3 @@

Initially made for **[Brunch](http://brunch.io)** (an ultra-swift web app build tool), it is now used in
[Microsoft's Visual Studio Code](https://github.com/microsoft/vscode),
[gulp](https://github.com/gulpjs/gulp/),

@@ -34,3 +34,2 @@ [karma](http://karma-runner.github.io),

[BrowserSync](http://www.browsersync.io/),
[Microsoft's Visual Studio Code](https://github.com/microsoft/vscode),
and [many others](https://www.npmjs.org/browse/depended/chokidar/).

@@ -65,3 +64,3 @@ It has proven itself in production environments.

```javascript
var chokidar = require('chokidar');
const chokidar = require('chokidar');

@@ -74,2 +73,4 @@ // One-liner for current directory, ignores .dotfiles

## API
```javascript

@@ -79,3 +80,3 @@ // Example of a more typical implementation structure:

// Initialize watcher.
var watcher = chokidar.watch('file, dir, glob, or array', {
const watcher = chokidar.watch('file, dir, glob, or array', {
ignored: /(^|[\/\\])\../,

@@ -86,3 +87,3 @@ persistent: true

// Something to use when events are received.
var log = console.log.bind(console);
const log = console.log.bind(console);
// Add event listeners.

@@ -149,4 +150,2 @@ watcher

## API
`chokidar.watch(paths, [options])`

@@ -280,24 +279,14 @@

## License
The MIT License (MIT)
## Changelog
Copyright (c) 2012-2019 Paul Miller (https://paulmillr.com) & Elan Shanker
For more detailed changelog, see `.github/full_changelog.md`
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the “Software”), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
- v3 (Apr 30, 2019): Massive CPU & RAM consumption improvements. 17x package & deps size reduction. Node 8+-only
- v2 (Dec 29, 2017): Globs are now posix-style-only; without windows support. Tons of bugfixes.
- v1 (Apr 7, 2015): Glob support, symlink support, tons of bugfixes. Node 0.8+ is supported
- v0.1 (Apr 20, 2012): Initial release, extracted from [Brunch](https://github.com/brunch/brunch/blob/9847a065aea300da99bd0753f90354cde9de1261/src/helpers.coffee#L66)
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
## License
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
MIT (c) Paul Miller (https://paulmillr.com), see LICENSE file.

@@ -8,11 +8,5 @@ // TypeScript Version: 3.0

/**
* The object's keys are all the directories (using absolute paths unless the `cwd` option was
* used), and the values are arrays of the names of the items contained in each directory.
*/
export interface WatchedPaths {
[directory: string]: string[];
}
export class FSWatcher extends EventEmitter implements fs.FSWatcher {
options: WatchOptions;
export class FSWatcher extends EventEmitter implements fs.FSWatcher {
/**

@@ -41,3 +35,5 @@ * Constructs a new FSWatcher instance with optional WatchOptions parameter.

*/
getWatched(): WatchedPaths;
getWatched(): {
[directory: string]: string[];
};

@@ -44,0 +40,0 @@ /**

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