name: Docs Check on: push: branches: ["**"] paths: - "**.md" - ".gitea/workflows/docs-check.yml" pull_request: branches: ["**"] 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-js CLI run: npm install -g @mermaid-js/mermaid-cli - name: Write Puppeteer config (no-sandbox for CI) run: | echo '{"args":["--no-sandbox","--disable-setuid-sandbox"]}' > /tmp/puppeteer-config.json - name: Extract and validate Mermaid diagrams run: | node - <<'EOF' const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const os = require('os'); function findMarkdownFiles(dir) { const results = []; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { const full = path.join(dir, entry.name); if (entry.isDirectory() && entry.name !== 'node_modules' && !entry.name.startsWith('.')) { results.push(...findMarkdownFiles(full)); } else if (entry.isFile() && entry.name.endsWith('.md')) { results.push(full); } } return results; } const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mermaid-')); let errors = 0; let total = 0; for (const mdFile of findMarkdownFiles('.')) { 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))`); blocks.forEach((match, i) => { total++; const mmdFile = path.join(tmpDir, `diagram-${total}.mmd`); const svgFile = path.join(tmpDir, `diagram-${total}.svg`); fs.writeFileSync(mmdFile, match[1]); try { execSync( `mmdc -i "${mmdFile}" -o "${svgFile}" --puppeteerConfigFile /tmp/puppeteer-config.json`, { stdio: 'pipe' } ); console.log(` [OK] diagram ${i + 1}`); } catch (err) { const msg = (err.stderr || err.stdout || '').toString().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 diagrams checked: ${total}. Failures: ${errors}`); if (errors > 0) { console.log(`::warning::${errors} Mermaid diagram(s) failed to parse.`); process.exit(1); } EOF