Skip to content
Merged
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
25 changes: 20 additions & 5 deletions .github/workflows/md-extension-autofix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
--jq '{message: .commit.message}')
MESSAGE=$(echo "$COMMIT" | jq -r '.message')
echo "Latest commit message: $MESSAGE"
if echo "$MESSAGE" | grep -qE '^fix\(docs\): add missing \.md extension'; then
if echo "$MESSAGE" | grep -qE '^fix\(docs\): add missing \.md extension and inject frontmatter'; then
echo "Skipping: commit is from md-extension-autofix workflow"
echo "skip=true" >> "$GITHUB_OUTPUT"
else
Expand Down Expand Up @@ -71,19 +71,21 @@ jobs:
echo "$SUMMARY" > /tmp/md-extension-summary.json
RENAMED_COUNT=$(echo "$SUMMARY" | jq '.renamed | length')
SKIPPED_COUNT=$(echo "$SUMMARY" | jq '.skipped | length')
FRONTMATTER_COUNT=$(echo "$SUMMARY" | jq '.frontmatter_injected | length')
echo "renamed=$RENAMED_COUNT" >> "$GITHUB_OUTPUT"
echo "skipped=$SKIPPED_COUNT" >> "$GITHUB_OUTPUT"
echo "Renamed: $RENAMED_COUNT, Skipped: $SKIPPED_COUNT"
echo "frontmatter=$FRONTMATTER_COUNT" >> "$GITHUB_OUTPUT"
echo "Renamed: $RENAMED_COUNT, Skipped: $SKIPPED_COUNT, Frontmatter injected: $FRONTMATTER_COUNT"

- name: Commit renamed files
- name: Commit fixes
id: commit
if: steps.autofix.outputs.renamed > 0
run: |
if git diff --quiet && git diff --staged --quiet; then
echo "committed=false" >> "$GITHUB_OUTPUT"
else
git add -A docs/
git commit -m "fix(docs): add missing .md extension to renamed files"
git commit -m "fix(docs): add missing .md extension and inject frontmatter"
echo "committed=true" >> "$GITHUB_OUTPUT"
fi

Expand All @@ -106,9 +108,11 @@ jobs:

RENAMED_COUNT=0
SKIPPED_COUNT=0
FRONTMATTER_COUNT=0
if [ -f /tmp/md-extension-summary.json ]; then
RENAMED_COUNT=$(jq '.renamed | length' /tmp/md-extension-summary.json)
SKIPPED_COUNT=$(jq '.skipped | length' /tmp/md-extension-summary.json)
FRONTMATTER_COUNT=$(jq '.frontmatter_injected | length' /tmp/md-extension-summary.json)
fi

# Nothing to report — exit silently
Expand All @@ -129,8 +133,19 @@ jobs:
echo "Links to these files in other pages have been updated. Please review the changes in the commit above."
fi

if [ "$FRONTMATTER_COUNT" -gt 0 ]; then
echo ""
echo "**Frontmatter injected**"
echo ""
echo "The following files were missing frontmatter. \`title\`, \`description\`, and \`sidebar_position\` were derived automatically and may need manual review:"
echo ""
echo "| File |"
echo "|---|"
jq -r '.frontmatter_injected[] | "| `\(.)` |"' /tmp/md-extension-summary.json
fi

