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
6
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.5.1
to
2.5.2
+142
lib/util/globToRegExp.js
/*
MIT License http://www.opensource.org/licenses/mit-license.php
Author Haijie Xie @hai-x
*/
"use strict";
// Based on https://github.com/fitzgen/glob-to-regexp (MIT)
// Specialized for watchpack: `extended` and `globstar` always enabled.
// Returns the regexp source without `^`/`$` anchors.
const CC_EXCLAMATION = 33; // "!"
const CC_DOLLAR = 36; // "$"
const CC_LEFT_PARENTHESIS = 40; // "("
const CC_RIGHT_PARENTHESIS = 41; // ")"
const CC_ASTERISK = 42; // "*"
const CC_PLUS = 43; // "+"
const CC_COMMA = 44; // ","
const CC_DOT = 46; // "."
const CC_SLASH = 47; // "/"
const CC_EQUAL = 61; // "="
const CC_QUESTION_MARK = 63; // "?"
const CC_LEFT_BRACKET = 91; // "["
const CC_RIGHT_BRACKET = 93; // "]"
const CC_CARET = 94; // "^"
const CC_LEFT_BRACE = 123; // "{"
const CC_PIPE = 124; // "|"
const CC_RIGHT_BRACE = 125; // "}"
/**
* @param {string} glob glob pattern
* @returns {string} regexp source without anchors
*/
module.exports = (glob) => {
if (typeof glob !== "string") {
throw new TypeError("Expected a string");
}
const len = glob.length;
let reStr = "";
let inGroup = false;
// Start of the current run of literal characters, copied with one slice
let literalStart = 0;
for (let i = 0; i < len; i++) {
const cc = glob.charCodeAt(i);
const tokenStart = i;
let mapped;
switch (cc) {
case CC_SLASH:
mapped = "\\/";
break;
case CC_DOLLAR:
mapped = "\\$";
break;
case CC_CARET:
mapped = "\\^";
break;
case CC_PLUS:
mapped = "\\+";
break;
case CC_DOT:
mapped = "\\.";
break;
case CC_LEFT_PARENTHESIS:
mapped = "\\(";
break;
case CC_RIGHT_PARENTHESIS:
mapped = "\\)";
break;
case CC_EQUAL:
mapped = "\\=";
break;
case CC_EXCLAMATION:
mapped = "\\!";
break;
case CC_PIPE:
mapped = "\\|";
break;
case CC_QUESTION_MARK:
mapped = ".";
break;
case CC_LEFT_BRACKET:
mapped = "[";
break;
case CC_RIGHT_BRACKET:
mapped = "]";
break;
case CC_LEFT_BRACE:
inGroup = true;
mapped = "(";
break;
case CC_RIGHT_BRACE:
inGroup = false;
mapped = ")";
break;
case CC_COMMA:
mapped = inGroup ? "|" : "\\,";
break;
case CC_ASTERISK: {
const atStart = i === 0;
const afterSlash = !atStart && glob.charCodeAt(i - 1) === CC_SLASH;
let starCount = 1;
while (i + 1 < len && glob.charCodeAt(i + 1) === CC_ASTERISK) {
starCount++;
i++;
}
const atEnd = i + 1 === len;
const beforeSlash = !atEnd && glob.charCodeAt(i + 1) === CC_SLASH;
if (
starCount > 1 &&
(atStart || afterSlash) &&
(atEnd || beforeSlash)
) {
// Globstar segment, matches zero or more path segments
mapped = "((?:[^/]*(?:\\/|$))*)";
i++; // Move over the "/"
} else {
// Not a globstar, matches one path segment
mapped = "([^/]*)";
}
break;
}
default:
// Literal character, extend the current run
continue;
}
if (literalStart < tokenStart) {
reStr += glob.slice(literalStart, tokenStart);
}
reStr += mapped;
literalStart = i + 1;
}
if (literalStart < len) {
reStr += literalStart === 0 ? glob : glob.slice(literalStart);
}
return reStr;
};
declare function _exports(glob: string): string;
export = _exports;
+182
-25

