authorgravatar for 132888838+arhxam@users.noreply.github.comArham Amin <132888838+arhxam@users.noreply.github.com> 2026-08-11 21:22:23+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-11 15:52:23+00:00
log6befe42eb09633f3adb500ae906a0e1da3f15f64
treefaec3e62eab38dce58e36c518170b5b4fa35821c
parentb30a9bc4143910f12d9c990c221fb9bc20cbc293
signature Signed by PGP key B5690EEEBB952194

fix(shared): normalize a bare Windows drive root the same as C:\ / C:/ (#6189)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

2 files changed, 17 insertions(+), 1 deletions(-)

packages/shared/src/path.test.ts+12
...@@ -4,6 +4,8 @@ import {...@@ -4,6 +4,8 @@ import {
4 isUncPath,4 isUncPath,
5 isWindowsAbsolutePath,5 isWindowsAbsolutePath,
6 isWindowsDrivePath,6 isWindowsDrivePath,
7 normalizeProjectPathForComparison,
8 normalizeProjectPathForDispatch,
7} from "./path.ts";9} from "./path.ts";
810
9describe("path helpers", () => {11describe("path helpers", () => {
...@@ -31,4 +33,14 @@ describe("path helpers", () => {...@@ -31,4 +33,14 @@ describe("path helpers", () => {
31 expect(isExplicitRelativePath("..\\repo")).toBe(true);33 expect(isExplicitRelativePath("..\\repo")).toBe(true);
32 expect(isExplicitRelativePath("~/repo")).toBe(false);34 expect(isExplicitRelativePath("~/repo")).toBe(false);
33 });35 });
36
37 it("normalizes a bare Windows drive root the same as one with a trailing separator", () => {
38 // `C:`, `C:\` and `C:/` all refer to the drive root and must compare equal.
39 expect(normalizeProjectPathForDispatch("C:")).toBe("C:\\");
40 expect(normalizeProjectPathForComparison("C:")).toBe("c:\\");
41 expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:\\"));
42 expect(normalizeProjectPathForComparison("C:")).toBe(normalizeProjectPathForComparison("C:/"));
43 // Non-root drive paths keep their trailing separator trimmed as before.
44 expect(normalizeProjectPathForDispatch("C:\\repo\\")).toBe("C:\\repo");
45 });
34});46});
packages/shared/src/path.ts+5-1
...@@ -22,7 +22,11 @@ export function isExplicitRelativePath(value: string): boolean {...@@ -22,7 +22,11 @@ export function isExplicitRelativePath(value: string): boolean {
22}22}
2323
24function isRootPath(value: string): boolean {24function isRootPath(value: string): boolean {
25 return value === "/" || value === "\\" || /^[a-zA-Z]:[/\\]?$/.test(value);25 // The drive separator is required: a bare `C:` is not the drive root (it
26 // means "current directory on C:"), and treating it as already-canonical
27 // would leave it as `C:` while `C:\` and `C:/` normalize to the drive root,
28 // so the same location would fail project identity/dedup comparisons.
29 return value === "/" || value === "\\" || /^[a-zA-Z]:[/\\]$/.test(value);
26}30}
2731
28function trimTrailingPathSeparators(value: string): string {32function trimTrailingPathSeparators(value: string): string {