Files
my-pi/pi-permission-system/src/path/canonicalize-path.ts
T

38 lines
1.3 KiB
TypeScript

import { realpathSync } from "node:fs";
import type { PathFlavor } from "#src/path/path-flavor";
/**
* Resolve symlinks in an absolute path, best-effort.
*
* Splits the path into components and tries `realpathSync` from the full path
* down to `/`, re-appending the non-existent tail to the first ancestor that
* resolves. Returns the input unchanged when no ancestor resolves (unreachable
* in practice since `/` always exists) or when a non-ENOENT/ENOTDIR error is
* encountered (e.g. `EACCES`, `ELOOP`), so callers fall back to lexical
* containment for paths that cannot be resolved.
*/
export function canonicalizePath(
absolutePath: string,
flavor: PathFlavor,
): string {
if (!absolutePath) return absolutePath;
const { impl } = flavor;
const root = impl.parse(absolutePath).root;
const rest = absolutePath.slice(root.length);
const parts = rest.split(impl.sep).filter(Boolean);
for (let i = parts.length; i >= 0; i--) {
const candidate = root + parts.slice(0, i).join(impl.sep);
try {
const real = realpathSync(candidate);
const tail = parts.slice(i);
return tail.length === 0 ? real : impl.join(real, ...tail);
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code !== "ENOENT" && code !== "ENOTDIR") return absolutePath;
}
}
return absolutePath;
}