diff --git a/.gitea/workflows/docs-check.yml b/.gitea/workflows/docs-check.yml new file mode 100644 index 0000000..14f4e86 --- /dev/null +++ b/.gitea/workflows/docs-check.yml @@ -0,0 +1,108 @@ +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 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 diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..0546a3e --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,17 @@ +{ + "default": true, + "MD009": false, + "MD012": false, + "MD013": false, + "MD022": false, + "MD029": false, + "MD031": false, + "MD032": false, + "MD033": false, + "MD034": false, + "MD036": false, + "MD040": false, + "MD041": false, + "MD058": false, + "MD060": false +} diff --git a/README.md b/README.md index 3e60666..a825280 100644 --- a/README.md +++ b/README.md @@ -342,3 +342,4 @@ MIT --- *sofarr: See what has downloaded "so far" from the comfort of your "sofa"* + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e566804..4afe98e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1469,3 +1469,6 @@ flowchart TD style AF fill:#d4edda style AG fill:#f8d7da ``` + + +