From 42d01da7f7a069bdf900e0e9fd5c5c87867acf2b Mon Sep 17 00:00:00 2001 From: Gronod Date: Sun, 17 May 2026 18:58:43 +0100 Subject: [PATCH] =?UTF-8?q?ci:=20fix=20mermaid=20parse=20=E2=80=94=20use?= =?UTF-8?q?=20jsdom=20to=20provide=20browser=20globals=20required=20by=20m?= =?UTF-8?q?ermaid.core.mjs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitea/workflows/docs-check.yml | 76 +++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 32 deletions(-) diff --git a/.gitea/workflows/docs-check.yml b/.gitea/workflows/docs-check.yml index f05352b..14f4e86 100644 --- a/.gitea/workflows/docs-check.yml +++ b/.gitea/workflows/docs-check.yml @@ -43,20 +43,29 @@ jobs: with: node-version: "22" - - name: Install mermaid - run: npm install mermaid + - name: Install mermaid and jsdom + run: npm install mermaid jsdom - name: Extract and validate Mermaid diagrams run: | - cat > check-mermaid.mjs << 'SCRIPT' - import { readFileSync, readdirSync } from 'fs'; - import { join } from 'path'; - import mermaid from './node_modules/mermaid/dist/mermaid.core.mjs'; + cat > check-mermaid.cjs << 'SCRIPT' + const { JSDOM } = require('jsdom'); + const fs = require('fs'); + const path = require('path'); + + // Provide minimal browser globals so mermaid.parse() works in Node + const dom = new JSDOM('', { url: 'http://localhost' }); + globalThis.window = dom.window; + globalThis.document = dom.window.document; + globalThis.DOMPurify = { + addHook: () => {}, removeHook: () => {}, setConfig: () => {}, + sanitize: (s) => s, isValidAttribute: () => true, + }; function findMdFiles(dir) { const out = []; - for (const e of readdirSync(dir, { withFileTypes: true })) { - const full = join(dir, e.name); + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, e.name); if (e.isDirectory() && e.name !== 'node_modules' && !e.name.startsWith('.')) out.push(...findMdFiles(full)); else if (e.isFile() && e.name.endsWith('.md')) @@ -65,32 +74,35 @@ jobs: return out; } - let errors = 0, total = 0; + import('./node_modules/mermaid/dist/mermaid.core.mjs').then(async (m) => { + const mermaid = m.default; + let errors = 0, total = 0; - for (const mdFile of findMdFiles('.')) { - const content = readFileSync(mdFile, 'utf8'); - const blocks = [...content.matchAll(/^```mermaid\n([\s\S]*?)^```/gm)]; - if (!blocks.length) continue; - console.log(`\nChecking ${mdFile} (${blocks.length} diagram(s))`); - for (let i = 0; i < blocks.length; i++) { - total++; - const diagram = blocks[i][1].trim(); - try { - await mermaid.parse(diagram); - console.log(` [OK] diagram ${i + 1}`); - } catch (err) { - const msg = String(err.message || err).split('\n')[0]; - console.error(` [FAIL] diagram ${i + 1}: ${msg}`); - console.log(`::warning file=${mdFile}::Mermaid diagram ${i + 1} failed: ${msg}`); - errors++; + for (const mdFile of findMdFiles('.')) { + const content = fs.readFileSync(mdFile, 'utf8'); + const blocks = [...content.matchAll(/^```mermaid\n([\s\S]*?)^```/gm)]; + if (!blocks.length) continue; + console.log(`\nChecking ${mdFile} (${blocks.length} diagram(s))`); + for (let i = 0; i < blocks.length; i++) { + total++; + const diagram = blocks[i][1].trim(); + try { + await mermaid.parse(diagram); + console.log(` [OK] diagram ${i + 1}`); + } catch (err) { + const msg = String(err.message || err).split('\n')[0]; + console.error(` [FAIL] diagram ${i + 1}: ${msg}`); + console.log(`::warning file=${mdFile}::Mermaid diagram ${i + 1} failed: ${msg}`); + errors++; + } } } - } - console.log(`\nTotal: ${total}. Failures: ${errors}`); - if (errors > 0) { - console.log(`::warning::${errors} Mermaid diagram(s) failed to parse.`); - process.exit(1); - } + console.log(`\nTotal: ${total}. Failures: ${errors}`); + if (errors > 0) { + console.log(`::warning::${errors} Mermaid diagram(s) failed to parse.`); + process.exit(1); + } + }).catch(e => { console.error('Fatal:', e.message); process.exit(1); }); SCRIPT - node check-mermaid.mjs + node check-mermaid.cjs