259 lines · 7.3 KB · TypeScript
RawDownload
import {  isExplicitRelativePath,  isUncPath,  isWindowsAbsolutePath,  isWindowsDrivePath,} from "@t3tools/shared/path";import { isWindowsPlatform } from "./utils"; function isRootPath(value: string): boolean {  return value === "/" || value === "\\" || /^[a-zA-Z]:[/\\]?$/.test(value);} function getAbsolutePathKind(value: string): "unix" | "windows" | null {  if (isWindowsDrivePath(value) || isUncPath(value)) {    return "windows";  }   if (value.startsWith("/")) {    return "unix";  }   return null;} function trimTrailingPathSeparators(value: string): string {  if (value.length === 0 || isRootPath(value)) {    return value;  }   const trimmed =    getAbsolutePathKind(value) === "unix"      ? value.replace(/\/+$/g, "")      : value.replace(/[\\/]+$/g, "");  if (trimmed.length === 0) {    return value;  }   return /^[a-zA-Z]:$/.test(trimmed) ? `${trimmed}\\` : trimmed;} function preferredPathSeparator(value: string): "/" | "\\" {  const absolutePathKind = getAbsolutePathKind(value);  if (absolutePathKind === "windows") {    return "\\";  }  if (absolutePathKind === "unix") {    return "/";  }   return value.includes("\\") ? "\\" : "/";} export function hasTrailingPathSeparator(value: string): boolean {  return (getAbsolutePathKind(value) === "unix" ? /\/$/ : /[\\/]$/).test(value);} export { isExplicitRelativePath as isExplicitRelativeProjectPath }; function splitPathSegments(value: string, separator: "/" | "\\"): string[] {  return value.split(separator === "/" ? /\/+/ : /[\\/]+/).filter(Boolean);} function getLastPathSeparatorIndex(value: string): number {  if (getAbsolutePathKind(value) === "unix") {    return value.lastIndexOf("/");  }   return Math.max(value.lastIndexOf("/"), value.lastIndexOf("\\"));} function splitAbsolutePath(value: string): {  root: string;  separator: "/" | "\\";  segments: string[];} | null {  if (isWindowsDrivePath(value)) {    const root = `${value.slice(0, 2)}\\`;    const segments = splitPathSegments(value.slice(root.length), "\\");    return { root, separator: "\\", segments };  }  if (isUncPath(value)) {    const segments = splitPathSegments(value, "\\");    const [server, share, ...rest] = segments;    if (!server || !share) {      return null;    }    return {      root: `\\\\${server}\\${share}\\`,      separator: "\\",      segments: rest,    };  }  if (value.startsWith("/")) {    return {      root: "/",      separator: "/",      segments: splitPathSegments(value.slice(1), "/"),    };  }  return null;} export function isFilesystemBrowseQuery(  value: string,  platform = typeof navigator === "undefined" ? "" : navigator.platform,): boolean {  const allowWindowsPaths = isWindowsPlatform(platform);  return (    value.startsWith("./") ||    value.startsWith("../") ||    value.startsWith(".\\") ||    value.startsWith("..\\") ||    value.startsWith("/") ||    value.startsWith("~/") ||    (allowWindowsPaths && isWindowsAbsolutePath(value))  );} export function isUnsupportedWindowsProjectPath(value: string, platform: string): boolean {  return isWindowsAbsolutePath(value) && !isWindowsPlatform(platform);} export function normalizeProjectPathForDispatch(value: string): string {  return trimTrailingPathSeparators(value.trim());} export function resolveProjectPathForDispatch(value: string, cwd?: string | null): string {  const trimmedValue = value.trim();  if (!isExplicitRelativePath(trimmedValue) || !cwd) {    return normalizeProjectPathForDispatch(trimmedValue);  }   const absoluteBase = splitAbsolutePath(normalizeProjectPathForDispatch(cwd));  if (!absoluteBase) {    return normalizeProjectPathForDispatch(trimmedValue);  }   const nextSegments = [...absoluteBase.segments];  for (const segment of trimmedValue.split(/[\\/]+/)) {    if (segment.length === 0 || segment === ".") {      continue;    }    if (segment === "..") {      nextSegments.pop();      continue;    }    nextSegments.push(segment);  }   const joinedPath = nextSegments.join(absoluteBase.separator);  if (joinedPath.length === 0) {    return normalizeProjectPathForDispatch(absoluteBase.root);  }   return normalizeProjectPathForDispatch(`${absoluteBase.root}${joinedPath}`);} export function normalizeProjectPathForComparison(value: string): string {  const normalized = normalizeProjectPathForDispatch(value);  if (isWindowsDrivePath(normalized) || normalized.startsWith("\\\\")) {    return normalized.replaceAll("/", "\\").toLowerCase();  }  return normalized;} export function findProjectByPath<T extends { cwd: string }>(  projects: ReadonlyArray<T>,  candidatePath: string,): T | undefined {  const normalizedCandidate = normalizeProjectPathForComparison(candidatePath);  if (normalizedCandidate.length === 0) {    return undefined;  }   return projects.find(    (project) => normalizeProjectPathForComparison(project.cwd) === normalizedCandidate,  );} export function inferProjectTitleFromPath(value: string): string {  const normalized = normalizeProjectPathForDispatch(value);  const absolutePath = splitAbsolutePath(normalized);  if (absolutePath) {    return absolutePath.segments.findLast(Boolean) ?? normalized;  }   const segments = normalized.split(/[/\\]/);  return segments.findLast(Boolean) ?? normalized;} export function appendBrowsePathSegment(currentPath: string, segment: string): string {  const separator = preferredPathSeparator(currentPath);  return `${getBrowseDirectoryPath(currentPath)}${segment}${separator}`;} export function getBrowseLeafPathSegment(currentPath: string): string {  const lastSeparatorIndex = getLastPathSeparatorIndex(currentPath);  return currentPath.slice(lastSeparatorIndex + 1);} export function getBrowseDirectoryPath(currentPath: string): string {  if (hasTrailingPathSeparator(currentPath)) {    return currentPath;  }   const lastSeparatorIndex = getLastPathSeparatorIndex(currentPath);  if (lastSeparatorIndex < 0) {    return currentPath;  }   return currentPath.slice(0, lastSeparatorIndex + 1);} export function ensureBrowseDirectoryPath(currentPath: string): string {  const trimmed = currentPath.trim();  if (trimmed.length === 0) {    return trimmed;  }   if (hasTrailingPathSeparator(trimmed)) {    return trimmed;  }   return `${trimmed}${preferredPathSeparator(trimmed)}`;} export function getBrowseParentPath(currentPath: string): string | null {  const trimmed = trimTrailingPathSeparators(currentPath);  const absolutePath = splitAbsolutePath(trimmed);  if (absolutePath) {    if (absolutePath.segments.length === 0) {      return null;    }     if (absolutePath.segments.length === 1) {      return absolutePath.root;    }     const parentSegments = absolutePath.segments.slice(0, -1).join(absolutePath.separator);    return `${absolutePath.root}${parentSegments}${absolutePath.separator}`;  }   const separator = preferredPathSeparator(currentPath);  const lastSeparatorIndex = getLastPathSeparatorIndex(trimmed);   if (lastSeparatorIndex < 0) {    return null;  }   if (lastSeparatorIndex === 2 && /^[a-zA-Z]:/.test(trimmed)) {    return `${trimmed.slice(0, 2)}${separator}`;  }   return trimmed.slice(0, lastSeparatorIndex + 1);} export function canNavigateUp(currentPath: string): boolean {  return hasTrailingPathSeparator(currentPath) && getBrowseParentPath(currentPath) !== null;}