Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/horst/settings.ts

Raw
// horst — settings.
//
// Parses the `horst` block from Pi settings.

export interface HorstToolDelta {
	add?: string[];
	remove?: string[];
}

export interface HorstSettings {
	os: Record<string, HorstToolDelta>;
	hosts: Record<string, HorstToolDelta>;
	preferredShell?: string;
}

export const EMPTY_HORST_SETTINGS: HorstSettings = { os: {}, hosts: {} };

export function horstOsKey(platform: string): string | undefined {
	if (platform === "linux") return "lnx";
	if (platform === "darwin") return "mac";
	if (platform === "win32") return "win32";
	return undefined;
}

function isObject(value: unknown): value is Record<string, unknown> {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}

function toolNames(value: unknown, at: string): string[] | undefined {
	if (value === undefined) return undefined;
	if (
		!Array.isArray(value) ||
		!value.every((name) => typeof name === "string" && name.trim())
	)
		throw new Error(`${at} must be an array of non-empty strings`);
	return value.length > 0 ? value : undefined;
}

function pickMap(value: unknown, at: string): Record<string, HorstToolDelta> {
	if (value === undefined) return {};
	if (!isObject(value)) throw new Error(`${at} must be an object`);
	const map: Record<string, HorstToolDelta> = {};
	for (const [key, entry] of Object.entries(value)) {
		const tools = isObject(entry) ? entry.tools : undefined;
		if (!isObject(tools))
			throw new Error(`${at}.${key}.tools must be an object`);
		const delta: HorstToolDelta = {};
		const add = toolNames(tools.add, `${at}.${key}.tools.add`);
		const remove = toolNames(tools.remove, `${at}.${key}.tools.remove`);
		if (add) delta.add = add;
		if (remove) delta.remove = remove;
		if (delta.add || delta.remove) map[key.toLowerCase()] = delta;
	}
	return map;
}

/** Validate the `horst` block; throws on malformed values. */
export function parseHorst(raw: unknown): HorstSettings {
	if (!isObject(raw)) throw new Error("expected an object");
	const { preferredShell } = raw;
	if (
		preferredShell !== undefined &&
		(typeof preferredShell !== "string" || !preferredShell.trim())
	)
		throw new Error("preferredShell must be a non-empty string");
	return {
		os: pickMap(raw.os, "os"),
		hosts: pickMap(raw.hosts, "hosts"),
		...(preferredShell ? { preferredShell: preferredShell.trim() } : {}),
	};
}

/**
 * Resolve the tool delta for this machine.
 * `os` deltas apply first, a matching `hosts` entry wins per field.
 */
export function resolveHorstTools(
	settings: HorstSettings,
	platform: string,
	hostname: string,
): HorstToolDelta | undefined {
	const osDelta = settings.os[horstOsKey(platform) ?? ""];
	const hostDelta = settings.hosts[hostname.toLowerCase()];
	if (!osDelta && !hostDelta) return undefined;
	return {
		add: hostDelta?.add ?? osDelta?.add,
		remove: hostDelta?.remove ?? osDelta?.remove,
	};
}