@@ -31,3 +31,3 @@ /*

const { WATCHPACK_POLLING } = process.env;
const { WATCHPACK_POLLING, WATCHPACK_RETRIES } = process.env;
const FORCE_POLLING =

@@ -39,2 +39,17 @@ // @ts-expect-error avoid additional checks

// Number of retries (and delay between retries, in ms) when an fs operation
// returns EBUSY. EBUSY is transient on Windows when an AV scanner, indexer,
// or another process briefly locks a file; retrying avoids incorrectly
// reporting the file as removed (see webpack/watchpack#223, #44).
//
// Configurable via `WATCHPACK_RETRIES` env var: an integer >= 0, or "false"
// to disable retries entirely. Unset / invalid values fall back to 3.
const BUSY_RETRIES = (() => {
if (WATCHPACK_RETRIES === undefined) return 3;
if (WATCHPACK_RETRIES === "false") return 0;
const n = Number(WATCHPACK_RETRIES);
return Number.isFinite(n) && n >= 0 ? Math.floor(n) : 3;
})();
const BUSY_RETRY_DELAY = 100;
/**

@@ -83,2 +98,34 @@ * @param {string} str string

/**
* Call `fs.lstat` with retries on EBUSY. Transient EBUSY errors are common
* on Windows when another process (AV scanner, indexer, editor) holds an
* open handle on the file. See webpack/watchpack#223, #44.
*
* The retry count is taken from the `WATCHPACK_RETRIES` env var (default
* 3, set to "0" or "false" to disable retrying). The hot path is a single
* `fs.lstat` call with one inline callback; the timer and the recursive
* call are only scheduled when an EBUSY is actually observed.
* @param {string} target target path
* @param {{ closed: boolean }} watcher owning watcher (checked between retries)
* @param {(err: NodeJS.ErrnoException | null, stats: import("fs").Stats) => void} callback callback
* @param {number=} remaining retries remaining (defaults to `BUSY_RETRIES`)
*/
function lstatWithRetry(target, watcher, callback, remaining = BUSY_RETRIES) {
fs.lstat(target, (err, stats) => {
if (
err &&
/** @type {NodeJS.ErrnoException} */ (err).code === "EBUSY" &&
remaining > 0 &&
!watcher.closed
) {
setTimeout(
() => lstatWithRetry(target, watcher, callback, remaining - 1),
BUSY_RETRY_DELAY,
);
return;
}
callback(err, stats);
});
}
/**
* @typedef {object} FileWatcherEvents

@@ -173,2 +220,4 @@ * @property {(type: EventType) => void} initial-missing initial missing event

this.directories = new Map();
/** @type {Map<string, Watcher<FileWatcherEvents>>} */
this._symlinkTargetWatchers = new Map();
this.lastWatchEvent = 0;

@@ -434,2 +483,6 @@ this.initialScan = true;

}
for (const w of this._symlinkTargetWatchers.values()) {
w.close();
}
this._symlinkTargetWatchers.clear();
}

@@ -558,3 +611,3 @@ }

