zerobyte/packages/core/src/utils/path.ts
Nicolas Meienberger bb7d650bcd
Some checks failed
Release Workflow / determine-release-type (push) Has been cancelled
Release Workflow / checks (push) Has been cancelled
Release Workflow / e2e-tests (push) Has been cancelled
Release Workflow / build-images (push) Has been cancelled
Release Workflow / publish-release (push) Has been cancelled
chore: default values in schemas
2026-03-12 22:34:00 +01:00

49 lines
1.2 KiB
TypeScript

export const normalizeAbsolutePath = (value?: string): string => {
const trimmed = value?.trim();
if (!trimmed) return "/";
let normalizedInput: string;
try {
normalizedInput = decodeURIComponent(trimmed).replace(/\\+/g, "/");
} catch {
normalizedInput = trimmed.replace(/\\+/g, "/");
}
const withLeadingSlash = normalizedInput.startsWith("/") ? normalizedInput : `/${normalizedInput}`;
const parts = withLeadingSlash.split("/");
const stack: string[] = [];
for (const part of parts) {
if (part === "" || part === ".") {
continue;
}
if (part === "..") {
if (stack.length > 0) {
stack.pop();
}
} else {
stack.push(part);
}
}
let normalized = "/" + stack.join("/");
if (!normalized || normalized === "." || normalized.startsWith("..")) {
return "/";
}
if (normalized.length > 1000) {
throw new Error("Normalized path is too long");
}
const withoutTrailingSlash = normalized.replace(/\/+$/, "");
if (!withoutTrailingSlash) {
return "/";
}
const withSingleLeadingSlash = withoutTrailingSlash.startsWith("/")
? `/${withoutTrailingSlash.replace(/^\/+/, "")}`
: `/${withoutTrailingSlash}`;
return withSingleLeadingSlash || "/";
};