Skip to content

site

site #7

name: Docs (GitHub Pages)
on:
push:
branches: [ main ]
paths:
- README.md
- docs/**
- github-pages/**
- .github/workflows/jekyll-gh-pages.yml
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup Pages
uses: actions/configure-pages@v5
with:
enablement: true
- name: Add version and build date
run: |
set -euo pipefail
VERSION="$(python3 - <<'PY'
import xml.etree.ElementTree as ET
tree = ET.parse("Directory.Build.props")
root = tree.getroot()
def find_text(suffix: str) -> str:
for element in root.iter():
if element.tag.endswith(suffix) and element.text and element.text.strip():
return element.text.strip()
return ""
version = find_text("PackageVersion") or find_text("Version")
print(version)
PY
)"
if [ -z "${VERSION}" ]; then
echo "::error::Could not read <PackageVersion>/<Version> from Directory.Build.props"
exit 1
fi
echo "version: \"${VERSION}\"" >> github-pages/_config.yml
echo "build_date: \"$(date +'%Y-%m-%d')\"" >> github-pages/_config.yml
- name: Generate site pages from docs
run: |
set -euo pipefail
REPO="${{ github.repository }}"
REPO_BLOB_BASE="https://github.com/${REPO}/blob/main"
REPO_TREE_BASE="https://github.com/${REPO}/tree/main"
rewrite_md_links() {
# Rewrite *.md links to *.html for the rendered site (keep source docs GitHub-friendly).
# Only rewrites relative links (no ':' in the target) so external URLs are untouched.
sed -E \
-e 's|\]\(README\.md\)|](/)|g' \
-e 's|\]\(docs/Development/setup\.md\)|](/setup/)|g' \
-e 's|\]\(docs/Development/credentials\.md\)|](/credentials/)|g' \
-e 's|\]\(docs/Testing/strategy\.md\)|](/testing/)|g' \
-e 's|\]\(docs/Features/index\.md\)|](/features/)|g' \
-e 's|\]\(docs/ADR/index\.md\)|](/adr/)|g' \
-e 's|\]\(docs/API/index\.md\)|](/api/)|g' \
-e 's|\]\(docs/API/([^):]+)\.md(#[^)]*)?\)|](/api/\\1.html\\2)|g' \
-e 's|\]\(docs/Features/([^):]+)\.md(#[^)]*)?\)|](/features/\\1.html\\2)|g' \
-e 's|\]\(docs/ADR/([^):]+)\.md(#[^)]*)?\)|](/adr/\\1.html\\2)|g' \
-e 's|\]\(\\.\\./Development/setup\.md\)|](/setup/)|g' \
-e 's|\]\(\\.\\./Development/credentials\.md\)|](/credentials/)|g' \
-e 's|\]\(\\.\\./Testing/strategy\.md\)|](/testing/)|g' \
-e 's|\]\(\\.\\./ADR/([^):]+)\.md(#[^)]*)?\)|](/adr/\\1.html\\2)|g' \
-e 's|\]\(\\.\\./Features/([^):]+)\.md(#[^)]*)?\)|](/features/\\1.html\\2)|g' \
-e 's|\]\(\\.\\./API/([^):]+)\.md(#[^)]*)?\)|](/api/\\1.html\\2)|g' \
-e 's|\]\(([^):]+)\.md(#[^)]*)?\)|](\1.html\2)|g'
}
linkify_repo_paths() {
local script
script="$(mktemp)"
cat > "${script}" <<'PY'
import re
import sys
blob_base = sys.argv[1].rstrip("/")
tree_base = sys.argv[2].rstrip("/")
prefixes = r"(?:\\.github/|github-pages/|docs/|Tests/|Storages/|Integraions/|ManagedCode\\.Storage\\.[^`/]+/)"
root_files = r"(?:Directory\\.Build\\.props|ManagedCode\\.Storage\\.slnx|README\\.md|AGENTS\\.md|LICENSE)"
text = sys.stdin.read()
def link_tree(match: re.Match[str]) -> str:
path = match.group(1)
return f"[`{path}/`]({tree_base}/{path})"
def link_blob(match: re.Match[str]) -> str:
path = match.group(1)
if path.endswith("/"):
return match.group(0)
last_segment = path.rsplit("/", 1)[-1]
if "." not in last_segment:
return match.group(0)
return f"[`{path}`]({blob_base}/{path})"
def link_root_file(match: re.Match[str]) -> str:
path = match.group(1)
return f"[`{path}`]({blob_base}/{path})"
text = re.sub(rf"(?<!\[)`(({prefixes})[^`]*)/`", link_tree, text)
text = re.sub(rf"(?<!\[)`(({prefixes})[^`]*)`", link_blob, text)
text = re.sub(rf"(?<!\[)`({root_files})`", link_root_file, text)
sys.stdout.write(text)
PY
python3 "${script}" "${REPO_BLOB_BASE}" "${REPO_TREE_BASE}"
rm -f "${script}"
}
extract_description() {
# Grab the first meaningful paragraph (skips headings, code fences, and bullet lists).
awk '
BEGIN { in_code=0 }
/^```/ { in_code = !in_code; next }
in_code { next }
NR == 1 { next } # skip H1
/^[[:space:]]*$/ { next } # skip blanks
/^#/ { next } # skip headings
/^(Accepted|Proposed|Rejected|Superseded|Deprecated)[[:space:]]/ { next } # skip ADR status line
/^[[:space:]]*[-*]/ { next } # skip bullets
{ print; exit }
'
}
strip_front_matter() {
awk '
NR==1 && $0=="---" { in_front=1; next }
in_front && $0=="---" { in_front=0; next }
!in_front { print }
' "$1"
}
read_front_matter_value() {
local file="$1"
local key="$2"
awk -v key="$key" '
NR==1 && $0=="---" { in_front=1; next }
in_front && $0=="---" { exit }
in_front {
if ($0 ~ ("^" key ":[[:space:]]*")) {
sub("^" key ":[[:space:]]*", "", $0)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $0)
if ($0 ~ /^".*"$/) { sub(/^"/, "", $0); sub(/"$/, "", $0) }
print $0
exit
}
}
' "$file"
}
extract_h1() {
awk '/^# / { sub(/^# /, ""); print; exit }'
}
write_doc_page() {
local src="$1"
local dst="$2"
local default_permalink="${3-}"
local default_nav_order="${4-}"
local title desc keywords permalink nav_order
title=$(read_front_matter_value "$src" "title")
if [ -z "${title}" ]; then
title=$(strip_front_matter "$src" | extract_h1)
fi
desc=$(read_front_matter_value "$src" "description")
if [ -z "${desc}" ]; then
desc=$(strip_front_matter "$src" | extract_description | tr -d '\r\n')
desc=$(printf '%s' "$desc" | sed -E 's/[[:space:]]+/ /g; s/[[:space:]]+$//; s/:$//')
if [ ${#desc} -gt 160 ]; then
desc="${desc:0:159}…"
fi
if [ -z "${desc}" ]; then
desc="${title} documentation."
fi
fi
keywords=$(read_front_matter_value "$src" "keywords")
permalink=$(read_front_matter_value "$src" "permalink")
nav_order=$(read_front_matter_value "$src" "nav_order")
if [ -z "${permalink}" ] && [ -n "${default_permalink}" ]; then
permalink="$default_permalink"
fi
if [ -z "${nav_order}" ] && [ -n "${default_nav_order}" ]; then
nav_order="$default_nav_order"
fi
local title_escaped desc_escaped keywords_escaped
title_escaped=$(printf '%s' "$title" | sed 's/"/\\"/g')
desc_escaped=$(printf '%s' "$desc" | sed 's/"/\\"/g')
keywords_escaped=$(printf '%s' "$keywords" | sed 's/"/\\"/g')
{
echo "---"
echo "layout: default"
echo "title: \"${title_escaped}\""
echo "description: \"${desc_escaped}\""
if [ -n "${keywords}" ]; then
echo "keywords: \"${keywords_escaped}\""
fi
if [ -n "${permalink}" ]; then
echo "permalink: ${permalink}"
fi
if [ -n "${nav_order}" ]; then
echo "nav_order: ${nav_order}"
fi
echo "---"
} > "$dst"
strip_front_matter "$src" | rewrite_md_links | linkify_repo_paths >> "$dst"
}
mkdir -p github-pages/features
WORDS=$(wc -w < README.md)
MINUTES=$(( (WORDS + 200) / 200 ))
{
echo "---"
echo "layout: default"
echo "title: Home"
echo "is_home: true"
echo "permalink: /"
echo "nav_order: 1"
echo "---"
} > github-pages/index.md
rewrite_md_links < README.md | linkify_repo_paths >> github-pages/index.md
echo "" >> github-pages/index.md
echo "<p class=\"reading-time\">${MINUTES} min read</p>" >> github-pages/index.md
write_doc_page docs/Development/setup.md github-pages/setup.md /setup/ 2
write_doc_page docs/Development/credentials.md github-pages/credentials.md /credentials/ 3
write_doc_page docs/Testing/strategy.md github-pages/testing.md /testing/ 4
write_doc_page docs/Features/index.md github-pages/features/index.md /features/ 5
for file in docs/Features/*.md; do
base=$(basename "$file")
if [ "$base" = "index.md" ]; then
continue
fi
write_doc_page "$file" "github-pages/features/$base"
done
mkdir -p github-pages/adr
write_doc_page docs/ADR/index.md github-pages/adr/index.md /adr/ 6
for file in docs/ADR/*.md; do
base=$(basename "$file")
if [ "$base" = "index.md" ]; then
continue
fi
write_doc_page "$file" "github-pages/adr/$base"
done
mkdir -p github-pages/api
write_doc_page docs/API/index.md github-pages/api/index.md /api/ 7
for file in docs/API/*.md; do
base=$(basename "$file")
if [ "$base" = "index.md" ]; then
continue
fi
write_doc_page "$file" "github-pages/api/$base"
done
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
with:
source: ./github-pages
destination: ./_site
- name: Upload artifact
uses: actions/upload-pages-artifact@v4
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4