From ea20bcde180d01bd0a0b81e189ba31b2e35b99c3 Mon Sep 17 00:00:00 2001 From: Equal Ma Date: Tue, 24 Jun 2025 21:44:50 +0800 Subject: [PATCH 1/3] fix: irrelevant files are watched if they have the same prefix as app directory For example, if app directory is `/workspace/app`, `/workspace/apps/irrelevant/file` should be ignored. --- .../__tests__/path-starts-with-test.ts | 40 +++++++++++++++++++ packages/react-router-dev/config/config.ts | 3 +- .../config/path-starts-with.ts | 17 ++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 packages/react-router-dev/__tests__/path-starts-with-test.ts create mode 100644 packages/react-router-dev/config/path-starts-with.ts diff --git a/packages/react-router-dev/__tests__/path-starts-with-test.ts b/packages/react-router-dev/__tests__/path-starts-with-test.ts new file mode 100644 index 0000000000..8134abd63c --- /dev/null +++ b/packages/react-router-dev/__tests__/path-starts-with-test.ts @@ -0,0 +1,40 @@ +import pathStartsWith from "../config/path-starts-with"; + +describe("pathStartsWith", () => { + it("real world example", () => { + const APP_FOLDER = "/workspace/app"; + expect(pathStartsWith("/workspace/app/root.tsx", APP_FOLDER)).toBe(true); + + const IRRELEVANT_FILE = "/workspace/apps/irrelevant-project/package.json"; + expect(IRRELEVANT_FILE.startsWith(APP_FOLDER)).toBe(true); + expect(pathStartsWith(IRRELEVANT_FILE, APP_FOLDER)).toBe(false); + }); + + it("edge cases", () => { + expect(pathStartsWith("./dir", "./dir")).toBe(true); + expect(pathStartsWith("./dir/", "./dir")).toBe(true); + expect(pathStartsWith("./dir/path", "./dir")).toBe(true); + expect(pathStartsWith("./dir/path", "./dir/")).toBe(true); + expect(pathStartsWith("./dir/path/", "./dir")).toBe(true); + expect(pathStartsWith("./dir/path/", "./dir/")).toBe(true); + + expect(pathStartsWith("dir", "dir")).toBe(true); + expect(pathStartsWith("dir/", "dir")).toBe(true); + expect(pathStartsWith("dir/path", "dir")).toBe(true); + expect(pathStartsWith("dir/path", "dir/")).toBe(true); + expect(pathStartsWith("dir/path/", "dir")).toBe(true); + expect(pathStartsWith("dir/path/", "dir/")).toBe(true); + + expect(pathStartsWith("/dir", "/dir")).toBe(true); + expect(pathStartsWith("/dir/", "/dir")).toBe(true); + expect(pathStartsWith("/dir/path", "/dir")).toBe(true); + expect(pathStartsWith("/dir/path", "/dir/")).toBe(true); + expect(pathStartsWith("/dir/path/", "/dir")).toBe(true); + expect(pathStartsWith("/dir/path/", "/dir/")).toBe(true); + }); + + it("paths are not normalized intentionally", () => { + expect(pathStartsWith("./dir/path", "dir")).toBe(false); + expect(pathStartsWith("/dir/a/b/c/../../..", "/dir/a/b/c")).toBe(true); + }); +}); diff --git a/packages/react-router-dev/config/config.ts b/packages/react-router-dev/config/config.ts index 5057b1b8c3..a858eb8b66 100644 --- a/packages/react-router-dev/config/config.ts +++ b/packages/react-router-dev/config/config.ts @@ -22,6 +22,7 @@ import { configRoutesToRouteManifest, } from "./routes"; import { detectPackageManager } from "../cli/detectPackageManager"; +import pathStartsWith from "./path-starts-with"; const excludedConfigPresetKeys = ["presets"] as const satisfies ReadonlyArray< keyof ReactRouterConfig @@ -686,7 +687,7 @@ export async function createConfigLoader({ let dirname = Path.dirname(path); return ( - !dirname.startsWith(appDirectory) && + !pathStartsWith(dirname, appDirectory) && // Ensure we're only watching files outside of the app directory // that are at the root level, not nested in subdirectories path !== root && // Watch the root directory itself diff --git a/packages/react-router-dev/config/path-starts-with.ts b/packages/react-router-dev/config/path-starts-with.ts new file mode 100644 index 0000000000..e09ea346c7 --- /dev/null +++ b/packages/react-router-dev/config/path-starts-with.ts @@ -0,0 +1,17 @@ +/** + * Returns true if `a` is a path that starts with `b` and might contains subpath. + * + * Note that `a` and `b` will not be normalized + * so the returned boolean doesn't indicate whether `a` resolves to a path contained in `b`. + */ +export default function pathStartsWith(a: string, b: string) { + return ( + a.startsWith(b) && + // they are the same string + (a.length === b.length || + // or b is a directory path + b.endsWith("/") || + // or a is `${b}/${subpath}` + a[b.length] === "/") + ); +} From 1700536ca13b06da03967f16e1753c08d915e1bf Mon Sep 17 00:00:00 2001 From: Equal Ma Date: Tue, 24 Jun 2025 21:51:16 +0800 Subject: [PATCH 2/3] chore: sign --- contributors.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/contributors.yml b/contributors.yml index f7cc03ee2c..df9faae6c8 100644 --- a/contributors.yml +++ b/contributors.yml @@ -105,6 +105,7 @@ - elylucas - emzoumpo - engpetermwangi +- EqualMa - ericschn - esadek - faergeek From 2f7fc5bb74359ff594846336c463c26ded36437e Mon Sep 17 00:00:00 2001 From: Equal Ma Date: Wed, 25 Jun 2025 21:54:27 +0800 Subject: [PATCH 3/3] fix: support windows paths --- .../__tests__/path-starts-with-test.ts | 12 ++++++++++++ packages/react-router-dev/config/path-starts-with.ts | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/react-router-dev/__tests__/path-starts-with-test.ts b/packages/react-router-dev/__tests__/path-starts-with-test.ts index 8134abd63c..b08ff5a465 100644 --- a/packages/react-router-dev/__tests__/path-starts-with-test.ts +++ b/packages/react-router-dev/__tests__/path-starts-with-test.ts @@ -10,6 +10,18 @@ describe("pathStartsWith", () => { expect(pathStartsWith(IRRELEVANT_FILE, APP_FOLDER)).toBe(false); }); + it("windows paths", () => { + const APP_FOLDER = "C:\\\\workspace\\app"; + expect(pathStartsWith("C:\\\\workspace\\app\\root.tsx", APP_FOLDER)).toBe( + true + ); + + const IRRELEVANT_FILE = + "C:\\\\workspace\\apps\\irrelevant-project\\package.json"; + expect(IRRELEVANT_FILE.startsWith(APP_FOLDER)).toBe(true); + expect(pathStartsWith(IRRELEVANT_FILE, APP_FOLDER)).toBe(false); + }); + it("edge cases", () => { expect(pathStartsWith("./dir", "./dir")).toBe(true); expect(pathStartsWith("./dir/", "./dir")).toBe(true); diff --git a/packages/react-router-dev/config/path-starts-with.ts b/packages/react-router-dev/config/path-starts-with.ts index e09ea346c7..68a670e06d 100644 --- a/packages/react-router-dev/config/path-starts-with.ts +++ b/packages/react-router-dev/config/path-starts-with.ts @@ -11,7 +11,9 @@ export default function pathStartsWith(a: string, b: string) { (a.length === b.length || // or b is a directory path b.endsWith("/") || + b.endsWith("\\") || // or a is `${b}/${subpath}` - a[b.length] === "/") + a[b.length] === "/" || + a[b.length] === "\\") ); }