import { randomUUID } from "node:crypto"; import fs from "node:fs/promises"; import path from "node:path"; export async function readJsonFile(filePath: string): Promise { try { const raw = await fs.readFile(filePath, "utf8"); return JSON.parse(raw) as T; } catch { return null; } } export async function writeJsonAtomic( filePath: string, value: unknown, options?: { mode?: number }, ) { const mode = options?.mode ?? 0o600; const dir = path.dirname(filePath); await fs.mkdir(dir, { recursive: true }); const tmp = `${filePath}.${randomUUID()}.tmp`; await fs.writeFile(tmp, JSON.stringify(value, null, 2), "utf8"); try { await fs.chmod(tmp, mode); } catch { // best-effort; ignore on platforms without chmod } await fs.rename(tmp, filePath); try { await fs.chmod(filePath, mode); } catch { // best-effort; ignore on platforms without chmod } } export function createAsyncLock() { let lock: Promise = Promise.resolve(); return async function withLock(fn: () => Promise): Promise { const prev = lock; let release: (() => void) | undefined; lock = new Promise((resolve) => { release = resolve; }); await prev; try { return await fn(); } finally { release?.(); } }; }