| import { realpathSync, statSync } from "node:fs"; |
| import { homedir } from "node:os"; |
| import { isAbsolute, join, resolve as nodeResolvePath, relative, sep } from "node:path"; |
| import { fileURLToPath } from "node:url"; |
| import { spawnProcessSync } from "./child-process.ts"; |
|
|
| const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g; |
|
|
| export interface PathInputOptions { |
| |
| trim?: boolean; |
| |
| expandTilde?: boolean; |
| |
| homeDir?: string; |
| |
| stripAtPrefix?: boolean; |
| |
| normalizeUnicodeSpaces?: boolean; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function canonicalizePath(path: string): string { |
| try { |
| return realpathSync(path); |
| } catch { |
| return path; |
| } |
| } |
|
|
| export function getFileRevision(path: string): string | undefined { |
| try { |
| const stats = statSync(path, { bigint: true }); |
| return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeNs}:${stats.ctimeNs}`; |
| } catch { |
| return undefined; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| export function isLocalPath(value: string): boolean { |
| const trimmed = value.trim(); |
| |
| if ( |
| trimmed.startsWith("npm:") || |
| trimmed.startsWith("git:") || |
| trimmed.startsWith("github:") || |
| trimmed.startsWith("http:") || |
| trimmed.startsWith("https:") || |
| trimmed.startsWith("ssh:") |
| ) { |
| return false; |
| } |
| return true; |
| } |
|
|
| |
| export function normalizeWindowsShellPath(filePath: string): string { |
| if (!filePath.startsWith("/") || filePath.startsWith("//") || filePath.includes("\\")) return filePath; |
| const match = filePath.match(/^\/(?:mnt\/|cygdrive\/)?([a-z])(?:\/(.*))?$/i); |
| if (!match) return filePath; |
| const suffix = match[2]?.replaceAll("/", "\\"); |
| return `${match[1].toUpperCase()}:\\${suffix ?? ""}`; |
| } |
|
|
| export function normalizePath(input: string, options: PathInputOptions = {}): string { |
| let normalized = options.trim ? input.trim() : input; |
| if (options.normalizeUnicodeSpaces) { |
| normalized = normalized.replace(UNICODE_SPACES, " "); |
| } |
| if (options.stripAtPrefix && normalized.startsWith("@")) { |
| normalized = normalized.slice(1); |
| } |
| if (process.platform === "win32") { |
| normalized = normalizeWindowsShellPath(normalized); |
| } |
|
|
| if (options.expandTilde ?? true) { |
| const home = options.homeDir ?? homedir(); |
| if (normalized === "~") return home; |
| if (normalized.startsWith("~/") || (process.platform === "win32" && normalized.startsWith("~\\"))) { |
| return join(home, normalized.slice(2)); |
| } |
| } |
|
|
| if (/^file:\/\//.test(normalized)) { |
| return fileURLToPath(normalized); |
| } |
|
|
| return normalized; |
| } |
|
|
| export function resolvePath(input: string, baseDir: string = process.cwd(), options: PathInputOptions = {}): string { |
| const normalized = normalizePath(input, options); |
| const normalizedBaseDir = normalizePath(baseDir); |
| return isAbsolute(normalized) ? nodeResolvePath(normalized) : nodeResolvePath(normalizedBaseDir, normalized); |
| } |
|
|
| export function getCwdRelativePath(filePath: string, cwd: string): string | undefined { |
| const resolvedCwd = resolvePath(cwd); |
| const resolvedPath = resolvePath(filePath, resolvedCwd); |
| const relativePath = relative(resolvedCwd, resolvedPath); |
| const isInsideCwd = |
| relativePath === "" || |
| (relativePath !== ".." && !relativePath.startsWith(`..${sep}`) && !isAbsolute(relativePath)); |
|
|
| return isInsideCwd ? relativePath || "." : undefined; |
| } |
|
|
| export function formatPathRelativeToCwdOrAbsolute(filePath: string, cwd: string): string { |
| const absolutePath = resolvePath(filePath, cwd); |
| return (getCwdRelativePath(absolutePath, cwd) ?? absolutePath).split(sep).join("/"); |
| } |
|
|
| export function markPathIgnoredByCloudSync(path: string): void { |
| const attrs = |
| process.platform === "darwin" |
| ? ["com.dropbox.ignored", "com.apple.fileprovider.ignore#P"] |
| : process.platform === "linux" |
| ? ["user.com.dropbox.ignored"] |
| : []; |
|
|
| for (const attr of attrs) { |
| if (process.platform === "darwin") { |
| spawnProcessSync("xattr", ["-w", attr, "1", path], { encoding: "utf-8", stdio: "ignore" }); |
| } else { |
| spawnProcessSync("setfattr", ["-n", attr, "-v", "1", path], { encoding: "utf-8", stdio: "ignore" }); |
| } |
| } |
| } |
|
|