From 24ecb29c37b315bfdc7ecddd6beadfb5ea3b5469 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 18:09:28 +0000 Subject: [PATCH] chore(release): new release --- .changeset/ebusy-retries.md | 22 ----- .changeset/olive-onions-yawn.md | 5 -- .changeset/perf-ignored-reduceplan.md | 11 --- .changeset/silence-permission-scan-errors.md | 33 -------- .changeset/symlink-cycle-guard.md | 25 ------ CHANGELOG.md | 86 ++++++++++++++++++++ package.json | 2 +- 7 files changed, 87 insertions(+), 97 deletions(-) delete mode 100644 .changeset/ebusy-retries.md delete mode 100644 .changeset/olive-onions-yawn.md delete mode 100644 .changeset/perf-ignored-reduceplan.md delete mode 100644 .changeset/silence-permission-scan-errors.md delete mode 100644 .changeset/symlink-cycle-guard.md create mode 100644 CHANGELOG.md diff --git a/.changeset/ebusy-retries.md b/.changeset/ebusy-retries.md deleted file mode 100644 index 7b4163e..0000000 --- a/.changeset/ebusy-retries.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -"watchpack": patch ---- - -fix: retry `fs.lstat` on transient `EBUSY` errors instead of flagging the -file as removed (fixes #223, #44). - -On Windows it is common for anti-virus scanners, indexers or the editor -itself to briefly hold an exclusive handle on a file that has just -changed. Before this change the watcher would receive the `fs.watch` -event, call `lstat`, get back `EBUSY`, and fall through to `setMissing` -— causing a spurious `remove` event and in some cases leaving the -watcher unable to see further changes for that file until the directory -was re-scanned. - -`DirectoryWatcher` now retries `lstat` up to three times (100 ms apart) -before giving up, and does not emit a remove when the only reason the -file could not be stat'd was `EBUSY`. - -The retry count is controlled by the `WATCHPACK_RETRIES` environment -variable (default: `3`; set to `0` or `"false"` to disable retrying and -restore the previous behaviour). diff --git a/.changeset/olive-onions-yawn.md b/.changeset/olive-onions-yawn.md deleted file mode 100644 index 7dbed63..0000000 --- a/.changeset/olive-onions-yawn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"watchpack": patch ---- - -Improve perfomance for ignored and improve perfomance for reduce plan. diff --git a/.changeset/perf-ignored-reduceplan.md b/.changeset/perf-ignored-reduceplan.md deleted file mode 100644 index b171c22..0000000 --- a/.changeset/perf-ignored-reduceplan.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"watchpack": patch ---- - -perf: skip the path-separator replacement when the input has no backslash -(benchmarks measure ~35–45% less time for `ignored` matchers on POSIX paths), -fast-path single-element `ignored` arrays, and make `reducePlan`'s selection -loop walk only structurally valid candidates with an early exit when the -ideal reduction is found (measured ~20–40% faster on medium and large -plans). Adds a tinybench suite under `bench/` and a CodSpeed GitHub Actions -workflow so future regressions are caught automatically. diff --git a/.changeset/silence-permission-scan-errors.md b/.changeset/silence-permission-scan-errors.md deleted file mode 100644 index 8b36787..0000000 --- a/.changeset/silence-permission-scan-errors.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -"watchpack": patch ---- - -fix: don't log "Watchpack Error (initial scan)" for unreadable entries -inside a watched parent directory (fixes #187). - -Webpack registers every ancestor of a watched file as a watched -directory (so `/mnt/c/Users/me/proj` causes watchpack to scan `/mnt/c`, -`/mnt`, `/`). When such a parent contains entries the current process -can't `lstat` — `pagefile.sys` / `hiberfil.sys` on WSL, `/efi` on Linux -when the EFI partition isn't mounted, protected paths on Node ≥22.17 on -Windows where libuv now reports `EINVAL` instead of `EACCES` — the -initial scan would print: - - Watchpack Error (initial scan): Error: EACCES: permission denied, lstat '/mnt/c/pagefile.sys' - Watchpack Error (initial scan): Error: EINVAL: invalid argument, lstat 'C:\\hiberfil.sys' - Watchpack Error (initial scan): Error: ENODEV: no such device, lstat '/efi' - -These entries aren't actually being watched (only their sibling, e.g. -`/mnt/c/Users`, is) so the log was harmless but very noisy and sent a -lot of users on wild goose chases. - -`DirectoryWatcher#doScan` now treats `EACCES` / `ENODEV` (and `EINVAL` -on Windows) the same way it already treats `EPERM` / `ENOENT` / -`EBUSY`: the offending entry is recorded as missing and the scan -continues silently. The same set is applied to the `readdir` error path -on the watched directory itself, so an unreadable mount point is -treated as removed instead of logged. - -No public API change. If you were relying on the error appearing on -stderr, set the impacted entry up as an explicit watch so a real failure -on it surfaces through the existing `error` event instead. diff --git a/.changeset/symlink-cycle-guard.md b/.changeset/symlink-cycle-guard.md deleted file mode 100644 index 862d348..0000000 --- a/.changeset/symlink-cycle-guard.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"watchpack": patch ---- - -fix: prevent unbounded watcher growth when a symlinked directory points -back to one of its own ancestors (cycle protection for the -`followSymlinks: true` symlink-descent path). - -The recent fixes for #190 / #231 made `DirectoryWatcher` follow -symlinked directories whose realpath lives outside the watched real -directory, registering them as nested watched directories. That logic -short-circuits when the symlink target is a sibling in the same parent -(`dirname(realPath) === this.path`), but it does not catch the case -where the target is an _ancestor_ of the symlink itself — for example -`a/b/loop -> ..`. In that case `readdir` followed the symlink, found -the original tree again, and a new `DirectoryWatcher` was created at -each recursion level until the path exceeded `PATH_MAX` (locally -observed: ~1500 watchers within 2 s, ~2500 within 2.5 s). - -`DirectoryWatcher` now computes `path.relative(realPath, itemPath)` -before descending; if the relative path doesn't start with `..` and -isn't absolute (i.e. the symlink target is at-or-above the symlink in -the directory tree), the symlink is recorded as a plain entry instead -of being descended into. Behaviour for symlinks pointing outside the -watched tree (the case #231 fixes) is unchanged. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..416e5e9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,86 @@ +# watchpack + +## 2.5.2 + +### Patch Changes + +- fix: retry `fs.lstat` on transient `EBUSY` errors instead of flagging the (by [@alexander-akait](https://github.com/alexander-akait) in [#293](https://github.com/webpack/watchpack/pull/293)) + file as removed (fixes #223, #44). + + On Windows it is common for anti-virus scanners, indexers or the editor + itself to briefly hold an exclusive handle on a file that has just + changed. Before this change the watcher would receive the `fs.watch` + event, call `lstat`, get back `EBUSY`, and fall through to `setMissing` + — causing a spurious `remove` event and in some cases leaving the + watcher unable to see further changes for that file until the directory + was re-scanned. + + `DirectoryWatcher` now retries `lstat` up to three times (100 ms apart) + before giving up, and does not emit a remove when the only reason the + file could not be stat'd was `EBUSY`. + + The retry count is controlled by the `WATCHPACK_RETRIES` environment + variable (default: `3`; set to `0` or `"false"` to disable retrying and + restore the previous behaviour). + +- Improve perfomance for ignored and improve perfomance for reduce plan. (by [@alexander-akait](https://github.com/alexander-akait) in [#289](https://github.com/webpack/watchpack/pull/289)) + +- perf: skip the path-separator replacement when the input has no backslash (by [@alexander-akait](https://github.com/alexander-akait) in [#287](https://github.com/webpack/watchpack/pull/287)) + (benchmarks measure ~35–45% less time for `ignored` matchers on POSIX paths), + fast-path single-element `ignored` arrays, and make `reducePlan`'s selection + loop walk only structurally valid candidates with an early exit when the + ideal reduction is found (measured ~20–40% faster on medium and large + plans). Adds a tinybench suite under `bench/` and a CodSpeed GitHub Actions + workflow so future regressions are caught automatically. + +- fix: don't log "Watchpack Error (initial scan)" for unreadable entries (by [@alexander-akait](https://github.com/alexander-akait) in [#298](https://github.com/webpack/watchpack/pull/298)) + inside a watched parent directory (fixes #187). + + Webpack registers every ancestor of a watched file as a watched + directory (so `/mnt/c/Users/me/proj` causes watchpack to scan `/mnt/c`, + `/mnt`, `/`). When such a parent contains entries the current process + can't `lstat` — `pagefile.sys` / `hiberfil.sys` on WSL, `/efi` on Linux + when the EFI partition isn't mounted, protected paths on Node ≥22.17 on + Windows where libuv now reports `EINVAL` instead of `EACCES` — the + initial scan would print: + + Watchpack Error (initial scan): Error: EACCES: permission denied, lstat '/mnt/c/pagefile.sys' + Watchpack Error (initial scan): Error: EINVAL: invalid argument, lstat 'C:\\hiberfil.sys' + Watchpack Error (initial scan): Error: ENODEV: no such device, lstat '/efi' + + These entries aren't actually being watched (only their sibling, e.g. + `/mnt/c/Users`, is) so the log was harmless but very noisy and sent a + lot of users on wild goose chases. + + `DirectoryWatcher#doScan` now treats `EACCES` / `ENODEV` (and `EINVAL` + on Windows) the same way it already treats `EPERM` / `ENOENT` / + `EBUSY`: the offending entry is recorded as missing and the scan + continues silently. The same set is applied to the `readdir` error path + on the watched directory itself, so an unreadable mount point is + treated as removed instead of logged. + + No public API change. If you were relying on the error appearing on + stderr, set the impacted entry up as an explicit watch so a real failure + on it surfaces through the existing `error` event instead. + +- fix: prevent unbounded watcher growth when a symlinked directory points (by [@alexander-akait](https://github.com/alexander-akait) in [#297](https://github.com/webpack/watchpack/pull/297)) + back to one of its own ancestors (cycle protection for the + `followSymlinks: true` symlink-descent path). + + The recent fixes for #190 / #231 made `DirectoryWatcher` follow + symlinked directories whose realpath lives outside the watched real + directory, registering them as nested watched directories. That logic + short-circuits when the symlink target is a sibling in the same parent + (`dirname(realPath) === this.path`), but it does not catch the case + where the target is an _ancestor_ of the symlink itself — for example + `a/b/loop -> ..`. In that case `readdir` followed the symlink, found + the original tree again, and a new `DirectoryWatcher` was created at + each recursion level until the path exceeded `PATH_MAX` (locally + observed: ~1500 watchers within 2 s, ~2500 within 2.5 s). + + `DirectoryWatcher` now computes `path.relative(realPath, itemPath)` + before descending; if the relative path doesn't start with `..` and + isn't absolute (i.e. the symlink target is at-or-above the symlink in + the directory tree), the symlink is recorded as a plain entry instead + of being descended into. Behaviour for symlinks pointing outside the + watched tree (the case #231 fixes) is unchanged. diff --git a/package.json b/package.json index 11c8889..9cef6f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "watchpack", - "version": "2.5.1", + "version": "2.5.2", "description": "", "homepage": "https://github.com/webpack/watchpack", "bugs": {