muddle/core/paths.ts
2026-10-23 08:53:07 -04:00

81 lines
2.4 KiB
TypeScript

import { dirname, isAbsolute, resolve } from "node:path";
/** Root directory of the framework package. */
export const frameworkRoot = resolve(import.meta.dir, "..");
const INTERNAL_MARKERS = ["/bun:", "node:internal", "node_modules"];
export const frameworkSources = [
"adapters",
"contracts",
"core",
"decorators",
"integrations",
"orm",
"views",
].map((folder) => resolve(frameworkRoot, folder));
/** Extracts the file path of a stack frame. */
function frameFile(frame: string): string | undefined {
let text = frame.trim();
text = text.replace(/^at\s+/, "");
const parenthesised = /\((.*)\)$/.exec(text);
if (parenthesised) text = parenthesised[1] as string;
text = text.replace(/:\d+(?::\d+)?$/, "");
if (text.length === 0) return undefined;
if (text.startsWith("file://")) return text.slice(7);
if (!text.startsWith("/")) return undefined;
return text;
}
/** Returns the file that called the caller of this function. */
export function callerFile(
skipDirs: string[] = frameworkSources,
skip = 0,
): string | undefined {
const stack = new Error("caller").stack;
if (!stack) return undefined;
const frames = stack.split("\n").slice(1);
let seen = 0;
for (const frame of frames) {
const file = frameFile(frame);
if (!file) continue;
if (INTERNAL_MARKERS.some((marker) => file.startsWith(marker))) continue;
if (skipDirs.some((dir) => file.startsWith(dir))) continue;
if (seen < skip) {
seen += 1;
continue;
}
return file;
}
return undefined;
}
/** Directory of the file that called the caller of this function. */
export function callerDirectory(
skipDirs: string[] = frameworkSources,
skip = 0,
): string | undefined {
const file = callerFile(skipDirs, skip + 1);
return file ? dirname(file) : undefined;
}
/** Resolves a path relative to the project root. */
export function fromRoot(root: string, path: string): string {
return isAbsolute(path) ? path : resolve(root, path);
}
/** Normalises a path to posix separators, relative to the project root. */
export function relativeTo(root: string, path: string): string {
const resolvedRoot = resolve(root);
const resolvedPath = resolve(path);
if (!resolvedPath.startsWith(resolvedRoot)) return resolvedPath;
return resolvedPath.slice(resolvedRoot.length + 1).replace(/\\/g, "/");
}
/** Removes the extension of a path. */
export function stripExtension(path: string): string {
return path.replace(/\.[^./]+$/, "");
}