From 3cc37aabac32ae777eae2c5643620b96c26de012 Mon Sep 17 00:00:00 2001 From: alexander-akait <4567934+alexander-akait@users.noreply.github.com> Date: Fri, 16 Oct 2026 11:30:30 +0000 Subject: [PATCH] fix: treat a file where a watched directory should be like a missing directory --- .changeset/missing-under-file.md | 5 ++++ lib/DirectoryWatcher.js | 3 ++ test/MissingUnderFile.test.js | 50 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 .changeset/missing-under-file.md create mode 100644 test/MissingUnderFile.test.js diff --git a/.changeset/missing-under-file.md b/.changeset/missing-under-file.md new file mode 100644 index 0000000..b0bb0c4 --- /dev/null +++ b/.changeset/missing-under-file.md @@ -0,0 +1,5 @@ +--- +"watchpack": patch +--- + +Watch a missing file whose parent path is a file (e.g. `src/foo/index.js`, tried by a resolver alias, while `src/foo` is a file) like one whose parent is missing: no more `ENOTDIR` "Watchpack Error (initial scan)" log, and the file is reported once the parent becomes a directory (fixes #226's case). diff --git a/lib/DirectoryWatcher.js b/lib/DirectoryWatcher.js index 18452ce..c8fb44d 100644 --- a/lib/DirectoryWatcher.js +++ b/lib/DirectoryWatcher.js @@ -904,6 +904,9 @@ class DirectoryWatcher extends EventEmitter { // (see #187). if ( err.code === "ENOENT" || + // a file where the directory should be, e.g. `src/foo` for a + // missing `src/foo/index.js` that a resolver tried (#226) + err.code === "ENOTDIR" || err.code === "EPERM" || err.code === "EACCES" || err.code === "ENODEV" || diff --git a/test/MissingUnderFile.test.js b/test/MissingUnderFile.test.js new file mode 100644 index 0000000..4b0d061 --- /dev/null +++ b/test/MissingUnderFile.test.js @@ -0,0 +1,50 @@ +"use strict"; + +const path = require("path"); +const Watchpack = require("../lib"); +const TestHelper = require("./helpers/TestHelper"); + +const fixtures = path.join(__dirname, "fixtures"); +const testHelper = new TestHelper(fixtures); + +// eslint-disable-next-line jest/no-confusing-set-timeout +jest.setTimeout(20000); + +describe("Missing file below a path that is a file (#226)", () => { + beforeEach((done) => { + testHelper.before(done); + }); + + /** @type {InstanceType[]} */ + let watchers = []; + + afterEach((done) => { + // closed here, not in the test, so a failure cannot leave one running + for (const w of watchers) w.close(); + watchers = []; + jest.restoreAllMocks(); + testHelper.after(done); + }); + + it("should report the file once its parent turns from a file into a directory", (done) => { + // a resolver tried `foo/index.js` while `foo` is a file + testHelper.file("foo"); + const missing = path.join(fixtures, "foo", "index.js"); + const errors = jest.spyOn(console, "error").mockImplementation(() => {}); + testHelper.tick(1000, () => { + const w = new Watchpack({ aggregateTimeout: 100 }); + watchers.push(w); + w.on("change", (item) => { + expect(item).toBe(missing); + expect(errors).not.toHaveBeenCalled(); + done(); + }); + w.watch({ missing: [missing], startTime: Date.now() }); + testHelper.tick(1000, () => { + testHelper.remove("foo"); + testHelper.dir("foo"); + testHelper.file(path.join("foo", "index.js")); + }); + }); + }); +});