Skip to content

Commit 8c11978

Browse files
committed
feat: add frontmatter validation for new docs
Validates title, description, keywords (1-3 items) in new docs files. Signed-off-by: Marcin Skalski <marcin.skalski@konghq.com>
1 parent fc699fa commit 8c11978

File tree

5 files changed

+339
-2
lines changed

5 files changed

+339
-2
lines changed

.github/workflows/ci.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,20 @@ jobs:
198198
env:
199199
BASE_BRANCH: origin/${{ github.base_ref }}
200200
run: mise vale:version-urls
201+
202+
frontmatter:
203+
name: Validate frontmatter
204+
runs-on: ubuntu-24.04
205+
steps:
206+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
207+
with:
208+
fetch-depth: 0
209+
- uses: jdx/mise-action@be3be2260bc02bc3fbf94c5e2fed8b7964baf074 # v3.4.0
210+
env:
211+
GITHUB_TOKEN: ${{ github.token }}
212+
- name: Fetch base branch
213+
run: git fetch origin ${{ github.base_ref }}:${{ github.base_ref }}
214+
- name: Validate frontmatter in new docs
215+
env:
216+
BASE_BRANCH: origin/${{ github.base_ref }}
217+
run: mise frontmatter:validate

.github/workflows/test-tools.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Test tools
2+
on:
3+
push:
4+
branches: [master]
5+
paths: [tools/**]
6+
pull_request:
7+
paths: [tools/**]
8+
jobs:
9+
test-tools:
10+
name: Test tool scripts
11+
runs-on: ubuntu-24.04
12+
steps:
13+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
14+
- uses: jdx/mise-action@be3be2260bc02bc3fbf94c5e2fed8b7964baf074 # v3.4.0
15+
env:
16+
GITHUB_TOKEN: ${{ github.token }}
17+
- name: Run tool tests
18+
run: mise test:tools

mise.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ git diff --name-only --diff-filter=d origin/master...HEAD | \
106106
description = "Check for hardcoded version URLs in changed files"
107107
run = "tools/check-version-urls.sh"
108108

109+
[tasks."frontmatter:validate"]
110+
description = "Validate frontmatter in new docs files"
111+
run = "tools/validate-frontmatter.sh"
112+
113+
[tasks."test:tools"]
114+
description = "Run all tool tests (tools/test-*.sh)"
115+
run = "for f in tools/test-*.sh; do [ -x \"$f\" ] && \"$f\"; done"
116+
109117
[tasks.check]
110-
description = "Run vale and link checker (requires dev server running at localhost:7777)"
111-
depends = ["vale:branch", "vale:version-urls", "links:check"]
118+
description = "Run necessary checks"
119+
depends = ["vale:branch", "vale:version-urls", "frontmatter:validate"]

tools/test-validate-frontmatter.sh

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Tests for validate-frontmatter.sh
5+
# Runs validation logic against fixture files without git
6+
7+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8+
FIXTURES_DIR="$SCRIPT_DIR/test-fixtures/frontmatter"
9+
PASSED=0
10+
FAILED=0
11+
12+
# Create fixtures directory
13+
mkdir -p "$FIXTURES_DIR"
14+
15+
# Helper to run validation on a single file
16+
validate_file() {
17+
local file="$1"
18+
local ERRORS=0
19+
20+
FRONTMATTER=$(awk '/^---$/{if(++c==2)exit; next}c==1' "$file")
21+
22+
if [ -z "$FRONTMATTER" ]; then
23+
echo "no-frontmatter"
24+
return
25+
fi
26+
27+
if ! echo "$FRONTMATTER" | grep -qE '^title:'; then
28+
ERRORS=$((ERRORS + 1))
29+
fi
30+
31+
if ! echo "$FRONTMATTER" | grep -qE '^description:'; then
32+
ERRORS=$((ERRORS + 1))
33+
fi
34+
35+
if ! echo "$FRONTMATTER" | grep -qE '^keywords:'; then
36+
ERRORS=$((ERRORS + 1))
37+
else
38+
KEYWORDS_COUNT=$(echo "$FRONTMATTER" | awk '/^keywords:/{f=1;next} f && /^[a-z]/{exit} f && /^ *-/' | wc -l | tr -d ' ')
39+
if [ "$KEYWORDS_COUNT" -lt 1 ]; then
40+
ERRORS=$((ERRORS + 1))
41+
elif [ "$KEYWORDS_COUNT" -gt 3 ]; then
42+
ERRORS=$((ERRORS + 1))
43+
fi
44+
fi
45+
46+
echo "$ERRORS"
47+
}
48+
49+
# Test helper
50+
test_case() {
51+
local name="$1"
52+
local expected="$2"
53+
local file="$3"
54+
55+
result=$(validate_file "$file")
56+
if [ "$result" = "$expected" ]; then
57+
echo "$name"
58+
PASSED=$((PASSED + 1))
59+
else
60+
echo "$name (expected: $expected, got: $result)"
61+
FAILED=$((FAILED + 1))
62+
fi
63+
}
64+
65+
echo "Creating test fixtures..."
66+
67+
# Valid file with all fields
68+
cat > "$FIXTURES_DIR/valid.md" << 'EOF'
69+
---
70+
title: Valid Page
71+
description: This is a valid page with all required fields
72+
keywords:
73+
- test
74+
- valid
75+
---
76+
77+
# Content
78+
EOF
79+
80+
# Valid with 1 keyword
81+
cat > "$FIXTURES_DIR/valid-1-keyword.md" << 'EOF'
82+
---
83+
title: Valid Page
84+
description: This is valid
85+
keywords:
86+
- single
87+
---
88+
89+
# Content
90+
EOF
91+
92+
# Valid with 3 keywords
93+
cat > "$FIXTURES_DIR/valid-3-keywords.md" << 'EOF'
94+
---
95+
title: Valid Page
96+
description: This is valid
97+
keywords:
98+
- one
99+
- two
100+
- three
101+
---
102+
103+
# Content
104+
EOF
105+
106+
# Missing description
107+
cat > "$FIXTURES_DIR/missing-description.md" << 'EOF'
108+
---
109+
title: Missing Description
110+
keywords:
111+
- test
112+
---
113+
114+
# Content
115+
EOF
116+
117+
# Missing keywords
118+
cat > "$FIXTURES_DIR/missing-keywords.md" << 'EOF'
119+
---
120+
title: Missing Keywords
121+
description: No keywords here
122+
---
123+
124+
# Content
125+
EOF
126+
127+
# Missing title
128+
cat > "$FIXTURES_DIR/missing-title.md" << 'EOF'
129+
---
130+
description: No title here
131+
keywords:
132+
- test
133+
---
134+
135+
# Content
136+
EOF
137+
138+
# Too many keywords (4)
139+
cat > "$FIXTURES_DIR/too-many-keywords.md" << 'EOF'
140+
---
141+
title: Too Many Keywords
142+
description: Has 4 keywords
143+
keywords:
144+
- one
145+
- two
146+
- three
147+
- four
148+
---
149+
150+
# Content
151+
EOF
152+
153+
# Empty keywords array
154+
cat > "$FIXTURES_DIR/empty-keywords.md" << 'EOF'
155+
---
156+
title: Empty Keywords
157+
description: Keywords field exists but empty
158+
keywords:
159+
---
160+
161+
# Content
162+
EOF
163+
164+
# No frontmatter
165+
cat > "$FIXTURES_DIR/no-frontmatter.md" << 'EOF'
166+
# Just Content
167+
168+
No frontmatter here
169+
EOF
170+
171+
# All fields missing
172+
cat > "$FIXTURES_DIR/all-missing.md" << 'EOF'
173+
---
174+
layout: page
175+
---
176+
177+
# Content
178+
EOF
179+
180+
echo ""
181+
echo "Running tests..."
182+
echo ""
183+
184+
# Run tests
185+
test_case "valid file with all fields" "0" "$FIXTURES_DIR/valid.md"
186+
test_case "valid with 1 keyword" "0" "$FIXTURES_DIR/valid-1-keyword.md"
187+
test_case "valid with 3 keywords" "0" "$FIXTURES_DIR/valid-3-keywords.md"
188+
test_case "missing description" "1" "$FIXTURES_DIR/missing-description.md"
189+
test_case "missing keywords" "1" "$FIXTURES_DIR/missing-keywords.md"
190+
test_case "missing title" "1" "$FIXTURES_DIR/missing-title.md"
191+
test_case "too many keywords (4)" "1" "$FIXTURES_DIR/too-many-keywords.md"
192+
test_case "empty keywords array" "1" "$FIXTURES_DIR/empty-keywords.md"
193+
test_case "no frontmatter" "no-frontmatter" "$FIXTURES_DIR/no-frontmatter.md"
194+
test_case "all fields missing" "3" "$FIXTURES_DIR/all-missing.md"
195+
196+
echo ""
197+
echo "Results: $PASSED passed, $FAILED failed"
198+
199+
# Cleanup
200+
rm -rf "$FIXTURES_DIR"
201+
202+
if [ $FAILED -gt 0 ]; then
203+
exit 1
204+
fi

tools/validate-frontmatter.sh

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
# Validate frontmatter in new documentation files
5+
# Checks for required fields: title, description, keywords (1-3 items)
6+
# Usage: ./validate-frontmatter.sh [base-branch]
7+
8+
BASE_BRANCH="${1:-${BASE_BRANCH:-origin/master}}"
9+
10+
echo "Validating frontmatter in new docs files..."
11+
12+
FILES_FOUND=0
13+
ERRORS=0
14+
15+
# Get only NEW (added) markdown files in app/_src/
16+
while IFS= read -r file; do
17+
# Skip if no files
18+
[ -z "$file" ] && continue
19+
20+
FILES_FOUND=1
21+
FILE_ERRORS=0
22+
23+
# Extract frontmatter (between first two ---)
24+
FRONTMATTER=$(awk '/^---$/{if(++c==2)exit; next}c==1' "$file")
25+
26+
if [ -z "$FRONTMATTER" ]; then
27+
echo "ERROR: $file - no frontmatter found"
28+
ERRORS=$((ERRORS + 1))
29+
continue
30+
fi
31+
32+
# Check for title
33+
if ! echo "$FRONTMATTER" | grep -qE '^title:'; then
34+
echo "ERROR: $file - missing 'title' field"
35+
FILE_ERRORS=$((FILE_ERRORS + 1))
36+
fi
37+
38+
# Check for description
39+
if ! echo "$FRONTMATTER" | grep -qE '^description:'; then
40+
echo "ERROR: $file - missing 'description' field"
41+
FILE_ERRORS=$((FILE_ERRORS + 1))
42+
fi
43+
44+
# Check for keywords
45+
if ! echo "$FRONTMATTER" | grep -qE '^keywords:'; then
46+
echo "ERROR: $file - missing 'keywords' field"
47+
FILE_ERRORS=$((FILE_ERRORS + 1))
48+
else
49+
# Count keywords (lines starting with " - " after "keywords:")
50+
KEYWORDS_SECTION=$(echo "$FRONTMATTER" | awk '/^keywords:/{f=1;next} f && /^[a-z]/{exit} f && /^ *-/' | wc -l | tr -d ' ')
51+
52+
if [ "$KEYWORDS_SECTION" -lt 1 ]; then
53+
echo "ERROR: $file - keywords must have at least 1 item"
54+
FILE_ERRORS=$((FILE_ERRORS + 1))
55+
elif [ "$KEYWORDS_SECTION" -gt 3 ]; then
56+
echo "ERROR: $file - keywords must have at most 3 items (found $KEYWORDS_SECTION)"
57+
FILE_ERRORS=$((FILE_ERRORS + 1))
58+
fi
59+
fi
60+
61+
if [ $FILE_ERRORS -gt 0 ]; then
62+
ERRORS=$((ERRORS + FILE_ERRORS))
63+
fi
64+
65+
done < <(git diff --name-only --diff-filter=A "${BASE_BRANCH}...HEAD" 2>/dev/null | \
66+
grep -E '^app/_src/.*\.(md|markdown)$' | \
67+
grep -v '/generated/' | \
68+
grep -v '/raw/' || true)
69+
70+
if [ $FILES_FOUND -eq 0 ]; then
71+
echo "No new docs files found"
72+
exit 0
73+
fi
74+
75+
if [ $ERRORS -gt 0 ]; then
76+
echo ""
77+
echo "Found $ERRORS frontmatter error(s)"
78+
echo ""
79+
echo "Required frontmatter format:"
80+
echo "---"
81+
echo "title: Page Title"
82+
echo "description: 1-2 sentence description for SEO"
83+
echo "keywords:"
84+
echo " - keyword1"
85+
echo " - keyword2"
86+
echo "---"
87+
exit 1
88+
fi
89+
90+
echo "All frontmatter valid"

0 commit comments

Comments
 (0)