| import fs from 'fs/promises'; |
| import path from 'path'; |
| import { fileURLToPath } from 'url'; |
|
|
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| const DOCS_PATH = path.resolve(__dirname, '..', 'app-docs.md'); |
|
|
| let cachedSections = []; |
| let cachedMtimeMs = 0; |
|
|
| function slugify(text) { |
| return String(text || '') |
| .toLowerCase() |
| .replace(/[^a-z0-9]+/g, '-') |
| .replace(/^-+|-+$/g, ''); |
| } |
|
|
| async function loadSections() { |
| const stats = await fs.stat(DOCS_PATH); |
| if (cachedSections.length && cachedMtimeMs === stats.mtimeMs) return cachedSections; |
|
|
| const raw = await fs.readFile(DOCS_PATH, 'utf8'); |
| const parts = raw.split(/^##\s+/m); |
| const first = parts.shift() || ''; |
| const sections = []; |
|
|
| const overview = first.replace(/^#.*$/m, '').trim(); |
| if (overview) { |
| sections.push({ |
| id: 'overview', |
| title: 'Overview', |
| content: overview, |
| }); |
| } |
|
|
| parts.forEach((part) => { |
| const lines = part.split('\n'); |
| const title = (lines.shift() || '').trim(); |
| const content = lines.join('\n').trim(); |
| if (!title || !content) return; |
| sections.push({ |
| id: slugify(title), |
| title, |
| content, |
| }); |
| }); |
|
|
| cachedSections = sections; |
| cachedMtimeMs = stats.mtimeMs; |
| return sections; |
| } |
|
|
| export async function listAppDocSections() { |
| const sections = await loadSections(); |
| return sections.map(({ id, title }) => ({ id, title })); |
| } |
|
|
| export async function readAppDocSection(sectionId) { |
| const target = String(sectionId || '').trim().toLowerCase(); |
| if (!target) return null; |
| const sections = await loadSections(); |
| return sections.find((section) => |
| section.id === target || section.title.toLowerCase() === target |
| ) || null; |
| } |
|
|