From efbc2ffe6a1ee5ec38a4396305561d6515d0943e Mon Sep 17 00:00:00 2001 From: littleblack111 Date: Fri, 30 Aug 2024 17:21:18 +0800 Subject: node fixes, updates --- node_modules/watchpack/LICENSE | 20 + node_modules/watchpack/README.md | 133 ++++ node_modules/watchpack/lib/DirectoryWatcher.js | 787 ++++++++++++++++++++++++ node_modules/watchpack/lib/LinkResolver.js | 107 ++++ node_modules/watchpack/lib/getWatcherManager.js | 52 ++ node_modules/watchpack/lib/reducePlan.js | 138 +++++ node_modules/watchpack/lib/watchEventSource.js | 333 ++++++++++ node_modules/watchpack/lib/watchpack.js | 393 ++++++++++++ node_modules/watchpack/package.json | 49 ++ 9 files changed, 2012 insertions(+) create mode 100644 node_modules/watchpack/LICENSE create mode 100644 node_modules/watchpack/README.md create mode 100644 node_modules/watchpack/lib/DirectoryWatcher.js create mode 100644 node_modules/watchpack/lib/LinkResolver.js create mode 100644 node_modules/watchpack/lib/getWatcherManager.js create mode 100644 node_modules/watchpack/lib/reducePlan.js create mode 100644 node_modules/watchpack/lib/watchEventSource.js create mode 100644 node_modules/watchpack/lib/watchpack.js create mode 100644 node_modules/watchpack/package.json (limited to 'node_modules/watchpack') diff --git a/node_modules/watchpack/LICENSE b/node_modules/watchpack/LICENSE new file mode 100644 index 00000000..8c11fc72 --- /dev/null +++ b/node_modules/watchpack/LICENSE @@ -0,0 +1,20 @@ +Copyright JS Foundation and other contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/node_modules/watchpack/README.md b/node_modules/watchpack/README.md new file mode 100644 index 00000000..a005f50d --- /dev/null +++ b/node_modules/watchpack/README.md @@ -0,0 +1,133 @@ +# watchpack + +Wrapper library for directory and file watching. + +[![Test](https://github.com/webpack/watchpack/actions/workflows/test.yml/badge.svg)](https://github.com/webpack/watchpack/actions/workflows/test.yml) +[![Codecov](https://codecov.io/gh/webpack/watchpack/graph/badge.svg?token=8xk2OrrxWm)](https://codecov.io/gh/webpack/watchpack) +[![Downloads](https://img.shields.io/npm/dm/watchpack.svg)](https://www.npmjs.com/package/watchpack) + +## Concept + +watchpack high level API doesn't map directly to watchers. Instead a three level architecture ensures that for each directory only a single watcher exists. + +- The high level API requests `DirectoryWatchers` from a `WatcherManager`, which ensures that only a single `DirectoryWatcher` per directory is created. +- A user-faced `Watcher` can be obtained from a `DirectoryWatcher` and provides a filtered view on the `DirectoryWatcher`. +- Reference-counting is used on the `DirectoryWatcher` and `Watcher` to decide when to close them. +- The real watchers are created by the `DirectoryWatcher`. +- Files are never watched directly. This should keep the watcher count low. +- Watching can be started in the past. This way watching can start after file reading. +- Symlinks are not followed, instead the symlink is watched. + +## API + +```javascript +var Watchpack = require("watchpack"); + +var wp = new Watchpack({ + // options: + aggregateTimeout: 1000, + // fire "aggregated" event when after a change for 1000ms no additional change occurred + // aggregated defaults to undefined, which doesn't fire an "aggregated" event + + poll: true, + // poll: true - use polling with the default interval + // poll: 10000 - use polling with an interval of 10s + // poll defaults to undefined, which prefer native watching methods + // Note: enable polling when watching on a network path + // When WATCHPACK_POLLING environment variable is set it will override this option + + followSymlinks: true, + // true: follows symlinks and watches symlinks and real files + // (This makes sense when symlinks has not been resolved yet, comes with a performance hit) + // false (default): watches only specified item they may be real files or symlinks + // (This makes sense when symlinks has already been resolved) + + ignored: "**/.git" + // ignored: "string" - a glob pattern for files or folders that should not be watched + // ignored: ["string", "string"] - multiple glob patterns that should be ignored + // ignored: /regexp/ - a regular expression for files or folders that should not be watched + // ignored: (entry) => boolean - an arbitrary function which must return truthy to ignore an entry + // For all cases expect the arbitrary function the path will have path separator normalized to '/'. + // All subdirectories are ignored too +}); + +// Watchpack.prototype.watch({ +// files: Iterable, +// directories: Iterable, +// missing: Iterable, +// startTime?: number +// }) +wp.watch({ + files: listOfFiles, + directories: listOfDirectories, + missing: listOfNotExistingItems, + startTime: Date.now() - 10000 +}); +// starts watching these files and directories +// calling this again will override the files and directories +// files: can be files or directories, for files: content and existence changes are tracked +// for directories: only existence and timestamp changes are tracked +// directories: only directories, directory content (and content of children, ...) and +// existence changes are tracked. +// assumed to exist, when directory is not found without further information a remove event is emitted +// missing: can be files or directorees, +// only existence changes are tracked +// expected to not exist, no remove event is emitted when not found initially +// files and directories are assumed to exist, when they are not found without further information a remove event is emitted +// missing is assumed to not exist and no remove event is emitted + +wp.on("change", function(filePath, mtime, explanation) { + // filePath: the changed file + // mtime: last modified time for the changed file + // explanation: textual information how this change was detected +}); + +wp.on("remove", function(filePath, explanation) { + // filePath: the removed file or directory + // explanation: textual information how this change was detected +}); + +wp.on("aggregated", function(changes, removals) { + // changes: a Set of all changed files + // removals: a Set of all removed files + // watchpack gives up ownership on these Sets. +}); + +// Watchpack.prototype.pause() +wp.pause(); +// stops emitting events, but keeps watchers open +// next "watch" call can reuse the watchers +// The watcher will keep aggregating events +// which can be received with getAggregated() + +// Watchpack.prototype.close() +wp.close(); +// stops emitting events and closes all watchers + +// Watchpack.prototype.getAggregated(): { changes: Set, removals: Set } +const { changes, removals } = wp.getAggregated(); +// returns the current aggregated info and removes that from the watcher +// The next aggregated event won't include that info and will only emitted +// when futher changes happen +// Can also be used when paused. + +// Watchpack.prototype.collectTimeInfoEntries(fileInfoEntries: Map, directoryInfoEntries: Map) +wp.collectTimeInfoEntries(fileInfoEntries, directoryInfoEntries); +// collects time info objects for all known files and directories +// this include info from files not directly watched +// key: absolute path, value: object with { safeTime, timestamp } +// safeTime: a point in time at which it is safe to say all changes happened before that +// timestamp: only for files, the mtime timestamp of the file + +// Watchpack.prototype.getTimeInfoEntries() +var fileTimes = wp.getTimeInfoEntries(); +// returns a Map with all known time info objects for files and directories +// similar to collectTimeInfoEntries but returns a single map with all entries + +// (deprecated) +// Watchpack.prototype.getTimes() +var fileTimes = wp.getTimes(); +// returns an object with all known change times for files +// this include timestamps from files not directly watched +// key: absolute path, value: timestamp as number +``` diff --git a/node_modules/watchpack/lib/DirectoryWatcher.js b/node_modules/watchpack/lib/DirectoryWatcher.js new file mode 100644 index 00000000..034447de --- /dev/null +++ b/node_modules/watchpack/lib/DirectoryWatcher.js @@ -0,0 +1,787 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const EventEmitter = require("events").EventEmitter; +const fs = require("graceful-fs"); +const path = require("path"); + +const watchEventSource = require("./watchEventSource"); + +const EXISTANCE_ONLY_TIME_ENTRY = Object.freeze({}); + +let FS_ACCURACY = 2000; + +const IS_OSX = require("os").platform() === "darwin"; +const WATCHPACK_POLLING = process.env.WATCHPACK_POLLING; +const FORCE_POLLING = + `${+WATCHPACK_POLLING}` === WATCHPACK_POLLING + ? +WATCHPACK_POLLING + : !!WATCHPACK_POLLING && WATCHPACK_POLLING !== "false"; + +function withoutCase(str) { + return str.toLowerCase(); +} + +function needCalls(times, callback) { + return function() { + if (--times === 0) { + return callback(); + } + }; +} + +class Watcher extends EventEmitter { + constructor(directoryWatcher, filePath, startTime) { + super(); + this.directoryWatcher = directoryWatcher; + this.path = filePath; + this.startTime = startTime && +startTime; + } + + checkStartTime(mtime, initial) { + const startTime = this.startTime; + if (typeof startTime !== "number") return !initial; + return startTime <= mtime; + } + + close() { + this.emit("closed"); + } +} + +class DirectoryWatcher extends EventEmitter { + constructor(watcherManager, directoryPath, options) { + super(); + if (FORCE_POLLING) { + options.poll = FORCE_POLLING; + } + this.watcherManager = watcherManager; + this.options = options; + this.path = directoryPath; + // safeTime is the point in time after which reading is safe to be unchanged + // timestamp is a value that should be compared with another timestamp (mtime) + /** @type {Map} */ + this.filesWithoutCase = new Map(); + this.directories = new Map(); + this.lastWatchEvent = 0; + this.initialScan = true; + this.ignored = options.ignored || (() => false); + this.nestedWatching = false; + this.polledWatching = + typeof options.poll === "number" + ? options.poll + : options.poll + ? 5007 + : false; + this.timeout = undefined; + this.initialScanRemoved = new Set(); + this.initialScanFinished = undefined; + /** @type {Map>} */ + this.watchers = new Map(); + this.parentWatcher = null; + this.refs = 0; + this._activeEvents = new Map(); + this.closed = false; + this.scanning = false; + this.scanAgain = false; + this.scanAgainInitial = false; + + this.createWatcher(); + this.doScan(true); + } + + createWatcher() { + try { + if (this.polledWatching) { + this.watcher = { + close: () => { + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = undefined; + } + } + }; + } else { + if (IS_OSX) { + this.watchInParentDirectory(); + } + this.watcher = watchEventSource.watch(this.path); + this.watcher.on("change", this.onWatchEvent.bind(this)); + this.watcher.on("error", this.onWatcherError.bind(this)); + } + } catch (err) { + this.onWatcherError(err); + } + } + + forEachWatcher(path, fn) { + const watchers = this.watchers.get(withoutCase(path)); + if (watchers !== undefined) { + for (const w of watchers) { + fn(w); + } + } + } + + setMissing(itemPath, initial, type) { + if (this.initialScan) { + this.initialScanRemoved.add(itemPath); + } + + const oldDirectory = this.directories.get(itemPath); + if (oldDirectory) { + if (this.nestedWatching) oldDirectory.close(); + this.directories.delete(itemPath); + + this.forEachWatcher(itemPath, w => w.emit("remove", type)); + if (!initial) { + this.forEachWatcher(this.path, w => + w.emit("change", itemPath, null, type, initial) + ); + } + } + + const oldFile = this.files.get(itemPath); + if (oldFile) { + this.files.delete(itemPath); + const key = withoutCase(itemPath); + const count = this.filesWithoutCase.get(key) - 1; + if (count <= 0) { + this.filesWithoutCase.delete(key); + this.forEachWatcher(itemPath, w => w.emit("remove", type)); + } else { + this.filesWithoutCase.set(key, count); + } + + if (!initial) { + this.forEachWatcher(this.path, w => + w.emit("change", itemPath, null, type, initial) + ); + } + } + } + + setFileTime(filePath, mtime, initial, ignoreWhenEqual, type) { + const now = Date.now(); + + if (this.ignored(filePath)) return; + + const old = this.files.get(filePath); + + let safeTime, accuracy; + if (initial) { + safeTime = Math.min(now, mtime) + FS_ACCURACY; + accuracy = FS_ACCURACY; + } else { + safeTime = now; + accuracy = 0; + + if (old && old.timestamp === mtime && mtime + FS_ACCURACY < now) { + // We are sure that mtime is untouched + // This can be caused by some file attribute change + // e. g. when access time has been changed + // but the file content is untouched + return; + } + } + + if (ignoreWhenEqual && old && old.timestamp === mtime) return; + + this.files.set(filePath, { + safeTime, + accuracy, + timestamp: mtime + }); + + if (!old) { + const key = withoutCase(filePath); + const count = this.filesWithoutCase.get(key); + this.filesWithoutCase.set(key, (count || 0) + 1); + if (count !== undefined) { + // There is already a file with case-insensitive-equal name + // On a case-insensitive filesystem we may miss the renaming + // when only casing is changed. + // To be sure that our information is correct + // we trigger a rescan here + this.doScan(false); + } + + this.forEachWatcher(filePath, w => { + if (!initial || w.checkStartTime(safeTime, initial)) { + w.emit("change", mtime, type); + } + }); + } else if (!initial) { + this.forEachWatcher(filePath, w => w.emit("change", mtime, type)); + } + this.forEachWatcher(this.path, w => { + if (!initial || w.checkStartTime(safeTime, initial)) { + w.emit("change", filePath, safeTime, type, initial); + } + }); + } + + setDirectory(directoryPath, birthtime, initial, type) { + if (this.ignored(directoryPath)) return; + if (directoryPath === this.path) { + if (!initial) { + this.forEachWatcher(this.path, w => + w.emit("change", directoryPath, birthtime, type, initial) + ); + } + } else { + const old = this.directories.get(directoryPath); + if (!old) { + const now = Date.now(); + + if (this.nestedWatching) { + this.createNestedWatcher(directoryPath); + } else { + this.directories.set(directoryPath, true); + } + + let safeTime; + if (initial) { + safeTime = Math.min(now, birthtime) + FS_ACCURACY; + } else { + safeTime = now; + } + + this.forEachWatcher(directoryPath, w => { + if (!initial || w.checkStartTime(safeTime, false)) { + w.emit("change", birthtime, type); + } + }); + this.forEachWatcher(this.path, w => { + if (!initial || w.checkStartTime(safeTime, initial)) { + w.emit("change", directoryPath, safeTime, type, initial); + } + }); + } + } + } + + createNestedWatcher(directoryPath) { + const watcher = this.watcherManager.watchDirectory(directoryPath, 1); + watcher.on("change", (filePath, mtime, type, initial) => { + this.forEachWatcher(this.path, w => { + if (!initial || w.checkStartTime(mtime, initial)) { + w.emit("change", filePath, mtime, type, initial); + } + }); + }); + this.directories.set(directoryPath, watcher); + } + + setNestedWatching(flag) { + if (this.nestedWatching !== !!flag) { + this.nestedWatching = !!flag; + if (this.nestedWatching) { + for (const directory of this.directories.keys()) { + this.createNestedWatcher(directory); + } + } else { + for (const [directory, watcher] of this.directories) { + watcher.close(); + this.directories.set(directory, true); + } + } + } + } + + watch(filePath, startTime) { + const key = withoutCase(filePath); + let watchers = this.watchers.get(key); + if (watchers === undefined) { + watchers = new Set(); + this.watchers.set(key, watchers); + } + this.refs++; + const watcher = new Watcher(this, filePath, startTime); + watcher.on("closed", () => { + if (--this.refs <= 0) { + this.close(); + return; + } + watchers.delete(watcher); + if (watchers.size === 0) { + this.watchers.delete(key); + if (this.path === filePath) this.setNestedWatching(false); + } + }); + watchers.add(watcher); + let safeTime; + if (filePath === this.path) { + this.setNestedWatching(true); + safeTime = this.lastWatchEvent; + for (const entry of this.files.values()) { + fixupEntryAccuracy(entry); + safeTime = Math.max(safeTime, entry.safeTime); + } + } else { + const entry = this.files.get(filePath); + if (entry) { + fixupEntryAccuracy(entry); + safeTime = entry.safeTime; + } else { + safeTime = 0; + } + } + if (safeTime) { + if (safeTime >= startTime) { + process.nextTick(() => { + if (this.closed) return; + if (filePath === this.path) { + watcher.emit( + "change", + filePath, + safeTime, + "watch (outdated on attach)", + true + ); + } else { + watcher.emit( + "change", + safeTime, + "watch (outdated on attach)", + true + ); + } + }); + } + } else if (this.initialScan) { + if (this.initialScanRemoved.has(filePath)) { + process.nextTick(() => { + if (this.closed) return; + watcher.emit("remove"); + }); + } + } else if ( + filePath !== this.path && + !this.directories.has(filePath) && + watcher.checkStartTime(this.initialScanFinished, false) + ) { + process.nextTick(() => { + if (this.closed) return; + watcher.emit("initial-missing", "watch (missing on attach)"); + }); + } + return watcher; + } + + onWatchEvent(eventType, filename) { + if (this.closed) return; + if (!filename) { + // In some cases no filename is provided + // This seem to happen on windows + // So some event happened but we don't know which file is affected + // We have to do a full scan of the directory + this.doScan(false); + return; + } + + const filePath = path.join(this.path, filename); + if (this.ignored(filePath)) return; + + if (this._activeEvents.get(filename) === undefined) { + this._activeEvents.set(filename, false); + const checkStats = () => { + if (this.closed) return; + this._activeEvents.set(filename, false); + fs.lstat(filePath, (err, stats) => { + if (this.closed) return; + if (this._activeEvents.get(filename) === true) { + process.nextTick(checkStats); + return; + } + this._activeEvents.delete(filename); + // ENOENT happens when the file/directory doesn't exist + // EPERM happens when the containing directory doesn't exist + if (err) { + if ( + err.code !== "ENOENT" && + err.code !== "EPERM" && + err.code !== "EBUSY" + ) { + 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"); + } + } + } + } + this.lastWatchEvent = Date.now(); + if (!stats) { + this.setMissing(filePath, false, eventType); + } else if (stats.isDirectory()) { + this.setDirectory( + filePath, + +stats.birthtime || 1, + false, + eventType + ); + } else if (stats.isFile() || stats.isSymbolicLink()) { + if (stats.mtime) { + ensureFsAccuracy(stats.mtime); + } + this.setFileTime( + filePath, + +stats.mtime || +stats.ctime || 1, + false, + false, + eventType + ); + } + }); + }; + process.nextTick(checkStats); + } else { + this._activeEvents.set(filename, true); + } + } + + onWatcherError(err) { + if (this.closed) return; + if (err) { + if (err.code !== "EPERM" && err.code !== "ENOENT") { + console.error("Watchpack Error (watcher): " + err); + } + this.onDirectoryRemoved("watch error"); + } + } + + onStatsError(err) { + if (err) { + console.error("Watchpack Error (stats): " + err); + } + } + + onScanError(err) { + if (err) { + console.error("Watchpack Error (initial scan): " + err); + } + this.onScanFinished(); + } + + onScanFinished() { + if (this.polledWatching) { + this.timeout = setTimeout(() => { + if (this.closed) return; + this.doScan(false); + }, this.polledWatching); + } + } + + onDirectoryRemoved(reason) { + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + this.watchInParentDirectory(); + const type = `directory-removed (${reason})`; + for (const directory of this.directories.keys()) { + this.setMissing(directory, null, type); + } + for (const file of this.files.keys()) { + this.setMissing(file, null, type); + } + } + + watchInParentDirectory() { + if (!this.parentWatcher) { + const parentDir = path.dirname(this.path); + // avoid watching in the root directory + // removing directories in the root directory is not supported + if (path.dirname(parentDir) === parentDir) return; + + this.parentWatcher = this.watcherManager.watchFile(this.path, 1); + this.parentWatcher.on("change", (mtime, type) => { + if (this.closed) return; + + // On non-osx platforms we don't need this watcher to detect + // directory removal, as an EPERM error indicates that + if ((!IS_OSX || this.polledWatching) && this.parentWatcher) { + this.parentWatcher.close(); + this.parentWatcher = null; + } + // Try to create the watcher when parent directory is found + if (!this.watcher) { + this.createWatcher(); + this.doScan(false); + + // directory was created so we emit an event + this.forEachWatcher(this.path, w => + w.emit("change", this.path, mtime, type, false) + ); + } + }); + this.parentWatcher.on("remove", () => { + this.onDirectoryRemoved("parent directory removed"); + }); + } + } + + doScan(initial) { + if (this.scanning) { + if (this.scanAgain) { + if (!initial) this.scanAgainInitial = false; + } else { + this.scanAgain = true; + this.scanAgainInitial = initial; + } + return; + } + this.scanning = true; + if (this.timeout) { + clearTimeout(this.timeout); + this.timeout = undefined; + } + process.nextTick(() => { + if (this.closed) return; + fs.readdir(this.path, (err, items) => { + if (this.closed) return; + if (err) { + if (err.code === "ENOENT" || err.code === "EPERM") { + this.onDirectoryRemoved("scan readdir failed"); + } else { + this.onScanError(err); + } + this.initialScan = false; + this.initialScanFinished = Date.now(); + if (initial) { + for (const watchers of this.watchers.values()) { + for (const watcher of watchers) { + if (watcher.checkStartTime(this.initialScanFinished, false)) { + watcher.emit( + "initial-missing", + "scan (parent directory missing in initial scan)" + ); + } + } + } + } + if (this.scanAgain) { + this.scanAgain = false; + this.doScan(this.scanAgainInitial); + } else { + this.scanning = false; + } + return; + } + const itemPaths = new Set( + items.map(item => path.join(this.path, item.normalize("NFC"))) + ); + for (const file of this.files.keys()) { + if (!itemPaths.has(file)) { + this.setMissing(file, initial, "scan (missing)"); + } + } + for (const directory of this.directories.keys()) { + if (!itemPaths.has(directory)) { + this.setMissing(directory, initial, "scan (missing)"); + } + } + if (this.scanAgain) { + // Early repeat of scan + this.scanAgain = false; + this.doScan(initial); + return; + } + const itemFinished = needCalls(itemPaths.size + 1, () => { + if (this.closed) return; + this.initialScan = false; + this.initialScanRemoved = null; + this.initialScanFinished = Date.now(); + if (initial) { + const missingWatchers = new Map(this.watchers); + missingWatchers.delete(withoutCase(this.path)); + for (const item of itemPaths) { + missingWatchers.delete(withoutCase(item)); + } + for (const watchers of missingWatchers.values()) { + for (const watcher of watchers) { + if (watcher.checkStartTime(this.initialScanFinished, false)) { + watcher.emit( + "initial-missing", + "scan (missing in initial scan)" + ); + } + } + } + } + if (this.scanAgain) { + this.scanAgain = false; + this.doScan(this.scanAgainInitial); + } else { + this.scanning = false; + this.onScanFinished(); + } + }); + for (const itemPath of itemPaths) { + fs.lstat(itemPath, (err2, stats) => { + if (this.closed) return; + if (err2) { + if ( + err2.code === "ENOENT" || + err2.code === "EPERM" || + err2.code === "EACCES" || + err2.code === "EBUSY" + ) { + this.setMissing(itemPath, initial, "scan (" + err2.code + ")"); + } else { + this.onScanError(err2); + } + itemFinished(); + return; + } + if (stats.isFile() || stats.isSymbolicLink()) { + if (stats.mtime) { + ensureFsAccuracy(stats.mtime); + } + this.setFileTime( + itemPath, + +stats.mtime || +stats.ctime || 1, + initial, + true, + "scan (file)" + ); + } else if (stats.isDirectory()) { + if (!initial || !this.directories.has(itemPath)) + this.setDirectory( + itemPath, + +stats.birthtime || 1, + initial, + "scan (dir)" + ); + } + itemFinished(); + }); + } + itemFinished(); + }); + }); + } + + getTimes() { + const obj = Object.create(null); + let safeTime = this.lastWatchEvent; + for (const [file, entry] of this.files) { + fixupEntryAccuracy(entry); + safeTime = Math.max(safeTime, entry.safeTime); + obj[file] = Math.max(entry.safeTime, entry.timestamp); + } + if (this.nestedWatching) { + for (const w of this.directories.values()) { + const times = w.directoryWatcher.getTimes(); + for (const file of Object.keys(times)) { + const time = times[file]; + safeTime = Math.max(safeTime, time); + obj[file] = time; + } + } + obj[this.path] = safeTime; + } + if (!this.initialScan) { + for (const watchers of this.watchers.values()) { + for (const watcher of watchers) { + const path = watcher.path; + if (!Object.prototype.hasOwnProperty.call(obj, path)) { + obj[path] = null; + } + } + } + } + return obj; + } + + collectTimeInfoEntries(fileTimestamps, directoryTimestamps) { + let safeTime = this.lastWatchEvent; + for (const [file, entry] of this.files) { + fixupEntryAccuracy(entry); + safeTime = Math.max(safeTime, entry.safeTime); + fileTimestamps.set(file, entry); + } + if (this.nestedWatching) { + for (const w of this.directories.values()) { + safeTime = Math.max( + safeTime, + w.directoryWatcher.collectTimeInfoEntries( + fileTimestamps, + directoryTimestamps + ) + ); + } + fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); + directoryTimestamps.set(this.path, { + safeTime + }); + } else { + for (const dir of this.directories.keys()) { + // No additional info about this directory + // but maybe another DirectoryWatcher has info + fileTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY); + if (!directoryTimestamps.has(dir)) + directoryTimestamps.set(dir, EXISTANCE_ONLY_TIME_ENTRY); + } + fileTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); + directoryTimestamps.set(this.path, EXISTANCE_ONLY_TIME_ENTRY); + } + if (!this.initialScan) { + for (const watchers of this.watchers.values()) { + for (const watcher of watchers) { + const path = watcher.path; + if (!fileTimestamps.has(path)) { + fileTimestamps.set(path, null); + } + } + } + } + return safeTime; + } + + close() { + this.closed = true; + this.initialScan = false; + if (this.watcher) { + this.watcher.close(); + this.watcher = null; + } + if (this.nestedWatching) { + for (const w of this.directories.values()) { + w.close(); + } + this.directories.clear(); + } + if (this.parentWatcher) { + this.parentWatcher.close(); + this.parentWatcher = null; + } + this.emit("closed"); + } +} + +module.exports = DirectoryWatcher; +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; +} diff --git a/node_modules/watchpack/lib/LinkResolver.js b/node_modules/watchpack/lib/LinkResolver.js new file mode 100644 index 00000000..daea90bf --- /dev/null +++ b/node_modules/watchpack/lib/LinkResolver.js @@ -0,0 +1,107 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const fs = require("fs"); +const path = require("path"); + +// macOS, Linux, and Windows all rely on these errors +const EXPECTED_ERRORS = new Set(["EINVAL", "ENOENT"]); + +// On Windows there is also this error in some cases +if (process.platform === "win32") EXPECTED_ERRORS.add("UNKNOWN"); + +class LinkResolver { + constructor() { + this.cache = new Map(); + } + + /** + * @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) + */ + resolve(file) { + const cacheEntry = this.cache.get(file); + if (cacheEntry !== undefined) { + return cacheEntry; + } + const parent = path.dirname(file); + if (parent === file) { + // At root of filesystem there can't be a link + const result = Object.freeze([file]); + this.cache.set(file, result); + return result; + } + // resolve the parent directory to find links there and get the real path + const parentResolved = this.resolve(parent); + let realFile = file; + + // is the parent directory really somewhere else? + if (parentResolved[0] !== parent) { + // get the real location of file + const basename = path.basename(file); + realFile = path.resolve(parentResolved[0], basename); + } + // try to read the link content + try { + const linkContent = fs.readlinkSync(realFile); + + // resolve the link content relative to the parent directory + const resolvedLink = path.resolve(parentResolved[0], linkContent); + + // recursive resolve the link content for more links in the structure + const linkResolved = this.resolve(resolvedLink); + + // merge parent and link resolve results + let result; + if (linkResolved.length > 1 && parentResolved.length > 1) { + // when both contain links we need to duplicate them with a Set + const resultSet = new Set(linkResolved); + // add the link + resultSet.add(realFile); + // add all symlinks of the parent + for (let i = 1; i < parentResolved.length; i++) { + resultSet.add(parentResolved[i]); + } + result = Object.freeze(Array.from(resultSet)); + } else if (parentResolved.length > 1) { + // we have links in the parent but not for the link content location + result = parentResolved.slice(); + result[0] = linkResolved[0]; + // add the link + result.push(realFile); + Object.freeze(result); + } else if (linkResolved.length > 1) { + // we can return the link content location result + result = linkResolved.slice(); + // add the link + result.push(realFile); + Object.freeze(result); + } else { + // neither link content location nor parent have links + // this link is the only link here + result = Object.freeze([ + // the resolve real location + linkResolved[0], + // add the link + realFile + ]); + } + this.cache.set(file, result); + return result; + } catch (e) { + if (!EXPECTED_ERRORS.has(e.code)) { + throw e; + } + // no link + const result = parentResolved.slice(); + result[0] = realFile; + Object.freeze(result); + this.cache.set(file, result); + return result; + } + } +} +module.exports = LinkResolver; diff --git a/node_modules/watchpack/lib/getWatcherManager.js b/node_modules/watchpack/lib/getWatcherManager.js new file mode 100644 index 00000000..e0dcd6ac --- /dev/null +++ b/node_modules/watchpack/lib/getWatcherManager.js @@ -0,0 +1,52 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const path = require("path"); +const DirectoryWatcher = require("./DirectoryWatcher"); + +class WatcherManager { + constructor(options) { + this.options = options; + this.directoryWatchers = new Map(); + } + + getDirectoryWatcher(directory) { + const watcher = this.directoryWatchers.get(directory); + if (watcher === undefined) { + const newWatcher = new DirectoryWatcher(this, directory, this.options); + this.directoryWatchers.set(directory, newWatcher); + newWatcher.on("closed", () => { + this.directoryWatchers.delete(directory); + }); + return newWatcher; + } + return watcher; + } + + watchFile(p, startTime) { + const directory = path.dirname(p); + if (directory === p) return null; + return this.getDirectoryWatcher(directory).watch(p, startTime); + } + + watchDirectory(directory, startTime) { + return this.getDirectoryWatcher(directory).watch(directory, startTime); + } +} + +const watcherManagers = new WeakMap(); +/** + * @param {object} options options + * @returns {WatcherManager} the watcher manager + */ +module.exports = options => { + const watcherManager = watcherManagers.get(options); + if (watcherManager !== undefined) return watcherManager; + const newWatcherManager = new WatcherManager(options); + watcherManagers.set(options, newWatcherManager); + return newWatcherManager; +}; +module.exports.WatcherManager = WatcherManager; diff --git a/node_modules/watchpack/lib/reducePlan.js b/node_modules/watchpack/lib/reducePlan.js new file mode 100644 index 00000000..b281ed69 --- /dev/null +++ b/node_modules/watchpack/lib/reducePlan.js @@ -0,0 +1,138 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const path = require("path"); + +/** + * @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 + */ + +/** + * @template T + * @param {Map>} the new plan + */ +module.exports = (plan, limit) => { + const treeMap = new Map(); + // Convert to tree + for (const [filePath, value] of plan) { + treeMap.set(filePath, { + filePath, + parent: undefined, + children: undefined, + entries: 1, + active: true, + value + }); + } + let currentCount = treeMap.size; + // Create parents and calculate sum of entries + for (const node of treeMap.values()) { + const parentPath = path.dirname(node.filePath); + if (parentPath !== node.filePath) { + let parent = treeMap.get(parentPath); + if (parent === undefined) { + parent = { + filePath: parentPath, + parent: undefined, + children: [node], + entries: node.entries, + active: false, + value: undefined + }; + treeMap.set(parentPath, parent); + node.parent = parent; + } else { + node.parent = parent; + if (parent.children === undefined) { + parent.children = [node]; + } else { + parent.children.push(node); + } + do { + parent.entries += node.entries; + parent = parent.parent; + } while (parent); + } + } + } + // Reduce until limit reached + while (currentCount > limit) { + // Select node that helps reaching the limit most effectively without overmerging + const overLimit = currentCount - limit; + let bestNode = undefined; + let bestCost = Infinity; + for (const node of treeMap.values()) { + if (node.entries <= 1 || !node.children || !node.parent) continue; + if (node.children.length === 0) continue; + if (node.children.length === 1 && !node.value) continue; + // Try to select the node with has just a bit more entries than we need to reduce + // When just a bit more is over 30% over the limit, + // also consider just a bit less entries then we need to reduce + const cost = + node.entries - 1 >= overLimit + ? node.entries - 1 - overLimit + : overLimit - node.entries + 1 + limit * 0.3; + if (cost < bestCost) { + bestNode = node; + bestCost = cost; + } + } + if (!bestNode) break; + // Merge all children + const reduction = bestNode.entries - 1; + bestNode.active = true; + bestNode.entries = 1; + currentCount -= reduction; + let parent = bestNode.parent; + while (parent) { + parent.entries -= reduction; + parent = parent.parent; + } + const queue = new Set(bestNode.children); + for (const node of queue) { + node.active = false; + node.entries = 0; + if (node.children) { + for (const child of node.children) queue.add(child); + } + } + } + // Write down new plan + const newPlan = new Map(); + for (const rootNode of treeMap.values()) { + if (!rootNode.active) continue; + const map = new Map(); + const queue = new Set([rootNode]); + for (const node of queue) { + if (node.active && node !== rootNode) continue; + if (node.value) { + if (Array.isArray(node.value)) { + for (const item of node.value) { + map.set(item, node.filePath); + } + } else { + map.set(node.value, node.filePath); + } + } + if (node.children) { + for (const child of node.children) { + queue.add(child); + } + } + } + newPlan.set(rootNode.filePath, map); + } + return newPlan; +}; diff --git a/node_modules/watchpack/lib/watchEventSource.js b/node_modules/watchpack/lib/watchEventSource.js new file mode 100644 index 00000000..6df9ab0d --- /dev/null +++ b/node_modules/watchpack/lib/watchEventSource.js @@ -0,0 +1,333 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { EventEmitter } = require("events"); +const reducePlan = require("./reducePlan"); + +const IS_OSX = require("os").platform() === "darwin"; +const IS_WIN = require("os").platform() === "win32"; +const SUPPORTS_RECURSIVE_WATCHING = IS_OSX || IS_WIN; + +const watcherLimit = + +process.env.WATCHPACK_WATCHER_LIMIT || (IS_OSX ? 2000 : 10000); + +const recursiveWatcherLogging = !!process.env + .WATCHPACK_RECURSIVE_WATCHER_LOGGING; + +let isBatch = false; +let watcherCount = 0; + +/** @type {Map} */ +const pendingWatchers = new Map(); + +/** @type {Map} */ +const recursiveWatchers = new Map(); + +/** @type {Map} */ +const directWatchers = new Map(); + +/** @type {Map} */ +const underlyingWatcher = new Map(); + +class DirectWatcher { + constructor(filePath) { + this.filePath = filePath; + this.watchers = new Set(); + this.watcher = undefined; + try { + const watcher = fs.watch(filePath); + this.watcher = watcher; + watcher.on("change", (type, filename) => { + for (const w of this.watchers) { + w.emit("change", type, filename); + } + }); + watcher.on("error", error => { + for (const w of this.watchers) { + w.emit("error", error); + } + }); + } catch (err) { + process.nextTick(() => { + for (const w of this.watchers) { + w.emit("error", err); + } + }); + } + watcherCount++; + } + + add(watcher) { + underlyingWatcher.set(watcher, this); + this.watchers.add(watcher); + } + + remove(watcher) { + this.watchers.delete(watcher); + if (this.watchers.size === 0) { + directWatchers.delete(this.filePath); + watcherCount--; + if (this.watcher) this.watcher.close(); + } + } + + getWatchers() { + return this.watchers; + } +} + +class RecursiveWatcher { + constructor(rootPath) { + this.rootPath = rootPath; + /** @type {Map} */ + this.mapWatcherToPath = new Map(); + /** @type {Map>} */ + this.mapPathToWatchers = new Map(); + this.watcher = undefined; + try { + const watcher = fs.watch(rootPath, { + recursive: true + }); + this.watcher = watcher; + watcher.on("change", (type, filename) => { + if (!filename) { + if (recursiveWatcherLogging) { + process.stderr.write( + `[watchpack] dispatch ${type} event in recursive watcher (${this.rootPath}) to all watchers\n` + ); + } + for (const w of this.mapWatcherToPath.keys()) { + w.emit("change", type); + } + } else { + const dir = path.dirname(filename); + const watchers = this.mapPathToWatchers.get(dir); + if (recursiveWatcherLogging) { + process.stderr.write( + `[watchpack] dispatch ${type} event in recursive watcher (${ + this.rootPath + }) for '${filename}' to ${ + watchers ? watchers.size : 0 + } watchers\n` + ); + } + if (watchers === undefined) return; + for (const w of watchers) { + w.emit("change", type, path.basename(filename)); + } + } + }); + watcher.on("error", error => { + for (const w of this.mapWatcherToPath.keys()) { + w.emit("error", error); + } + }); + } catch (err) { + process.nextTick(() => { + for (const w of this.mapWatcherToPath.keys()) { + w.emit("error", err); + } + }); + } + watcherCount++; + if (recursiveWatcherLogging) { + process.stderr.write( + `[watchpack] created recursive watcher at ${rootPath}\n` + ); + } + } + + add(filePath, watcher) { + underlyingWatcher.set(watcher, this); + const subpath = filePath.slice(this.rootPath.length + 1) || "."; + this.mapWatcherToPath.set(watcher, subpath); + const set = this.mapPathToWatchers.get(subpath); + if (set === undefined) { + const newSet = new Set(); + newSet.add(watcher); + this.mapPathToWatchers.set(subpath, newSet); + } else { + set.add(watcher); + } + } + + remove(watcher) { + const subpath = this.mapWatcherToPath.get(watcher); + if (!subpath) return; + this.mapWatcherToPath.delete(watcher); + const set = this.mapPathToWatchers.get(subpath); + set.delete(watcher); + if (set.size === 0) { + this.mapPathToWatchers.delete(subpath); + } + if (this.mapWatcherToPath.size === 0) { + recursiveWatchers.delete(this.rootPath); + watcherCount--; + if (this.watcher) this.watcher.close(); + if (recursiveWatcherLogging) { + process.stderr.write( + `[watchpack] closed recursive watcher at ${this.rootPath}\n` + ); + } + } + } + + getWatchers() { + return this.mapWatcherToPath; + } +} + +class Watcher extends EventEmitter { + close() { + if (pendingWatchers.has(this)) { + pendingWatchers.delete(this); + return; + } + const watcher = underlyingWatcher.get(this); + watcher.remove(this); + underlyingWatcher.delete(this); + } +} + +const createDirectWatcher = filePath => { + const existing = directWatchers.get(filePath); + if (existing !== undefined) return existing; + const w = new DirectWatcher(filePath); + directWatchers.set(filePath, w); + return w; +}; + +const createRecursiveWatcher = rootPath => { + const existing = recursiveWatchers.get(rootPath); + if (existing !== undefined) return existing; + const w = new RecursiveWatcher(rootPath); + recursiveWatchers.set(rootPath, w); + return w; +}; + +const execute = () => { + /** @type {Map} */ + const map = new Map(); + const addWatcher = (watcher, filePath) => { + const entry = map.get(filePath); + if (entry === undefined) { + map.set(filePath, watcher); + } else if (Array.isArray(entry)) { + entry.push(watcher); + } else { + map.set(filePath, [entry, watcher]); + } + }; + for (const [watcher, filePath] of pendingWatchers) { + addWatcher(watcher, filePath); + } + pendingWatchers.clear(); + + // Fast case when we are not reaching the limit + if (!SUPPORTS_RECURSIVE_WATCHING || watcherLimit - watcherCount >= map.size) { + // Create watchers for all entries in the map + for (const [filePath, entry] of map) { + const w = createDirectWatcher(filePath); + if (Array.isArray(entry)) { + for (const item of entry) w.add(item); + } else { + w.add(entry); + } + } + return; + } + + // Reconsider existing watchers to improving watch plan + for (const watcher of recursiveWatchers.values()) { + for (const [w, subpath] of watcher.getWatchers()) { + addWatcher(w, path.join(watcher.rootPath, subpath)); + } + } + for (const watcher of directWatchers.values()) { + for (const w of watcher.getWatchers()) { + addWatcher(w, watcher.filePath); + } + } + + // Merge map entries to keep watcher limit + // Create a 10% buffer to be able to enter fast case more often + const plan = reducePlan(map, watcherLimit * 0.9); + + // Update watchers for all entries in the map + for (const [filePath, entry] of plan) { + if (entry.size === 1) { + for (const [watcher, filePath] of entry) { + const w = createDirectWatcher(filePath); + const old = underlyingWatcher.get(watcher); + if (old === w) continue; + w.add(watcher); + if (old !== undefined) old.remove(watcher); + } + } else { + const filePaths = new Set(entry.values()); + if (filePaths.size > 1) { + const w = createRecursiveWatcher(filePath); + for (const [watcher, watcherPath] of entry) { + const old = underlyingWatcher.get(watcher); + if (old === w) continue; + w.add(watcherPath, watcher); + if (old !== undefined) old.remove(watcher); + } + } else { + for (const filePath of filePaths) { + const w = createDirectWatcher(filePath); + for (const watcher of entry.keys()) { + const old = underlyingWatcher.get(watcher); + if (old === w) continue; + w.add(watcher); + if (old !== undefined) old.remove(watcher); + } + } + } + } + } +}; + +exports.watch = filePath => { + const watcher = new Watcher(); + // Find an existing watcher + const directWatcher = directWatchers.get(filePath); + if (directWatcher !== undefined) { + directWatcher.add(watcher); + return watcher; + } + let current = filePath; + for (;;) { + const recursiveWatcher = recursiveWatchers.get(current); + if (recursiveWatcher !== undefined) { + recursiveWatcher.add(filePath, watcher); + return watcher; + } + const parent = path.dirname(current); + if (parent === current) break; + current = parent; + } + // Queue up watcher for creation + pendingWatchers.set(watcher, filePath); + if (!isBatch) execute(); + return watcher; +}; + +exports.batch = fn => { + isBatch = true; + try { + fn(); + } finally { + isBatch = false; + execute(); + } +}; + +exports.getNumberOfWatchers = () => { + return watcherCount; +}; diff --git a/node_modules/watchpack/lib/watchpack.js b/node_modules/watchpack/lib/watchpack.js new file mode 100644 index 00000000..0b567fce --- /dev/null +++ b/node_modules/watchpack/lib/watchpack.js @@ -0,0 +1,393 @@ +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +"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; diff --git a/node_modules/watchpack/package.json b/node_modules/watchpack/package.json new file mode 100644 index 00000000..997e6064 --- /dev/null +++ b/node_modules/watchpack/package.json @@ -0,0 +1,49 @@ +{ + "name": "watchpack", + "version": "2.4.2", + "description": "", + "main": "./lib/watchpack.js", + "directories": { + "test": "test" + }, + "files": [ + "lib/" + ], + "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" + }, + "repository": { + "type": "git", + "url": "https://github.com/webpack/watchpack.git" + }, + "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", + "rimraf": "^2.6.2", + "should": "^8.3.1", + "write-file-atomic": "^3.0.1" + }, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } +} -- cgit v1.2.3