if [ "$SKIPPED_COUNT" -gt 0 ]; then
if [ "$RENAMED_COUNT" -gt 0 ]; then echo ""; fi
echo ""
echo "**Action needed: Possible missing \`.md\` extension**"
echo ""
echo "The following files were added to a docs directory without a \`.md\` extension, but couldn't be auto-renamed:"
Expand Down
87 changes: 74 additions & 13 deletions scripts/md-extension-autofix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,54 @@ is_ignored_extension() {

is_markdown_content() {
local file="$1"
local has_frontmatter=0
local has_heading=0
if head -10 "$file" | grep -qE '^---'; then
has_frontmatter=1
fi
if grep -qE '^#{1,6} ' "$file"; then
has_heading=1
fi
[ "$has_frontmatter" -eq 1 ] && [ "$has_heading" -eq 1 ]
grep -qE '^#{1,6} ' "$file"
}

has_frontmatter() {
local file="$1"
head -1 "$file" | grep -qE '^---'
}

extract_h1() {
local file="$1"
grep -m 1 -E '^# ' "$file" | sed 's/^# //'
}

calculate_sidebar_position() {
local file="$1"
local dir
dir=$(dirname "$file")
local basename
basename=$(basename "$file")
local position=1
local count=0
while IFS= read -r sibling; do
count=$((count + 1))
if [ "$(basename "$sibling")" = "$basename" ]; then
position=$count
fi
done < <(find "$dir" -maxdepth 1 -name '*.md' | sort)
echo $((position * 10))
}

inject_frontmatter() {
local file="$1"
local title
title=$(extract_h1 "$file")
local position
position=$(calculate_sidebar_position "$file")
local tmp
tmp=$(mktemp)
{
echo "---"
echo "title: \"$title\""
echo "description: \"$title\""
echo "sidebar_position: $position"
echo "---"
echo ""
cat "$file"
} > "$tmp"
mv "$tmp" "$file"
}

rewrite_links_in_docs() {
Expand All @@ -66,14 +105,15 @@ esac
CHANGED_FILES_LIST="${1:?Usage: md-extension-autofix.sh <changed-files-list>}"

if [ ! -f "$CHANGED_FILES_LIST" ]; then
echo '{"renamed": [], "skipped": []}'
echo '{"renamed": [], "skipped": [], "frontmatter_injected": []}'
exit 0
fi

RENAMED_FROM=()
RENAMED_TO=()
SKIPPED_FILES=()
SKIP_REASONS=()
FRONTMATTER_INJECTED=()

while IFS= read -r file; do
# Only process files inside docs/
Expand All @@ -82,8 +122,15 @@ while IFS= read -r file; do
# Skip deleted files
[ -f "$file" ] || continue

# Skip files that already have an extension
has_extension "$file" && continue
# Skip files that already have an extension — but still inject frontmatter into .md docs files missing it
if has_extension "$file"; then
if [[ "$file" == *.md ]] && [[ "$file" != docs/kb/* ]] && ! has_frontmatter "$file" && is_markdown_content "$file"; then
inject_frontmatter "$file"
git add "$file"
FRONTMATTER_INJECTED+=("$file")
fi
continue
fi

# Skip files with a known non-markdown extension
is_ignored_extension "$file" && continue
Expand Down Expand Up @@ -114,6 +161,13 @@ while IFS= read -r file; do
RENAMED_FROM+=("$file")
RENAMED_TO+=("$new_file")

# Inject frontmatter into renamed docs files (not KB — handled by derek skill)
if [[ "$new_file" != docs/kb/* ]] && ! has_frontmatter "$new_file"; then
inject_frontmatter "$new_file"
git add "$new_file"
FRONTMATTER_INJECTED+=("$new_file")
fi

done < "$CHANGED_FILES_LIST"

# Output JSON summary
Expand All @@ -131,4 +185,11 @@ for i in "${!SKIPPED_FILES[@]}"; do
done
SKIPPED_JSON+="]"

echo "{\"renamed\": ${RENAMED_JSON}, \"skipped\": ${SKIPPED_JSON}}"
FRONTMATTER_JSON="["
for i in "${!FRONTMATTER_INJECTED[@]}"; do
[ "$i" -gt 0 ] && FRONTMATTER_JSON+=","
FRONTMATTER_JSON+="\"${FRONTMATTER_INJECTED[$i]}\""
done
FRONTMATTER_JSON+="]"

echo "{\"renamed\": ${RENAMED_JSON}, \"skipped\": ${SKIPPED_JSON}, \"frontmatter_injected\": ${FRONTMATTER_JSON}}"
92 changes: 90 additions & 2 deletions scripts/test-md-extension-autofix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,14 @@ EOF
is_markdown_content "$TMPDIR_TEST/good.md" && R=$? || R=$?
assert_true "markdown: has frontmatter and heading" "$R"

# File with only heading, no frontmatter → not markdown
# File with only heading, no frontmatter → still markdown (frontmatter will be injected)
cat > "$TMPDIR_TEST/no-frontmatter" <<'EOF'
# My Heading

Some content here.
EOF
is_markdown_content "$TMPDIR_TEST/no-frontmatter" && R=$? || R=$?
assert_false "not markdown: heading only, no frontmatter" "$R"
assert_true "markdown: heading only, no frontmatter" "$R"

# File with only frontmatter, no heading → not markdown
cat > "$TMPDIR_TEST/no-heading" <<'EOF'
Expand Down Expand Up @@ -148,6 +148,94 @@ assert_true "link rewrite: relative path link updated" "$R"

rm -rf "$TMPDIR_LINKS"

# ---- has_frontmatter ----

TMPDIR_FM=$(mktemp -d)

cat > "$TMPDIR_FM/with-frontmatter.md" <<'EOF'
---
title: Test
---

# Heading
EOF

cat > "$TMPDIR_FM/no-frontmatter.md" <<'EOF'
# Heading

No frontmatter here.
EOF

has_frontmatter "$TMPDIR_FM/with-frontmatter.md" && R=$? || R=$?
assert_true "has_frontmatter: file starts with ---" "$R"

has_frontmatter "$TMPDIR_FM/no-frontmatter.md" && R=$? || R=$?
assert_false "has_frontmatter: file does not start with ---" "$R"

# ---- extract_h1 ----

cat > "$TMPDIR_FM/heading.md" <<'EOF'
---
title: Test
---

# My Article Title
EOF

RESULT=$(extract_h1 "$TMPDIR_FM/heading.md")
[ "$RESULT" = "My Article Title" ] && R=0 || R=1
assert_true "extract_h1: returns heading text without # prefix" "$R"

# ---- calculate_sidebar_position ----

mkdir -p "$TMPDIR_FM/siblings"
touch "$TMPDIR_FM/siblings/apple.md"
touch "$TMPDIR_FM/siblings/banana.md"
touch "$TMPDIR_FM/siblings/cherry.md"
touch "$TMPDIR_FM/siblings/date.md"
touch "$TMPDIR_FM/siblings/elderberry.md"

RESULT=$(calculate_sidebar_position "$TMPDIR_FM/siblings/cherry.md")
[ "$RESULT" = "30" ] && R=0 || R=1
assert_true "calculate_sidebar_position: 3rd of 5 → 30" "$R"

RESULT=$(calculate_sidebar_position "$TMPDIR_FM/siblings/apple.md")
[ "$RESULT" = "10" ] && R=0 || R=1
assert_true "calculate_sidebar_position: 1st of 5 → 10" "$R"

# ---- inject_frontmatter ----

mkdir -p "$TMPDIR_FM/article-dir"
touch "$TMPDIR_FM/article-dir/another.md"

cat > "$TMPDIR_FM/article-dir/my-article.md" <<'EOF'
# My Article

Some content here.
EOF

inject_frontmatter "$TMPDIR_FM/article-dir/my-article.md"

head -1 "$TMPDIR_FM/article-dir/my-article.md" | grep -q '^---' && R=0 || R=1
assert_true "inject_frontmatter: file now starts with ---" "$R"

grep -q 'title: "My Article"' "$TMPDIR_FM/article-dir/my-article.md" && R=0 || R=1
assert_true "inject_frontmatter: title derived from h1" "$R"

grep -q 'description: "My Article"' "$TMPDIR_FM/article-dir/my-article.md" && R=0 || R=1
assert_true "inject_frontmatter: description matches title" "$R"

grep -q 'sidebar_position:' "$TMPDIR_FM/article-dir/my-article.md" && R=0 || R=1
assert_true "inject_frontmatter: sidebar_position present" "$R"

grep -q '^# My Article' "$TMPDIR_FM/article-dir/my-article.md" && R=0 || R=1
assert_true "inject_frontmatter: original h1 preserved" "$R"

grep -q 'Some content here.' "$TMPDIR_FM/article-dir/my-article.md" && R=0 || R=1
assert_true "inject_frontmatter: original body content preserved" "$R"

rm -rf "$TMPDIR_FM"

echo ""
echo "Results: $PASS passed, $FAIL failed"
if [ "$FAIL" -gt 0 ]; then
Expand Down
Loading