/** * scripts/generate-docs/lib/markdown-parser.ts * * Reads `docs-src/*.md`, splits frontmatter (gray-matter) from body, and * converts the body to HTML (marked). Returns `PageMeta[]` ready for the * context builder. * * Frontmatter shape (per page): * slug: string # output filename (slug.html) * title: string | { fr, en } # full page title (used in + header) * pageTitle: string | { fr, en }# short title shown in the header bar * icon: string # emoji or short string * page: string # `pages/<x>.hbs` template name (default: slug) * breadcrumb: Array<string | { fr, en }> # optional crumb trail */ import { readFileSync, readdirSync } from 'node:fs'; import { join, basename } from 'node:path'; import matter from 'gray-matter'; import { marked } from 'marked'; const DOCS_SRC = join(__dirname, '..', '..', '..', 'docs-src'); export type LocalisedString = string | { fr: string; en: string }; export interface PageMeta { /** Filename slug (without extension): `installation`, `ba-develop`, … */ slug: string; /** Full HTML <title> + sidebar tooltip */ title: LocalisedString; /** Short label rendered in the header bar */ pageTitle: LocalisedString; /** Emoji or short string */ icon: string; /** Handlebars template file (without `.hbs`) — defaults to slug */ pageTemplate: string; /** Optional breadcrumb segments, leftmost first */ breadcrumb: LocalisedString[]; /** Raw markdown body — Handlebars-preprocessed then rendered at build time */ bodyMarkdown: string; /** Pre-rendered HTML from the markdown body (no Handlebars preprocessing) */ bodyHtml: string; /** Absolute path of the source markdown file */ sourcePath: string; } /** Render a markdown string to HTML with the generator's marked config. */ export function markdownToHtml(md: string): string { return String(marked.parse(md)); } function configureMarked(): void { marked.setOptions({ gfm: true, breaks: false, }); } configureMarked(); export function parsePageFile(absPath: string): PageMeta { const raw = readFileSync(absPath, 'utf-8'); const { data, content } = matter(raw); const slug = (data.slug as string) || basename(absPath, '.md'); const bodyHtml = String(marked.parse(content)); return { slug, title: (data.title as LocalisedString) || slug, pageTitle: (data.pageTitle as LocalisedString) || (data.title as LocalisedString) || slug, icon: (data.icon as string) || '📄', pageTemplate: (data.page as string) || slug, breadcrumb: (data.breadcrumb as LocalisedString[]) || [], bodyMarkdown: content, bodyHtml, sourcePath: absPath, }; } /** * Walks `docs-src/*.md` (excluding `_data/` and `_partials/`), parses each, * and returns them sorted by slug. */ export function parseAllPages(): PageMeta[] { const entries = readdirSync(DOCS_SRC, { withFileTypes: true }); const pages: PageMeta[] = []; for (const entry of entries) { if (!entry.isFile()) continue; if (!entry.name.endsWith('.md')) continue; if (entry.name.startsWith('_')) continue; pages.push(parsePageFile(join(DOCS_SRC, entry.name))); } pages.sort((a, b) => a.slug.localeCompare(b.slug)); return pages; }