// horst — settings. // // Parses the `horst` block from Pi settings. export interface HorstToolDelta { add?: string[]; remove?: string[]; } export interface HorstSettings { os: Record; hosts: Record; 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 { 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 { if (value === undefined) return {}; if (!isObject(value)) throw new Error(`${at} must be an object`); const map: Record = {}; 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, }; }