Project Files
src / paths.ts
import * as fs from "node:fs";
import * as path from "node:path";
/** Resolve *relative* under *root*, rejecting traversal outside *root* (Python resolve_under_root). */
export function resolveUnderRoot(rootAbs: string, relative: string): string {
const root = path.resolve(rootAbs);
const candidate = path.resolve(path.join(root, relative));
const rel = path.relative(root, candidate);
if (rel.startsWith("..") || path.isAbsolute(rel)) {
throw new Error(`Path escapes data directory: ${JSON.stringify(relative)}`);
}
return candidate;
}
const RELATIVE_NAME_PATTERN = /^[\w./-]+$/;
export function validateRelativeName(name: string, label: string): void {
const trimmed = name.trim();
if (!trimmed) {
throw new Error(`${label} cannot be empty`);
}
if (!RELATIVE_NAME_PATTERN.test(trimmed)) {
throw new Error(
`${label} may only contain letters, numbers, underscores, hyphens, dots, and slashes`,
);
}
for (const part of trimmed.split(/[/\\]/)) {
if (part === "..") {
throw new Error(`${label} must not contain parent directory segments`);
}
}
}
export function configuredBaseDir(folderName: string): string {
const raw = folderName.trim();
if (!raw) {
throw new Error(
"Error: Directory not set. Configure Base Directory (folderName) in plugin settings.",
);
}
const resolved = path.resolve(raw);
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
throw new Error(`Error: Directory not set or does not exist (${resolved})`);
}
return resolved;
}