fix: also follow symlinked directories nested in watched real directories (#190)#296
Conversation
…ries (#190) PR #291 only handled the case where a symlink discovered inside a watched real directory points to a file. When the symlink targeted a directory the entry was still recorded via setFileTime, so no nested DirectoryWatcher was created and changes inside the target were missed. Now, when a discovered symlink resolves to a directory, setDirectory is called for the symlink path so a nested watcher is created and its contents are scanned and watched through the symlink.
|
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #296 +/- ##
==========================================
- Coverage 95.82% 95.67% -0.15%
==========================================
Files 7 7
Lines 1173 1179 +6
Branches 342 345 +3
==========================================
+ Hits 1124 1128 +4
- Misses 44 46 +2
Partials 5 5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Add explicit tests pinning the symlink-path semantics established by PRs #291/#296 against the original bug report: - a file modified through a symlinked directory is reported with the symlink path (e.g. `a/b/ext_dir_link/inner`), not the resolved target path (`ext_dir/inner`). - the user-supplied `ignored` callback is queried with the symlink path, so an allowlist scoped to the watched subtree continues to match files that physically live outside it. These assertions go beyond the existing `expect([...changes]).toEqual` checks (which only verify the aggregated directory) and lock down the per-file path that consumers like webpack actually rely on. Refs #231 https://claude.ai/code/session_016tnQoHN9qHz9PRZo312iHs
The #190 / #231 fixes (PRs #291, #296) made DirectoryWatcher descend into symlinked directories whose realpath lives outside the watched real directory. The existing short-circuit only catches symlinks that point to a sibling in the same parent (`dirname(realPath) === this.path`); it does not catch the case where the target is an ancestor of the symlink itself, e.g. `a/b/loop -> ..`. Without protection, `readdir` follows the symlink, finds the original tree again, and a new DirectoryWatcher is created at every recursion level (locally observed: ~1500 watchers within 2 s, ~2500 within 2.5 s) until paths hit PATH_MAX. Compute `path.relative(realPath, itemPath)` before descending: when the relative path doesn't go up (no `..`, not absolute), the symlink target is at-or-above the symlink itself and would create a cycle. In that case, treat the symlink as a plain entry instead of descending. A regression test (`should not recurse infinitely when a symlinked directory points to one of its ancestors`) creates `a/b/cycle -> ..` and asserts the WatcherManager's `directoryWatchers` size stays under 10 — without the guard the test fails with ~1580 watchers. Also adds a changeset entry documenting the fix. Refs #231 https://claude.ai/code/session_016tnQoHN9qHz9PRZo312iHs
PR #291 only handled the case where a symlink discovered inside a
watched real directory points to a file. When the symlink targeted a
directory the entry was still recorded via setFileTime, so no nested
DirectoryWatcher was created and changes inside the target were missed.
Now, when a discovered symlink resolves to a directory, setDirectory is
called for the symlink path so a nested watcher is created and its
contents are scanned and watched through the symlink.