+554
| /* | ||
| MIT License http://www.opensource.org/licenses/mit-license.php | ||
| Author Tobias Koppers @sokra | ||
| */ | ||
| "use strict"; | ||
| const { EventEmitter } = require("events"); | ||
| const globToRegExp = require("glob-to-regexp"); | ||
| const LinkResolver = require("./LinkResolver"); | ||
| const getWatcherManager = require("./getWatcherManager"); | ||
| const watchEventSource = require("./watchEventSource"); | ||
| /** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */ | ||
| /** @typedef {import("./DirectoryWatcher")} DirectoryWatcher */ | ||
| /** @typedef {import("./DirectoryWatcher").DirectoryWatcherEvents} DirectoryWatcherEvents */ | ||
| /** @typedef {import("./DirectoryWatcher").FileWatcherEvents} FileWatcherEvents */ | ||
| // eslint-disable-next-line jsdoc/reject-any-type | ||
| /** @typedef {Record<string, (...args: any[]) => any>} EventMap */ | ||
| /** | ||
| * @template {EventMap} T | ||
| * @typedef {import("./DirectoryWatcher").Watcher<T>} Watcher | ||
| */ | ||
| /** @typedef {(item: string) => boolean} IgnoredFunction */ | ||
| /** @typedef {string[] | RegExp | string | IgnoredFunction} Ignored */ | ||
| /** | ||
| * @typedef {object} WatcherOptions | ||
| * @property {boolean=} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false | ||
| * @property {Ignored=} ignored ignore some files from watching (glob pattern or regexp) | ||
| * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false | ||
| */ | ||
| /** @typedef {WatcherOptions & { aggregateTimeout?: number }} WatchOptions */ | ||
| /** | ||
| * @typedef {object} NormalizedWatchOptions | ||
| * @property {boolean} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false | ||
| * @property {IgnoredFunction} ignored ignore some files from watching (glob pattern or regexp) | ||
| * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false | ||
| */ | ||
| /** @typedef {`scan (${string})` | "change" | "rename" | `watch ${string}` | `directory-removed ${string}`} EventType */ | ||
| /** @typedef {{ safeTime: number, timestamp: number, accuracy: number }} Entry */ | ||
| /** @typedef {{ safeTime: number }} OnlySafeTimeEntry */ | ||
| // eslint-disable-next-line jsdoc/ts-no-empty-object-type | ||
| /** @typedef {{}} ExistanceOnlyTimeEntry */ | ||
| /** @typedef {Map<string, Entry | OnlySafeTimeEntry | ExistanceOnlyTimeEntry | null>} TimeInfoEntries */ | ||
| /** @typedef {Set<string>} Changes */ | ||
| /** @typedef {Set<string>} Removals */ | ||
| /** @typedef {{ changes: Changes, removals: Removals }} Aggregated */ | ||
| /** @typedef {{ files?: Iterable<string>, directories?: Iterable<string>, missing?: Iterable<string>, startTime?: number }} WatchMethodOptions */ | ||
| /** @typedef {Record<string, number>} Times */ | ||
| /** | ||
| * @param {MapIterator<WatchpackFileWatcher> | MapIterator<WatchpackDirectoryWatcher>} watchers watchers | ||
| * @param {Set<DirectoryWatcher>} set set | ||
| */ | ||
| function addWatchersToSet(watchers, set) { | ||
| for (const ww of watchers) { | ||
| const w = ww.watcher; | ||
| if (!set.has(w.directoryWatcher)) { | ||
| set.add(w.directoryWatcher); | ||
| } | ||
| } | ||
| } | ||
| /** | ||
| * @param {string} ignored ignored | ||
| * @returns {string | undefined} resolved global to regexp | ||
| */ | ||
| const stringToRegexp = (ignored) => { | ||
| if (ignored.length === 0) { | ||
| return; | ||
| } | ||
| const { source } = globToRegExp(ignored, { globstar: true, extended: true }); | ||
| return `${source.slice(0, -1)}(?:$|\\/)`; | ||
| }; | ||
| /** | ||
| * @param {Ignored=} ignored ignored | ||
| * @returns {(item: string) => boolean} ignored to function | ||
| */ | ||
| const ignoredToFunction = (ignored) => { | ||
| if (Array.isArray(ignored)) { | ||
| const stringRegexps = ignored.map((i) => stringToRegexp(i)).filter(Boolean); | ||
| if (stringRegexps.length === 0) { | ||
| return () => false; | ||
| } | ||
| const regexp = new RegExp(stringRegexps.join("|")); | ||
| return (item) => regexp.test(item.replace(/\\/g, "/")); | ||
| } else if (typeof ignored === "string") { | ||
| const stringRegexp = stringToRegexp(ignored); | ||
| if (!stringRegexp) { | ||
| return () => false; | ||
| } | ||
| const regexp = new RegExp(stringRegexp); | ||
| return (item) => regexp.test(item.replace(/\\/g, "/")); | ||
| } else if (ignored instanceof RegExp) { | ||
| return (item) => ignored.test(item.replace(/\\/g, "/")); | ||
| } else if (typeof ignored === "function") { | ||
| return ignored; | ||
| } else if (ignored) { | ||
| throw new Error(`Invalid option for 'ignored': ${ignored}`); | ||
| } else { | ||
| return () => false; | ||
| } | ||
| }; | ||
| /** | ||
| * @param {WatchOptions} options options | ||
| * @returns {NormalizedWatchOptions} normalized options | ||
| */ | ||
| const normalizeOptions = (options) => ({ | ||
| followSymlinks: Boolean(options.followSymlinks), | ||
| ignored: ignoredToFunction(options.ignored), | ||
| poll: options.poll, | ||
| }); | ||
| const normalizeCache = new WeakMap(); | ||
| /** | ||
| * @param {WatchOptions} options options | ||
| * @returns {NormalizedWatchOptions} normalized options | ||
| */ | ||
| const cachedNormalizeOptions = (options) => { | ||
| const cacheEntry = normalizeCache.get(options); | ||
| if (cacheEntry !== undefined) return cacheEntry; | ||
| const normalized = normalizeOptions(options); | ||
| normalizeCache.set(options, normalized); | ||
| return normalized; | ||
| }; | ||
| class WatchpackFileWatcher { | ||
| /** | ||
| * @param {Watchpack} watchpack watchpack | ||
| * @param {Watcher<FileWatcherEvents>} watcher watcher | ||
| * @param {string | string[]} files files | ||
| */ | ||
| constructor(watchpack, watcher, files) { | ||
| /** @type {string[]} */ | ||
| this.files = Array.isArray(files) ? files : [files]; | ||
| this.watcher = watcher; | ||
| watcher.on("initial-missing", (type) => { | ||
| for (const file of this.files) { | ||
| if (!watchpack._missing.has(file)) { | ||
| watchpack._onRemove(file, file, type); | ||
| } | ||
| } | ||
| }); | ||
| watcher.on("change", (mtime, type, _initial) => { | ||
| for (const file of this.files) { | ||
| watchpack._onChange(file, mtime, file, type); | ||
| } | ||
| }); | ||
| watcher.on("remove", (type) => { | ||
| for (const file of this.files) { | ||
| watchpack._onRemove(file, file, type); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * @param {string | string[]} files files | ||
| */ | ||
| update(files) { | ||
| if (!Array.isArray(files)) { | ||
| if (this.files.length !== 1) { | ||
| this.files = [files]; | ||
| } else if (this.files[0] !== files) { | ||
| this.files[0] = files; | ||
| } | ||
| } else { | ||
| this.files = files; | ||
| } | ||
| } | ||
| close() { | ||
| this.watcher.close(); | ||
| } | ||
| } | ||
| class WatchpackDirectoryWatcher { | ||
| /** | ||
| * @param {Watchpack} watchpack watchpack | ||
| * @param {Watcher<DirectoryWatcherEvents>} watcher watcher | ||
| * @param {string} directories directories | ||
| */ | ||
| constructor(watchpack, watcher, directories) { | ||
| /** @type {string[]} */ | ||
| this.directories = Array.isArray(directories) ? directories : [directories]; | ||
| this.watcher = watcher; | ||
| watcher.on("initial-missing", (type) => { | ||
| for (const item of this.directories) { | ||
| watchpack._onRemove(item, item, type); | ||
| } | ||
| }); | ||
| watcher.on("change", (file, mtime, type, _initial) => { | ||
| for (const item of this.directories) { | ||
| watchpack._onChange(item, mtime, file, type); | ||
| } | ||
| }); | ||
| watcher.on("remove", (type) => { | ||
| for (const item of this.directories) { | ||
| watchpack._onRemove(item, item, type); | ||
| } | ||
| }); | ||
| } | ||
| /** | ||
| * @param {string | string[]} directories directories | ||
| */ | ||
| update(directories) { | ||
| if (!Array.isArray(directories)) { | ||
| if (this.directories.length !== 1) { | ||
| this.directories = [directories]; | ||
| } else if (this.directories[0] !== directories) { | ||
| this.directories[0] = directories; | ||
| } | ||
| } else { | ||
| this.directories = directories; | ||
| } | ||
| } | ||
| close() { | ||
| this.watcher.close(); | ||
| } | ||
| } | ||
| /** | ||
| * @typedef {object} WatchpackEvents | ||
| * @property {(file: string, mtime: number, type: EventType) => void} change change event | ||
| * @property {(file: string, type: EventType) => void} remove remove event | ||
| * @property {(changes: Changes, removals: Removals) => void} aggregated aggregated event | ||
| */ | ||
| /** | ||
| * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>} | ||
| */ | ||
| class Watchpack extends EventEmitter { | ||
| /** | ||
| * @param {WatchOptions=} options options | ||
| */ | ||
| constructor(options = {}) { | ||
| super(); | ||
| if (!options) options = {}; | ||
| /** @type {WatchOptions} */ | ||
| this.options = options; | ||
| this.aggregateTimeout = | ||
| typeof options.aggregateTimeout === "number" | ||
| ? options.aggregateTimeout | ||
| : 200; | ||
| /** @type {NormalizedWatchOptions} */ | ||
| this.watcherOptions = cachedNormalizeOptions(options); | ||
| /** @type {WatcherManager} */ | ||
| this.watcherManager = getWatcherManager(this.watcherOptions); | ||
| /** @type {Map<string, WatchpackFileWatcher>} */ | ||
| this.fileWatchers = new Map(); | ||
| /** @type {Map<string, WatchpackDirectoryWatcher>} */ | ||
| this.directoryWatchers = new Map(); | ||
| /** @type {Set<string>} */ | ||
| this._missing = new Set(); | ||
| this.startTime = undefined; | ||
| this.paused = false; | ||
| /** @type {Changes} */ | ||
| this.aggregatedChanges = new Set(); | ||
| /** @type {Removals} */ | ||
| this.aggregatedRemovals = new Set(); | ||
| /** @type {undefined | NodeJS.Timeout} */ | ||
| this.aggregateTimer = undefined; | ||
| this._onTimeout = this._onTimeout.bind(this); | ||
| } | ||
| /** | ||
| * @overload | ||
| * @param {Iterable<string>} arg1 files | ||
| * @param {Iterable<string>} arg2 directories | ||
| * @param {number=} arg3 startTime | ||
| * @returns {void} | ||
| */ | ||
| /** | ||
| * @overload | ||
| * @param {WatchMethodOptions} arg1 watch options | ||
| * @returns {void} | ||
| */ | ||
| /** | ||
| * @param {Iterable<string> | WatchMethodOptions} arg1 files | ||
| * @param {Iterable<string>=} arg2 directories | ||
| * @param {number=} arg3 startTime | ||
| * @returns {void} | ||
| */ | ||
| watch(arg1, arg2, arg3) { | ||
| /** @type {Iterable<string> | undefined} */ | ||
| let files; | ||
| /** @type {Iterable<string> | undefined} */ | ||
| let directories; | ||
| /** @type {Iterable<string> | undefined} */ | ||
| let missing; | ||
| /** @type {number | undefined} */ | ||
| let startTime; | ||
| if (!arg2) { | ||
| ({ | ||
| files = [], | ||
| directories = [], | ||
| missing = [], | ||
| startTime, | ||
| } = /** @type {WatchMethodOptions} */ (arg1)); | ||
| } else { | ||
| files = /** @type {Iterable<string>} */ (arg1); | ||
| directories = /** @type {Iterable<string>} */ (arg2); | ||
| missing = []; | ||
| startTime = /** @type {number} */ (arg3); | ||
| } | ||
| this.paused = false; | ||
| const { fileWatchers, directoryWatchers } = this; | ||
| const { ignored } = this.watcherOptions; | ||
| /** | ||
| * @param {string} path path | ||
| * @returns {boolean} true when need to filter, otherwise false | ||
| */ | ||
| const filter = (path) => !ignored(path); | ||
| /** | ||
| * @template K, V | ||
| * @param {Map<K, V | V[]>} map map | ||
| * @param {K} key key | ||
| * @param {V} item item | ||
| */ | ||
| const addToMap = (map, key, item) => { | ||
| const list = map.get(key); | ||
| if (list === undefined) { | ||
| map.set(key, item); | ||
| } else if (Array.isArray(list)) { | ||
| list.push(item); | ||
| } else { | ||
| map.set(key, [list, item]); | ||
| } | ||
| }; | ||
| const fileWatchersNeeded = new Map(); | ||
| const directoryWatchersNeeded = new Map(); | ||
| /** @type {Set<string>} */ | ||
| const missingFiles = new Set(); | ||
| if (this.watcherOptions.followSymlinks) { | ||
| const resolver = new LinkResolver(); | ||
| for (const file of files) { | ||
| if (filter(file)) { | ||
| for (const innerFile of resolver.resolve(file)) { | ||
| if (file === innerFile || filter(innerFile)) { | ||
| addToMap(fileWatchersNeeded, innerFile, file); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for (const file of missing) { | ||
| if (filter(file)) { | ||
| for (const innerFile of resolver.resolve(file)) { | ||
| if (file === innerFile || filter(innerFile)) { | ||
| missingFiles.add(file); | ||
| addToMap(fileWatchersNeeded, innerFile, file); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for (const dir of directories) { | ||
| if (filter(dir)) { | ||
| let first = true; | ||
| for (const innerItem of resolver.resolve(dir)) { | ||
| if (filter(innerItem)) { | ||
| addToMap( | ||
| first ? directoryWatchersNeeded : fileWatchersNeeded, | ||
| innerItem, | ||
| dir, | ||
| ); | ||
| } | ||
| first = false; | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| for (const file of files) { | ||
| if (filter(file)) { | ||
| addToMap(fileWatchersNeeded, file, file); | ||
| } | ||
| } | ||
| for (const file of missing) { | ||
| if (filter(file)) { | ||
| missingFiles.add(file); | ||
| addToMap(fileWatchersNeeded, file, file); | ||
| } | ||
| } | ||
| for (const dir of directories) { | ||
| if (filter(dir)) { | ||
| addToMap(directoryWatchersNeeded, dir, dir); | ||
| } | ||
| } | ||
| } | ||
| // Close unneeded old watchers | ||
| // and update existing watchers | ||
| for (const [key, w] of fileWatchers) { | ||
| const needed = fileWatchersNeeded.get(key); | ||
| if (needed === undefined) { | ||
| w.close(); | ||
| fileWatchers.delete(key); | ||
| } else { | ||
| w.update(needed); | ||
| fileWatchersNeeded.delete(key); | ||
| } | ||
| } | ||
| for (const [key, w] of directoryWatchers) { | ||
| const needed = directoryWatchersNeeded.get(key); | ||
| if (needed === undefined) { | ||
| w.close(); | ||
| directoryWatchers.delete(key); | ||
| } else { | ||
| w.update(needed); | ||
| directoryWatchersNeeded.delete(key); | ||
| } | ||
| } | ||
| // Create new watchers and install handlers on these watchers | ||
| watchEventSource.batch(() => { | ||
| for (const [key, files] of fileWatchersNeeded) { | ||
| const watcher = this.watcherManager.watchFile(key, startTime); | ||
| if (watcher) { | ||
| fileWatchers.set(key, new WatchpackFileWatcher(this, watcher, files)); | ||
| } | ||
| } | ||
| for (const [key, directories] of directoryWatchersNeeded) { | ||
| const watcher = this.watcherManager.watchDirectory(key, startTime); | ||
| if (watcher) { | ||
| directoryWatchers.set( | ||
| key, | ||
| new WatchpackDirectoryWatcher(this, watcher, directories), | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| this._missing = missingFiles; | ||
| this.startTime = startTime; | ||
| } | ||
| close() { | ||
| this.paused = true; | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| for (const w of this.fileWatchers.values()) w.close(); | ||
| for (const w of this.directoryWatchers.values()) w.close(); | ||
| this.fileWatchers.clear(); | ||
| this.directoryWatchers.clear(); | ||
| } | ||
| pause() { | ||
| this.paused = true; | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| } | ||
| /** | ||
| * @returns {Record<string, number>} times | ||
| */ | ||
| getTimes() { | ||
| /** @type {Set<DirectoryWatcher>} */ | ||
| const directoryWatchers = new Set(); | ||
| addWatchersToSet(this.fileWatchers.values(), directoryWatchers); | ||
| addWatchersToSet(this.directoryWatchers.values(), directoryWatchers); | ||
| /** @type {Record<string, number>} */ | ||
| const obj = Object.create(null); | ||
| for (const w of directoryWatchers) { | ||
| const times = w.getTimes(); | ||
| for (const file of Object.keys(times)) obj[file] = times[file]; | ||
| } | ||
| return obj; | ||
| } | ||
| /** | ||
| * @returns {TimeInfoEntries} time info entries | ||
| */ | ||
| getTimeInfoEntries() { | ||
| /** @type {TimeInfoEntries} */ | ||
| const map = new Map(); | ||
| this.collectTimeInfoEntries(map, map); | ||
| return map; | ||
| } | ||
| /** | ||
| * @param {TimeInfoEntries} fileTimestamps file timestamps | ||
| * @param {TimeInfoEntries} directoryTimestamps directory timestamps | ||
| */ | ||
| collectTimeInfoEntries(fileTimestamps, directoryTimestamps) { | ||
| /** @type {Set<DirectoryWatcher>} */ | ||
| const allWatchers = new Set(); | ||
| addWatchersToSet(this.fileWatchers.values(), allWatchers); | ||
| addWatchersToSet(this.directoryWatchers.values(), allWatchers); | ||
| for (const w of allWatchers) { | ||
| w.collectTimeInfoEntries(fileTimestamps, directoryTimestamps); | ||
| } | ||
| } | ||
| /** | ||
| * @returns {Aggregated} aggregated info | ||
| */ | ||
| getAggregated() { | ||
| if (this.aggregateTimer) { | ||
| clearTimeout(this.aggregateTimer); | ||
| this.aggregateTimer = undefined; | ||
| } | ||
| const changes = this.aggregatedChanges; | ||
| const removals = this.aggregatedRemovals; | ||
| this.aggregatedChanges = new Set(); | ||
| this.aggregatedRemovals = new Set(); | ||
| return { changes, removals }; | ||
| } | ||
| /** | ||
| * @param {string} item item | ||
| * @param {number} mtime mtime | ||
| * @param {string} file file | ||
| * @param {EventType} type type | ||
| */ | ||
| _onChange(item, mtime, file, type) { | ||
| file = file || item; | ||
| if (!this.paused) { | ||
| this.emit("change", file, mtime, type); | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout); | ||
| } | ||
| this.aggregatedRemovals.delete(item); | ||
| this.aggregatedChanges.add(item); | ||
| } | ||
| /** | ||
| * @param {string} item item | ||
| * @param {string} file file | ||
| * @param {EventType} type type | ||
| */ | ||
| _onRemove(item, file, type) { | ||
| file = file || item; | ||
| if (!this.paused) { | ||
| this.emit("remove", file, type); | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout); | ||
| } | ||
| this.aggregatedChanges.delete(item); | ||
| this.aggregatedRemovals.add(item); | ||
| } | ||
| _onTimeout() { | ||
| this.aggregateTimer = undefined; | ||
| const changes = this.aggregatedChanges; | ||
| const removals = this.aggregatedRemovals; | ||
| this.aggregatedChanges = new Set(); | ||
| this.aggregatedRemovals = new Set(); | ||
| this.emit("aggregated", changes, removals); | ||
| } | ||
| } | ||
| module.exports = Watchpack; |
| export = DirectoryWatcher; | ||
| /** @typedef {Set<string>} InitialScanRemoved */ | ||
| /** | ||
| * @typedef {object} WatchpackEvents | ||
| * @property {(target: string, mtime: string, type: EventType, initial: boolean) => void} change change event | ||
| * @property {() => void} closed closed event | ||
| */ | ||
| /** | ||
| * @typedef {object} DirectoryWatcherOptions | ||
| * @property {boolean=} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false | ||
| * @property {IgnoredFunction=} ignored ignore some files from watching (glob pattern or regexp) | ||
| * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false | ||
| */ | ||
| /** | ||
| * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>} | ||
| */ | ||
| declare class DirectoryWatcher extends EventEmitter<{ | ||
| /** | ||
| * change event | ||
| */ | ||
| change: [ | ||
| target: string, | ||
| mtime: string, | ||
| type: import("./index").EventType, | ||
| initial: boolean, | ||
| ]; | ||
| /** | ||
| * closed event | ||
| */ | ||
| closed: []; | ||
| }> { | ||
| /** | ||
| * @param {WatcherManager} watcherManager a watcher manager | ||
| * @param {string} directoryPath directory path | ||
| * @param {DirectoryWatcherOptions=} options options | ||
| */ | ||
| constructor( | ||
| watcherManager: WatcherManager, | ||
| directoryPath: string, | ||
| options?: DirectoryWatcherOptions | undefined, | ||
| ); | ||
| watcherManager: import("./getWatcherManager").WatcherManager; | ||
| options: DirectoryWatcherOptions; | ||
| path: string; | ||
| /** @type {Map<string, Entry>} */ | ||
| files: Map<string, Entry>; | ||
| /** @type {Map<string, number>} */ | ||
| filesWithoutCase: Map<string, number>; | ||
| /** @type {Map<string, Watcher<DirectoryWatcherEvents> | boolean>} */ | ||
| directories: Map<string, Watcher<DirectoryWatcherEvents> | boolean>; | ||
| lastWatchEvent: number; | ||
| initialScan: boolean; | ||
| ignored: import("./index").IgnoredFunction; | ||
| nestedWatching: boolean; | ||
| /** @type {number | false} */ | ||
| polledWatching: number | false; | ||
| /** @type {undefined | NodeJS.Timeout} */ | ||
| timeout: undefined | NodeJS.Timeout; | ||
| /** @type {null | InitialScanRemoved} */ | ||
| initialScanRemoved: null | InitialScanRemoved; | ||
| /** @type {undefined | number} */ | ||
| initialScanFinished: undefined | number; | ||
| /** @type {Map<string, Set<Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>>>} */ | ||
| watchers: Map< | ||
| string, | ||
| Set<Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>> | ||
| >; | ||
| /** @type {Watcher<FileWatcherEvents> | null} */ | ||
| parentWatcher: Watcher<FileWatcherEvents> | null; | ||
| refs: number; | ||
| /** @type {Map<string, boolean>} */ | ||
| _activeEvents: Map<string, boolean>; | ||
| closed: boolean; | ||
| scanning: boolean; | ||
| scanAgain: boolean; | ||
| scanAgainInitial: boolean; | ||
| createWatcher(): void; | ||
| watcher: watchEventSource.Watcher | null | undefined; | ||
| /** | ||
| * @template {(watcher: Watcher<EventMap>) => void} T | ||
| * @param {string} path path | ||
| * @param {T} fn function | ||
| */ | ||
| forEachWatcher<T extends (watcher: Watcher<EventMap>) => void>( | ||
| path: string, | ||
| fn: T, | ||
| ): void; | ||
| /** | ||
| * @param {string} itemPath an item path | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @param {EventType} type even type | ||
| */ | ||
| setMissing(itemPath: string, initial: boolean, type: EventType): void; | ||
| /** | ||
| * @param {string} target a target to set file time | ||
| * @param {number} mtime mtime | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @param {boolean} ignoreWhenEqual true to ignore when equal, otherwise false | ||
| * @param {EventType} type type | ||
| */ | ||
| setFileTime( | ||
| target: string, | ||
| mtime: number, | ||
| initial: boolean, | ||
| ignoreWhenEqual: boolean, | ||
| type: EventType, | ||
| ): void; | ||
| /** | ||
| * @param {string} directoryPath directory path | ||
| * @param {number} birthtime birthtime | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @param {EventType} type even type | ||
| */ | ||
| setDirectory( | ||
| directoryPath: string, | ||
| birthtime: number, | ||
| initial: boolean, | ||
| type: EventType, | ||
| ): void; | ||
| /** | ||
| * @param {string} directoryPath directory path | ||
| */ | ||
| createNestedWatcher(directoryPath: string): void; | ||
| /** | ||
| * @param {boolean} flag true when nested, otherwise false | ||
| */ | ||
| setNestedWatching(flag: boolean): void; | ||
| /** | ||
| * @param {string} target a target to watch | ||
| * @param {number=} startTime start time | ||
| * @returns {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} watcher | ||
| */ | ||
| watch( | ||
| target: string, | ||
| startTime?: number | undefined, | ||
| ): Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>; | ||
| /** | ||
| * @param {EventType} eventType event type | ||
| * @param {string=} filename filename | ||
| */ | ||
| onWatchEvent(eventType: EventType, filename?: string | undefined): void; | ||
| /** | ||
| * @param {unknown=} err error | ||
| */ | ||
| onWatcherError(err?: unknown | undefined): void; | ||
| /** | ||
| * @param {Error | NodeJS.ErrnoException=} err error | ||
| */ | ||
| onStatsError(err?: (Error | NodeJS.ErrnoException) | undefined): void; | ||
| /** | ||
| * @param {Error | NodeJS.ErrnoException=} err error | ||
| */ | ||
| onScanError(err?: (Error | NodeJS.ErrnoException) | undefined): void; | ||
| onScanFinished(): void; | ||
| /** | ||
| * @param {string} reason a reason | ||
| */ | ||
| onDirectoryRemoved(reason: string): void; | ||
| watchInParentDirectory(): void; | ||
| /** | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| */ | ||
| doScan(initial: boolean): void; | ||
| /** | ||
| * @returns {Record<string, number>} times | ||
| */ | ||
| getTimes(): Record<string, number>; | ||
| /** | ||
| * @param {TimeInfoEntries} fileTimestamps file timestamps | ||
| * @param {TimeInfoEntries} directoryTimestamps directory timestamps | ||
| * @returns {number} safe time | ||
| */ | ||
| collectTimeInfoEntries( | ||
| fileTimestamps: TimeInfoEntries, | ||
| directoryTimestamps: TimeInfoEntries, | ||
| ): number; | ||
| close(): void; | ||
| } | ||
| declare namespace DirectoryWatcher { | ||
| export { | ||
| EXISTANCE_ONLY_TIME_ENTRY, | ||
| Watcher, | ||
| IgnoredFunction, | ||
| EventType, | ||
| TimeInfoEntries, | ||
| Entry, | ||
| ExistanceOnlyTimeEntry, | ||
| OnlySafeTimeEntry, | ||
| EventMap, | ||
| WatcherManager, | ||
| EventSourceWatcher, | ||
| FileWatcherEvents, | ||
| DirectoryWatcherEvents, | ||
| InitialScanRemoved, | ||
| WatchpackEvents, | ||
| DirectoryWatcherOptions, | ||
| }; | ||
| } | ||
| import { EventEmitter } from "events"; | ||
| /** | ||
| * @typedef {object} FileWatcherEvents | ||
| * @property {(type: EventType) => void} initial-missing initial missing event | ||
| * @property {(mtime: number, type: EventType, initial: boolean) => void} change change event | ||
| * @property {(type: EventType) => void} remove remove event | ||
| * @property {() => void} closed closed event | ||
| */ | ||
| /** | ||
| * @typedef {object} DirectoryWatcherEvents | ||
| * @property {(type: EventType) => void} initial-missing initial missing event | ||
| * @property {((file: string, mtime: number, type: EventType, initial: boolean) => void)} change change event | ||
| * @property {(type: EventType) => void} remove remove event | ||
| * @property {() => void} closed closed event | ||
| */ | ||
| /** | ||
| * @template {EventMap} T | ||
| * @extends {EventEmitter<{ [K in keyof T]: Parameters<T[K]> }>} | ||
| */ | ||
| declare class Watcher<T extends EventMap> extends EventEmitter<{ | ||
| [K in keyof T]: Parameters<T[K]>; | ||
| }> { | ||
| /** | ||
| * @param {DirectoryWatcher} directoryWatcher a directory watcher | ||
| * @param {string} target a target to watch | ||
| * @param {number=} startTime start time | ||
| */ | ||
| constructor( | ||
| directoryWatcher: DirectoryWatcher, | ||
| target: string, | ||
| startTime?: number | undefined, | ||
| ); | ||
| directoryWatcher: DirectoryWatcher; | ||
| path: string; | ||
| startTime: number | undefined; | ||
| /** | ||
| * @param {number} mtime mtime | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @returns {boolean} true of start time less than mtile, otherwise false | ||
| */ | ||
| checkStartTime(mtime: number, initial: boolean): boolean; | ||
| close(): void; | ||
| } | ||
| import watchEventSource = require("./watchEventSource"); | ||
| /** @typedef {import("./index").IgnoredFunction} IgnoredFunction */ | ||
| /** @typedef {import("./index").EventType} EventType */ | ||
| /** @typedef {import("./index").TimeInfoEntries} TimeInfoEntries */ | ||
| /** @typedef {import("./index").Entry} Entry */ | ||
| /** @typedef {import("./index").ExistanceOnlyTimeEntry} ExistanceOnlyTimeEntry */ | ||
| /** @typedef {import("./index").OnlySafeTimeEntry} OnlySafeTimeEntry */ | ||
| /** @typedef {import("./index").EventMap} EventMap */ | ||
| /** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */ | ||
| /** @typedef {import("./watchEventSource").Watcher} EventSourceWatcher */ | ||
| /** @type {ExistanceOnlyTimeEntry} */ | ||
| declare const EXISTANCE_ONLY_TIME_ENTRY: ExistanceOnlyTimeEntry; | ||
| type IgnoredFunction = import("./index").IgnoredFunction; | ||
| type EventType = import("./index").EventType; | ||
| type TimeInfoEntries = import("./index").TimeInfoEntries; | ||
| type Entry = import("./index").Entry; | ||
| type ExistanceOnlyTimeEntry = import("./index").ExistanceOnlyTimeEntry; | ||
| type OnlySafeTimeEntry = import("./index").OnlySafeTimeEntry; | ||
| type EventMap = import("./index").EventMap; | ||
| type WatcherManager = import("./getWatcherManager").WatcherManager; | ||
| type EventSourceWatcher = import("./watchEventSource").Watcher; | ||
| type FileWatcherEvents = { | ||
| /** | ||
| * initial missing event | ||
| */ | ||
| "initial-missing": (type: EventType) => void; | ||
| /** | ||
| * change event | ||
| */ | ||
| change: (mtime: number, type: EventType, initial: boolean) => void; | ||
| /** | ||
| * remove event | ||
| */ | ||
| remove: (type: EventType) => void; | ||
| /** | ||
| * closed event | ||
| */ | ||
| closed: () => void; | ||
| }; | ||
| type DirectoryWatcherEvents = { | ||
| /** | ||
| * initial missing event | ||
| */ | ||
| "initial-missing": (type: EventType) => void; | ||
| /** | ||
| * change event | ||
| */ | ||
| change: ( | ||
| file: string, | ||
| mtime: number, | ||
| type: EventType, | ||
| initial: boolean, | ||
| ) => void; | ||
| /** | ||
| * remove event | ||
| */ | ||
| remove: (type: EventType) => void; | ||
| /** | ||
| * closed event | ||
| */ | ||
| closed: () => void; | ||
| }; | ||
| type InitialScanRemoved = Set<string>; | ||
| type WatchpackEvents = { | ||
| /** | ||
| * change event | ||
| */ | ||
| change: ( | ||
| target: string, | ||
| mtime: string, | ||
| type: EventType, | ||
| initial: boolean, | ||
| ) => void; | ||
| /** | ||
| * closed event | ||
| */ | ||
| closed: () => void; | ||
| }; | ||
| type DirectoryWatcherOptions = { | ||
| /** | ||
| * true when need to resolve symlinks and watch symlink and real file, otherwise false | ||
| */ | ||
| followSymlinks?: boolean | undefined; | ||
| /** | ||
| * ignore some files from watching (glob pattern or regexp) | ||
| */ | ||
| ignored?: IgnoredFunction | undefined; | ||
| /** | ||
| * true when need to enable polling mode for watching, otherwise false | ||
| */ | ||
| poll?: (number | boolean) | undefined; | ||
| }; |
| declare namespace _exports { | ||
| export { | ||
| EventMap, | ||
| DirectoryWatcherOptions, | ||
| DirectoryWatcherEvents, | ||
| FileWatcherEvents, | ||
| Watcher, | ||
| }; | ||
| } | ||
| declare function _exports(options: DirectoryWatcherOptions): WatcherManager; | ||
| declare namespace _exports { | ||
| export { WatcherManager }; | ||
| } | ||
| export = _exports; | ||
| type EventMap = import("./index").EventMap; | ||
| type DirectoryWatcherOptions = | ||
| import("./DirectoryWatcher").DirectoryWatcherOptions; | ||
| type DirectoryWatcherEvents = | ||
| import("./DirectoryWatcher").DirectoryWatcherEvents; | ||
| type FileWatcherEvents = import("./DirectoryWatcher").FileWatcherEvents; | ||
| type Watcher<T extends EventMap> = import("./DirectoryWatcher").Watcher<T>; | ||
| /** @typedef {import("./index").EventMap} EventMap */ | ||
| /** @typedef {import("./DirectoryWatcher").DirectoryWatcherOptions} DirectoryWatcherOptions */ | ||
| /** @typedef {import("./DirectoryWatcher").DirectoryWatcherEvents} DirectoryWatcherEvents */ | ||
| /** @typedef {import("./DirectoryWatcher").FileWatcherEvents} FileWatcherEvents */ | ||
| /** | ||
| * @template {EventMap} T | ||
| * @typedef {import("./DirectoryWatcher").Watcher<T>} Watcher | ||
| */ | ||
| declare class WatcherManager { | ||
| /** | ||
| * @param {DirectoryWatcherOptions=} options options | ||
| */ | ||
| constructor(options?: DirectoryWatcherOptions | undefined); | ||
| options: DirectoryWatcher.DirectoryWatcherOptions; | ||
| /** @type {Map<string, DirectoryWatcher>} */ | ||
| directoryWatchers: Map<string, DirectoryWatcher>; | ||
| /** | ||
| * @param {string} directory a directory | ||
| * @returns {DirectoryWatcher} a directory watcher | ||
| */ | ||
| getDirectoryWatcher(directory: string): DirectoryWatcher; | ||
| /** | ||
| * @param {string} file file | ||
| * @param {number=} startTime start time | ||
| * @returns {Watcher<FileWatcherEvents> | null} watcher or null if file has no directory | ||
| */ | ||
| watchFile( | ||
| file: string, | ||
| startTime?: number | undefined, | ||
| ): Watcher<FileWatcherEvents> | null; | ||
| /** | ||
| * @param {string} directory directory | ||
| * @param {number=} startTime start time | ||
| * @returns {Watcher<DirectoryWatcherEvents>} watcher | ||
| */ | ||
| watchDirectory( | ||
| directory: string, | ||
| startTime?: number | undefined, | ||
| ): Watcher<DirectoryWatcherEvents>; | ||
| } | ||
| import DirectoryWatcher = require("./DirectoryWatcher"); |
+261
| export = Watchpack; | ||
| /** | ||
| * @typedef {object} WatchpackEvents | ||
| * @property {(file: string, mtime: number, type: EventType) => void} change change event | ||
| * @property {(file: string, type: EventType) => void} remove remove event | ||
| * @property {(changes: Changes, removals: Removals) => void} aggregated aggregated event | ||
| */ | ||
| /** | ||
| * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>} | ||
| */ | ||
| declare class Watchpack extends EventEmitter<{ | ||
| /** | ||
| * change event | ||
| */ | ||
| change: [file: string, mtime: number, type: EventType]; | ||
| /** | ||
| * remove event | ||
| */ | ||
| remove: [file: string, type: EventType]; | ||
| /** | ||
| * aggregated event | ||
| */ | ||
| aggregated: [changes: Changes, removals: Removals]; | ||
| }> { | ||
| /** | ||
| * @param {WatchOptions=} options options | ||
| */ | ||
| constructor(options?: WatchOptions | undefined); | ||
| /** @type {WatchOptions} */ | ||
| options: WatchOptions; | ||
| aggregateTimeout: number; | ||
| /** @type {NormalizedWatchOptions} */ | ||
| watcherOptions: NormalizedWatchOptions; | ||
| /** @type {WatcherManager} */ | ||
| watcherManager: WatcherManager; | ||
| /** @type {Map<string, WatchpackFileWatcher>} */ | ||
| fileWatchers: Map<string, WatchpackFileWatcher>; | ||
| /** @type {Map<string, WatchpackDirectoryWatcher>} */ | ||
| directoryWatchers: Map<string, WatchpackDirectoryWatcher>; | ||
| /** @type {Set<string>} */ | ||
| _missing: Set<string>; | ||
| startTime: number | undefined; | ||
| paused: boolean; | ||
| /** @type {Changes} */ | ||
| aggregatedChanges: Changes; | ||
| /** @type {Removals} */ | ||
| aggregatedRemovals: Removals; | ||
| /** @type {undefined | NodeJS.Timeout} */ | ||
| aggregateTimer: undefined | NodeJS.Timeout; | ||
| _onTimeout(): void; | ||
| /** | ||
| * @overload | ||
| * @param {Iterable<string>} arg1 files | ||
| * @param {Iterable<string>} arg2 directories | ||
| * @param {number=} arg3 startTime | ||
| * @returns {void} | ||
| */ | ||
| watch( | ||
| arg1: Iterable<string>, | ||
| arg2: Iterable<string>, | ||
| arg3?: number | undefined, | ||
| ): void; | ||
| /** | ||
| * @overload | ||
| * @param {WatchMethodOptions} arg1 watch options | ||
| * @returns {void} | ||
| */ | ||
| watch(arg1: WatchMethodOptions): void; | ||
| close(): void; | ||
| pause(): void; | ||
| /** | ||
| * @returns {Record<string, number>} times | ||
| */ | ||
| getTimes(): Record<string, number>; | ||
| /** | ||
| * @returns {TimeInfoEntries} time info entries | ||
| */ | ||
| getTimeInfoEntries(): TimeInfoEntries; | ||
| /** | ||
| * @param {TimeInfoEntries} fileTimestamps file timestamps | ||
| * @param {TimeInfoEntries} directoryTimestamps directory timestamps | ||
| */ | ||
| collectTimeInfoEntries( | ||
| fileTimestamps: TimeInfoEntries, | ||
| directoryTimestamps: TimeInfoEntries, | ||
| ): void; | ||
| /** | ||
| * @returns {Aggregated} aggregated info | ||
| */ | ||
| getAggregated(): Aggregated; | ||
| /** | ||
| * @param {string} item item | ||
| * @param {number} mtime mtime | ||
| * @param {string} file file | ||
| * @param {EventType} type type | ||
| */ | ||
| _onChange(item: string, mtime: number, file: string, type: EventType): void; | ||
| /** | ||
| * @param {string} item item | ||
| * @param {string} file file | ||
| * @param {EventType} type type | ||
| */ | ||
| _onRemove(item: string, file: string, type: EventType): void; | ||
| } | ||
| declare namespace Watchpack { | ||
| export { | ||
| WatcherManager, | ||
| DirectoryWatcher, | ||
| DirectoryWatcherEvents, | ||
| FileWatcherEvents, | ||
| EventMap, | ||
| Watcher, | ||
| IgnoredFunction, | ||
| Ignored, | ||
| WatcherOptions, | ||
| WatchOptions, | ||
| NormalizedWatchOptions, | ||
| EventType, | ||
| Entry, | ||
| OnlySafeTimeEntry, | ||
| ExistanceOnlyTimeEntry, | ||
| TimeInfoEntries, | ||
| Changes, | ||
| Removals, | ||
| Aggregated, | ||
| WatchMethodOptions, | ||
| Times, | ||
| WatchpackEvents, | ||
| }; | ||
| } | ||
| import { EventEmitter } from "events"; | ||
| declare class WatchpackFileWatcher { | ||
| /** | ||
| * @param {Watchpack} watchpack watchpack | ||
| * @param {Watcher<FileWatcherEvents>} watcher watcher | ||
| * @param {string | string[]} files files | ||
| */ | ||
| constructor( | ||
| watchpack: Watchpack, | ||
| watcher: Watcher<FileWatcherEvents>, | ||
| files: string | string[], | ||
| ); | ||
| /** @type {string[]} */ | ||
| files: string[]; | ||
| watcher: import("./DirectoryWatcher").Watcher< | ||
| import("./DirectoryWatcher").FileWatcherEvents | ||
| >; | ||
| /** | ||
| * @param {string | string[]} files files | ||
| */ | ||
| update(files: string | string[]): void; | ||
| close(): void; | ||
| } | ||
| declare class WatchpackDirectoryWatcher { | ||
| /** | ||
| * @param {Watchpack} watchpack watchpack | ||
| * @param {Watcher<DirectoryWatcherEvents>} watcher watcher | ||
| * @param {string} directories directories | ||
| */ | ||
| constructor( | ||
| watchpack: Watchpack, | ||
| watcher: Watcher<DirectoryWatcherEvents>, | ||
| directories: string, | ||
| ); | ||
| /** @type {string[]} */ | ||
| directories: string[]; | ||
| watcher: import("./DirectoryWatcher").Watcher< | ||
| import("./DirectoryWatcher").DirectoryWatcherEvents | ||
| >; | ||
| /** | ||
| * @param {string | string[]} directories directories | ||
| */ | ||
| update(directories: string | string[]): void; | ||
| close(): void; | ||
| } | ||
| type WatcherManager = import("./getWatcherManager").WatcherManager; | ||
| type DirectoryWatcher = import("./DirectoryWatcher"); | ||
| type DirectoryWatcherEvents = | ||
| import("./DirectoryWatcher").DirectoryWatcherEvents; | ||
| type FileWatcherEvents = import("./DirectoryWatcher").FileWatcherEvents; | ||
| type EventMap = Record<string, (...args: any[]) => any>; | ||
| type Watcher<T extends EventMap> = import("./DirectoryWatcher").Watcher<T>; | ||
| type IgnoredFunction = (item: string) => boolean; | ||
| type Ignored = string[] | RegExp | string | IgnoredFunction; | ||
| type WatcherOptions = { | ||
| /** | ||
| * true when need to resolve symlinks and watch symlink and real file, otherwise false | ||
| */ | ||
| followSymlinks?: boolean | undefined; | ||
| /** | ||
| * ignore some files from watching (glob pattern or regexp) | ||
| */ | ||
| ignored?: Ignored | undefined; | ||
| /** | ||
| * true when need to enable polling mode for watching, otherwise false | ||
| */ | ||
| poll?: (number | boolean) | undefined; | ||
| }; | ||
| type WatchOptions = WatcherOptions & { | ||
| aggregateTimeout?: number; | ||
| }; | ||
| type NormalizedWatchOptions = { | ||
| /** | ||
| * true when need to resolve symlinks and watch symlink and real file, otherwise false | ||
| */ | ||
| followSymlinks: boolean; | ||
| /** | ||
| * ignore some files from watching (glob pattern or regexp) | ||
| */ | ||
| ignored: IgnoredFunction; | ||
| /** | ||
| * true when need to enable polling mode for watching, otherwise false | ||
| */ | ||
| poll?: (number | boolean) | undefined; | ||
| }; | ||
| type EventType = | ||
| | `scan (${string})` | ||
| | "change" | ||
| | "rename" | ||
| | `watch ${string}` | ||
| | `directory-removed ${string}`; | ||
| type Entry = { | ||
| safeTime: number; | ||
| timestamp: number; | ||
| accuracy: number; | ||
| }; | ||
| type OnlySafeTimeEntry = { | ||
| safeTime: number; | ||
| }; | ||
| type ExistanceOnlyTimeEntry = {}; | ||
| type TimeInfoEntries = Map< | ||
| string, | ||
| Entry | OnlySafeTimeEntry | ExistanceOnlyTimeEntry | null | ||
| >; | ||
| type Changes = Set<string>; | ||
| type Removals = Set<string>; | ||
| type Aggregated = { | ||
| changes: Changes; | ||
| removals: Removals; | ||
| }; | ||
| type WatchMethodOptions = { | ||
| files?: Iterable<string>; | ||
| directories?: Iterable<string>; | ||
| missing?: Iterable<string>; | ||
| startTime?: number; | ||
| }; | ||
| type Times = Record<string, number>; | ||
| type WatchpackEvents = { | ||
| /** | ||
| * change event | ||
| */ | ||
| change: (file: string, mtime: number, type: EventType) => void; | ||
| /** | ||
| * remove event | ||
| */ | ||
| remove: (file: string, type: EventType) => void; | ||
| /** | ||
| * aggregated event | ||
| */ | ||
| aggregated: (changes: Changes, removals: Removals) => void; | ||
| }; |
| export = LinkResolver; | ||
| declare class LinkResolver { | ||
| /** @type {Map<string, readonly string[]>} */ | ||
| cache: Map<string, readonly string[]>; | ||
| /** | ||
| * @param {string} file path to file or directory | ||
| * @returns {readonly string[]} array of file and all symlinks contributed in the resolving process (first item is the resolved file) | ||
| */ | ||
| resolve(file: string): readonly string[]; | ||
| } |
| declare namespace _exports { | ||
| export { TreeNode }; | ||
| } | ||
| declare function _exports<T>( | ||
| plan: Map<string, T[] | T>, | ||
| limit: number, | ||
| ): Map<string, Map<T, string>>; | ||
| export = _exports; | ||
| type TreeNode<T> = { | ||
| /** | ||
| * target | ||
| */ | ||
| target: string; | ||
| /** | ||
| * parent | ||
| */ | ||
| parent: TreeNode<T>; | ||
| /** | ||
| * children | ||
| */ | ||
| children: TreeNode<T>[]; | ||
| /** | ||
| * number of entries | ||
| */ | ||
| entries: number; | ||
| /** | ||
| * true when active, otherwise false | ||
| */ | ||
| active: boolean; | ||
| /** | ||
| * value | ||
| */ | ||
| value: T[] | T | undefined; | ||
| }; |
| export function batch(fn: () => void): void; | ||
| export function getNumberOfWatchers(): number; | ||
| export function watch(filePath: string): Watcher; | ||
| export type FSWatcher = import("fs").FSWatcher; | ||
| export type EventType = import("./index").EventType; | ||
| export type WatcherSet = Set<Watcher>; | ||
| export type WatcherEvents = { | ||
| /** | ||
| * change event | ||
| */ | ||
| change: (eventType: EventType, filename?: string) => void; | ||
| /** | ||
| * error event | ||
| */ | ||
| error: (err: unknown) => void; | ||
| }; | ||
| /** | ||
| * @typedef {object} WatcherEvents | ||
| * @property {(eventType: EventType, filename?: string) => void} change change event | ||
| * @property {(err: unknown) => void} error error event | ||
| */ | ||
| /** | ||
| * @extends {EventEmitter<{ [K in keyof WatcherEvents]: Parameters<WatcherEvents[K]> }>} | ||
| */ | ||
| export class Watcher extends EventEmitter<{ | ||
| /** | ||
| * change event | ||
| */ | ||
| change: [ | ||
| eventType: import("./index").EventType, | ||
| filename?: string | undefined, | ||
| ]; | ||
| /** | ||
| * error event | ||
| */ | ||
| error: [err: unknown]; | ||
| }> { | ||
| constructor(); | ||
| close(): void; | ||
| } | ||
| /** | ||
| * @param {FSWatcher} watcher watcher | ||
| * @param {string} filePath a file path | ||
| * @param {(type: "rename" | "change", filename: string) => void} handleChangeEvent function to handle change | ||
| * @returns {(type: "rename" | "change", filename: string) => void} handler of change event | ||
| */ | ||
| export function createHandleChangeEvent( | ||
| watcher: FSWatcher, | ||
| filePath: string, | ||
| handleChangeEvent: (type: "rename" | "change", filename: string) => void, | ||
| ): (type: "rename" | "change", filename: string) => void; | ||
| export const watcherLimit: number; | ||
| import { EventEmitter } from "events"; |
| declare const _exports: typeof import("./index"); | ||
| export = _exports; |
+314
-140
@@ -7,8 +7,19 @@ /* | ||
| const EventEmitter = require("events").EventEmitter; | ||
| const { EventEmitter } = require("events"); | ||
| const path = require("path"); | ||
| const fs = require("graceful-fs"); | ||
| const path = require("path"); | ||
| const watchEventSource = require("./watchEventSource"); | ||
| /** @typedef {import("./index").IgnoredFunction} IgnoredFunction */ | ||
| /** @typedef {import("./index").EventType} EventType */ | ||
| /** @typedef {import("./index").TimeInfoEntries} TimeInfoEntries */ | ||
| /** @typedef {import("./index").Entry} Entry */ | ||
| /** @typedef {import("./index").ExistanceOnlyTimeEntry} ExistanceOnlyTimeEntry */ | ||
| /** @typedef {import("./index").OnlySafeTimeEntry} OnlySafeTimeEntry */ | ||
| /** @typedef {import("./index").EventMap} EventMap */ | ||
| /** @typedef {import("./getWatcherManager").WatcherManager} WatcherManager */ | ||
| /** @typedef {import("./watchEventSource").Watcher} EventSourceWatcher */ | ||
| /** @type {ExistanceOnlyTimeEntry} */ | ||
| const EXISTANCE_ONLY_TIME_ENTRY = Object.freeze({}); | ||
@@ -21,8 +32,13 @@ | ||
| const WATCHPACK_POLLING = process.env.WATCHPACK_POLLING; | ||
| const { WATCHPACK_POLLING } = process.env; | ||
| const FORCE_POLLING = | ||
| // @ts-expect-error avoid additional checks | ||
| `${+WATCHPACK_POLLING}` === WATCHPACK_POLLING | ||
| ? +WATCHPACK_POLLING | ||
| : !!WATCHPACK_POLLING && WATCHPACK_POLLING !== "false"; | ||
| : Boolean(WATCHPACK_POLLING) && WATCHPACK_POLLING !== "false"; | ||
| /** | ||
| * @param {string} str string | ||
| * @returns {string} lower cased string | ||
| */ | ||
| function withoutCase(str) { | ||
@@ -32,4 +48,9 @@ return str.toLowerCase(); | ||
| /** | ||
| * @param {number} times times | ||
| * @param {() => void} callback callback | ||
| * @returns {() => void} result | ||
| */ | ||
| function needCalls(times, callback) { | ||
| return function() { | ||
| return function needCallsCallback() { | ||
| if (--times === 0) { | ||
@@ -41,12 +62,63 @@ return callback(); | ||
| /** | ||
| * @param {Entry} entry entry | ||
| */ | ||
| function fixupEntryAccuracy(entry) { | ||
| if (entry.accuracy > FS_ACCURACY) { | ||
| entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY; | ||
| entry.accuracy = FS_ACCURACY; | ||
| } | ||
| } | ||
| /** | ||
| * @param {number=} mtime mtime | ||
| */ | ||
| function ensureFsAccuracy(mtime) { | ||
| if (!mtime) return; | ||
| if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1; | ||
| else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10; | ||
| else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100; | ||
| else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000; | ||
| } | ||
| /** | ||
| * @typedef {object} FileWatcherEvents | ||
| * @property {(type: EventType) => void} initial-missing initial missing event | ||
| * @property {(mtime: number, type: EventType, initial: boolean) => void} change change event | ||
| * @property {(type: EventType) => void} remove remove event | ||
| * @property {() => void} closed closed event | ||
| */ | ||
| /** | ||
| * @typedef {object} DirectoryWatcherEvents | ||
| * @property {(type: EventType) => void} initial-missing initial missing event | ||
| * @property {((file: string, mtime: number, type: EventType, initial: boolean) => void)} change change event | ||
| * @property {(type: EventType) => void} remove remove event | ||
| * @property {() => void} closed closed event | ||
| */ | ||
| /** | ||
| * @template {EventMap} T | ||
| * @extends {EventEmitter<{ [K in keyof T]: Parameters<T[K]> }>} | ||
| */ | ||
| class Watcher extends EventEmitter { | ||
| constructor(directoryWatcher, filePath, startTime) { | ||
| /** | ||
| * @param {DirectoryWatcher} directoryWatcher a directory watcher | ||
| * @param {string} target a target to watch | ||
| * @param {number=} startTime start time | ||
| */ | ||
| constructor(directoryWatcher, target, startTime) { | ||
| super(); | ||
| this.directoryWatcher = directoryWatcher; | ||
| this.path = filePath; | ||
| this.path = target; | ||
| this.startTime = startTime && +startTime; | ||
| } | ||
| /** | ||
| * @param {number} mtime mtime | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @returns {boolean} true of start time less than mtile, otherwise false | ||
| */ | ||
| checkStartTime(mtime, initial) { | ||
| const startTime = this.startTime; | ||
| const { startTime } = this; | ||
| if (typeof startTime !== "number") return !initial; | ||
@@ -57,2 +129,3 @@ return startTime <= mtime; | ||
| close() { | ||
| // @ts-expect-error bad typing in EventEmitter | ||
| this.emit("closed"); | ||
@@ -62,4 +135,27 @@ } | ||
| /** @typedef {Set<string>} InitialScanRemoved */ | ||
| /** | ||
| * @typedef {object} WatchpackEvents | ||
| * @property {(target: string, mtime: string, type: EventType, initial: boolean) => void} change change event | ||
| * @property {() => void} closed closed event | ||
| */ | ||
| /** | ||
| * @typedef {object} DirectoryWatcherOptions | ||
| * @property {boolean=} followSymlinks true when need to resolve symlinks and watch symlink and real file, otherwise false | ||
| * @property {IgnoredFunction=} ignored ignore some files from watching (glob pattern or regexp) | ||
| * @property {number | boolean=} poll true when need to enable polling mode for watching, otherwise false | ||
| */ | ||
| /** | ||
| * @extends {EventEmitter<{ [K in keyof WatchpackEvents]: Parameters<WatchpackEvents[K]> }>} | ||
| */ | ||
| class DirectoryWatcher extends EventEmitter { | ||
| constructor(watcherManager, directoryPath, options) { | ||
| /** | ||
| * @param {WatcherManager} watcherManager a watcher manager | ||
| * @param {string} directoryPath directory path | ||
| * @param {DirectoryWatcherOptions=} options options | ||
| */ | ||
| constructor(watcherManager, directoryPath, options = {}) { | ||
| super(); | ||
@@ -74,6 +170,7 @@ if (FORCE_POLLING) { | ||
| // timestamp is a value that should be compared with another timestamp (mtime) | ||
| /** @type {Map<string, { safeTime: number, timestamp: number }} */ | ||
| /** @type {Map<string, Entry>} */ | ||
| this.files = new Map(); | ||
| /** @type {Map<string, number>} */ | ||
| this.filesWithoutCase = new Map(); | ||
| /** @type {Map<string, Watcher<DirectoryWatcherEvents> | boolean>} */ | ||
| this.directories = new Map(); | ||
@@ -84,2 +181,3 @@ this.lastWatchEvent = 0; | ||
| this.nestedWatching = false; | ||
| /** @type {number | false} */ | ||
| this.polledWatching = | ||
@@ -89,11 +187,16 @@ typeof options.poll === "number" | ||
| : options.poll | ||
| ? 5007 | ||
| : false; | ||
| ? 5007 | ||
| : false; | ||
| /** @type {undefined | NodeJS.Timeout} */ | ||
| this.timeout = undefined; | ||
| /** @type {null | InitialScanRemoved} */ | ||
| this.initialScanRemoved = new Set(); | ||
| /** @type {undefined | number} */ | ||
| this.initialScanFinished = undefined; | ||
| /** @type {Map<string, Set<Watcher>>} */ | ||
| /** @type {Map<string, Set<Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>>>} */ | ||
| this.watchers = new Map(); | ||
| /** @type {Watcher<FileWatcherEvents> | null} */ | ||
| this.parentWatcher = null; | ||
| this.refs = 0; | ||
| /** @type {Map<string, boolean>} */ | ||
| this._activeEvents = new Map(); | ||
@@ -112,3 +215,4 @@ this.closed = false; | ||
| if (this.polledWatching) { | ||
| this.watcher = { | ||
| /** @type {EventSourceWatcher} */ | ||
| (this.watcher) = /** @type {EventSourceWatcher} */ ({ | ||
| close: () => { | ||
@@ -119,4 +223,4 @@ if (this.timeout) { | ||
| } | ||
| } | ||
| }; | ||
| }, | ||
| }); | ||
| } else { | ||
@@ -126,3 +230,5 @@ if (IS_OSX) { | ||
| } | ||
| this.watcher = watchEventSource.watch(this.path); | ||
| this.watcher = | ||
| /** @type {EventSourceWatcher} */ | ||
| (watchEventSource.watch(this.path)); | ||
| this.watcher.on("change", this.onWatchEvent.bind(this)); | ||
@@ -136,2 +242,7 @@ this.watcher.on("error", this.onWatcherError.bind(this)); | ||
| /** | ||
| * @template {(watcher: Watcher<EventMap>) => void} T | ||
| * @param {string} path path | ||
| * @param {T} fn function | ||
| */ | ||
| forEachWatcher(path, fn) { | ||
@@ -146,5 +257,11 @@ const watchers = this.watchers.get(withoutCase(path)); | ||
| /** | ||
| * @param {string} itemPath an item path | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @param {EventType} type even type | ||
| */ | ||
| setMissing(itemPath, initial, type) { | ||
| if (this.initialScan) { | ||
| this.initialScanRemoved.add(itemPath); | ||
| /** @type {InitialScanRemoved} */ | ||
| (this.initialScanRemoved).add(itemPath); | ||
| } | ||
@@ -154,9 +271,11 @@ | ||
| if (oldDirectory) { | ||
| if (this.nestedWatching) oldDirectory.close(); | ||
| if (this.nestedWatching) { | ||
| /** @type {Watcher<DirectoryWatcherEvents>} */ | ||
| (oldDirectory).close(); | ||
| } | ||
| this.directories.delete(itemPath); | ||
| this.forEachWatcher(itemPath, w => w.emit("remove", type)); | ||
| this.forEachWatcher(itemPath, (w) => w.emit("remove", type)); | ||
| if (!initial) { | ||
| this.forEachWatcher(this.path, w => | ||
| w.emit("change", itemPath, null, type, initial) | ||
| this.forEachWatcher(this.path, (w) => | ||
| w.emit("change", itemPath, null, type, initial), | ||
| ); | ||
@@ -170,6 +289,6 @@ } | ||
| const key = withoutCase(itemPath); | ||
| const count = this.filesWithoutCase.get(key) - 1; | ||
| const count = /** @type {number} */ (this.filesWithoutCase.get(key)) - 1; | ||
| if (count <= 0) { | ||
| this.filesWithoutCase.delete(key); | ||
| this.forEachWatcher(itemPath, w => w.emit("remove", type)); | ||
| this.forEachWatcher(itemPath, (w) => w.emit("remove", type)); | ||
| } else { | ||
@@ -180,4 +299,4 @@ this.filesWithoutCase.set(key, count); | ||
| if (!initial) { | ||
| this.forEachWatcher(this.path, w => | ||
| w.emit("change", itemPath, null, type, initial) | ||
| this.forEachWatcher(this.path, (w) => | ||
| w.emit("change", itemPath, null, type, initial), | ||
| ); | ||
@@ -188,10 +307,18 @@ } | ||
| setFileTime(filePath, mtime, initial, ignoreWhenEqual, type) { | ||
| /** | ||
| * @param {string} target a target to set file time | ||
| * @param {number} mtime mtime | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @param {boolean} ignoreWhenEqual true to ignore when equal, otherwise false | ||
| * @param {EventType} type type | ||
| */ | ||
| setFileTime(target, mtime, initial, ignoreWhenEqual, type) { | ||
| const now = Date.now(); | ||
| if (this.ignored(filePath)) return; | ||
| if (this.ignored(target)) return; | ||
| const old = this.files.get(filePath); | ||
| const old = this.files.get(target); | ||
| let safeTime, accuracy; | ||
| let safeTime; | ||
| let accuracy; | ||
| if (initial) { | ||
@@ -215,10 +342,10 @@ safeTime = Math.min(now, mtime) + FS_ACCURACY; | ||
| this.files.set(filePath, { | ||
| this.files.set(target, { | ||
| safeTime, | ||
| accuracy, | ||
| timestamp: mtime | ||
| timestamp: mtime, | ||
| }); | ||
| if (!old) { | ||
| const key = withoutCase(filePath); | ||
| const key = withoutCase(target); | ||
| const count = this.filesWithoutCase.get(key); | ||
@@ -235,3 +362,3 @@ this.filesWithoutCase.set(key, (count || 0) + 1); | ||
| this.forEachWatcher(filePath, w => { | ||
| this.forEachWatcher(target, (w) => { | ||
| if (!initial || w.checkStartTime(safeTime, initial)) { | ||
@@ -242,7 +369,7 @@ w.emit("change", mtime, type); | ||
| } else if (!initial) { | ||
| this.forEachWatcher(filePath, w => w.emit("change", mtime, type)); | ||
| this.forEachWatcher(target, (w) => w.emit("change", mtime, type)); | ||
| } | ||
| this.forEachWatcher(this.path, w => { | ||
| this.forEachWatcher(this.path, (w) => { | ||
| if (!initial || w.checkStartTime(safeTime, initial)) { | ||
| w.emit("change", filePath, safeTime, type, initial); | ||
| w.emit("change", target, safeTime, type, initial); | ||
| } | ||
@@ -252,2 +379,8 @@ }); | ||
| /** | ||
| * @param {string} directoryPath directory path | ||
| * @param {number} birthtime birthtime | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| * @param {EventType} type even type | ||
| */ | ||
| setDirectory(directoryPath, birthtime, initial, type) { | ||
@@ -257,4 +390,4 @@ if (this.ignored(directoryPath)) return; | ||
| if (!initial) { | ||
| this.forEachWatcher(this.path, w => | ||
| w.emit("change", directoryPath, birthtime, type, initial) | ||
| this.forEachWatcher(this.path, (w) => | ||
| w.emit("change", directoryPath, birthtime, type, initial), | ||
| ); | ||
@@ -273,10 +406,5 @@ } | ||
| let safeTime; | ||
| if (initial) { | ||
| safeTime = Math.min(now, birthtime) + FS_ACCURACY; | ||
| } else { | ||
| safeTime = now; | ||
| } | ||
| const safeTime = initial ? Math.min(now, birthtime) + FS_ACCURACY : now; | ||
| this.forEachWatcher(directoryPath, w => { | ||
| this.forEachWatcher(directoryPath, (w) => { | ||
| if (!initial || w.checkStartTime(safeTime, false)) { | ||
@@ -286,3 +414,3 @@ w.emit("change", birthtime, type); | ||
| }); | ||
| this.forEachWatcher(this.path, w => { | ||
| this.forEachWatcher(this.path, (w) => { | ||
| if (!initial || w.checkStartTime(safeTime, initial)) { | ||
@@ -296,8 +424,11 @@ w.emit("change", directoryPath, safeTime, type, initial); | ||
| /** | ||
| * @param {string} directoryPath directory path | ||
| */ | ||
| createNestedWatcher(directoryPath) { | ||
| const watcher = this.watcherManager.watchDirectory(directoryPath, 1); | ||
| watcher.on("change", (filePath, mtime, type, initial) => { | ||
| this.forEachWatcher(this.path, w => { | ||
| watcher.on("change", (target, mtime, type, initial) => { | ||
| this.forEachWatcher(this.path, (w) => { | ||
| if (!initial || w.checkStartTime(mtime, initial)) { | ||
| w.emit("change", filePath, mtime, type, initial); | ||
| w.emit("change", target, mtime, type, initial); | ||
| } | ||
@@ -309,5 +440,8 @@ }); | ||
| /** | ||
| * @param {boolean} flag true when nested, otherwise false | ||
| */ | ||
| setNestedWatching(flag) { | ||
| if (this.nestedWatching !== !!flag) { | ||
| this.nestedWatching = !!flag; | ||
| if (this.nestedWatching !== Boolean(flag)) { | ||
| this.nestedWatching = Boolean(flag); | ||
| if (this.nestedWatching) { | ||
@@ -319,3 +453,4 @@ for (const directory of this.directories.keys()) { | ||
| for (const [directory, watcher] of this.directories) { | ||
| watcher.close(); | ||
| /** @type {Watcher<DirectoryWatcherEvents>} */ | ||
| (watcher).close(); | ||
| this.directories.set(directory, true); | ||
@@ -327,4 +462,9 @@ } | ||
| watch(filePath, startTime) { | ||
| const key = withoutCase(filePath); | ||
| /** | ||
| * @param {string} target a target to watch | ||
| * @param {number=} startTime start time | ||
| * @returns {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} watcher | ||
| */ | ||
| watch(target, startTime) { | ||
| const key = withoutCase(target); | ||
| let watchers = this.watchers.get(key); | ||
@@ -336,3 +476,5 @@ if (watchers === undefined) { | ||
| this.refs++; | ||
| const watcher = new Watcher(this, filePath, startTime); | ||
| const watcher = | ||
| /** @type {Watcher<DirectoryWatcherEvents> | Watcher<FileWatcherEvents>} */ | ||
| (new Watcher(this, target, startTime)); | ||
| watcher.on("closed", () => { | ||
@@ -346,3 +488,3 @@ if (--this.refs <= 0) { | ||
| this.watchers.delete(key); | ||
| if (this.path === filePath) this.setNestedWatching(false); | ||
| if (this.path === target) this.setNestedWatching(false); | ||
| } | ||
@@ -352,3 +494,3 @@ }); | ||
| let safeTime; | ||
| if (filePath === this.path) { | ||
| if (target === this.path) { | ||
| this.setNestedWatching(true); | ||
@@ -361,3 +503,3 @@ safeTime = this.lastWatchEvent; | ||
| } else { | ||
| const entry = this.files.get(filePath); | ||
| const entry = this.files.get(target); | ||
| if (entry) { | ||
@@ -371,19 +513,21 @@ fixupEntryAccuracy(entry); | ||
| if (safeTime) { | ||
| if (safeTime >= startTime) { | ||
| if (startTime && safeTime >= startTime) { | ||
| process.nextTick(() => { | ||
| if (this.closed) return; | ||
| if (filePath === this.path) { | ||
| watcher.emit( | ||
| if (target === this.path) { | ||
| /** @type {Watcher<DirectoryWatcherEvents>} */ | ||
| (watcher).emit( | ||
| "change", | ||
| filePath, | ||
| target, | ||
| safeTime, | ||
| "watch (outdated on attach)", | ||
| true | ||
| true, | ||
| ); | ||
| } else { | ||
| watcher.emit( | ||
| /** @type {Watcher<FileWatcherEvents>} */ | ||
| (watcher).emit( | ||
| "change", | ||
| safeTime, | ||
| "watch (outdated on attach)", | ||
| true | ||
| true, | ||
| ); | ||
@@ -394,3 +538,6 @@ } | ||
| } else if (this.initialScan) { | ||
| if (this.initialScanRemoved.has(filePath)) { | ||
| if ( | ||
| /** @type {InitialScanRemoved} */ | ||
| (this.initialScanRemoved).has(target) | ||
| ) { | ||
| process.nextTick(() => { | ||
@@ -402,5 +549,9 @@ if (this.closed) return; | ||
| } else if ( | ||
| filePath !== this.path && | ||
| !this.directories.has(filePath) && | ||
| watcher.checkStartTime(this.initialScanFinished, false) | ||
| target !== this.path && | ||
| !this.directories.has(target) && | ||
| watcher.checkStartTime( | ||
| /** @type {number} */ | ||
| (this.initialScanFinished), | ||
| false, | ||
| ) | ||
| ) { | ||
@@ -415,2 +566,6 @@ process.nextTick(() => { | ||
| /** | ||
| * @param {EventType} eventType event type | ||
| * @param {string=} filename filename | ||
| */ | ||
| onWatchEvent(eventType, filename) { | ||
@@ -427,4 +582,4 @@ if (this.closed) return; | ||
| const filePath = path.join(this.path, filename); | ||
| if (this.ignored(filePath)) return; | ||
| const target = path.join(this.path, filename); | ||
| if (this.ignored(target)) return; | ||
@@ -436,3 +591,3 @@ if (this._activeEvents.get(filename) === undefined) { | ||
| this._activeEvents.set(filename, false); | ||
| fs.lstat(filePath, (err, stats) => { | ||
| fs.lstat(target, (err, stats) => { | ||
| if (this.closed) return; | ||
@@ -453,9 +608,7 @@ if (this._activeEvents.get(filename) === true) { | ||
| this.onStatsError(err); | ||
| } else { | ||
| if (filename === path.basename(this.path)) { | ||
| // This may indicate that the directory itself was removed | ||
| if (!fs.existsSync(this.path)) { | ||
| this.onDirectoryRemoved("stat failed"); | ||
| } | ||
| } | ||
| } else if ( | ||
| filename === path.basename(this.path) && // This may indicate that the directory itself was removed | ||
| !fs.existsSync(this.path) | ||
| ) { | ||
| this.onDirectoryRemoved("stat failed"); | ||
| } | ||
@@ -465,20 +618,15 @@ } | ||
| if (!stats) { | ||
| this.setMissing(filePath, false, eventType); | ||
| this.setMissing(target, false, eventType); | ||
| } else if (stats.isDirectory()) { | ||
| this.setDirectory( | ||
| filePath, | ||
| +stats.birthtime || 1, | ||
| false, | ||
| eventType | ||
| ); | ||
| this.setDirectory(target, +stats.birthtime || 1, false, eventType); | ||
| } else if (stats.isFile() || stats.isSymbolicLink()) { | ||
| if (stats.mtime) { | ||
| ensureFsAccuracy(stats.mtime); | ||
| ensureFsAccuracy(+stats.mtime); | ||
| } | ||
| this.setFileTime( | ||
| filePath, | ||
| target, | ||
| +stats.mtime || +stats.ctime || 1, | ||
| false, | ||
| false, | ||
| eventType | ||
| eventType, | ||
| ); | ||
@@ -494,7 +642,16 @@ } | ||
| /** | ||
| * @param {unknown=} err error | ||
| */ | ||
| onWatcherError(err) { | ||
| if (this.closed) return; | ||
| if (err) { | ||
| if (err.code !== "EPERM" && err.code !== "ENOENT") { | ||
| console.error("Watchpack Error (watcher): " + err); | ||
| if ( | ||
| /** @type {NodeJS.ErrnoException} */ | ||
| (err).code !== "EPERM" && | ||
| /** @type {NodeJS.ErrnoException} */ | ||
| (err).code !== "ENOENT" | ||
| ) { | ||
| // eslint-disable-next-line no-console | ||
| console.error(`Watchpack Error (watcher): ${err}`); | ||
| } | ||
@@ -505,11 +662,19 @@ this.onDirectoryRemoved("watch error"); | ||
| /** | ||
| * @param {Error | NodeJS.ErrnoException=} err error | ||
| */ | ||
| onStatsError(err) { | ||
| if (err) { | ||
| console.error("Watchpack Error (stats): " + err); | ||
| // eslint-disable-next-line no-console | ||
| console.error(`Watchpack Error (stats): ${err}`); | ||
| } | ||
| } | ||
| /** | ||
| * @param {Error | NodeJS.ErrnoException=} err error | ||
| */ | ||
| onScanError(err) { | ||
| if (err) { | ||
| console.error("Watchpack Error (initial scan): " + err); | ||
| // eslint-disable-next-line no-console | ||
| console.error(`Watchpack Error (initial scan): ${err}`); | ||
| } | ||
@@ -528,2 +693,5 @@ this.onScanFinished(); | ||
| /** | ||
| * @param {string} reason a reason | ||
| */ | ||
| onDirectoryRemoved(reason) { | ||
@@ -535,8 +703,8 @@ if (this.watcher) { | ||
| this.watchInParentDirectory(); | ||
| const type = `directory-removed (${reason})`; | ||
| const type = /** @type {EventType} */ (`directory-removed (${reason})`); | ||
| for (const directory of this.directories.keys()) { | ||
| this.setMissing(directory, null, type); | ||
| this.setMissing(directory, false, type); | ||
| } | ||
| for (const file of this.files.keys()) { | ||
| this.setMissing(file, null, type); | ||
| this.setMissing(file, false, type); | ||
| } | ||
@@ -553,3 +721,4 @@ } | ||
| this.parentWatcher = this.watcherManager.watchFile(this.path, 1); | ||
| this.parentWatcher.on("change", (mtime, type) => { | ||
| /** @type {Watcher<FileWatcherEvents>} */ | ||
| (this.parentWatcher).on("change", (mtime, type) => { | ||
| if (this.closed) return; | ||
@@ -569,8 +738,9 @@ | ||
| // directory was created so we emit an event | ||
| this.forEachWatcher(this.path, w => | ||
| w.emit("change", this.path, mtime, type, false) | ||
| this.forEachWatcher(this.path, (w) => | ||
| w.emit("change", this.path, mtime, type, false), | ||
| ); | ||
| } | ||
| }); | ||
| this.parentWatcher.on("remove", () => { | ||
| /** @type {Watcher<FileWatcherEvents>} */ | ||
| (this.parentWatcher).on("remove", () => { | ||
| this.onDirectoryRemoved("parent directory removed"); | ||
@@ -581,2 +751,5 @@ }); | ||
| /** | ||
| * @param {boolean} initial true when initial, otherwise false | ||
| */ | ||
| doScan(initial) { | ||
@@ -615,3 +788,3 @@ if (this.scanning) { | ||
| "initial-missing", | ||
| "scan (parent directory missing in initial scan)" | ||
| "scan (parent directory missing in initial scan)", | ||
| ); | ||
@@ -631,3 +804,3 @@ } | ||
| const itemPaths = new Set( | ||
| items.map(item => path.join(this.path, item.normalize("NFC"))) | ||
| items.map((item) => path.join(this.path, item.normalize("NFC"))), | ||
| ); | ||
@@ -666,3 +839,3 @@ for (const file of this.files.keys()) { | ||
| "initial-missing", | ||
| "scan (missing in initial scan)" | ||
| "scan (missing in initial scan)", | ||
| ); | ||
@@ -693,3 +866,3 @@ } | ||
| ) { | ||
| this.setMissing(itemPath, initial, "scan (" + err2.code + ")"); | ||
| this.setMissing(itemPath, initial, `scan (${err2.code})`); | ||
| } else { | ||
@@ -703,3 +876,3 @@ this.onScanError(err2); | ||
| if (stats.mtime) { | ||
| ensureFsAccuracy(stats.mtime); | ||
| ensureFsAccuracy(+stats.mtime); | ||
| } | ||
@@ -711,12 +884,14 @@ this.setFileTime( | ||
| true, | ||
| "scan (file)" | ||
| "scan (file)", | ||
| ); | ||
| } else if (stats.isDirectory()) { | ||
| if (!initial || !this.directories.has(itemPath)) | ||
| this.setDirectory( | ||
| itemPath, | ||
| +stats.birthtime || 1, | ||
| initial, | ||
| "scan (dir)" | ||
| ); | ||
| } else if ( | ||
| stats.isDirectory() && | ||
| (!initial || !this.directories.has(itemPath)) | ||
| ) { | ||
| this.setDirectory( | ||
| itemPath, | ||
| +stats.birthtime || 1, | ||
| initial, | ||
| "scan (dir)", | ||
| ); | ||
| } | ||
@@ -731,2 +906,5 @@ itemFinished(); | ||
| /** | ||
| * @returns {Record<string, number>} times | ||
| */ | ||
| getTimes() { | ||
@@ -742,3 +920,5 @@ const obj = Object.create(null); | ||
| for (const w of this.directories.values()) { | ||
| const times = w.directoryWatcher.getTimes(); | ||
| const times = | ||
| /** @type {Watcher<DirectoryWatcherEvents>} */ | ||
| (w).directoryWatcher.getTimes(); | ||
| for (const file of Object.keys(times)) { | ||
@@ -755,3 +935,3 @@ const time = times[file]; | ||
| for (const watcher of watchers) { | ||
| const path = watcher.path; | ||
| const { path } = watcher; | ||
| if (!Object.prototype.hasOwnProperty.call(obj, path)) { | ||
@@ -766,2 +946,7 @@ obj[path] = null; | ||
| /** | ||
| * @param {TimeInfoEntries} fileTimestamps file timestamps | ||
| * @param {TimeInfoEntries} directoryTimestamps directory timestamps | ||
| * @returns {number} safe time | ||
| */ | ||
| collectTimeInfoEntries(fileTimestamps, directoryTimestamps) { | ||
@@ -778,6 +963,7 @@ let safeTime = this.lastWatchEvent; | ||
| safeTime, | ||
| w.directoryWatcher.collectTimeInfoEntries( | ||
| /** @type {Watcher<DirectoryWatcherEvents>} */ | ||
| (w).directoryWatcher.collectTimeInfoEntries( | ||
| fileTimestamps, | ||
| directoryTimestamps | ||
| ) | ||
| directoryTimestamps, | ||
| ), | ||
| ); | ||
@@ -787,3 +973,3 @@ } | ||
| directoryTimestamps.set(this.path, { | ||
| safeTime | ||
| safeTime, | ||
| }); | ||
@@ -795,4 +981,5 @@ } else { | ||
| fileTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY); | ||
| if (!directoryTimestamps.has(dir)) | ||
| if (!directoryTimestamps.has(dir)) { | ||
| directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY); | ||
| } | ||
| } | ||
@@ -805,3 +992,3 @@ fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); | ||
| for (const watcher of watchers) { | ||
| const path = watcher.path; | ||
| const { path } = watcher; | ||
| if (!fileTimestamps.has(path)) { | ||
@@ -825,3 +1012,4 @@ fileTimestamps.set(path, null); | ||
| for (const w of this.directories.values()) { | ||
| w.close(); | ||
| /** @type {Watcher<DirectoryWatcherEvents>} */ | ||
| (w).close(); | ||
| } | ||
@@ -840,16 +1028,2 @@ this.directories.clear(); | ||
| module.exports.EXISTANCE_ONLY_TIME_ENTRY = EXISTANCE_ONLY_TIME_ENTRY; | ||
| function fixupEntryAccuracy(entry) { | ||
| if (entry.accuracy > FS_ACCURACY) { | ||
| entry.safeTime = entry.safeTime - entry.accuracy + FS_ACCURACY; | ||
| entry.accuracy = FS_ACCURACY; | ||
| } | ||
| } | ||
| function ensureFsAccuracy(mtime) { | ||
| if (!mtime) return; | ||
| if (FS_ACCURACY > 1 && mtime % 1 !== 0) FS_ACCURACY = 1; | ||
| else if (FS_ACCURACY > 10 && mtime % 10 !== 0) FS_ACCURACY = 10; | ||
| else if (FS_ACCURACY > 100 && mtime % 100 !== 0) FS_ACCURACY = 100; | ||
| else if (FS_ACCURACY > 1000 && mtime % 1000 !== 0) FS_ACCURACY = 1000; | ||
| } | ||
| module.exports.Watcher = Watcher; |
@@ -10,8 +10,25 @@ /* | ||
| /** @typedef {import("./index").EventMap} EventMap */ | ||
| /** @typedef {import("./DirectoryWatcher").DirectoryWatcherOptions} DirectoryWatcherOptions */ | ||
| /** @typedef {import("./DirectoryWatcher").DirectoryWatcherEvents} DirectoryWatcherEvents */ | ||
| /** @typedef {import("./DirectoryWatcher").FileWatcherEvents} FileWatcherEvents */ | ||
| /** | ||
| * @template {EventMap} T | ||
| * @typedef {import("./DirectoryWatcher").Watcher<T>} Watcher | ||
| */ | ||
| class WatcherManager { | ||
| constructor(options) { | ||
| /** | ||
| * @param {DirectoryWatcherOptions=} options options | ||
| */ | ||
| constructor(options = {}) { | ||
| this.options = options; | ||
| /** @type {Map<string, DirectoryWatcher>} */ | ||
| this.directoryWatchers = new Map(); | ||
| } | ||
| /** | ||
| * @param {string} directory a directory | ||
| * @returns {DirectoryWatcher} a directory watcher | ||
| */ | ||
| getDirectoryWatcher(directory) { | ||
@@ -30,8 +47,18 @@ const watcher = this.directoryWatchers.get(directory); | ||
| watchFile(p, startTime) { | ||
| const directory = path.dirname(p); | ||
| if (directory === p) return null; | ||
| return this.getDirectoryWatcher(directory).watch(p, startTime); | ||
| /** | ||
| * @param {string} file file | ||
| * @param {number=} startTime start time | ||
| * @returns {Watcher<FileWatcherEvents> | null} watcher or null if file has no directory | ||
| */ | ||
| watchFile(file, startTime) { | ||
| const directory = path.dirname(file); | ||
| if (directory === file) return null; | ||
| return this.getDirectoryWatcher(directory).watch(file, startTime); | ||
| } | ||
| /** | ||
| * @param {string} directory directory | ||
| * @param {number=} startTime start time | ||
| * @returns {Watcher<DirectoryWatcherEvents>} watcher | ||
| */ | ||
| watchDirectory(directory, startTime) { | ||
@@ -43,7 +70,8 @@ return this.getDirectoryWatcher(directory).watch(directory, startTime); | ||
| const watcherManagers = new WeakMap(); | ||
| /** | ||
| * @param {object} options options | ||
| * @param {DirectoryWatcherOptions} options options | ||
| * @returns {WatcherManager} the watcher manager | ||
| */ | ||
| module.exports = options => { | ||
| module.exports = (options) => { | ||
| const watcherManager = watcherManagers.get(options); | ||
@@ -50,0 +78,0 @@ if (watcherManager !== undefined) return watcherManager; |
+16
-9
@@ -18,2 +18,3 @@ /* | ||
| constructor() { | ||
| /** @type {Map<string, readonly string[]>} */ | ||
| this.cache = new Map(); | ||
@@ -24,3 +25,3 @@ } | ||
| * @param {string} file path to file or directory | ||
| * @returns {string[]} array of file and all symlinks contributed in the resolving process (first item is the resolved file) | ||
| * @returns {readonly string[]} array of file and all symlinks contributed in the resolving process (first item is the resolved file) | ||
| */ | ||
@@ -70,6 +71,7 @@ resolve(file) { | ||
| } | ||
| result = Object.freeze(Array.from(resultSet)); | ||
| result = Object.freeze([...resultSet]); | ||
| } else if (parentResolved.length > 1) { | ||
| // we have links in the parent but not for the link content location | ||
| result = parentResolved.slice(); | ||
| result = [...parentResolved]; | ||
| // eslint-disable-next-line prefer-destructuring | ||
| result[0] = linkResolved[0]; | ||
@@ -81,3 +83,3 @@ // add the link | ||
| // we can return the link content location result | ||
| result = linkResolved.slice(); | ||
| result = [...linkResolved]; | ||
| // add the link | ||
@@ -93,3 +95,3 @@ result.push(realFile); | ||
| // add the link | ||
| realFile | ||
| realFile, | ||
| ]); | ||
@@ -99,8 +101,12 @@ } | ||
| return result; | ||
| } catch (e) { | ||
| if (!EXPECTED_ERRORS.has(e.code)) { | ||
| throw e; | ||
| } catch (err) { | ||
| if ( | ||
| /** @type {NodeJS.ErrnoException} */ | ||
| (err).code && | ||
| !EXPECTED_ERRORS.has(/** @type {NodeJS.ErrnoException} */ (err).code) | ||
| ) { | ||
| throw err; | ||
| } | ||
| // no link | ||
| const result = parentResolved.slice(); | ||
| const result = [...parentResolved]; | ||
| result[0] = realFile; | ||
@@ -113,2 +119,3 @@ Object.freeze(result); | ||
| } | ||
| module.exports = LinkResolver; |
+22
-22
@@ -11,9 +11,9 @@ /* | ||
| * @template T | ||
| * @typedef {Object} TreeNode | ||
| * @property {string} filePath | ||
| * @property {TreeNode} parent | ||
| * @property {TreeNode[]} children | ||
| * @property {number} entries | ||
| * @property {boolean} active | ||
| * @property {T[] | T | undefined} value | ||
| * @typedef {object} TreeNode | ||
| * @property {string} target target | ||
| * @property {TreeNode<T>} parent parent | ||
| * @property {TreeNode<T>[]} children children | ||
| * @property {number} entries number of entries | ||
| * @property {boolean} active true when active, otherwise false | ||
| * @property {T[] | T | undefined} value value | ||
| */ | ||
@@ -23,4 +23,4 @@ | ||
| * @template T | ||
| * @param {Map<string, T[] | T} plan | ||
| * @param {number} limit | ||
| * @param {Map<string, T[] | T>} plan plan | ||
| * @param {number} limit limit | ||
| * @returns {Map<string, Map<T, string>>} the new plan | ||
@@ -31,5 +31,5 @@ */ | ||
| // Convert to tree | ||
| for (const [filePath, value] of plan) { | ||
| treeMap.set(filePath, { | ||
| filePath, | ||
| for (const [target, value] of plan) { | ||
| treeMap.set(target, { | ||
| target, | ||
| parent: undefined, | ||
@@ -39,3 +39,3 @@ children: undefined, | ||
| active: true, | ||
| value | ||
| value, | ||
| }); | ||
@@ -46,8 +46,8 @@ } | ||
| for (const node of treeMap.values()) { | ||
| const parentPath = path.dirname(node.filePath); | ||
| if (parentPath !== node.filePath) { | ||
| const parentPath = path.dirname(node.target); | ||
| if (parentPath !== node.target) { | ||
| let parent = treeMap.get(parentPath); | ||
| if (parent === undefined) { | ||
| parent = { | ||
| filePath: parentPath, | ||
| target: parentPath, | ||
| parent: undefined, | ||
@@ -57,3 +57,3 @@ children: [node], | ||
| active: false, | ||
| value: undefined | ||
| value: undefined, | ||
| }; | ||
@@ -80,3 +80,3 @@ treeMap.set(parentPath, parent); | ||
| const overLimit = currentCount - limit; | ||
| let bestNode = undefined; | ||
| let bestNode; | ||
| let bestCost = Infinity; | ||
@@ -105,3 +105,3 @@ for (const node of treeMap.values()) { | ||
| currentCount -= reduction; | ||
| let parent = bestNode.parent; | ||
| let { parent } = bestNode; | ||
| while (parent) { | ||
@@ -131,6 +131,6 @@ parent.entries -= reduction; | ||
| for (const item of node.value) { | ||
| map.set(item, node.filePath); | ||
| map.set(item, node.target); | ||
| } | ||
| } else { | ||
| map.set(node.value, node.filePath); | ||
| map.set(node.value, node.target); | ||
| } | ||
@@ -144,5 +144,5 @@ } | ||
| } | ||
| newPlan.set(rootNode.filePath, map); | ||
| newPlan.set(rootNode.target, map); | ||
| } | ||
| return newPlan; | ||
| }; |
+115
-37
@@ -7,9 +7,13 @@ /* | ||
| const { EventEmitter } = require("events"); | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
| const { EventEmitter } = require("events"); | ||
| const reducePlan = require("./reducePlan"); | ||
| /** @typedef {import("fs").FSWatcher} FSWatcher */ | ||
| /** @typedef {import("./index").EventType} EventType */ | ||
| const IS_OSX = require("os").platform() === "darwin"; | ||
| const IS_WIN = require("os").platform() === "win32"; | ||
| const SUPPORTS_RECURSIVE_WATCHING = IS_OSX || IS_WIN; | ||
@@ -20,6 +24,8 @@ | ||
| const watcherLimit = | ||
| // @ts-expect-error avoid additional checks | ||
| +process.env.WATCHPACK_WATCHER_LIMIT || (IS_OSX ? 20 : 10000); | ||
| const recursiveWatcherLogging = !!process.env | ||
| .WATCHPACK_RECURSIVE_WATCHER_LOGGING; | ||
| const recursiveWatcherLogging = Boolean( | ||
| process.env.WATCHPACK_RECURSIVE_WATCHER_LOGGING, | ||
| ); | ||
@@ -41,4 +47,10 @@ let isBatch = false; | ||
| /** | ||
| * @param {string} filePath file path | ||
| * @returns {NodeJS.ErrnoException} new error with file path in the message | ||
| */ | ||
| function createEPERMError(filePath) { | ||
| const error = new Error(`Operation not permitted: ${filePath}`); | ||
| const error = | ||
| /** @type {NodeJS.ErrnoException} */ | ||
| (new Error(`Operation not permitted: ${filePath}`)); | ||
| error.code = "EPERM"; | ||
@@ -48,2 +60,8 @@ return error; | ||
| /** | ||
| * @param {FSWatcher} watcher watcher | ||
| * @param {string} filePath a file path | ||
| * @param {(type: "rename" | "change", filename: string) => void} handleChangeEvent function to handle change | ||
| * @returns {(type: "rename" | "change", filename: string) => void} handler of change event | ||
| */ | ||
| function createHandleChangeEvent(watcher, filePath, handleChangeEvent) { | ||
@@ -71,5 +89,9 @@ return (type, filename) => { | ||
| class DirectWatcher { | ||
| /** | ||
| * @param {string} filePath file path | ||
| */ | ||
| constructor(filePath) { | ||
| this.filePath = filePath; | ||
| this.watchers = new Set(); | ||
| /** @type {FSWatcher | undefined} */ | ||
| this.watcher = undefined; | ||
@@ -87,6 +109,6 @@ try { | ||
| } | ||
| } | ||
| }, | ||
| ); | ||
| watcher.on("change", handleChangeEvent); | ||
| watcher.on("error", error => { | ||
| watcher.on("error", (error) => { | ||
| for (const w of this.watchers) { | ||
@@ -106,2 +128,5 @@ w.emit("error", error); | ||
| /** | ||
| * @param {Watcher} watcher a watcher | ||
| */ | ||
| add(watcher) { | ||
@@ -112,2 +137,5 @@ underlyingWatcher.set(watcher, this); | ||
| /** | ||
| * @param {Watcher} watcher a watcher | ||
| */ | ||
| remove(watcher) { | ||
@@ -127,3 +155,8 @@ this.watchers.delete(watcher); | ||
| /** @typedef {Set<Watcher>} WatcherSet */ | ||
| class RecursiveWatcher { | ||
| /** | ||
| * @param {string} rootPath a root path | ||
| */ | ||
| constructor(rootPath) { | ||
@@ -133,3 +166,3 @@ this.rootPath = rootPath; | ||
| this.mapWatcherToPath = new Map(); | ||
| /** @type {Map<string, Set<Watcher>>} */ | ||
| /** @type {Map<string, WatcherSet>} */ | ||
| this.mapPathToWatchers = new Map(); | ||
@@ -139,3 +172,3 @@ this.watcher = undefined; | ||
| const watcher = fs.watch(rootPath, { | ||
| recursive: true | ||
| recursive: true, | ||
| }); | ||
@@ -147,10 +180,10 @@ this.watcher = watcher; | ||
| process.stderr.write( | ||
| `[watchpack] dispatch ${type} event in recursive watcher (${this.rootPath}) to all watchers\n` | ||
| `[watchpack] dispatch ${type} event in recursive watcher (${this.rootPath}) to all watchers\n`, | ||
| ); | ||
| } | ||
| for (const w of this.mapWatcherToPath.keys()) { | ||
| w.emit("change", type); | ||
| w.emit("change", /** @type {EventType} */ (type)); | ||
| } | ||
| } else { | ||
| const dir = path.dirname(filename); | ||
| const dir = path.dirname(/** @type {string} */ (filename)); | ||
| const watchers = this.mapPathToWatchers.get(dir); | ||
@@ -163,3 +196,3 @@ if (recursiveWatcherLogging) { | ||
| watchers ? watchers.size : 0 | ||
| } watchers\n` | ||
| } watchers\n`, | ||
| ); | ||
@@ -169,7 +202,11 @@ } | ||
| for (const w of watchers) { | ||
| w.emit("change", type, path.basename(filename)); | ||
| w.emit( | ||
| "change", | ||
| /** @type {EventType} */ (type), | ||
| path.basename(/** @type {string} */ (filename)), | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| watcher.on("error", error => { | ||
| watcher.on("error", (error) => { | ||
| for (const w of this.mapWatcherToPath.keys()) { | ||
@@ -189,3 +226,3 @@ w.emit("error", error); | ||
| process.stderr.write( | ||
| `[watchpack] created recursive watcher at ${rootPath}\n` | ||
| `[watchpack] created recursive watcher at ${rootPath}\n`, | ||
| ); | ||
@@ -195,2 +232,6 @@ } | ||
| /** | ||
| * @param {string} filePath a file path | ||
| * @param {Watcher} watcher a watcher | ||
| */ | ||
| add(filePath, watcher) { | ||
@@ -210,2 +251,5 @@ underlyingWatcher.set(watcher, this); | ||
| /** | ||
| * @param {Watcher} watcher a watcher | ||
| */ | ||
| remove(watcher) { | ||
@@ -215,3 +259,3 @@ const subpath = this.mapWatcherToPath.get(watcher); | ||
| this.mapWatcherToPath.delete(watcher); | ||
| const set = this.mapPathToWatchers.get(subpath); | ||
| const set = /** @type {WatcherSet} */ (this.mapPathToWatchers.get(subpath)); | ||
| set.delete(watcher); | ||
@@ -227,3 +271,3 @@ if (set.size === 0) { | ||
| process.stderr.write( | ||
| `[watchpack] closed recursive watcher at ${this.rootPath}\n` | ||
| `[watchpack] closed recursive watcher at ${this.rootPath}\n`, | ||
| ); | ||
@@ -239,3 +283,16 @@ } | ||
| /** | ||
| * @typedef {object} WatcherEvents | ||
| * @property {(eventType: EventType, filename?: string) => void} change change event | ||
| * @property {(err: unknown) => void} error error event | ||
| */ | ||
| /** | ||
| * @extends {EventEmitter<{ [K in keyof WatcherEvents]: Parameters<WatcherEvents[K]> }>} | ||
| */ | ||
| class Watcher extends EventEmitter { | ||
| constructor() { | ||
| super(); | ||
| } | ||
| close() { | ||
@@ -247,3 +304,4 @@ if (pendingWatchers.has(this)) { | ||
| const watcher = underlyingWatcher.get(this); | ||
| watcher.remove(this); | ||
| /** @type {RecursiveWatcher | DirectWatcher} */ | ||
| (watcher).remove(this); | ||
| underlyingWatcher.delete(this); | ||
@@ -253,3 +311,7 @@ } | ||
| const createDirectWatcher = filePath => { | ||
| /** | ||
| * @param {string} filePath a file path | ||
| * @returns {DirectWatcher} a directory watcher | ||
| */ | ||
| const createDirectWatcher = (filePath) => { | ||
| const existing = directWatchers.get(filePath); | ||
@@ -262,3 +324,7 @@ if (existing !== undefined) return existing; | ||
| const createRecursiveWatcher = rootPath => { | ||
| /** | ||
| * @param {string} rootPath a root path | ||
| * @returns {RecursiveWatcher} a recursive watcher | ||
| */ | ||
| const createRecursiveWatcher = (rootPath) => { | ||
| const existing = recursiveWatchers.get(rootPath); | ||
@@ -274,2 +340,6 @@ if (existing !== undefined) return existing; | ||
| const map = new Map(); | ||
| /** | ||
| * @param {Watcher} watcher a watcher | ||
| * @param {string} filePath a file path | ||
| */ | ||
| const addWatcher = (watcher, filePath) => { | ||
@@ -355,3 +425,26 @@ const entry = map.get(filePath); | ||
| exports.watch = filePath => { | ||
| module.exports.Watcher = Watcher; | ||
| /** | ||
| * @param {() => void} fn a function | ||
| */ | ||
| module.exports.batch = (fn) => { | ||
| isBatch = true; | ||
| try { | ||
| fn(); | ||
| } finally { | ||
| isBatch = false; | ||
| execute(); | ||
| } | ||
| }; | ||
| module.exports.createHandleChangeEvent = createHandleChangeEvent; | ||
| module.exports.getNumberOfWatchers = () => watcherCount; | ||
| /** | ||
| * @param {string} filePath a file path | ||
| * @returns {Watcher} watcher | ||
| */ | ||
| module.exports.watch = (filePath) => { | ||
| const watcher = new Watcher(); | ||
@@ -381,17 +474,2 @@ // Find an existing watcher | ||
| exports.batch = fn => { | ||
| isBatch = true; | ||
| try { | ||
| fn(); | ||
| } finally { | ||
| isBatch = false; | ||
| execute(); | ||
| } | ||
| }; | ||
| exports.getNumberOfWatchers = () => { | ||
| return watcherCount; | ||
| }; | ||
| exports.createHandleChangeEvent = createHandleChangeEvent; | ||
| exports.watcherLimit = watcherLimit; | ||
| module.exports.watcherLimit = watcherLimit; |
+3
-388
| /* | ||
| MIT License http://www.opensource.org/licenses/mit-license.php | ||
| Author Tobias Koppers @sokra | ||
| Author Alexander Akait @akexander-akait | ||
| */ | ||
| "use strict"; | ||
| const getWatcherManager = require("./getWatcherManager"); | ||
| const LinkResolver = require("./LinkResolver"); | ||
| const EventEmitter = require("events").EventEmitter; | ||
| const globToRegExp = require("glob-to-regexp"); | ||
| const watchEventSource = require("./watchEventSource"); | ||
| const EMPTY_ARRAY = []; | ||
| const EMPTY_OPTIONS = {}; | ||
| function addWatchersToSet(watchers, set) { | ||
| for (const ww of watchers) { | ||
| const w = ww.watcher; | ||
| if (!set.has(w.directoryWatcher)) { | ||
| set.add(w.directoryWatcher); | ||
| } | ||
| } | ||
| } | ||
| const stringToRegexp = ignored => { | ||
| if (ignored.length === 0) { | ||
| return; | ||
| } | ||
| const source = globToRegExp(ignored, { globstar: true, extended: true }) | ||
| .source; | ||
| return source.slice(0, source.length - 1) + "(?:$|\\/)"; | ||
| }; | ||
| const ignoredToFunction = ignored => { | ||
| if (Array.isArray(ignored)) { | ||
| const stringRegexps = ignored.map(i => stringToRegexp(i)).filter(Boolean); | ||
| if (stringRegexps.length === 0) { | ||
| return () => false; | ||
| } | ||
| const regexp = new RegExp(stringRegexps.join("|")); | ||
| return x => regexp.test(x.replace(/\\/g, "/")); | ||
| } else if (typeof ignored === "string") { | ||
| const stringRegexp = stringToRegexp(ignored); | ||
| if (!stringRegexp) { | ||
| return () => false; | ||
| } | ||
| const regexp = new RegExp(stringRegexp); | ||
| return x => regexp.test(x.replace(/\\/g, "/")); | ||
| } else if (ignored instanceof RegExp) { | ||
| return x => ignored.test(x.replace(/\\/g, "/")); | ||
| } else if (ignored instanceof Function) { | ||
| return ignored; | ||
| } else if (ignored) { | ||
| throw new Error(`Invalid option for 'ignored': ${ignored}`); | ||
| } else { | ||
| return () => false; | ||
| } | ||
| }; | ||
| const normalizeOptions = options => { | ||
| return { | ||
| followSymlinks: !!options.followSymlinks, | ||
| ignored: ignoredToFunction(options.ignored), | ||
| poll: options.poll | ||
| }; | ||
| }; | ||
| const normalizeCache = new WeakMap(); | ||
| const cachedNormalizeOptions = options => { | ||
| const cacheEntry = normalizeCache.get(options); | ||
| if (cacheEntry !== undefined) return cacheEntry; | ||
| const normalized = normalizeOptions(options); | ||
| normalizeCache.set(options, normalized); | ||
| return normalized; | ||
| }; | ||
| class WatchpackFileWatcher { | ||
| constructor(watchpack, watcher, files) { | ||
| this.files = Array.isArray(files) ? files : [files]; | ||
| this.watcher = watcher; | ||
| watcher.on("initial-missing", type => { | ||
| for (const file of this.files) { | ||
| if (!watchpack._missing.has(file)) | ||
| watchpack._onRemove(file, file, type); | ||
| } | ||
| }); | ||
| watcher.on("change", (mtime, type) => { | ||
| for (const file of this.files) { | ||
| watchpack._onChange(file, mtime, file, type); | ||
| } | ||
| }); | ||
| watcher.on("remove", type => { | ||
| for (const file of this.files) { | ||
| watchpack._onRemove(file, file, type); | ||
| } | ||
| }); | ||
| } | ||
| update(files) { | ||
| if (!Array.isArray(files)) { | ||
| if (this.files.length !== 1) { | ||
| this.files = [files]; | ||
| } else if (this.files[0] !== files) { | ||
| this.files[0] = files; | ||
| } | ||
| } else { | ||
| this.files = files; | ||
| } | ||
| } | ||
| close() { | ||
| this.watcher.close(); | ||
| } | ||
| } | ||
| class WatchpackDirectoryWatcher { | ||
| constructor(watchpack, watcher, directories) { | ||
| this.directories = Array.isArray(directories) ? directories : [directories]; | ||
| this.watcher = watcher; | ||
| watcher.on("initial-missing", type => { | ||
| for (const item of this.directories) { | ||
| watchpack._onRemove(item, item, type); | ||
| } | ||
| }); | ||
| watcher.on("change", (file, mtime, type) => { | ||
| for (const item of this.directories) { | ||
| watchpack._onChange(item, mtime, file, type); | ||
| } | ||
| }); | ||
| watcher.on("remove", type => { | ||
| for (const item of this.directories) { | ||
| watchpack._onRemove(item, item, type); | ||
| } | ||
| }); | ||
| } | ||
| update(directories) { | ||
| if (!Array.isArray(directories)) { | ||
| if (this.directories.length !== 1) { | ||
| this.directories = [directories]; | ||
| } else if (this.directories[0] !== directories) { | ||
| this.directories[0] = directories; | ||
| } | ||
| } else { | ||
| this.directories = directories; | ||
| } | ||
| } | ||
| close() { | ||
| this.watcher.close(); | ||
| } | ||
| } | ||
| class Watchpack extends EventEmitter { | ||
| constructor(options) { | ||
| super(); | ||
| if (!options) options = EMPTY_OPTIONS; | ||
| this.options = options; | ||
| this.aggregateTimeout = | ||
| typeof options.aggregateTimeout === "number" | ||
| ? options.aggregateTimeout | ||
| : 200; | ||
| this.watcherOptions = cachedNormalizeOptions(options); | ||
| this.watcherManager = getWatcherManager(this.watcherOptions); | ||
| this.fileWatchers = new Map(); | ||
| this.directoryWatchers = new Map(); | ||
| this._missing = new Set(); | ||
| this.startTime = undefined; | ||
| this.paused = false; | ||
| this.aggregatedChanges = new Set(); | ||
| this.aggregatedRemovals = new Set(); | ||
| this.aggregateTimer = undefined; | ||
| this._onTimeout = this._onTimeout.bind(this); | ||
| } | ||
| watch(arg1, arg2, arg3) { | ||
| let files, directories, missing, startTime; | ||
| if (!arg2) { | ||
| ({ | ||
| files = EMPTY_ARRAY, | ||
| directories = EMPTY_ARRAY, | ||
| missing = EMPTY_ARRAY, | ||
| startTime | ||
| } = arg1); | ||
| } else { | ||
| files = arg1; | ||
| directories = arg2; | ||
| missing = EMPTY_ARRAY; | ||
| startTime = arg3; | ||
| } | ||
| this.paused = false; | ||
| const fileWatchers = this.fileWatchers; | ||
| const directoryWatchers = this.directoryWatchers; | ||
| const ignored = this.watcherOptions.ignored; | ||
| const filter = path => !ignored(path); | ||
| const addToMap = (map, key, item) => { | ||
| const list = map.get(key); | ||
| if (list === undefined) { | ||
| map.set(key, item); | ||
| } else if (Array.isArray(list)) { | ||
| list.push(item); | ||
| } else { | ||
| map.set(key, [list, item]); | ||
| } | ||
| }; | ||
| const fileWatchersNeeded = new Map(); | ||
| const directoryWatchersNeeded = new Map(); | ||
| const missingFiles = new Set(); | ||
| if (this.watcherOptions.followSymlinks) { | ||
| const resolver = new LinkResolver(); | ||
| for (const file of files) { | ||
| if (filter(file)) { | ||
| for (const innerFile of resolver.resolve(file)) { | ||
| if (file === innerFile || filter(innerFile)) { | ||
| addToMap(fileWatchersNeeded, innerFile, file); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for (const file of missing) { | ||
| if (filter(file)) { | ||
| for (const innerFile of resolver.resolve(file)) { | ||
| if (file === innerFile || filter(innerFile)) { | ||
| missingFiles.add(file); | ||
| addToMap(fileWatchersNeeded, innerFile, file); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| for (const dir of directories) { | ||
| if (filter(dir)) { | ||
| let first = true; | ||
| for (const innerItem of resolver.resolve(dir)) { | ||
| if (filter(innerItem)) { | ||
| addToMap( | ||
| first ? directoryWatchersNeeded : fileWatchersNeeded, | ||
| innerItem, | ||
| dir | ||
| ); | ||
| } | ||
| first = false; | ||
| } | ||
| } | ||
| } | ||
| } else { | ||
| for (const file of files) { | ||
| if (filter(file)) { | ||
| addToMap(fileWatchersNeeded, file, file); | ||
| } | ||
| } | ||
| for (const file of missing) { | ||
| if (filter(file)) { | ||
| missingFiles.add(file); | ||
| addToMap(fileWatchersNeeded, file, file); | ||
| } | ||
| } | ||
| for (const dir of directories) { | ||
| if (filter(dir)) { | ||
| addToMap(directoryWatchersNeeded, dir, dir); | ||
| } | ||
| } | ||
| } | ||
| // Close unneeded old watchers | ||
| // and update existing watchers | ||
| for (const [key, w] of fileWatchers) { | ||
| const needed = fileWatchersNeeded.get(key); | ||
| if (needed === undefined) { | ||
| w.close(); | ||
| fileWatchers.delete(key); | ||
| } else { | ||
| w.update(needed); | ||
| fileWatchersNeeded.delete(key); | ||
| } | ||
| } | ||
| for (const [key, w] of directoryWatchers) { | ||
| const needed = directoryWatchersNeeded.get(key); | ||
| if (needed === undefined) { | ||
| w.close(); | ||
| directoryWatchers.delete(key); | ||
| } else { | ||
| w.update(needed); | ||
| directoryWatchersNeeded.delete(key); | ||
| } | ||
| } | ||
| // Create new watchers and install handlers on these watchers | ||
| watchEventSource.batch(() => { | ||
| for (const [key, files] of fileWatchersNeeded) { | ||
| const watcher = this.watcherManager.watchFile(key, startTime); | ||
| if (watcher) { | ||
| fileWatchers.set(key, new WatchpackFileWatcher(this, watcher, files)); | ||
| } | ||
| } | ||
| for (const [key, directories] of directoryWatchersNeeded) { | ||
| const watcher = this.watcherManager.watchDirectory(key, startTime); | ||
| if (watcher) { | ||
| directoryWatchers.set( | ||
| key, | ||
| new WatchpackDirectoryWatcher(this, watcher, directories) | ||
| ); | ||
| } | ||
| } | ||
| }); | ||
| this._missing = missingFiles; | ||
| this.startTime = startTime; | ||
| } | ||
| close() { | ||
| this.paused = true; | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| for (const w of this.fileWatchers.values()) w.close(); | ||
| for (const w of this.directoryWatchers.values()) w.close(); | ||
| this.fileWatchers.clear(); | ||
| this.directoryWatchers.clear(); | ||
| } | ||
| pause() { | ||
| this.paused = true; | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| } | ||
| getTimes() { | ||
| const directoryWatchers = new Set(); | ||
| addWatchersToSet(this.fileWatchers.values(), directoryWatchers); | ||
| addWatchersToSet(this.directoryWatchers.values(), directoryWatchers); | ||
| const obj = Object.create(null); | ||
| for (const w of directoryWatchers) { | ||
| const times = w.getTimes(); | ||
| for (const file of Object.keys(times)) obj[file] = times[file]; | ||
| } | ||
| return obj; | ||
| } | ||
| getTimeInfoEntries() { | ||
| const map = new Map(); | ||
| this.collectTimeInfoEntries(map, map); | ||
| return map; | ||
| } | ||
| collectTimeInfoEntries(fileTimestamps, directoryTimestamps) { | ||
| const allWatchers = new Set(); | ||
| addWatchersToSet(this.fileWatchers.values(), allWatchers); | ||
| addWatchersToSet(this.directoryWatchers.values(), allWatchers); | ||
| const safeTime = { value: 0 }; | ||
| for (const w of allWatchers) { | ||
| w.collectTimeInfoEntries(fileTimestamps, directoryTimestamps, safeTime); | ||
| } | ||
| } | ||
| getAggregated() { | ||
| if (this.aggregateTimer) { | ||
| clearTimeout(this.aggregateTimer); | ||
| this.aggregateTimer = undefined; | ||
| } | ||
| const changes = this.aggregatedChanges; | ||
| const removals = this.aggregatedRemovals; | ||
| this.aggregatedChanges = new Set(); | ||
| this.aggregatedRemovals = new Set(); | ||
| return { changes, removals }; | ||
| } | ||
| _onChange(item, mtime, file, type) { | ||
| file = file || item; | ||
| if (!this.paused) { | ||
| this.emit("change", file, mtime, type); | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout); | ||
| } | ||
| this.aggregatedRemovals.delete(item); | ||
| this.aggregatedChanges.add(item); | ||
| } | ||
| _onRemove(item, file, type) { | ||
| file = file || item; | ||
| if (!this.paused) { | ||
| this.emit("remove", file, type); | ||
| if (this.aggregateTimer) clearTimeout(this.aggregateTimer); | ||
| this.aggregateTimer = setTimeout(this._onTimeout, this.aggregateTimeout); | ||
| } | ||
| this.aggregatedChanges.delete(item); | ||
| this.aggregatedRemovals.add(item); | ||
| } | ||
| _onTimeout() { | ||
| this.aggregateTimer = undefined; | ||
| const changes = this.aggregatedChanges; | ||
| const removals = this.aggregatedRemovals; | ||
| this.aggregatedChanges = new Set(); | ||
| this.aggregatedRemovals = new Set(); | ||
| this.emit("aggregated", changes, removals); | ||
| } | ||
| } | ||
| module.exports = Watchpack; | ||
| // TODO remove this file in the next major release | ||
| module.exports = require("./index"); |
+55
-30
| { | ||
| "name": "watchpack", | ||
| "version": "2.4.4", | ||
| "version": "2.5.0", | ||
| "description": "", | ||
| "main": "./lib/watchpack.js", | ||
| "homepage": "https://github.com/webpack/watchpack", | ||
| "bugs": { | ||
| "url": "https://github.com/webpack/watchpack/issues" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/webpack/watchpack.git" | ||
| }, | ||
| "license": "MIT", | ||
| "author": "Tobias Koppers @sokra", | ||
| "main": "lib/index.js", | ||
| "types": "types/index.js", | ||
| "directories": { | ||
@@ -10,38 +21,52 @@ "test": "test" | ||
| "files": [ | ||
| "lib/" | ||
| "lib/", | ||
| "types/" | ||
| ], | ||
| "scripts": { | ||
| "pretty-files": "prettier \"lib/**.*\" \"test/**/*.js\" --write", | ||
| "lint": "eslint lib", | ||
| "test:only": "mocha", | ||
| "test:coverage": "istanbul cover node_modules/mocha/bin/_mocha", | ||
| "pretest": "yarn lint", | ||
| "test": "mocha" | ||
| "lint": "npm run lint:code && npm run lint:types && npm run lint:types-test && npm run lint:declarations && npm run fmt:check", | ||
| "lint:code": "eslint --cache .", | ||
| "lint:types": "tsc", | ||
| "lint:types-test": "tsc -p tsconfig.types.test.json", | ||
| "lint:declarations": "npm run fix:declarations && git diff --exit-code ./types", | ||
| "fix": "npm run fix:code && npm run fix:declarations", | ||
| "fix:code": "npm run lint:code -- --fix", | ||
| "fix:declarations": "tsc --noEmit false --declaration --emitDeclarationOnly --outDir types && npm run fmt -- ./types", | ||
| "fmt": "npm run fmt:base -- --log-level warn --write", | ||
| "fmt:check": "npm run fmt:base -- --check", | ||
| "fmt:base": "prettier --cache --ignore-unknown .", | ||
| "pretest": "npm run lint", | ||
| "test": "npm run test:coverage", | ||
| "test:base": "jest --runInBand", | ||
| "test:only": "npm run test:base", | ||
| "test:watch": "npm run test:base -- --watch", | ||
| "test:coverage": "npm run test:base -- --collectCoverageFrom=\"lib/**/*.js\" --coverage" | ||
| }, | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "https://github.com/webpack/watchpack.git" | ||
| "dependencies": { | ||
| "glob-to-regexp": "^0.4.1", | ||
| "graceful-fs": "^4.1.2" | ||
| }, | ||
| "author": "Tobias Koppers @sokra", | ||
| "license": "MIT", | ||
| "bugs": { | ||
| "url": "https://github.com/webpack/watchpack/issues" | ||
| }, | ||
| "homepage": "https://github.com/webpack/watchpack", | ||
| "devDependencies": { | ||
| "coveralls": "^3.0.0", | ||
| "eslint": "^5.11.1", | ||
| "eslint-config-prettier": "^4.3.0", | ||
| "eslint-plugin-prettier": "^3.1.0", | ||
| "istanbul": "^0.4.3", | ||
| "mocha": "^5.0.1", | ||
| "prettier": "^1.11.0", | ||
| "@eslint/js": "^9.28.0", | ||
| "@eslint/markdown": "^7.5.1", | ||
| "@stylistic/eslint-plugin": "^5.6.1", | ||
| "@types/glob-to-regexp": "^0.4.4", | ||
| "@types/graceful-fs": "^4.1.9", | ||
| "@types/jest": "^27.5.1", | ||
| "@types/node": "^24.10.4", | ||
| "eslint": "^9.39.2", | ||
| "eslint-config-prettier": "^10.1.8", | ||
| "eslint-config-webpack": "^4.7.3", | ||
| "eslint-plugin-import": "^2.32.0", | ||
| "eslint-plugin-jest": "^29.5.0", | ||
| "eslint-plugin-jsdoc": "^61.5.0", | ||
| "eslint-plugin-n": "^17.23.1", | ||
| "eslint-plugin-prettier": "^5.5.4", | ||
| "eslint-plugin-unicorn": "^62.0.0", | ||
| "globals": "^16.5.0", | ||
| "jest": "^27.5.1", | ||
| "prettier": "^3.7.4", | ||
| "rimraf": "^2.6.2", | ||
| "should": "^8.3.1", | ||
| "typescript": "^5.9.3", | ||
| "write-file-atomic": "^3.0.1" | ||
| }, | ||
| "dependencies": { | ||
| "glob-to-regexp": "^0.4.1", | ||
| "graceful-fs": "^4.1.2" | ||
| }, | ||
| "engines": { | ||
@@ -48,0 +73,0 @@ "node": ">=10.13.0" |
+9
-9
@@ -24,5 +24,5 @@ # watchpack | ||
| ```javascript | ||
| var Watchpack = require("watchpack"); | ||
| const Watchpack = require("watchpack"); | ||
| var wp = new Watchpack({ | ||
| const wp = new Watchpack({ | ||
| // options: | ||
@@ -46,3 +46,3 @@ aggregateTimeout: 1000, | ||
| ignored: "**/.git" | ||
| ignored: "**/.git", | ||
| // ignored: "string" - a glob pattern for files or folders that should not be watched | ||
@@ -66,3 +66,3 @@ // ignored: ["string", "string"] - multiple glob patterns that should be ignored | ||
| missing: listOfNotExistingItems, | ||
| startTime: Date.now() - 10000 | ||
| startTime: Date.now() - 10000, | ||
| }); | ||
@@ -82,3 +82,3 @@ // starts watching these files and directories | ||
| wp.on("change", function(filePath, mtime, explanation) { | ||
| wp.on("change", (filePath, mtime, explanation) => { | ||
| // filePath: the changed file | ||
@@ -89,3 +89,3 @@ // mtime: last modified time for the changed file | ||
| wp.on("remove", function(filePath, explanation) { | ||
| wp.on("remove", (filePath, explanation) => { | ||
| // filePath: the removed file or directory | ||
@@ -95,3 +95,3 @@ // explanation: textual information how this change was detected | ||
| wp.on("aggregated", function(changes, removals) { | ||
| wp.on("aggregated", (changes, removals) => { | ||
| // changes: a Set of all changed files | ||
@@ -129,3 +129,3 @@ // removals: a Set of all removed files | ||
| // Watchpack.prototype.getTimeInfoEntries() | ||
| var fileTimes = wp.getTimeInfoEntries(); | ||
| const fileTimes = wp.getTimeInfoEntries(); | ||
| // returns a Map with all known time info objects for files and directories | ||
@@ -136,3 +136,3 @@ // similar to collectTimeInfoEntries but returns a single map with all entries | ||
| // Watchpack.prototype.getTimes() | ||
| var fileTimes = wp.getTimes(); | ||
| const fileTimesOld = wp.getTimes(); | ||
| // returns an object with all known change times for files | ||
@@ -139,0 +139,0 @@ // this include timestamps from files not directly watched |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
95722
66.01%17
88.89%2909
68.93%22
120%1
Infinity%