feat: Add JSONC support for oh-my-opencode config files (#275)

Uses Microsoft's jsonc-parser package for reliable JSONC parsing:
- oh-my-opencode.jsonc (preferred) or oh-my-opencode.json
- Supports line comments (//), block comments (/* */), and trailing commas
- Better error reporting with line/column positions

Core changes:
- Added jsonc-parser dependency (Microsoft's VS Code parser)
- Shared JSONC utilities (parseJsonc, parseJsoncSafe, readJsoncFile, detectConfigFile)
- Main plugin config loader uses detectConfigFile for .jsonc priority
- CLI config manager supports JSONC parsing

Comprehensive test suite with 18 tests for JSONC parsing.

Fixes #265

🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: sisyphus-dev-ai <sisyphus-dev-ai@users.noreply.github.com>
This commit is contained in:
Sisyphus
2025-12-28 00:45:17 +09:00
committed by GitHub
parent c4c0d82f97
commit b2c2c6eab7
8 changed files with 381 additions and 88 deletions

View File

@@ -0,0 +1,66 @@
import { existsSync, readFileSync } from "node:fs"
import { parse, ParseError, printParseErrorCode } from "jsonc-parser"
export interface JsoncParseResult<T> {
data: T | null
errors: Array<{ message: string; offset: number; length: number }>
}
export function parseJsonc<T = unknown>(content: string): T {
const errors: ParseError[] = []
const result = parse(content, errors, {
allowTrailingComma: true,
disallowComments: false,
}) as T
if (errors.length > 0) {
const errorMessages = errors
.map((e) => `${printParseErrorCode(e.error)} at offset ${e.offset}`)
.join(", ")
throw new SyntaxError(`JSONC parse error: ${errorMessages}`)
}
return result
}
export function parseJsoncSafe<T = unknown>(content: string): JsoncParseResult<T> {
const errors: ParseError[] = []
const data = parse(content, errors, {
allowTrailingComma: true,
disallowComments: false,
}) as T | null
return {
data: errors.length > 0 ? null : data,
errors: errors.map((e) => ({
message: printParseErrorCode(e.error),
offset: e.offset,
length: e.length,
})),
}
}
export function readJsoncFile<T = unknown>(filePath: string): T | null {
try {
const content = readFileSync(filePath, "utf-8")
return parseJsonc<T>(content)
} catch {
return null
}
}
export function detectConfigFile(basePath: string): {
format: "json" | "jsonc" | "none"
path: string
} {
const jsoncPath = `${basePath}.jsonc`
const jsonPath = `${basePath}.json`
if (existsSync(jsoncPath)) {
return { format: "jsonc", path: jsoncPath }
}
if (existsSync(jsonPath)) {
return { format: "json", path: jsonPath }
}
return { format: "none", path: jsonPath }
}