this._activeEvents.set(filename, false);
fs.lstat(target, (err, stats) => {
lstatWithRetry(target, this, (err, stats) => {
if (this.closed) return;

@@ -568,2 +621,5 @@ if (this._activeEvents.get(filename) === true) {

// EPERM happens when the containing directory doesn't exist
// EBUSY happens when another process has the file locked (e.g.
// Windows AV scanner). lstatWithRetry already retried before
// giving up here.
if (err) {

@@ -585,2 +641,9 @@ if (

if (!stats) {
// On EBUSY we keep the tracked state: the file is likely still
// there and a later event or scan will reconcile. Emitting a
// remove here would make the watch appear to stop after a
// transient lock (webpack/watchpack#223, #44).
if (err && err.code === "EBUSY" && BUSY_RETRIES > 0) {
return;
}
this.setMissing(target, false, eventType);

@@ -735,3 +798,16 @@ } else if (stats.isDirectory()) {

if (err) {
if (err.code === "ENOENT" || err.code === "EPERM") {
// Mirror the lstat error handling below: treat permission /
// invalid-argument / no-device errors on the directory itself
// as removed rather than logging "Watchpack Error (initial
// scan)". These surface for unreadable mounts (WSL `/mnt/c`,
// fuse mounts), unmounted devices (`/efi`), and libuv's
// post-Node 22.17 `EINVAL` on protected Windows paths
// (see #187).
if (
err.code === "ENOENT" ||
err.code === "EPERM" ||
err.code === "EACCES" ||
err.code === "ENODEV" ||
(err.code === "EINVAL" && IS_WIN)
) {
this.onDirectoryRemoved("scan readdir failed");

@@ -813,3 +889,3 @@ } else {

for (const itemPath of itemPaths) {
fs.lstat(itemPath, (err2, stats) => {
lstatWithRetry(itemPath, this, (err2, stats) => {
if (this.closed) return;

@@ -822,6 +898,18 @@ if (err2) {

err2.code === "EBUSY" ||
err2.code === "ENODEV" ||
// TODO https://github.com/libuv/libuv/pull/4566
(err2.code === "EINVAL" && IS_WIN)
) {
this.setMissing(itemPath, initial, `scan (${err2.code})`);
// readdir saw the entry but we can't stat it due to a
// transient lock — keep the previously-known entry instead
// of incorrectly flagging it as missing.
if (
!(
err2.code === "EBUSY" &&
BUSY_RETRIES > 0 &&
this.files.has(itemPath)
)
) {
this.setMissing(itemPath, initial, `scan (${err2.code})`);
}
} else {

@@ -833,25 +921,89 @@ this.onScanError(err2);

}
if (stats.isFile() || stats.isSymbolicLink()) {
if (stats.mtime) {
ensureFsAccuracy(+stats.mtime);
/**
* @param {string | null} realPath resolved real path for an outside-dir symlink target
*/
const apply = (realPath) => {
if (stats.isFile() || stats.isSymbolicLink()) {
if (stats.mtime) ensureFsAccuracy(+stats.mtime);
this.setFileTime(
itemPath,
+stats.mtime || +stats.ctime || 1,
initial,
true,
"scan (file)",
);
if (realPath && !this._symlinkTargetWatchers.has(itemPath)) {
const w = this.watcherManager.watchFile(realPath, Date.now());
if (w) {
w.on("change", (mtime, type, wInitial) => {
if (wInitial) return;
this.setFileTime(itemPath, mtime, false, false, type);
});
this._symlinkTargetWatchers.set(itemPath, w);
}
}
} else if (
stats.isDirectory() &&
(!initial || !this.directories.has(itemPath))
) {
this.setDirectory(
itemPath,
+stats.birthtime || 1,
initial,
"scan (dir)",
);
}
this.setFileTime(
itemPath,
+stats.mtime || +stats.ctime || 1,
initial,
true,
"scan (file)",
);
} else if (
stats.isDirectory() &&
(!initial || !this.directories.has(itemPath))
itemFinished();
};
if (
this.options.followSymlinks &&
this.nestedWatching &&
stats.isSymbolicLink()
) {
this.setDirectory(
itemPath,
+stats.birthtime || 1,
initial,
"scan (dir)",
);
fs.realpath(itemPath, (err3, realPath) => {
if (this.closed) return;
if (
err3 ||
!realPath ||
withoutCase(path.dirname(realPath)) === withoutCase(this.path)
) {
apply(null);
return;
}
// Cycle protection: when the symlink's target is the symlink
// path itself or one of its ancestors, descending would
// create an unbounded chain of `DirectoryWatcher`s as we walk
// back into territory we are already watching. Treat the
// symlink as a plain entry instead so the symlink itself is
// still tracked but no recursion happens.
const rel = path.relative(realPath, itemPath);
if (!path.isAbsolute(rel) && !rel.startsWith("..")) {
apply(null);
return;
}
fs.stat(realPath, (err4, targetStats) => {
if (this.closed) return;
if (err4 || !targetStats) {
apply(null);
} else if (targetStats.isFile()) {
apply(realPath);
} else if (targetStats.isDirectory()) {
// Treat a symlink whose target is a directory as a
// nested watched directory so files inside the target
// propagate change events to the symlink path.
this.setDirectory(
itemPath,
+targetStats.birthtime || +stats.birthtime || 1,
initial,
"scan (dir)",
);
itemFinished();
} else {
apply(null);
}
});
});
return;
}
itemFinished();
apply(null);
});

@@ -954,2 +1106,3 @@ }

close() {
if (this.closed) return;
this.closed = true;

@@ -968,2 +1121,6 @@ this.initialScan = false;

}
for (const w of this._symlinkTargetWatchers.values()) {
w.close();
}
this._symlinkTargetWatchers.clear();
if (this.parentWatcher) {

@@ -970,0 +1127,0 @@ this.parentWatcher.close();

+55
-15

@@ -8,5 +8,5 @@ /*

const { EventEmitter } = require("events");
const globToRegExp = require("glob-to-regexp");
const LinkResolver = require("./LinkResolver");
const getWatcherManager = require("./getWatcherManager");
const globToRegExp = require("./util/globToRegExp");
const watchEventSource = require("./watchEventSource");

@@ -49,4 +49,5 @@

/** @typedef {{ safeTime: number }} OnlySafeTimeEntry */
// eslint-disable-next-line jsdoc/ts-no-empty-object-type
/** @typedef {{}} ExistenceOnlyTimeEntry */
/** @typedef {{ }} ExistenceOnlyTimeEntry */
/** @typedef {Map<string, Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | null>} TimeInfoEntries */

@@ -65,6 +66,4 @@ /** @typedef {Set<string>} Changes */

for (const ww of watchers) {
const w = ww.watcher;
if (!set.has(w.directoryWatcher)) {
set.add(w.directoryWatcher);
}
// Set.add is already idempotent, so skip the redundant has() probe.
set.add(ww.watcher.directoryWatcher);
}

@@ -81,7 +80,18 @@ }

}
const { source } = globToRegExp(ignored, { globstar: true, extended: true });
return `${source.slice(0, -1)}(?:$|\\/)`;
return `^${globToRegExp(ignored)}(?:$|\\/)`;
};
/**
* Normalizes path separators for regex testing. `String.prototype.replace`
* always allocates a new string, even when the pattern finds nothing; for
* POSIX paths (the common case) that allocation is pure overhead. Check for
* a backslash with `indexOf` first so we skip the copy on paths that are
* already normalized.
* @param {string} item item
* @returns {string} item with backslashes normalized to forward slashes
*/
const normalizeSeparators = (item) =>
item.includes("\\") ? item.replace(/\\/g, "/") : item;
/**
* @param {Ignored=} ignored ignored

@@ -92,8 +102,13 @@ * @returns {(item: string) => boolean} ignored to function

if (Array.isArray(ignored)) {
const stringRegexps = ignored.map((i) => stringToRegexp(i)).filter(Boolean);
const stringRegexps =
/** @type {string[]} */
(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, "/"));
const regexp =
stringRegexps.length === 1
? new RegExp(stringRegexps[0])
: new RegExp(stringRegexps.join("|"));
return (item) => regexp.test(normalizeSeparators(item));
} else if (typeof ignored === "string") {

@@ -105,5 +120,5 @@ const stringRegexp = stringToRegexp(ignored);

const regexp = new RegExp(stringRegexp);
return (item) => regexp.test(item.replace(/\\/g, "/"));
return (item) => regexp.test(normalizeSeparators(item));
} else if (ignored instanceof RegExp) {
return (item) => ignored.test(item.replace(/\\/g, "/"));
return (item) => ignored.test(normalizeSeparators(item));
} else if (typeof ignored === "function") {

@@ -472,4 +487,6 @@ return ignored;

for (const w of directoryWatchers) {
// getTimes() returns a prototype-less object, so for...in is safe
// and avoids the throwaway array that Object.keys would allocate.
const times = w.getTimes();
for (const file of Object.keys(times)) obj[file] = times[file];
for (const file in times) obj[file] = times[file];
}

@@ -561,2 +578,25 @@ return obj;

module.exports = Watchpack;
/**
* @template A
* @template B
* @param {A} obj input a
* @param {B} exports input b
* @returns {A & B} merged
*/
const mergeExports = (obj, exports) => {
const descriptors = Object.getOwnPropertyDescriptors(exports);
Object.defineProperties(obj, descriptors);
return /** @type {A & B} */ (Object.freeze(obj));
};
/** @typedef {typeof Watchpack & { util: { readonly globToRegExp: typeof globToRegExp } }} WatchpackExports */
module.exports = /** @type {WatchpackExports} */ (
mergeExports(Watchpack, {
util: {
get globToRegExp() {
return globToRegExp;
},
},
})
);

@@ -70,42 +70,63 @@ /*

}
// Reduce until limit reached
while (currentCount > limit) {
// Select node that helps reaching the limit most effectively without overmerging
const overLimit = currentCount - limit;
let bestNode;
let bestCost = Infinity;
// Reduce until limit reached. When no reduction is needed at all, skip
// building the candidate set entirely to avoid paying for the setup on the
// common fast path.
if (currentCount > limit) {
// Pre-filter candidate nodes so the inner selection loop skips structural
// non-candidates entirely. `children` length and parent presence are
// fixed after tree construction; only `entries` can change (it can only
// decrease), so a node that fails the `entries` check in a later round
// is simply skipped via `continue`. When we merge a subtree we drop the
// descendants from the candidate set to keep it shrinking over
// iterations.
/** @type {Set<TreeNode<T>>} */
const candidates = new Set();
for (const node of treeMap.values()) {
if (node.entries <= 1 || !node.children || !node.parent) continue;
if (!node.parent || !node.children) 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;
}
candidates.add(node);
}
if (!bestNode) break;
// Merge all children
const reduction = bestNode.entries - 1;
bestNode.active = true;
bestNode.entries = 1;
currentCount -= reduction;
let { parent } = bestNode;
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);
const costBias = limit * 0.3;
while (currentCount > limit) {
// Select node that helps reaching the limit most effectively without overmerging
const overLimit = currentCount - limit;
let bestNode;
let bestCost = Infinity;
for (const node of candidates) {
if (node.entries <= 1) 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 diff = node.entries - 1 - overLimit;
const cost = diff >= 0 ? diff : -diff + costBias;
if (cost < bestCost) {
bestNode = node;
bestCost = cost;
// A cost of 0 means the merge reduces exactly to the limit;
// no further candidate can improve on that, so stop scanning.
if (cost === 0) break;
}
}
if (!bestNode) break;
// Merge all children
const reduction = bestNode.entries - 1;
bestNode.active = true;
bestNode.entries = 1;
candidates.delete(bestNode);
currentCount -= reduction;
let { parent } = bestNode;
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;
candidates.delete(node);
if (node.children) {
for (const child of node.children) queue.add(child);
}
}
}

@@ -112,0 +133,0 @@ }

@@ -64,2 +64,5 @@ /*

function createHandleChangeEvent(watcher, filePath, handleChangeEvent) {
// path.basename(filePath) is invariant for the lifetime of the watcher,
// so compute it once rather than on every dispatched event.
const ownBasename = path.basename(filePath);
return (type, filename) => {

@@ -72,3 +75,3 @@ // TODO: After Node.js v22, fs.watch(dir) and deleting a dir will trigger the rename change event.

path.isAbsolute(filename) &&
path.basename(filename) === path.basename(filePath)
path.basename(filename) === ownBasename
) {

@@ -434,12 +437,17 @@ if (!IS_OSX) {

}
let current = filePath;
for (;;) {
const recursiveWatcher = recursiveWatchers.get(current);
if (recursiveWatcher !== undefined) {
recursiveWatcher.add(filePath, watcher);
return watcher;
// Only platforms with recursive fs.watch ever populate recursiveWatchers,
// so skip the entire parent walk when the map is empty (always the case
// on Linux and the common case before the watcher limit is reached).
if (recursiveWatchers.size !== 0) {
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;
}
const parent = path.dirname(current);
if (parent === current) break;
current = parent;
}

@@ -446,0 +454,0 @@ // Queue up watcher for creation

{
"name": "watchpack",
"version": "2.5.1",
"version": "2.5.2",
"description": "",

@@ -27,8 +27,8 @@ "homepage": "https://github.com/webpack/watchpack",

"lint:code": "eslint --cache .",
"lint:types": "tsc",
"lint:types-test": "tsc -p tsconfig.types.test.json",
"lint:types": "tsc --noEmit",
"lint:types-test": "tsc -p tsconfig.types.test.json --noEmit",
"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",
"fix:declarations": "tsc && npm run fmt -- ./types",
"fmt": "npm run fmt:base -- --log-level warn --write",

@@ -42,31 +42,25 @@ "fmt:check": "npm run fmt:base -- --check",

"test:watch": "npm run test:base -- --watch",
"test:coverage": "npm run test:base -- --collectCoverageFrom=\"lib/**/*.js\" --coverage"
"test:coverage": "npm run test:base -- --collectCoverageFrom=\"lib/**/*.js\" --coverage",
"benchmark": "node --max-old-space-size=4096 --hash-seed=1 --random-seed=1 --no-opt --predictable --predictable-gc-schedule --interpreted-frames-native-stack --allow-natives-syntax --expose-gc --no-concurrent-sweeping ./benchmark/run.mjs",
"version": "changeset version",
"release": "changeset publish"
},
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
},
"devDependencies": {
"@eslint/js": "^9.28.0",
"@eslint/markdown": "^7.5.1",
"@stylistic/eslint-plugin": "^5.6.1",
"@changesets/cli": "^2.30.0",
"@changesets/get-github-info": "^0.8.0",
"@codspeed/core": "^5.2.0",
"@types/glob-to-regexp": "^0.4.4",
"@types/graceful-fs": "^4.1.9",
"@types/jest": "^27.5.1",
"@types/jest": "^30.0.0",
"@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",
"eslint-config-webpack": "^4.9.3",
"jest": "^30.3.0",
"prettier": "^3.7.4",
"rimraf": "^2.6.2",
"typescript": "^5.9.3",
"write-file-atomic": "^3.0.1"
"tinybench": "^6.0.0",
"typescript": "^6.0.2",
"write-file-atomic": "^8.0.0"
},

@@ -73,0 +67,0 @@ "engines": {

@@ -134,1 +134,10 @@ # watchpack

```
## Environment variables
- `WATCHPACK_POLLING`: when set, overrides the `poll` option (see above).
- `WATCHPACK_RETRIES`: number of times to retry `fs.lstat` when it returns
`EBUSY` (default: `3`). Useful on Windows where anti-virus scanners,
indexers or editors briefly lock files — without retries watchpack would
see a spurious `remove` and stop tracking the file. Set to `0` or
`"false"` to disable retrying.

@@ -51,2 +51,4 @@ export = DirectoryWatcher;

directories: Map<string, Watcher<DirectoryWatcherEvents> | boolean>;
/** @type {Map<string, Watcher<FileWatcherEvents>>} */
_symlinkTargetWatchers: Map<string, Watcher<FileWatcherEvents>>;
lastWatchEvent: number;

@@ -53,0 +55,0 @@ initialScan: boolean;

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

export = Watchpack;
declare namespace _exports {
export {
WatcherManager,
DirectoryWatcher,
DirectoryWatcherEvents,
FileWatcherEvents,
EventMap,
Watcher,
IgnoredFunction,
Ignored,
WatcherOptions,
WatchOptions,
NormalizedWatchOptions,
EventType,
Entry,
OnlySafeTimeEntry,
ExistenceOnlyTimeEntry,
TimeInfoEntries,
Changes,
Removals,
Aggregated,
WatchMethodOptions,
Times,
WatchpackEvents,
WatchpackExports,
};
}
declare const _exports: WatchpackExports;
export = _exports;
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 ExistenceOnlyTimeEntry = {};
type TimeInfoEntries = Map<
string,
Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | 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;
};
type WatchpackExports = typeof Watchpack & {
util: {
readonly globToRegExp: typeof globToRegExp;
};
};
/**

@@ -105,28 +224,3 @@ * @typedef {object} WatchpackEvents

}
declare namespace Watchpack {
export {
WatcherManager,
DirectoryWatcher,
DirectoryWatcherEvents,
FileWatcherEvents,
EventMap,
Watcher,
IgnoredFunction,
Ignored,
WatcherOptions,
WatchOptions,
NormalizedWatchOptions,
EventType,
Entry,
OnlySafeTimeEntry,
ExistenceOnlyTimeEntry,
TimeInfoEntries,
Changes,
Removals,
Aggregated,
WatchMethodOptions,
Times,
WatchpackEvents,
};
}
import globToRegExp = require("./util/globToRegExp");
import { EventEmitter } from "events";

@@ -177,87 +271,1 @@ declare class WatchpackFileWatcher {

}
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 ExistenceOnlyTimeEntry = {};
type TimeInfoEntries = Map<
string,
Entry | OnlySafeTimeEntry | ExistenceOnlyTimeEntry | 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;
};

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

declare const _exports: typeof import("./index");
declare const _exports: import("./index").WatchpackExports;
export = _exports;