Skip to content

Commit 082f0cc

Browse files
authored
fix(app): preserve native path separators in file path helpers (anomalyco#14912)
1 parent 2cee947 commit 082f0cc

2 files changed

Lines changed: 13 additions & 15 deletions

File tree

packages/app/src/context/file/path.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ describe("file path helpers", () => {
1515

1616
test("normalizes Windows absolute paths with mixed separators", () => {
1717
const path = createPathHelpers(() => "C:\\repo")
18-
expect(path.normalize("C:\\repo\\src\\app.ts")).toBe("src/app.ts")
18+
expect(path.normalize("C:\\repo\\src\\app.ts")).toBe("src\\app.ts")
1919
expect(path.normalize("C:/repo/src/app.ts")).toBe("src/app.ts")
2020
expect(path.normalize("file://C:/repo/src/app.ts")).toBe("src/app.ts")
21-
expect(path.normalize("c:\\repo\\src\\app.ts")).toBe("src/app.ts")
21+
expect(path.normalize("c:\\repo\\src\\app.ts")).toBe("src\\app.ts")
2222
})
2323

2424
test("keeps query/hash stripping behavior stable", () => {

packages/app/src/context/file/path.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -103,32 +103,30 @@ export function encodeFilePath(filepath: string): string {
103103

104104
export function createPathHelpers(scope: () => string) {
105105
const normalize = (input: string) => {
106-
const root = scope().replace(/\\/g, "/")
106+
const root = scope()
107107

108-
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input)))).replace(/\\/g, "/")
108+
let path = unquoteGitPath(decodeFilePath(stripQueryAndHash(stripFileProtocol(input))))
109109

110-
// Remove initial root prefix, if it's a complete match or followed by /
111-
// (don't want /foo/bar to root of /f).
112-
// For Windows paths, also check for case-insensitive match.
113-
const windows = /^[A-Za-z]:/.test(root)
114-
const canonRoot = windows ? root.toLowerCase() : root
115-
const canonPath = windows ? path.toLowerCase() : path
110+
// Separator-agnostic prefix stripping for Cygwin/native Windows compatibility
111+
// Only case-insensitive on Windows (drive letter or UNC paths)
112+
const windows = /^[A-Za-z]:/.test(root) || root.startsWith("\\\\")
113+
const canonRoot = windows ? root.replace(/\\/g, "/").toLowerCase() : root.replace(/\\/g, "/")
114+
const canonPath = windows ? path.replace(/\\/g, "/").toLowerCase() : path.replace(/\\/g, "/")
116115
if (
117116
canonPath.startsWith(canonRoot) &&
118-
(canonRoot.endsWith("/") || canonPath === canonRoot || canonPath.startsWith(canonRoot + "/"))
117+
(canonRoot.endsWith("/") || canonPath === canonRoot || canonPath[canonRoot.length] === "/")
119118
) {
120-
// If we match canonRoot + "/", the slash will be removed below.
119+
// Slice from original path to preserve native separators
121120
path = path.slice(root.length)
122121
}
123122

124-
if (path.startsWith("./")) {
123+
if (path.startsWith("./") || path.startsWith(".\\")) {
125124
path = path.slice(2)
126125
}
127126

128-
if (path.startsWith("/")) {
127+
if (path.startsWith("/") || path.startsWith("\\")) {
129128
path = path.slice(1)
130129
}
131-
132130
return path
133131
}
134132

0 commit comments

Comments
 (0)