diff --git a/.github/workflows/md-extension-autofix.yml b/.github/workflows/md-extension-autofix.yml index 269c5aec91..6d520b3a31 100644 --- a/.github/workflows/md-extension-autofix.yml +++ b/.github/workflows/md-extension-autofix.yml @@ -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 @@ -71,11 +71,13 @@ 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: | @@ -83,7 +85,7 @@ jobs: 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 @@ -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 @@ -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:" diff --git a/scripts/md-extension-autofix.sh b/scripts/md-extension-autofix.sh index faa6bd1b89..8dfe5246ec 100644 --- a/scripts/md-extension-autofix.sh +++ b/scripts/md-extension-autofix.sh @@ -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() { @@ -66,7 +105,7 @@ esac CHANGED_FILES_LIST="${1:?Usage: md-extension-autofix.sh }" if [ ! -f "$CHANGED_FILES_LIST" ]; then - echo '{"renamed": [], "skipped": []}' + echo '{"renamed": [], "skipped": [], "frontmatter_injected": []}' exit 0 fi @@ -74,6 +113,7 @@ RENAMED_FROM=() RENAMED_TO=() SKIPPED_FILES=() SKIP_REASONS=() +FRONTMATTER_INJECTED=() while IFS= read -r file; do # Only process files inside docs/ @@ -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 @@ -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 @@ -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}}" diff --git a/scripts/test-md-extension-autofix.sh b/scripts/test-md-extension-autofix.sh index 29d6bd3eef..f9b2ae518b 100644 --- a/scripts/test-md-extension-autofix.sh +++ b/scripts/test-md-extension-autofix.sh @@ -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' @@ -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