name: Docs Check on: push: branches: ["**", "!main", "!release/**"] paths: - "**.md" - ".gitea/workflows/docs-check.yml" pull_request: branches: ["**", "!main", "!release/**"] paths: - "**.md" - ".gitea/workflows/docs-check.yml" jobs: markdown-lint: name: Markdown lint runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "22" - name: Install markdownlint-cli run: npm install -g markdownlint-cli - name: Lint all Markdown files run: markdownlint "**/*.md" --ignore node_modules mermaid-parse: name: Mermaid diagram parse check runs-on: ubuntu-latest continue-on-error: true steps: - uses: actions/checkout@v4 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: "22" - name: Install mermaid and jsdom run: npm install mermaid jsdom - name: Extract and validate Mermaid diagrams run: | 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 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')) out.push(full); } return out; } 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 = 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); } }).catch(e => { console.error('Fatal:', e.message); process.exit(1); }); SCRIPT node check-mermaid.cjs