import { applyEdits, modify, parse, printParseErrorCode } from "jsonc-parser"; import { dirname } from "node:path"; import { readFile, writeFile } from "node:fs/promises"; import { ApiError } from "./errors.js"; import { ensureDir, exists } from "./utils.js"; interface ParseResult { data: T; raw: string; } export async function readJsoncFile(path: string, fallback: T): Promise> { if (!(await exists(path))) { return { data: fallback, raw: "" }; } const raw = await readFile(path, "utf8"); const errors: { error: number; offset: number; length: number }[] = []; const data = parse(raw, errors, { allowTrailingComma: true }) as T; if (errors.length > 0) { const details = errors.map((error) => ({ code: printParseErrorCode(error.error), offset: error.offset, length: error.length, })); throw new ApiError(422, "invalid_jsonc", "Failed to parse JSONC", details); } return { data, raw }; } export async function updateJsoncTopLevel(path: string, updates: Record): Promise { const hasFile = await exists(path); if (!hasFile) { await ensureDir(dirname(path)); const content = JSON.stringify(updates, null, 2) + "\n"; await writeFile(path, content, "utf8"); return; } let content = await readFile(path, "utf8"); const formattingOptions = { insertSpaces: true, tabSize: 2, eol: "\n" }; for (const [key, value] of Object.entries(updates)) { const edits = modify(content, [key], value, { formattingOptions }); content = applyEdits(content, edits); } await writeFile(path, content.endsWith("\n") ? content : content + "\n", "utf8"); } export async function updateJsoncPath(path: string, jsonPath: (string | number)[], value: unknown): Promise { const formattingOptions = { insertSpaces: true, tabSize: 2, eol: "\n" }; const hasFile = await exists(path); if (!hasFile) { await ensureDir(dirname(path)); let content = "{}\n"; const edits = modify(content, jsonPath, value, { formattingOptions }); content = applyEdits(content, edits); await writeFile(path, content.endsWith("\n") ? content : content + "\n", "utf8"); return; } let content = await readFile(path, "utf8"); const edits = modify(content, jsonPath, value, { formattingOptions }); content = applyEdits(content, edits); await writeFile(path, content.endsWith("\n") ? content : content + "\n", "utf8"); } export async function writeJsoncFile(path: string, value: unknown): Promise { await ensureDir(dirname(path)); const content = JSON.stringify(value, null, 2) + "\n"; await writeFile(path, content, "utf8"); }