import path from "node:path" ;
import { normalizeWindowsPathForComparison } from "../infra/path-guards.js" ;
import { resolveSandboxInputPath } from "./sandbox-paths.js" ;
type RelativePathOptions = {
allowRoot?: boolean ;
cwd?: string;
boundaryLabel?: string;
includeRootInError?: boolean ;
};
function throwPathEscapesBoundary(params: {
options?: RelativePathOptions;
rootResolved: string;
candidate: string;
}): never {
const boundary = params.options?.boundaryLabel ?? "workspace root" ;
const suffix = params.options?.includeRootInError ? ` (${params.rootResolved})` : "" ;
throw new Error(`Path escapes ${boundary}${suffix}: ${params.candidate}`);
}
function validateRelativePathWithinBoundary(params: {
relativePath: string;
isAbsolutePath: (path: string) => boolean ;
options?: RelativePathOptions;
rootResolved: string;
candidate: string;
}): string {
if (params.relativePath === "" || params.relativePath === "." ) {
if (params.options?.allowRoot) {
return "" ;
}
throwPathEscapesBoundary({
options: params.options,
rootResolved: params.rootResolved,
candidate: params.candidate,
});
}
if (params.relativePath.startsWith(".." ) || params.isAbsolutePath(params.relativePath)) {
throwPathEscapesBoundary({
options: params.options,
rootResolved: params.rootResolved,
candidate: params.candidate,
});
}
return params.relativePath;
}
function toRelativePathUnderRoot(params: {
root: string;
candidate: string;
options?: RelativePathOptions;
}): string {
const resolvedInput = resolveSandboxInputPath(
params.candidate,
params.options?.cwd ?? params.root,
);
if (process.platform === "win32" ) {
const rootResolved = path.win32.resolve(params.root);
const resolvedCandidate = path.win32.resolve(resolvedInput);
const rootForCompare = normalizeWindowsPathForComparison(rootResolved);
const targetForCompare = normalizeWindowsPathForComparison(resolvedCandidate);
const relative = path.win32.relative(rootForCompare, targetForCompare);
return validateRelativePathWithinBoundary({
relativePath: relative,
isAbsolutePath: path.win32.isAbsolute,
options: params.options,
rootResolved,
candidate: params.candidate,
});
}
const rootResolved = path.resolve(params.root);
const resolvedCandidate = path.resolve(resolvedInput);
const relative = path.relative(rootResolved, resolvedCandidate);
return validateRelativePathWithinBoundary({
relativePath: relative,
isAbsolutePath: path.isAbsolute,
options: params.options,
rootResolved,
candidate: params.candidate,
});
}
function toRelativeBoundaryPath(params: {
root: string;
candidate: string;
options?: Pick<RelativePathOptions, "allowRoot" | "cwd" >;
boundaryLabel: string;
includeRootInError?: boolean ;
}): string {
return toRelativePathUnderRoot({
root: params.root,
candidate: params.candidate,
options: {
allowRoot: params.options?.allowRoot,
cwd: params.options?.cwd,
boundaryLabel: params.boundaryLabel,
includeRootInError: params.includeRootInError,
},
});
}
export function toRelativeWorkspacePath(
root: string,
candidate: string,
options?: Pick<RelativePathOptions, "allowRoot" | "cwd" >,
): string {
return toRelativeBoundaryPath({
root,
candidate,
options,
boundaryLabel: "workspace root" ,
});
}
export function toRelativeSandboxPath(
root: string,
candidate: string,
options?: Pick<RelativePathOptions, "allowRoot" | "cwd" >,
): string {
return toRelativeBoundaryPath({
root,
candidate,
options,
boundaryLabel: "sandbox root" ,
includeRootInError: true ,
});
}
export function resolvePathFromInput(filePath: string, cwd: string): string {
return path.normalize(resolveSandboxInputPath(filePath, cwd));
}
Messung V0.5 in Prozent C=98 H=97 G=97
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-05-26)
¤
*© Formatika GbR, Deutschland