import { execFileSync } from 'node:child_process' import { readdir, readFile, stat } from 'node:fs/promises' import { join } from 'node:path' export const stripSidenotes = (text) => { let result = '' for (let index = 0; index < text.length;) { if (text[index] === '^' && text[index + 1] === '[') { const end = text.indexOf(']', index + 2) if (end >= 0) { index = end + 1 continue } } result += text[index++] } return result } export const dateFor = async (file) => { try { const gitDate = execFileSync( 'git', ['log', '-1', '--date=format-local:%Y-%m-%dT%H:%M:%SZ', '--format=%ad', '--', file], { env: { ...process.env, TZ: 'UTC' }, stdio: ['ignore', 'pipe', 'ignore'] }, ).toString().trim() if (gitDate) return gitDate } catch {} try { return (await stat(file)).mtime.toISOString() } catch { return '' } } export const readPages = async (dir, { includeDrafts = false } = {}) => { let entries try { entries = await readdir(dir, { withFileTypes: true }) } catch (error) { if (error.code === 'ENOENT') return [] throw error } const markdownEntries = entries .filter((entry) => entry.isFile() && entry.name.endsWith('.md') && entry.name !== '.md' && (includeDrafts || !entry.name.startsWith('.'))) .sort((a, b) => a.name.localeCompare(b.name)) const pages = await Promise.all(markdownEntries.map(async (entry) => { const file = join(dir, entry.name) const source = await readFile(file, 'utf8') const titleLine = source.split(/\r?\n/).find((line) => line.startsWith('# ')) if (!titleLine) return undefined const draft = entry.name.startsWith('.') const name = (draft ? entry.name.slice(1) : entry.name).slice(0, -3) if (!name) return undefined return { name, title: stripSidenotes(titleLine.slice(2).trim()), file, source, date: await dateFor(file), draft, } })) const byName = new Map() for (const page of pages.filter(Boolean)) { const existing = byName.get(page.name) if (!existing || (existing.draft && !page.draft)) byName.set(page.name, page) } return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name)) }