Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/the-system/project-root.ts

Raw
import { lstatSync } from "node:fs";
import path from "node:path";

export class SystemDocumentError extends Error {
	readonly code: string;

	constructor(code: string, message: string) {
		super(message);
		this.code = code;
		this.name = "SystemDocumentError";
	}
}

export function resolveGovernedPath(root: string, candidate: string): string {
	const normalizedRoot = path.resolve(root);
	const target = path.resolve(normalizedRoot, candidate);
	const relative = path.relative(normalizedRoot, target);
	if (
		relative === ".." ||
		relative.startsWith(`..${path.sep}`) ||
		path.isAbsolute(relative)
	)
		throw new SystemDocumentError(
			"path.outside-root",
			`Path is outside project root: ${candidate}`,
		);
	return target;
}

export function discoverProjectRoot(cwd: string): string | undefined {
	let current = path.resolve(cwd);
	while (true) {
		const stats = lstatSync(path.join(current, ".system"), {
			throwIfNoEntry: false,
		});
		if (stats?.isDirectory()) return current;
		const parent = path.dirname(current);
		if (parent === current) return undefined;
		current = parent;
	}
}

export function requireProjectRoot(cwd: string): string {
	const root = discoverProjectRoot(cwd);
	if (!root)
		throw new SystemDocumentError(
			"root.not-found",
			`No System project found at or above ${path.resolve(cwd)}`,
		);
	return root;
}