/** * Loud-fail port-env resolver. Single doctrine surface for every runtime * site that reads a network port from the environment. * * Per Task 139: no hardcoded numeric fallbacks. If the env var is missing * or doesn't parse as an integer in the valid TCP port range, throw with * a one-line diagnostic in the canonical shape * * [] reason=missing-env env= * * matching the Task 134 hook diagnostic so one grep * (`reason=missing-env env=`) lists every misconfigured device in the field. * * Callers in server entry points should let the throw propagate; systemd's * Restart=on-failure cycles visibly. Callers in long-lived processes should * surface the same line on stderr before exit. */ export interface RequirePortEnvOptions { /** Diagnostic tag prefixed to the fail line. Defaults to "port-env". */ tag?: string; /** Fail line written after the tag. Defaults to "missing-port-env". */ failLine?: string; } export class MissingPortEnvError extends Error { readonly envName: string; readonly tag: string; readonly failLine: string; constructor(envName: string, tag: string, failLine: string, message: string) { super(message); this.name = "MissingPortEnvError"; this.envName = envName; this.tag = tag; this.failLine = failLine; } } const VALID_PORT_MIN = 1; const VALID_PORT_MAX = 65535; export function requirePortEnv(envName: string, options: RequirePortEnvOptions = {}): number { const tag = options.tag ?? "port-env"; const failLine = options.failLine ?? "missing-port-env"; const raw = process.env[envName]; if (raw === undefined || raw === "") { const msg = `[${tag}] ${failLine} reason=missing-env env=${envName}`; throw new MissingPortEnvError(envName, tag, failLine, msg); } const parsed = Number.parseInt(raw, 10); if (!Number.isInteger(parsed) || parsed < VALID_PORT_MIN || parsed > VALID_PORT_MAX || String(parsed) !== raw.trim()) { const msg = `[${tag}] ${failLine} reason=invalid-port-env env=${envName} value=${JSON.stringify(raw)}`; throw new MissingPortEnvError(envName, tag, failLine, msg); } return parsed; }