* Deep merge two objects, preserving existing values and adding missing ones from template * Handles nested objects recursively * @param {object} template - Template object with all possible fields * @param {object} existing - Existing object with user values * @param {string} path - Cur
(template, existing, path = '')
| 113 | * @returns {object} Object with { merged, fieldsAdded: string[] } |
| 114 | */ |
| 115 | deepMerge(template, existing, path = '') { |
| 116 | const merged = {}; |
| 117 | const fieldsAdded = []; |
| 118 | |
| 119 | // First, copy all template keys |
| 120 | for (const key in template) { |
| 121 | if (key === '//comment') continue; // Skip comment keys |
| 122 | |
| 123 | const templateValue = template[key]; |
| 124 | const existingValue = existing[key]; |
| 125 | const fieldPath = path ? `${path}.${key}` : key; |
| 126 | |
| 127 | if (!(key in existing)) { |
| 128 | // Key missing from existing - add from template |
| 129 | merged[key] = templateValue; |
| 130 | fieldsAdded.push(fieldPath); |
| 131 | } else if (typeof templateValue === 'object' && templateValue !== null && !Array.isArray(templateValue)) { |
| 132 | // Template value is an object - existing should be too |
| 133 | if (typeof existingValue === 'object' && existingValue !== null && !Array.isArray(existingValue)) { |
| 134 | // Both are objects - recurse to merge nested fields |
| 135 | const result = this.deepMerge(templateValue, existingValue, fieldPath); |
| 136 | merged[key] = result.merged; |
| 137 | fieldsAdded.push(...result.fieldsAdded); |
| 138 | } else { |
| 139 | // Type mismatch - template is object but existing is not |
| 140 | // Fix corrupted data by using template value |
| 141 | merged[key] = templateValue; |
| 142 | fieldsAdded.push(fieldPath); |
| 143 | logger.warn( |
| 144 | { field: fieldPath, expectedType: 'object', actualType: typeof existingValue }, |
| 145 | 'Config field has incorrect type, replacing with template value' |
| 146 | ); |
| 147 | } |
| 148 | } else { |
| 149 | // Primitive or array - use existing value |
| 150 | merged[key] = existingValue; |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | // Copy any keys from existing that aren't in template (preserve extra user fields) |
| 155 | for (const key in existing) { |
| 156 | if (!(key in merged)) { |
| 157 | merged[key] = existing[key]; |
| 158 | } |
| 159 | } |
| 160 | |
| 161 | return { merged, fieldsAdded }; |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * Merge existing config with template to add missing fields |
no outgoing calls
no test coverage detected