| "use strict"; |
| Object.defineProperty(exports, "__esModule", { value: true }); |
| exports.FSWatcher = exports.WatchHelper = void 0; |
| exports.watch = watch; |
| |
| const fs_1 = require("fs"); |
| const promises_1 = require("fs/promises"); |
| const events_1 = require("events"); |
| const sysPath = require("path"); |
| const readdirp_1 = require("readdirp"); |
| const handler_js_1 = require("./handler.js"); |
| const SLASH = '/'; |
| const SLASH_SLASH = '//'; |
| const ONE_DOT = '.'; |
| const TWO_DOTS = '..'; |
| const STRING_TYPE = 'string'; |
| const BACK_SLASH_RE = /\\/g; |
| const DOUBLE_SLASH_RE = /\/\//; |
| const DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/; |
| const REPLACER_RE = /^\.[/\\]/; |
| function arrify(item) { |
| return Array.isArray(item) ? item : [item]; |
| } |
| const isMatcherObject = (matcher) => typeof matcher === 'object' && matcher !== null && !(matcher instanceof RegExp); |
| function createPattern(matcher) { |
| if (typeof matcher === 'function') |
| return matcher; |
| if (typeof matcher === 'string') |
| return (string) => matcher === string; |
| if (matcher instanceof RegExp) |
| return (string) => matcher.test(string); |
| if (typeof matcher === 'object' && matcher !== null) { |
| return (string) => { |
| if (matcher.path === string) |
| return true; |
| if (matcher.recursive) { |
| const relative = sysPath.relative(matcher.path, string); |
| if (!relative) { |
| return false; |
| } |
| return !relative.startsWith('..') && !sysPath.isAbsolute(relative); |
| } |
| return false; |
| }; |
| } |
| return () => false; |
| } |
| function normalizePath(path) { |
| if (typeof path !== 'string') |
| throw new Error('string expected'); |
| path = sysPath.normalize(path); |
| path = path.replace(/\\/g, '/'); |
| let prepend = false; |
| if (path.startsWith('//')) |
| prepend = true; |
| const DOUBLE_SLASH_RE = /\/\//; |
| while (path.match(DOUBLE_SLASH_RE)) |
| path = path.replace(DOUBLE_SLASH_RE, '/'); |
| if (prepend) |
| path = '/' + path; |
| return path; |
| } |
| function matchPatterns(patterns, testString, stats) { |
| const path = normalizePath(testString); |
| for (let index = 0; index < patterns.length; index++) { |
| const pattern = patterns[index]; |
| if (pattern(path, stats)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| function anymatch(matchers, testString) { |
| if (matchers == null) { |
| throw new TypeError('anymatch: specify first argument'); |
| } |
| |
| const matchersArray = arrify(matchers); |
| const patterns = matchersArray.map((matcher) => createPattern(matcher)); |
| if (testString == null) { |
| return (testString, stats) => { |
| return matchPatterns(patterns, testString, stats); |
| }; |
| } |
| return matchPatterns(patterns, testString); |
| } |
| const unifyPaths = (paths_) => { |
| const paths = arrify(paths_).flat(); |
| if (!paths.every((p) => typeof p === STRING_TYPE)) { |
| throw new TypeError(`Non-string provided as watch path: ${paths}`); |
| } |
| return paths.map(normalizePathToUnix); |
| }; |
| |
| |
| const toUnix = (string) => { |
| let str = string.replace(BACK_SLASH_RE, SLASH); |
| let prepend = false; |
| if (str.startsWith(SLASH_SLASH)) { |
| prepend = true; |
| } |
| while (str.match(DOUBLE_SLASH_RE)) { |
| str = str.replace(DOUBLE_SLASH_RE, SLASH); |
| } |
| if (prepend) { |
| str = SLASH + str; |
| } |
| return str; |
| }; |
| |
| |
| const normalizePathToUnix = (path) => toUnix(sysPath.normalize(toUnix(path))); |
| |
| const normalizeIgnored = (cwd = '') => (path) => { |
| if (typeof path === 'string') { |
| return normalizePathToUnix(sysPath.isAbsolute(path) ? path : sysPath.join(cwd, path)); |
| } |
| else { |
| return path; |
| } |
| }; |
| const getAbsolutePath = (path, cwd) => { |
| if (sysPath.isAbsolute(path)) { |
| return path; |
| } |
| return sysPath.join(cwd, path); |
| }; |
| const EMPTY_SET = Object.freeze(new Set()); |
| |
| |
| |
| class DirEntry { |
| constructor(dir, removeWatcher) { |
| this.path = dir; |
| this._removeWatcher = removeWatcher; |
| this.items = new Set(); |
| } |
| add(item) { |
| const { items } = this; |
| if (!items) |
| return; |
| if (item !== ONE_DOT && item !== TWO_DOTS) |
| items.add(item); |
| } |
| async remove(item) { |
| const { items } = this; |
| if (!items) |
| return; |
| items.delete(item); |
| if (items.size > 0) |
| return; |
| const dir = this.path; |
| try { |
| await (0, promises_1.readdir)(dir); |
| } |
| catch (err) { |
| if (this._removeWatcher) { |
| this._removeWatcher(sysPath.dirname(dir), sysPath.basename(dir)); |
| } |
| } |
| } |
| has(item) { |
| const { items } = this; |
| if (!items) |
| return; |
| return items.has(item); |
| } |
| getChildren() { |
| const { items } = this; |
| if (!items) |
| return []; |
| return [...items.values()]; |
| } |
| dispose() { |
| this.items.clear(); |
| this.path = ''; |
| this._removeWatcher = handler_js_1.EMPTY_FN; |
| this.items = EMPTY_SET; |
| Object.freeze(this); |
| } |
| } |
| const STAT_METHOD_F = 'stat'; |
| const STAT_METHOD_L = 'lstat'; |
| class WatchHelper { |
| constructor(path, follow, fsw) { |
| this.fsw = fsw; |
| const watchPath = path; |
| this.path = path = path.replace(REPLACER_RE, ''); |
| this.watchPath = watchPath; |
| this.fullWatchPath = sysPath.resolve(watchPath); |
| this.dirParts = []; |
| this.dirParts.forEach((parts) => { |
| if (parts.length > 1) |
| parts.pop(); |
| }); |
| this.followSymlinks = follow; |
| this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L; |
| } |
| entryPath(entry) { |
| return sysPath.join(this.watchPath, sysPath.relative(this.watchPath, entry.fullPath)); |
| } |
| filterPath(entry) { |
| const { stats } = entry; |
| if (stats && stats.isSymbolicLink()) |
| return this.filterDir(entry); |
| const resolvedPath = this.entryPath(entry); |
| |
| return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats); |
| } |
| filterDir(entry) { |
| return this.fsw._isntIgnored(this.entryPath(entry), entry.stats); |
| } |
| } |
| exports.WatchHelper = WatchHelper; |
| |
| |
| |
| |
| |
| |
| |
| |
| class FSWatcher extends events_1.EventEmitter { |
| |
| constructor(_opts = {}) { |
| super(); |
| this.closed = false; |
| this._closers = new Map(); |
| this._ignoredPaths = new Set(); |
| this._throttled = new Map(); |
| this._streams = new Set(); |
| this._symlinkPaths = new Map(); |
| this._watched = new Map(); |
| this._pendingWrites = new Map(); |
| this._pendingUnlinks = new Map(); |
| this._readyCount = 0; |
| this._readyEmitted = false; |
| const awf = _opts.awaitWriteFinish; |
| const DEF_AWF = { stabilityThreshold: 2000, pollInterval: 100 }; |
| const opts = { |
| |
| persistent: true, |
| ignoreInitial: false, |
| ignorePermissionErrors: false, |
| interval: 100, |
| binaryInterval: 300, |
| followSymlinks: true, |
| usePolling: false, |
| |
| atomic: true, |
| ..._opts, |
| |
| ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]), |
| awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === 'object' ? { ...DEF_AWF, ...awf } : false, |
| }; |
| |
| if (handler_js_1.isIBMi) |
| opts.usePolling = true; |
| |
| if (opts.atomic === undefined) |
| opts.atomic = !opts.usePolling; |
| |
| |
| |
| const envPoll = process.env.CHOKIDAR_USEPOLLING; |
| if (envPoll !== undefined) { |
| const envLower = envPoll.toLowerCase(); |
| if (envLower === 'false' || envLower === '0') |
| opts.usePolling = false; |
| else if (envLower === 'true' || envLower === '1') |
| opts.usePolling = true; |
| else |
| opts.usePolling = !!envLower; |
| } |
| const envInterval = process.env.CHOKIDAR_INTERVAL; |
| if (envInterval) |
| opts.interval = Number.parseInt(envInterval, 10); |
| |
| let readyCalls = 0; |
| this._emitReady = () => { |
| readyCalls++; |
| if (readyCalls >= this._readyCount) { |
| this._emitReady = handler_js_1.EMPTY_FN; |
| this._readyEmitted = true; |
| |
| process.nextTick(() => this.emit(handler_js_1.EVENTS.READY)); |
| } |
| }; |
| this._emitRaw = (...args) => this.emit(handler_js_1.EVENTS.RAW, ...args); |
| this._boundRemove = this._remove.bind(this); |
| this.options = opts; |
| this._nodeFsHandler = new handler_js_1.NodeFsHandler(this); |
| |
| Object.freeze(opts); |
| } |
| _addIgnoredPath(matcher) { |
| if (isMatcherObject(matcher)) { |
| |
| for (const ignored of this._ignoredPaths) { |
| if (isMatcherObject(ignored) && |
| ignored.path === matcher.path && |
| ignored.recursive === matcher.recursive) { |
| return; |
| } |
| } |
| } |
| this._ignoredPaths.add(matcher); |
| } |
| _removeIgnoredPath(matcher) { |
| this._ignoredPaths.delete(matcher); |
| |
| if (typeof matcher === 'string') { |
| for (const ignored of this._ignoredPaths) { |
| |
| |
| |
| if (isMatcherObject(ignored) && ignored.path === matcher) { |
| this._ignoredPaths.delete(ignored); |
| } |
| } |
| } |
| } |
| |
| |
| |
| |
| |
| add(paths_, _origAdd, _internal) { |
| const { cwd } = this.options; |
| this.closed = false; |
| this._closePromise = undefined; |
| let paths = unifyPaths(paths_); |
| if (cwd) { |
| paths = paths.map((path) => { |
| const absPath = getAbsolutePath(path, cwd); |
| |
| return absPath; |
| }); |
| } |
| paths.forEach((path) => { |
| this._removeIgnoredPath(path); |
| }); |
| this._userIgnored = undefined; |
| if (!this._readyCount) |
| this._readyCount = 0; |
| this._readyCount += paths.length; |
| Promise.all(paths.map(async (path) => { |
| const res = await this._nodeFsHandler._addToNodeFs(path, !_internal, undefined, 0, _origAdd); |
| if (res) |
| this._emitReady(); |
| return res; |
| })).then((results) => { |
| if (this.closed) |
| return; |
| results.forEach((item) => { |
| if (item) |
| this.add(sysPath.dirname(item), sysPath.basename(_origAdd || item)); |
| }); |
| }); |
| return this; |
| } |
| |
| |
| |
| unwatch(paths_) { |
| if (this.closed) |
| return this; |
| const paths = unifyPaths(paths_); |
| const { cwd } = this.options; |
| paths.forEach((path) => { |
| |
| if (!sysPath.isAbsolute(path) && !this._closers.has(path)) { |
| if (cwd) |
| path = sysPath.join(cwd, path); |
| path = sysPath.resolve(path); |
| } |
| this._closePath(path); |
| this._addIgnoredPath(path); |
| if (this._watched.has(path)) { |
| this._addIgnoredPath({ |
| path, |
| recursive: true, |
| }); |
| } |
| |
| |
| this._userIgnored = undefined; |
| }); |
| return this; |
| } |
| |
| |
| |
| close() { |
| if (this._closePromise) { |
| return this._closePromise; |
| } |
| this.closed = true; |
| |
| this.removeAllListeners(); |
| const closers = []; |
| this._closers.forEach((closerList) => closerList.forEach((closer) => { |
| const promise = closer(); |
| if (promise instanceof Promise) |
| closers.push(promise); |
| })); |
| this._streams.forEach((stream) => stream.destroy()); |
| this._userIgnored = undefined; |
| this._readyCount = 0; |
| this._readyEmitted = false; |
| this._watched.forEach((dirent) => dirent.dispose()); |
| this._closers.clear(); |
| this._watched.clear(); |
| this._streams.clear(); |
| this._symlinkPaths.clear(); |
| this._throttled.clear(); |
| this._closePromise = closers.length |
| ? Promise.all(closers).then(() => undefined) |
| : Promise.resolve(); |
| return this._closePromise; |
| } |
| |
| |
| |
| |
| getWatched() { |
| const watchList = {}; |
| this._watched.forEach((entry, dir) => { |
| const key = this.options.cwd ? sysPath.relative(this.options.cwd, dir) : dir; |
| const index = key || ONE_DOT; |
| watchList[index] = entry.getChildren().sort(); |
| }); |
| return watchList; |
| } |
| emitWithAll(event, args) { |
| this.emit(event, ...args); |
| if (event !== handler_js_1.EVENTS.ERROR) |
| this.emit(handler_js_1.EVENTS.ALL, event, ...args); |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async _emit(event, path, stats) { |
| if (this.closed) |
| return; |
| const opts = this.options; |
| if (handler_js_1.isWindows) |
| path = sysPath.normalize(path); |
| if (opts.cwd) |
| path = sysPath.relative(opts.cwd, path); |
| const args = [path]; |
| if (stats != null) |
| args.push(stats); |
| const awf = opts.awaitWriteFinish; |
| let pw; |
| if (awf && (pw = this._pendingWrites.get(path))) { |
| pw.lastChange = new Date(); |
| return this; |
| } |
| if (opts.atomic) { |
| if (event === handler_js_1.EVENTS.UNLINK) { |
| this._pendingUnlinks.set(path, [event, ...args]); |
| setTimeout(() => { |
| this._pendingUnlinks.forEach((entry, path) => { |
| this.emit(...entry); |
| this.emit(handler_js_1.EVENTS.ALL, ...entry); |
| this._pendingUnlinks.delete(path); |
| }); |
| }, typeof opts.atomic === 'number' ? opts.atomic : 100); |
| return this; |
| } |
| if (event === handler_js_1.EVENTS.ADD && this._pendingUnlinks.has(path)) { |
| event = handler_js_1.EVENTS.CHANGE; |
| this._pendingUnlinks.delete(path); |
| } |
| } |
| if (awf && (event === handler_js_1.EVENTS.ADD || event === handler_js_1.EVENTS.CHANGE) && this._readyEmitted) { |
| const awfEmit = (err, stats) => { |
| if (err) { |
| event = handler_js_1.EVENTS.ERROR; |
| args[0] = err; |
| this.emitWithAll(event, args); |
| } |
| else if (stats) { |
| |
| if (args.length > 1) { |
| args[1] = stats; |
| } |
| else { |
| args.push(stats); |
| } |
| this.emitWithAll(event, args); |
| } |
| }; |
| this._awaitWriteFinish(path, awf.stabilityThreshold, event, awfEmit); |
| return this; |
| } |
| if (event === handler_js_1.EVENTS.CHANGE) { |
| const isThrottled = !this._throttle(handler_js_1.EVENTS.CHANGE, path, 50); |
| if (isThrottled) |
| return this; |
| } |
| if (opts.alwaysStat && |
| stats === undefined && |
| (event === handler_js_1.EVENTS.ADD || event === handler_js_1.EVENTS.ADD_DIR || event === handler_js_1.EVENTS.CHANGE)) { |
| const fullPath = opts.cwd ? sysPath.join(opts.cwd, path) : path; |
| let stats; |
| try { |
| stats = await (0, promises_1.stat)(fullPath); |
| } |
| catch (err) { |
| |
| } |
| |
| if (!stats || this.closed) |
| return; |
| args.push(stats); |
| } |
| this.emitWithAll(event, args); |
| return this; |
| } |
| |
| |
| |
| |
| _handleError(error) { |
| const code = error && error.code; |
| if (error && |
| code !== 'ENOENT' && |
| code !== 'ENOTDIR' && |
| (!this.options.ignorePermissionErrors || (code !== 'EPERM' && code !== 'EACCES'))) { |
| this.emit(handler_js_1.EVENTS.ERROR, error); |
| } |
| return error || this.closed; |
| } |
| |
| |
| |
| |
| |
| |
| |
| _throttle(actionType, path, timeout) { |
| if (!this._throttled.has(actionType)) { |
| this._throttled.set(actionType, new Map()); |
| } |
| const action = this._throttled.get(actionType); |
| if (!action) |
| throw new Error('invalid throttle'); |
| const actionPath = action.get(path); |
| if (actionPath) { |
| actionPath.count++; |
| return false; |
| } |
| |
| 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; |
| }; |
| timeoutObject = setTimeout(clear, timeout); |
| const thr = { timeoutObject, clear, count: 0 }; |
| action.set(path, thr); |
| return thr; |
| } |
| _incrReadyCount() { |
| return this._readyCount++; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| _awaitWriteFinish(path, threshold, event, awfEmit) { |
| const awf = this.options.awaitWriteFinish; |
| if (typeof awf !== 'object') |
| return; |
| const pollInterval = awf.pollInterval; |
| let timeoutHandler; |
| let fullPath = path; |
| if (this.options.cwd && !sysPath.isAbsolute(path)) { |
| fullPath = sysPath.join(this.options.cwd, path); |
| } |
| const now = new Date(); |
| const writes = this._pendingWrites; |
| function awaitWriteFinishFn(prevStat) { |
| (0, fs_1.stat)(fullPath, (err, curStat) => { |
| if (err || !writes.has(path)) { |
| if (err && err.code !== 'ENOENT') |
| awfEmit(err); |
| return; |
| } |
| const now = Number(new Date()); |
| if (prevStat && curStat.size !== prevStat.size) { |
| writes.get(path).lastChange = now; |
| } |
| const pw = writes.get(path); |
| const df = now - pw.lastChange; |
| if (df >= threshold) { |
| writes.delete(path); |
| awfEmit(undefined, curStat); |
| } |
| else { |
| timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat); |
| } |
| }); |
| } |
| if (!writes.has(path)) { |
| writes.set(path, { |
| lastChange: now, |
| cancelWait: () => { |
| writes.delete(path); |
| clearTimeout(timeoutHandler); |
| return event; |
| }, |
| }); |
| timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval); |
| } |
| } |
| |
| |
| |
| _isIgnored(path, stats) { |
| if (this.options.atomic && DOT_RE.test(path)) |
| return true; |
| if (!this._userIgnored) { |
| const { cwd } = this.options; |
| const ign = this.options.ignored; |
| const ignored = (ign || []).map(normalizeIgnored(cwd)); |
| const ignoredPaths = [...this._ignoredPaths]; |
| const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored]; |
| this._userIgnored = anymatch(list, undefined); |
| } |
| return this._userIgnored(path, stats); |
| } |
| _isntIgnored(path, stat) { |
| return !this._isIgnored(path, stat); |
| } |
| |
| |
| |
| |
| _getWatchHelpers(path) { |
| return new WatchHelper(path, this.options.followSymlinks, this); |
| } |
| |
| |
| |
| |
| |
| |
| _getWatchedDir(directory) { |
| const dir = sysPath.resolve(directory); |
| if (!this._watched.has(dir)) |
| this._watched.set(dir, new DirEntry(dir, this._boundRemove)); |
| return this._watched.get(dir); |
| } |
| |
| |
| |
| |
| |
| _hasReadPermissions(stats) { |
| if (this.options.ignorePermissionErrors) |
| return true; |
| return Boolean(Number(stats.mode) & 0o400); |
| } |
| |
| |
| |
| |
| |
| |
| |
| _remove(directory, item, isDirectory) { |
| |
| |
| |
| const path = sysPath.join(directory, item); |
| const fullPath = sysPath.resolve(path); |
| isDirectory = |
| isDirectory != null ? isDirectory : this._watched.has(path) || this._watched.has(fullPath); |
| |
| |
| if (!this._throttle('remove', path, 100)) |
| return; |
| |
| if (!isDirectory && this._watched.size === 1) { |
| this.add(directory, item, true); |
| } |
| |
| |
| const wp = this._getWatchedDir(path); |
| const nestedDirectoryChildren = wp.getChildren(); |
| |
| nestedDirectoryChildren.forEach((nested) => this._remove(path, nested)); |
| |
| const parent = this._getWatchedDir(directory); |
| const wasTracked = parent.has(item); |
| parent.remove(item); |
| |
| |
| |
| |
| |
| if (this._symlinkPaths.has(fullPath)) { |
| this._symlinkPaths.delete(fullPath); |
| } |
| |
| let relPath = path; |
| if (this.options.cwd) |
| relPath = sysPath.relative(this.options.cwd, path); |
| if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) { |
| const event = this._pendingWrites.get(relPath).cancelWait(); |
| if (event === handler_js_1.EVENTS.ADD) |
| return; |
| } |
| |
| |
| this._watched.delete(path); |
| this._watched.delete(fullPath); |
| const eventName = isDirectory ? handler_js_1.EVENTS.UNLINK_DIR : handler_js_1.EVENTS.UNLINK; |
| if (wasTracked && !this._isIgnored(path)) |
| this._emit(eventName, path); |
| |
| this._closePath(path); |
| } |
| |
| |
| |
| _closePath(path) { |
| this._closeFile(path); |
| const dir = sysPath.dirname(path); |
| this._getWatchedDir(dir).remove(sysPath.basename(path)); |
| } |
| |
| |
| |
| _closeFile(path) { |
| const closers = this._closers.get(path); |
| if (!closers) |
| return; |
| closers.forEach((closer) => closer()); |
| this._closers.delete(path); |
| } |
| _addPathCloser(path, closer) { |
| if (!closer) |
| return; |
| let list = this._closers.get(path); |
| if (!list) { |
| list = []; |
| this._closers.set(path, list); |
| } |
| list.push(closer); |
| } |
| _readdirp(root, opts) { |
| if (this.closed) |
| return; |
| const options = { type: handler_js_1.EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 }; |
| let stream = (0, readdirp_1.readdirp)(root, options); |
| this._streams.add(stream); |
| stream.once(handler_js_1.STR_CLOSE, () => { |
| stream = undefined; |
| }); |
| stream.once(handler_js_1.STR_END, () => { |
| if (stream) { |
| this._streams.delete(stream); |
| stream = undefined; |
| } |
| }); |
| return stream; |
| } |
| } |
| exports.FSWatcher = FSWatcher; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function watch(paths, options = {}) { |
| const watcher = new FSWatcher(options); |
| watcher.add(paths); |
| return watcher; |
| } |
| exports.default = { watch, FSWatcher }; |
|
|