-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpath-utils.ts
More file actions
142 lines (122 loc) · 4.81 KB
/
Copy pathpath-utils.ts
File metadata and controls
142 lines (122 loc) · 4.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
import path from "path";
import os from "os";
import { CASE_INSENSITIVE_FS } from "./constants.js";
export function convertToWindowsPath(p: string): string {
// WSL paths (/mnt/c/...) are valid Linux paths — never convert them
if (p.startsWith("/mnt/")) return p;
// Unix-style Windows paths (/c/...) — only convert on Windows
if (p.match(/^\/[a-zA-Z]\//) && process.platform === "win32") {
const driveLetter = p.charAt(1).toUpperCase();
const pathPart = p.slice(2).replace(/\//g, "\\");
return `${driveLetter}:${pathPart}`;
}
// Standard Windows paths — ensure backslashes
if (p.match(/^[a-zA-Z]:/)) return p.replace(/\//g, "\\");
return p;
}
export function normalizePath(p: string): string {
// Remove surrounding quotes and whitespace
p = p.trim().replace(/^["']|["']$/g, "");
// Detect Unix paths that should NOT be converted to Windows format
const isUnixPath =
p.startsWith("/") &&
(p.match(/^\/mnt\/[a-z]\//i) ||
process.platform !== "win32" ||
(process.platform === "win32" && !p.match(/^\/[a-zA-Z]\//)));
if (isUnixPath) {
// Normalize double slashes and remove trailing slash (preserving root /)
return p.replace(/\/+/g, "/").replace(/(?<!^)\/$/, "");
}
// Convert Unix-style Windows paths to Windows format
p = convertToWindowsPath(p);
// Handle UNC paths (\\server\share) — preserve leading \\
if (p.startsWith("\\\\")) {
const uncPath = p.replace(/^\\{2,}/, "\\\\");
const restOfPath = uncPath.substring(2).replace(/\\\\/g, "\\");
p = "\\\\" + restOfPath;
} else {
p = p.replace(/\\\\/g, "\\");
}
let normalized = path.normalize(p);
// Fix UNC paths after normalization (path.normalize can remove a leading backslash)
if (p.startsWith("\\\\") && !normalized.startsWith("\\\\")) {
normalized = "\\" + normalized;
}
// Handle Windows paths: convert slashes and capitalize drive letter
if (normalized.match(/^[a-zA-Z]:/)) {
let result = normalized.replace(/\//g, "\\");
if (/^[a-z]:/.test(result)) {
result = result.charAt(0).toUpperCase() + result.slice(1);
}
return result;
}
// On Windows, convert forward slashes for relative paths
if (process.platform === "win32") {
return normalized.replace(/\//g, "\\");
}
return normalized;
}
export function expandHome(filepath: string): string {
if (filepath.startsWith("~/") || filepath === "~") {
return path.join(os.homedir(), filepath.slice(1));
}
return filepath;
}
function isWindowsPathLike(p: string): boolean {
return /^[a-zA-Z]:[\\/]/.test(p) || p.startsWith("\\\\") || (p.includes("\\") && !p.includes("/"));
}
function separatorForPath(p: string): "\\" | "/" {
return isWindowsPathLike(p) ? "\\" : "/";
}
function normalizeSeparatorsForStyle(p: string, separator: "\\" | "/"): string {
if (separator === "\\") {
const isUnc = /^[/\\]{2}/.test(p);
const normalized = p.replace(/[\\/]+/g, "\\");
return isUnc && !normalized.startsWith("\\\\") ? `\\${normalized}` : normalized;
}
return p.replace(/\/+/g, "/");
}
function pathRootForStyle(p: string, separator: "\\" | "/"): string {
return separator === "\\" ? path.win32.parse(p).root : path.posix.parse(p).root;
}
function trimTrailingSeparatorsExceptRoot(p: string, separator: "\\" | "/"): string {
const root = pathRootForStyle(p, separator);
let result = p;
while (result.length > root.length && /[\\/]$/.test(result)) {
result = result.slice(0, -1);
}
return result;
}
export function ensureTrailingSeparator(p: string): string {
const separator = separatorForPath(p);
const normalized = normalizeSeparatorsForStyle(p, separator);
const root = pathRootForStyle(normalized, separator);
if (normalized === root) return normalized;
return /[\\/]$/.test(normalized) ? normalized : normalized + separator;
}
export interface PathContainmentOptions {
caseSensitive?: boolean;
}
export function isPathWithinDirectory(
childPath: string,
parentDir: string,
options: PathContainmentOptions = {}
): boolean {
const separator: "\\" | "/" =
isWindowsPathLike(childPath) || isWindowsPathLike(parentDir) ? "\\" : "/";
const child = trimTrailingSeparatorsExceptRoot(
normalizeSeparatorsForStyle(childPath, separator),
separator
);
const parent = trimTrailingSeparatorsExceptRoot(
normalizeSeparatorsForStyle(parentDir, separator),
separator
);
const caseSensitive = options.caseSensitive ?? !CASE_INSENSITIVE_FS;
const comparableChild = caseSensitive ? child : child.toLowerCase();
const comparableParent = caseSensitive ? parent : parent.toLowerCase();
if (comparableChild === comparableParent) return true;
const parentWithSeparator = ensureTrailingSeparator(comparableParent);
if (comparableParent === parentWithSeparator) return comparableChild.startsWith(comparableParent);
return comparableChild.startsWith(parentWithSeparator);
}