Files
brain/tools/secretary-index.mjs
T

27 lines
1.7 KiB
JavaScript

import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import lockfile from 'proper-lockfile';
import { writeFileAtomic } from './secretary-layer1.mjs';
// Апсерт строки дела в оглавление (§D8). Ключ — <slug>/protocol.md.
export function upsertIndexEntry(indexMd, { slug, title, goal, status, date }) {
const line = `- [${title}](${slug}/protocol.md) — ${goal} · ${status} · ${date}`;
const key = `(${slug}/protocol.md)`;
const lines = String(indexMd || '').split('\n').filter((l) => l.length > 0);
const idx = lines.findIndex((l) => l.includes(key));
if (idx >= 0) lines[idx] = line;
else lines.push(line);
return lines.join('\n');
}
/** Безопасная к параллели запись строки темы в оглавление: read-modify-write под замком файла.
* Две темы пишут одно оглавление — замок сериализует, upsert по ключу-теме не теряет строк.
* proper-lockfile стат-ит цель → файл должен существовать ДО взятия замка (создаём пустой). */
export async function writeIndexSafely(idxFile, entry, { lockImpl = lockfile } = {}) {
if (!existsSync(idxFile)) { try { writeFileSync(idxFile, ''); } catch { /* гонка создания — переживём */ } }
const release = await lockImpl.lock(idxFile, { stale: 15_000, retries: { retries: 10, minTimeout: 20, maxTimeout: 200 }, realpath: false });
try {
const cur = existsSync(idxFile) ? readFileSync(idxFile, 'utf-8') : '';
writeFileAtomic(idxFile, upsertIndexEntry(cur, entry));
} finally { await release(); }
}