/**
* 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/.hbs` template name (default: slug)
* breadcrumb: Array # 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 + 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;
}