Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

watchpack

Package Overview
Dependencies
Maintainers
2
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

watchpack - npm Package Compare versions

Comparing version
2.4.4
to
2.5.0
+554
lib/index.js
/*
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");
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;

@@ -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;

@@ -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;
};

@@ -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;
/*
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");
{
"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"

@@ -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