import path from "node:path"; import { isUsableModulesDirectoryPath, MODULES_DIRECTORY_INVALID_MESSAGE, ModuleDiscoveryError, } from "./discovery.js"; /** The host environment variable that selects the installed-modules directory (ADR 0048). */ export const MODULES_DIRECTORY_ENVIRONMENT_VARIABLE = "EKKLESIA_MODULES_DIR"; export interface ResolveModulesDirectoryOptions { /** The raw configured value, exactly as the host environment supplied it; `undefined` when the * variable is unset. */ readonly configured: string | undefined; /** Where the platform itself is installed. Only consulted for the development default. */ readonly repositoryRoot: string; } /** Spreads directly into `ModuleDiscoveryOptions`, so every consumer discovers through the same * selection, including whether a missing directory is an error. */ export interface ModulesDirectorySelection { readonly modulesDirectory: string; readonly modulesDirectoryRequired: boolean; } /** * The one place an installation's modules directory is chosen (ADR 0048). * * - Unset: `/modules`, the development checkout's own directory. A checkout without * one has no modules, exactly as before. * - Set: an absolute path, normalized and used as given — never resolved against the repository or * the working directory — and required to exist when discovery reads it. The shipping image sets * it to `/var/lib/ekklesia/modules`. * * Anything else throws a `ModuleDiscoveryError` carrying `MODULES_DIRECTORY_INVALID` and a fixed * message, so every executable already reports it through the discovery error path it has, and the * supplied value never reaches an error, a log, or a report. * * Nothing here — and nothing that calls it — locates the platform's own files relative to the * result: Core's migrations and package metadata live under `repositoryRoot`, wherever modules are. */ export function resolveModulesDirectory( options: ResolveModulesDirectoryOptions, ): ModulesDirectorySelection { if (options.configured === undefined) { return Object.freeze({ modulesDirectory: path.join(options.repositoryRoot, "modules"), modulesDirectoryRequired: false, }); } if (!isUsableModulesDirectoryPath(options.configured)) { throw new ModuleDiscoveryError([ { code: "MODULES_DIRECTORY_INVALID", message: MODULES_DIRECTORY_INVALID_MESSAGE, moduleDirectory: ".", }, ]); } const normalized = path.normalize(options.configured); return Object.freeze({ modulesDirectory: normalized.length > 1 && normalized.endsWith(path.sep) ? normalized.slice(0, -1) : normalized, modulesDirectoryRequired: true, }); }