112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
/**
|
|
* Path resolution utilities for the store.
|
|
*/
|
|
|
|
import { stripTrailingSeparators } from '@shared/utils/platformPath';
|
|
|
|
/**
|
|
* Resolves a relative path against a base path, handling various path formats.
|
|
* Handles:
|
|
* - Absolute paths: /full/path/file.tsx (returned as-is)
|
|
* - Relative paths with ./: ./apps/foo/bar.tsx (strips ./)
|
|
* - Parent paths with ../: ../other/file.tsx (walks up directories)
|
|
* - Plain paths: apps/foo/bar.tsx (joins with base)
|
|
* - Paths with @ prefix: @apps/foo/bar.tsx (strips @ then joins)
|
|
*/
|
|
export function resolveFilePath(base: string, relativePath: string): string {
|
|
// If already absolute, return as-is
|
|
if (isAbsolutePath(relativePath)) {
|
|
return relativePath;
|
|
}
|
|
|
|
const cleanBase = stripTrailingSeparators(base);
|
|
|
|
// Handle @ prefix (file mention marker) - strip it if present
|
|
let cleanRelative = relativePath;
|
|
if (cleanRelative.startsWith('@')) {
|
|
cleanRelative = cleanRelative.slice(1);
|
|
}
|
|
|
|
// Tilde paths (~/) are home-relative absolute paths - pass through as-is
|
|
// The main process will expand ~ to the actual home directory
|
|
if (cleanRelative.startsWith('~/') || cleanRelative.startsWith('~\\') || cleanRelative === '~') {
|
|
return cleanRelative;
|
|
}
|
|
|
|
// Handle ./ prefix (current directory)
|
|
if (cleanRelative.startsWith('./') || cleanRelative.startsWith('.\\')) {
|
|
cleanRelative = cleanRelative.slice(2);
|
|
}
|
|
|
|
// Handle ../ prefixes (parent directory)
|
|
const separator = cleanBase.includes('\\') ? '\\' : '/';
|
|
const hasUncRoot = cleanBase.startsWith('\\\\') || cleanBase.startsWith('//');
|
|
const hasUnixRoot = !hasUncRoot && cleanBase.startsWith('/');
|
|
const minRootParts = hasUncRoot ? 2 : 1;
|
|
const normalizedRelative = normalizeSeparators(cleanRelative, separator);
|
|
const baseParts = splitPath(cleanBase);
|
|
let remainingRelative = normalizedRelative;
|
|
|
|
while (remainingRelative.startsWith(`..${separator}`)) {
|
|
remainingRelative = remainingRelative.slice(3);
|
|
if (baseParts.length > minRootParts) {
|
|
baseParts.pop();
|
|
}
|
|
}
|
|
|
|
// Join the normalized paths
|
|
let normalizedBase = baseParts.join(separator);
|
|
if (hasUnixRoot && !normalizedBase.startsWith('/')) {
|
|
normalizedBase = `/${normalizedBase}`;
|
|
}
|
|
if (hasUncRoot && !normalizedBase.startsWith(`${separator}${separator}`)) {
|
|
normalizedBase = `${separator}${separator}${normalizedBase}`;
|
|
}
|
|
return remainingRelative ? `${normalizedBase}${separator}${remainingRelative}` : normalizedBase;
|
|
}
|
|
|
|
function isAbsolutePath(input: string): boolean {
|
|
return input.startsWith('/') || input.startsWith('\\\\') || /^[a-zA-Z]:[\\/]/.test(input);
|
|
}
|
|
|
|
function normalizeSeparators(input: string, separator: '/' | '\\'): string {
|
|
let output = '';
|
|
let prevWasSeparator = false;
|
|
|
|
for (const char of input) {
|
|
const isSeparator = char === '/' || char === '\\';
|
|
if (isSeparator) {
|
|
if (!prevWasSeparator) {
|
|
output += separator;
|
|
}
|
|
prevWasSeparator = true;
|
|
} else {
|
|
output += char;
|
|
prevWasSeparator = false;
|
|
}
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
function splitPath(input: string): string[] {
|
|
const parts: string[] = [];
|
|
let current = '';
|
|
|
|
for (const char of input) {
|
|
if (char === '/' || char === '\\') {
|
|
if (current.length > 0) {
|
|
parts.push(current);
|
|
current = '';
|
|
}
|
|
} else {
|
|
current += char;
|
|
}
|
|
}
|
|
|
|
if (current.length > 0) {
|
|
parts.push(current);
|
|
}
|
|
|
|
return parts;
|
|
}
|