Update Docs
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Validates ```mermaid fenced blocks in Markdown by rendering with @mermaid-js/mermaid-cli.
|
||||
*
|
||||
* - Skips unless `BARE_OS_VALIDATE_MERMAID=1` or `GITHUB_ACTIONS=true` (keeps local `pretest` fast).
|
||||
* - Installs mermaid-cli once into a temp directory (requires network when not cached).
|
||||
*
|
||||
* Used by `.github/workflows/docs-mermaid-smoke.yml` (workflow_dispatch + weekly schedule).
|
||||
*/
|
||||
import { execFileSync, spawnSync } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
const SHOULD_RUN =
|
||||
process.env.BARE_OS_VALIDATE_MERMAID === '1' || process.env.GITHUB_ACTIONS === 'true'
|
||||
|
||||
if (!SHOULD_RUN) {
|
||||
console.log(
|
||||
'validate-mermaid-syntax: skip (set BARE_OS_VALIDATE_MERMAID=1 or run in GitHub Actions)'
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const GLOBS = ['docs', 'handbook', 'developer-guide', 'users-manual', 'scripts', 'packages']
|
||||
const ROOT_MD = ['README.md', 'DOCUMENTATION.md']
|
||||
|
||||
const SKIP_FILES = new Set(['packages/bare-os-bare-libs/README.kernel-lib-bare.md'])
|
||||
|
||||
function isSkippedPath(relFromRoot) {
|
||||
const norm = relFromRoot.split(path.sep).join('/')
|
||||
return norm === 'packages/bare-os-seeder/kernel' || norm.startsWith('packages/bare-os-seeder/kernel/')
|
||||
}
|
||||
|
||||
function walkMd(dir) {
|
||||
const abs = path.join(root, dir)
|
||||
if (!fs.existsSync(abs)) return []
|
||||
const out = []
|
||||
const st = fs.statSync(abs)
|
||||
if (st.isFile() && abs.endsWith('.md')) {
|
||||
out.push(abs)
|
||||
return out
|
||||
}
|
||||
if (!st.isDirectory()) return out
|
||||
for (const ent of fs.readdirSync(abs, { withFileTypes: true })) {
|
||||
if (ent.name === 'node_modules' || ent.name === '.git') continue
|
||||
const p = path.join(abs, ent.name)
|
||||
const relFromRoot = p.slice(root.length + 1)
|
||||
if (isSkippedPath(relFromRoot)) continue
|
||||
if (ent.isDirectory()) out.push(...walkMd(relFromRoot))
|
||||
else if (ent.isFile() && ent.name.endsWith('.md')) out.push(p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** @param {string} file */
|
||||
function extractMermaidBlocks(file) {
|
||||
const text = fs.readFileSync(file, 'utf8')
|
||||
const re = /```mermaid\n([\s\S]*?)```/g
|
||||
/** @type {string[]} */
|
||||
const blocks = []
|
||||
let m
|
||||
while ((m = re.exec(text))) {
|
||||
const body = m[1].trimEnd()
|
||||
if (body.length) blocks.push(body)
|
||||
}
|
||||
return blocks
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
const files = []
|
||||
for (const d of GLOBS) files.push(...walkMd(d))
|
||||
for (const name of ROOT_MD) {
|
||||
const p = path.join(root, name)
|
||||
if (fs.existsSync(p)) files.push(p)
|
||||
}
|
||||
|
||||
/** @type {{ file: string, index: number, body: string }[]} */
|
||||
const diagrams = []
|
||||
for (const file of files) {
|
||||
const rel = path.relative(root, file)
|
||||
if (SKIP_FILES.has(rel.split(path.sep).join('/'))) continue
|
||||
const blocks = extractMermaidBlocks(file)
|
||||
blocks.forEach((body, i) => diagrams.push({ file: rel, index: i, body }))
|
||||
}
|
||||
|
||||
if (diagrams.length === 0) {
|
||||
console.log('validate-mermaid-syntax: no mermaid blocks found')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const work = fs.mkdtempSync(path.join(os.tmpdir(), 'bare-os-mmdc-'))
|
||||
const npmInstall = spawnSync(
|
||||
'npm',
|
||||
['install', '--no-fund', '--no-audit', '@mermaid-js/[email protected]'],
|
||||
{ cwd: work, stdio: 'inherit', encoding: 'utf8' }
|
||||
)
|
||||
if (npmInstall.status !== 0) {
|
||||
console.error('validate-mermaid-syntax: npm install mermaid-cli failed')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const mmdc = path.join(work, 'node_modules', '.bin', 'mmdc')
|
||||
if (!fs.existsSync(mmdc)) {
|
||||
console.error('validate-mermaid-syntax: mmdc not found after install')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
let failed = 0
|
||||
for (let i = 0; i < diagrams.length; i++) {
|
||||
const { file, index, body } = diagrams[i]
|
||||
const base = `diagram-${i}`
|
||||
const input = path.join(work, `${base}.mmd`)
|
||||
const output = path.join(work, `${base}.svg`)
|
||||
fs.writeFileSync(input, `${body}\n`, 'utf8')
|
||||
try {
|
||||
execFileSync(mmdc, ['-i', input, '-o', output, '-b', 'transparent'], {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
encoding: 'utf8',
|
||||
})
|
||||
} catch (e) {
|
||||
failed++
|
||||
const stderr = e instanceof Error && 'stderr' in e ? String(e.stderr) : String(e)
|
||||
console.error(`\n--- Mermaid render failed: ${file} block #${index + 1} ---\n${stderr}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (failed > 0) {
|
||||
console.error(`\nvalidate-mermaid-syntax: ${failed} of ${diagrams.length} diagram(s) failed`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`validate-mermaid-syntax: OK (${diagrams.length} diagram(s))`)
|
||||
Reference in New Issue
Block a user