| |
| |
| |
| |
| |
|
|
| import * as fs from 'node:fs'; |
| import { parse, stringify } from 'comment-json'; |
| import { coreEvents } from '@google/gemini-cli-core'; |
|
|
| |
| |
| |
| type CommentedRecord = Record<string | symbol, unknown>; |
|
|
| |
| |
| |
| export function updateSettingsFilePreservingFormat( |
| filePath: string, |
| updates: Record<string, unknown>, |
| ): void { |
| if (!fs.existsSync(filePath)) { |
| fs.writeFileSync(filePath, JSON.stringify(updates, null, 2), 'utf-8'); |
| return; |
| } |
|
|
| const originalContent = fs.readFileSync(filePath, 'utf-8'); |
|
|
| let parsed: Record<string, unknown>; |
| try { |
| |
| parsed = parse(originalContent) as Record<string, unknown>; |
| } catch (error) { |
| coreEvents.emitFeedback( |
| 'error', |
| 'Error parsing settings file. Please check the JSON syntax.', |
| error, |
| ); |
| return; |
| } |
|
|
| const updatedStructure = applyUpdates(parsed, updates); |
| const updatedContent = stringify(updatedStructure, null, 2); |
|
|
| fs.writeFileSync(filePath, updatedContent, 'utf-8'); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| function preserveCommentsOnPropertyDeletion( |
| container: Record<string, unknown>, |
| propName: string, |
| ): void { |
| const target = container as CommentedRecord; |
| const beforeSym = Symbol.for(`before:${propName}`); |
| const afterSym = Symbol.for(`after:${propName}`); |
|
|
| |
| const beforeComments = target[beforeSym] as unknown[] | undefined; |
| |
| const afterComments = target[afterSym] as unknown[] | undefined; |
|
|
| if (!beforeComments && !afterComments) return; |
|
|
| const keys = Object.getOwnPropertyNames(container); |
| const idx = keys.indexOf(propName); |
| const nextKey = idx >= 0 && idx + 1 < keys.length ? keys[idx + 1] : undefined; |
| const prevKey = idx > 0 ? keys[idx - 1] : undefined; |
|
|
| function appendToSymbol(destSym: symbol, comments: unknown[]) { |
| if (!comments || comments.length === 0) return; |
| const existing = target[destSym]; |
| target[destSym] = Array.isArray(existing) |
| ? existing.concat(comments) |
| : comments; |
| } |
|
|
| if (beforeComments && beforeComments.length > 0) { |
| if (nextKey) { |
| appendToSymbol(Symbol.for(`before:${nextKey}`), beforeComments); |
| } else if (prevKey) { |
| appendToSymbol(Symbol.for(`after:${prevKey}`), beforeComments); |
| } else { |
| appendToSymbol(Symbol.for('before'), beforeComments); |
| } |
| delete target[beforeSym]; |
| } |
|
|
| if (afterComments && afterComments.length > 0) { |
| if (nextKey) { |
| appendToSymbol(Symbol.for(`before:${nextKey}`), afterComments); |
| } else if (prevKey) { |
| appendToSymbol(Symbol.for(`after:${prevKey}`), afterComments); |
| } else { |
| appendToSymbol(Symbol.for('after'), afterComments); |
| } |
| delete target[afterSym]; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| function applyKeyDiff( |
| base: Record<string, unknown>, |
| desired: Record<string, unknown>, |
| ): void { |
| for (const existingKey of Object.getOwnPropertyNames(base)) { |
| if (!Object.prototype.hasOwnProperty.call(desired, existingKey)) { |
| preserveCommentsOnPropertyDeletion(base, existingKey); |
| delete base[existingKey]; |
| } |
| } |
|
|
| for (const nextKey of Object.getOwnPropertyNames(desired)) { |
| const nextVal = desired[nextKey]; |
| const baseVal = base[nextKey]; |
|
|
| const isObj = |
| typeof nextVal === 'object' && |
| nextVal !== null && |
| !Array.isArray(nextVal); |
| const isBaseObj = |
| typeof baseVal === 'object' && |
| baseVal !== null && |
| !Array.isArray(baseVal); |
| const isArr = Array.isArray(nextVal); |
| const isBaseArr = Array.isArray(baseVal); |
|
|
| if (isObj && isBaseObj) { |
| applyKeyDiff( |
| |
| baseVal as Record<string, unknown>, |
| |
| nextVal as Record<string, unknown>, |
| ); |
| } else if (isArr && isBaseArr) { |
| |
| const baseArr = baseVal as unknown[]; |
| const desiredArr = nextVal as unknown[]; |
| baseArr.length = 0; |
| for (const el of desiredArr) { |
| baseArr.push(el); |
| } |
| } else { |
| base[nextKey] = nextVal; |
| } |
| } |
| } |
|
|
| function applyUpdates( |
| current: Record<string, unknown>, |
| updates: Record<string, unknown>, |
| ): Record<string, unknown> { |
| |
| applyKeyDiff(current, updates); |
| return current; |
| } |
|
|