* Deep merge two configuration objects. * Rules: * - If override is undefined, keep base. * - If types mismatch (array vs object, primitive vs object), override wins outright — no merging. * - Arrays are NEVER merged element-by-element — override array replaces base array. * - Plain objects
(base: T, override: any)
| 16 | * - Primitives: override wins. |
| 17 | */ |
| 18 | function deepMerge<T>(base: T, override: any): T { |
| 19 | if (override === undefined) return base; |
| 20 | if (override === null) return null as unknown as T; |
| 21 | |
| 22 | const baseIsArray = Array.isArray(base); |
| 23 | const overrideIsArray = Array.isArray(override); |
| 24 | |
| 25 | // Arrays never merge — override replaces. Same when types mismatch. |
| 26 | if (baseIsArray || overrideIsArray) { |
| 27 | return overrideIsArray ? (override as T) : (override as T); |
| 28 | } |
| 29 | |
| 30 | const baseIsObj = typeof base === 'object' && base !== null; |
| 31 | const overrideIsObj = typeof override === 'object' && override !== null; |
| 32 | |
| 33 | // Type mismatch (e.g., user wrote a string where default is an object) → override wins |
| 34 | if (!baseIsObj || !overrideIsObj) { |
| 35 | return override as T; |
| 36 | } |
| 37 | |
| 38 | // Both are plain objects → recurse per key |
| 39 | const result: any = { ...(base as any) }; |
| 40 | for (const key of Object.keys(override)) { |
| 41 | const ov = override[key]; |
| 42 | if (ov === undefined) continue; |
| 43 | result[key] = deepMerge((base as any)[key], ov); |
| 44 | } |
| 45 | return result as T; |
| 46 | } |
| 47 | |
| 48 | export async function ensureQodexHome(): Promise<void> { |
| 49 | try { |