Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 204 additions & 0 deletions .github/workflows/validate-mermaid-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
name: Validate Mermaid Diagrams

on:
pull_request:
branches:
- develop
- main
paths:
- "**/*.md"
- "**/*.mdx"
push:
branches-ignore:
- main
- develop
paths:
- "**/*.md"
- "**/*.mdx"

concurrency:
group: mermaid-validate-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
name: Mermaid Diagram Validation
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "npm"

- name: Install dependencies
run: npm ci

- name: Identify changed Markdown files
id: changed
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
else
BASE="${{ github.event.before }}"
HEAD="${{ github.sha }}"
fi

# Fall back to HEAD~1 when base is the null SHA (first push on a branch)
if [ "$BASE" = "0000000000000000000000000000000000000000" ]; then
BASE="HEAD~1"
fi

CHANGED=$(git diff --name-only "$BASE" "$HEAD" -- '*.md' '*.mdx' 2>/dev/null || git diff --name-only HEAD~1 HEAD -- '*.md' '*.mdx')
echo "files=$CHANGED" >> "$GITHUB_OUTPUT"

if [ -z "$CHANGED" ]; then
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "Changed Markdown files:"
echo "$CHANGED"
fi

- name: Check for Mermaid diagrams in changed files
id: has_diagrams
if: steps.changed.outputs.has_changes == 'true'
run: |
CHANGED="${{ steps.changed.outputs.files }}"
HAS_MERMAID=false
for f in $CHANGED; do
if [ -f "$f" ] && grep -q '```mermaid' "$f"; then
HAS_MERMAID=true
break
fi
done
echo "result=$HAS_MERMAID" >> "$GITHUB_OUTPUT"

- name: Skip — no Mermaid diagrams in changed files
if: steps.has_diagrams.outputs.result != 'true'
run: echo "No Mermaid diagrams found in changed files — skipping validation."

- name: Validate diagram syntax
id: syntax
if: steps.has_diagrams.outputs.result == 'true'
run: npm run validate:mermaid-syntax
continue-on-error: true

- name: Validate accessibility (accTitle / accDescr)
id: accessibility
if: steps.has_diagrams.outputs.result == 'true'
run: npm run validate:mermaid-accessibility
continue-on-error: true

- name: Validate colour contrast (WCAG 2.2 AA)
id: contrast
if: steps.has_diagrams.outputs.result == 'true'
run: npm run validate:mermaid-contrast
continue-on-error: true

- name: Collect results
id: results
if: steps.has_diagrams.outputs.result == 'true'
run: |
SYNTAX="${{ steps.syntax.outcome }}"
A11Y="${{ steps.accessibility.outcome }}"
CONTRAST="${{ steps.contrast.outcome }}"

echo "syntax_ok=$([ "$SYNTAX" = "success" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
echo "a11y_ok=$([ "$A11Y" = "success" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
echo "contrast_ok=$([ "$CONTRAST" = "success" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"

if [ "$SYNTAX" = "success" ] && [ "$A11Y" = "success" ] && [ "$CONTRAST" = "success" ]; then
echo "all_passed=true" >> "$GITHUB_OUTPUT"
else
echo "all_passed=false" >> "$GITHUB_OUTPUT"
fi

- name: Post PR comment with results
if: github.event_name == 'pull_request' && steps.has_diagrams.outputs.result == 'true'
uses: actions/github-script@v7
with:
script: |
const syntaxOk = '${{ steps.results.outputs.syntax_ok }}' === 'true';
const a11yOk = '${{ steps.results.outputs.a11y_ok }}' === 'true';
const contrastOk = '${{ steps.results.outputs.contrast_ok }}' === 'true';
const allPassed = syntaxOk && a11yOk && contrastOk;

const icon = (ok) => ok ? '✅' : '❌';
const label = (ok) => ok ? 'Passed' : 'Failed';

const body = [
'## 🎨 Mermaid Diagram Validation',
'',
allPassed
? '✅ All Mermaid diagram checks passed.'
: '❌ One or more Mermaid diagram checks failed. Review the job logs and fix before merging.',
'',
'| Check | Result |',
'|-------|--------|',
`| ${icon(syntaxOk)} Syntax (diagram type, direction, bracket matching) | ${label(syntaxOk)} |`,
`| ${icon(a11yOk)} Accessibility (\`accTitle\` / \`accDescr\` present) | ${label(a11yOk)} |`,
`| ${icon(contrastOk)} Colour contrast (WCAG 2.2 AA ≥ 4.5:1) | ${label(contrastOk)} |`,
'',
allPassed
? ''
: '**Fix guidance:** See [`instructions/mermaid.instructions.md`](./instructions/mermaid.instructions.md) for the approved colour palette and required structure.',
].join('\n');

// Find and update an existing comment if present
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const existing = comments.find(
(c) => c.user.type === 'Bot' && c.body.includes('Mermaid Diagram Validation')
);

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}

- name: Fail if any check failed
if: steps.results.outputs.all_passed == 'false'
run: |
echo "One or more Mermaid validation checks failed."
echo " Syntax: ${{ steps.syntax.outcome }}"
echo " A11y: ${{ steps.accessibility.outcome }}"
echo " Contrast: ${{ steps.contrast.outcome }}"
exit 1

- name: Upload validation reports
if: always() && steps.has_diagrams.outputs.result == 'true'
uses: actions/upload-artifact@v4
with:
name: mermaid-validation-reports-${{ github.run_number }}
path: |
.github/reports/mermaid-validation-report.md
.github/reports/mermaid-accessibility-report.md
.github/reports/mermaid/colour-contrast-report-*.md
if-no-files-found: ignore
retention-days: 14
Loading
Loading