Skip to content

Commit 4221ba8

Browse files
authored
fix: use jsonc-parser for all JSON config files to support comments (#41)
Some clients like opencode use .json files that contain JSONC content (comments). Previously, only .jsonc files were parsed with jsonc-parser, causing JSON.parse() to fail on .json files with comments. This resulted in the entire config being replaced with just the MCP section, losing all other settings. Now we always use jsonc-parser for reading JSON files and use jsonc.modify() to update only the specific configKey path when writing, preserving comments, formatting, and all other config keys. Indentation is detected from the existing file content so the output matches the original style.
2 parents c039317 + 3c4299a commit 4221ba8

File tree

1 file changed

+34
-25
lines changed

1 file changed

+34
-25
lines changed

src/client-config.ts

Lines changed: 34 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -279,11 +279,10 @@ export function readConfig(client: string, local?: boolean): ClientConfig {
279279
rawConfig = (yaml.load(fileContent) as ClientConfig) || {}
280280
} else if (configPath.format === 'toml') {
281281
rawConfig = TOML.parse(fileContent) as ClientConfig
282-
} else if (configPath.path.endsWith('.jsonc')) {
283-
// Use jsonc-parser for .jsonc files to support comments
284-
rawConfig = jsonc.parse(fileContent) as ClientConfig
285282
} else {
286-
rawConfig = JSON.parse(fileContent)
283+
// Always use jsonc-parser for JSON files to support comments
284+
// This handles both .json and .jsonc files with comments
285+
rawConfig = jsonc.parse(fileContent) as ClientConfig
287286
}
288287

289288
verbose(`Config loaded successfully: ${JSON.stringify(rawConfig, null, 2)}`)
@@ -319,6 +318,26 @@ export function writeConfig(config: ClientConfig, client?: string, local?: boole
319318
writeConfigFile(config, configPath)
320319
}
321320

321+
// Detect indentation style from JSON content using jsonc-parser
322+
function detectIndent(text: string): { tabSize: number; insertSpaces: boolean } {
323+
let result: { tabSize: number; insertSpaces: boolean } | null = null
324+
325+
jsonc.visit(text, {
326+
onObjectProperty: (_property, offset, _length, startLine, startCharacter) => {
327+
if (result === null && startLine > 0 && startCharacter > 0) {
328+
const lineStart = text.lastIndexOf('\n', offset - 1) + 1
329+
const whitespace = text.slice(lineStart, offset)
330+
result = {
331+
tabSize: startCharacter,
332+
insertSpaces: !whitespace.includes('\t'),
333+
}
334+
}
335+
},
336+
})
337+
338+
return result || { tabSize: 2, insertSpaces: true }
339+
}
340+
322341
// Helper function for deep merge
323342
function deepMerge(target: ClientConfig, source: ClientConfig): ClientConfig {
324343
const result = { ...target }
@@ -356,11 +375,9 @@ function writeConfigFile(config: ClientConfig, target: ClientFileTarget): void {
356375
existingConfig = (yaml.load(originalFileContent) as ClientConfig) || {}
357376
} else if (target.format === 'toml') {
358377
existingConfig = TOML.parse(originalFileContent) as ClientConfig
359-
} else if (target.path.endsWith('.jsonc')) {
360-
// Use jsonc-parser for .jsonc files to support comments
361-
existingConfig = jsonc.parse(originalFileContent) as ClientConfig
362378
} else {
363-
existingConfig = JSON.parse(originalFileContent)
379+
// Always use jsonc-parser for JSON files to support comments
380+
existingConfig = jsonc.parse(originalFileContent) as ClientConfig
364381
}
365382

366383
verbose(`Existing config loaded: ${JSON.stringify(existingConfig, null, 2)}`)
@@ -385,24 +402,16 @@ function writeConfigFile(config: ClientConfig, target: ClientFileTarget): void {
385402
})
386403
} else if (target.format === 'toml') {
387404
configContent = TOML.stringify(mergedConfig)
388-
} else if (target.path.endsWith('.jsonc') && originalFileContent) {
389-
// For .jsonc files, try to preserve comments and formatting using jsonc-parser
405+
} else if (originalFileContent) {
406+
// For JSON/JSONC files with existing content, use jsonc-parser to preserve comments
390407
try {
391-
// Apply modifications to preserve existing structure
392-
const editedContent = originalFileContent
393-
const modifications: Array<jsonc.Edit> = []
394-
395-
// Generate edit operations for each key in the merged config
396-
for (const key of Object.keys(mergedConfig)) {
397-
const path = [key]
398-
const edits = jsonc.modify(editedContent, path, mergedConfig[key], {
399-
formattingOptions: { tabSize: 2, insertSpaces: true },
400-
})
401-
modifications.push(...edits)
402-
}
403-
404-
// Apply all edits
405-
configContent = jsonc.applyEdits(originalFileContent, modifications)
408+
// Only modify the specific configKey path, preserving everything else
409+
const configKeyPath = target.configKey.split('.')
410+
const newValue = getNestedValue(mergedConfig, target.configKey)
411+
const edits = jsonc.modify(originalFileContent, configKeyPath, newValue, {
412+
formattingOptions: detectIndent(originalFileContent),
413+
})
414+
configContent = jsonc.applyEdits(originalFileContent, edits)
406415
} catch (error) {
407416
// Fallback to standard JSON.stringify if edit fails
408417
verbose(`Error applying JSONC edits: ${error instanceof Error ? error.message : String(error)}`)

0 commit comments

Comments
 (0)