Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e39088f
Migrate docs site from Jekyll to DocFX
clairernovotny Nov 5, 2025
b75435a
Docs: refine examples and configuration guidance across documentation
clairernovotny Nov 6, 2025
9d0b15e
Docs: extract DocFX multi-version build into scripts/build-docfx.sh a…
clairernovotny Nov 6, 2025
402b28b
Merge branch 'main' into use-docfx
clairernovotny Nov 6, 2025
ca4b984
Address code review feedback: fix documentation syntax and handle leg…
Copilot Nov 6, 2025
5d312f0
Update docs/application-integration.md
clairernovotny Nov 6, 2025
b8fdf57
Update docs/string-transformations.md
clairernovotny Nov 6, 2025
4e8df74
Fix documentation paths, error codes, and script robustness (#1644)
Copilot Nov 6, 2025
f05cc09
Automate API toc.yml generation in DocFX (#1645)
Copilot Nov 6, 2025
ff6efd6
fixes
clairernovotny Nov 7, 2025
2aae79f
add docfx prerelease feed
clairernovotny Nov 7, 2025
47746b8
remove old feed
clairernovotny Nov 7, 2025
18cb468
Adopt Docusaurus shell with DocFX API pipeline
clairernovotny Nov 7, 2025
7a4db65
add serena config
clairernovotny Nov 7, 2025
37257ee
Add docs generation helper script
clairernovotny Nov 7, 2025
7341a4e
Improve API docs navigation and theming
clairernovotny Nov 7, 2025
de24982
Enable Prism syntax highlighting for docs
clairernovotny Nov 7, 2025
99505c3
Fix API page layout and hook usage
clairernovotny Nov 7, 2025
3c91ddd
Enhance documentation generation and API reference structure
clairernovotny Nov 7, 2025
4fab166
Refactor build commands in suggested_commands.md for clarity
clairernovotny Nov 7, 2025
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
20 changes: 20 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"version": 1,
"isRoot": true,
"tools": {
"docfx": {
"version": "2.78.4",
"commands": [
"docfx"
],
"rollForward": false
},
"docfxmarkdowngen": {
"version": "0.5.0",
"commands": [
"dfmg"
],
"rollForward": false
}
}
}
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ to clarify expected behavior in our community.
For more information see the [.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct).

### <a id="getting-started">Getting started</a>
This project uses C# 8 language features and SDK-style projects, so you'll need any edition of [Visual Studio 2019](https://www.visualstudio.com/downloads/download-visual-studio-vs) to open and compile the project. The free [Community Edition](https://go.microsoft.com/fwlink/?LinkId=532606&clcid=0x409) will work.
This project uses C# 14 language features and SDK-style projects, so you'll need .NET 10 SDK to compile.

### <a id="contribution-guideline">Contribution guideline</a>
This project uses [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html) for pull requests.
Expand Down
70 changes: 70 additions & 0 deletions .github/workflows/docfx-gh-pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Build and Deploy Docs

on:
push:
branches: ["main"]
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@v4
with:
fetch-depth: 0

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json

- name: Restore .NET tools
run: dotnet tool restore

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
cache-dependency-path: website/package-lock.json

- name: Install Docusaurus dependencies
working-directory: website
run: npm ci

- name: Build DocFX API versions
shell: bash
run: .github/workflows/scripts/build-docfx.sh

- name: Build Docusaurus site
working-directory: website
run: npm run build

- name: Setup Pages
uses: actions/configure-pages@v5

- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: website/build

deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
251 changes: 251 additions & 0 deletions .github/workflows/scripts/build-docfx.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
#!/bin/bash

set -euo pipefail

ROOT="$(pwd)"
ARTIFACT_DIR="$ROOT/.docfx-artifacts"
WORKTREE_ROOT="$ROOT/.docfx-worktrees"
STATIC_API_DIR="$ROOT/website/static/api"
METADATA_ROOT="$ROOT/.dfmg-metadata"
API_DOCS_ROOT="$ROOT/website/api-docs"
API_VERSIONS_FILE="$API_DOCS_ROOT/versions.json"
DFMG_CONFIG_PATH="$ROOT/docs/dfmg.config.yaml"
WORKTREES_TO_REMOVE=()
DOCFX_CMD="dotnet tool run docfx"

cleanup() {
if [ ${#WORKTREES_TO_REMOVE[@]} -gt 0 ]; then
for worktree in "${WORKTREES_TO_REMOVE[@]}"; do
if [ -d "$worktree" ]; then
git worktree remove --force "$worktree" >/dev/null 2>&1 || true
fi
done
fi

rm -rf "$ARTIFACT_DIR" "$WORKTREE_ROOT"
}
trap cleanup EXIT

rm -rf "$ARTIFACT_DIR" "$WORKTREE_ROOT" "$METADATA_ROOT"
mkdir -p "$ARTIFACT_DIR" "$WORKTREE_ROOT" "$METADATA_ROOT"
rm -rf "$STATIC_API_DIR"
mkdir -p "$STATIC_API_DIR"
mkdir -p "$API_DOCS_ROOT"
rm -rf "$API_DOCS_ROOT"/dev "$API_DOCS_ROOT"/v*

build_docfx_for_checkout() {
local checkout_root="$1"
local destination_name="$2"

pushd "$checkout_root" >/dev/null
$DOCFX_CMD metadata docs/docfx.json
$DOCFX_CMD build docs/docfx.json
popd >/dev/null

mkdir -p "$ARTIFACT_DIR/$destination_name"
cp -a "$checkout_root/docs/_site/." "$ARTIFACT_DIR/$destination_name/"

if [ -d "$checkout_root/docs/api" ]; then
mkdir -p "$METADATA_ROOT/$destination_name"
cp -a "$checkout_root/docs/api/." "$METADATA_ROOT/$destination_name/"
fi
}

generate_markdown_for_version() {
local metadata_key="$1"
local slug="$2"
local label="$3"

local metadata_dir="$METADATA_ROOT/$metadata_key"
if [ ! -d "$metadata_dir" ]; then
echo "Skipping markdown generation for $metadata_key (metadata missing)"
return
fi

local output_dir="$API_DOCS_ROOT/$slug"
rm -rf "$output_dir"
mkdir -p "$output_dir"

DFMG_CONFIG="$DFMG_CONFIG_PATH" \
DFMG_YAML_PATH="$metadata_dir" \
DFMG_OUTPUT_PATH="$output_dir" \
dotnet tool run dfmg >/dev/null

# add a small marker so we know the directory corresponds to this slug
printf '{\n "label": "%s",\n "slug": "%s"\n}\n' "$label" "$slug" >"$output_dir/.dfmg-version.json"

sanitize_markdown_directory "$output_dir" "$slug"
}

sanitize_markdown_directory() {
local target_dir="$1"
local slug="$2"
python3 - "$target_dir" "$slug" <<'PY'
import pathlib
import re
import sys

root = pathlib.Path(sys.argv[1])
slug = sys.argv[2]
pattern = re.compile(r'(?<![`\\])\{([A-Za-z0-9_]+)\}')
link_pattern = re.compile(r'\]\(((?:\./|\.\./)+)([^)]+)\)')

for md_path in root.rglob('*.md'):
text = md_path.read_text(encoding='utf-8')
lines = text.splitlines()
inside_code = False
changed = False
for index, line in enumerate(lines):
stripped = line.strip()
if stripped.startswith('```'):
inside_code = not inside_code
continue
if inside_code:
continue
new_line = pattern.sub(r'\\{\1\\}', line)
if new_line != line:
lines[index] = new_line
changed = True
if changed:
text = '\n'.join(lines) + '\n'
else:
text = '\n'.join(lines) + '\n'

def replace_links(match):
target = match.group(2)
if target.endswith('.md'):
target = target[:-3]
return f'](/api/{slug}/{target})'

new_text = link_pattern.sub(replace_links, text)
duplicate_namespace_pattern = re.compile(rf'/api/{slug}/([^/]+)/\1(?![A-Za-z0-9_])')
new_text = duplicate_namespace_pattern.sub(rf'/api/{slug}/\1', new_text)
if new_text != text:
text = new_text

md_path.write_text(text, encoding='utf-8')
PY
}

# Build current checkout (development)
build_docfx_for_checkout "$ROOT" "main"
rm -rf "$ROOT/docs/_site"

# Discover release branches
RELEASE_VERSIONS=()
SKIP_FETCH="${SKIP_RELEASE_FETCH:-0}"
MIN_RELEASE_VERSION="${MIN_DOCFX_RELEASE:-v2.14}"

should_build_release() {
local version="$1"
local min="$MIN_RELEASE_VERSION"
if [[ -z "$min" ]]; then
return 0
fi

local current="${version#v}"
local required="${min#v}"
local smallest
smallest="$(printf '%s\n%s\n' "$current" "$required" | sort -V | head -n1)"
if [[ "$smallest" == "$required" ]]; then
return 0
fi
return 1
}

if [ "$SKIP_FETCH" != "1" ]; then
RELEASE_TMP="$(mktemp)"
if git ls-remote --heads origin 'rel/v*' >"$RELEASE_TMP"; then
if [ -s "$RELEASE_TMP" ]; then
git fetch --no-tags origin 'refs/heads/rel/*:refs/remotes/origin/rel/*'
mapfile -t RELEASE_VERSIONS < <(git for-each-ref --format='%(refname:short)' refs/remotes/origin/rel | sed 's#^origin/rel/##' | sort -rV)
fi
else
echo "Warning: unable to query release branches; continuing with development API only."
fi
rm -f "$RELEASE_TMP"
else
echo "Skipping release branch builds per SKIP_RELEASE_FETCH=$SKIP_FETCH"
fi

LATEST_RELEASE=""
BUILT_RELEASES=()

for version in "${RELEASE_VERSIONS[@]}"; do
[ -n "$version" ] || continue
if ! should_build_release "$version"; then
echo "Skipping $version: older than minimum DocFX release $MIN_RELEASE_VERSION"
continue
fi

branch="origin/rel/$version"
worktree="$WORKTREE_ROOT/$version"
git worktree add --force "$worktree" "$branch"
WORKTREES_TO_REMOVE+=("$worktree")

if [ ! -f "$worktree/docs/docfx.json" ]; then
echo "Skipping $version: no DocFX configuration found"
git worktree remove --force "$worktree"
unset 'WORKTREES_TO_REMOVE[-1]'
continue
fi

if build_docfx_for_checkout "$worktree" "$version"; then
BUILT_RELEASES+=("$version")
if [ -z "$LATEST_RELEASE" ]; then
LATEST_RELEASE="$version"
fi
else
echo "Failed to build documentation for $version, skipping"
fi

git worktree remove --force "$worktree" || true
unset 'WORKTREES_TO_REMOVE[-1]'
done

rm -rf "$STATIC_API_DIR"
mkdir -p "$STATIC_API_DIR"

cp -a "$ARTIFACT_DIR/main/." "$STATIC_API_DIR/main/"

for version in "${BUILT_RELEASES[@]}"; do
mkdir -p "$STATIC_API_DIR/$version"
cp -a "$ARTIFACT_DIR/$version/." "$STATIC_API_DIR/$version/"
done

generate_markdown_for_version "main" "dev" "Latest dev (main)"
for version in "${BUILT_RELEASES[@]}"; do
generate_markdown_for_version "$version" "$version" "$version"
done

if [ -n "$LATEST_RELEASE" ]; then
DEFAULT_RELEASE="$LATEST_RELEASE"
DEFAULT_SLUG="$LATEST_RELEASE"
else
DEFAULT_RELEASE="main"
DEFAULT_SLUG="dev"
fi

if [ "${#BUILT_RELEASES[@]}" -gt 0 ]; then
release_objects="["
for version in "${BUILT_RELEASES[@]}"; do
release_objects+="{\"label\":\"$version\",\"slug\":\"$version\"},"
done
release_objects="${release_objects%,}]"
else
release_objects="[]"
fi

cat >"$API_VERSIONS_FILE" <<MANIFEST
{
"latest": "$DEFAULT_RELEASE",
"releases": $release_objects,
"development": {
"label": "Latest dev (main)",
"slug": "dev"
},
"defaultSlug": "$DEFAULT_SLUG"
}
MANIFEST

cp "$API_VERSIONS_FILE" "$STATIC_API_DIR/versions.json"
31 changes: 29 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,37 @@ src/Humanizer.Tests/ApiApprover/PublicApiApprovalTest.approve_public_api.receive

*.received.*

# Jekyll
# Documentation output
docs/_site/
docs/.jekyll-cache/
docs/.sass-cache/
docs/.docfx/
docs/obj/
docs/api/**.yml
docs/api/.manifest
docs/api/.xrefmap.json
docs/api/.xrefmap.yml
docfx
.docfx-artifacts/
.docfx-worktrees/

# BenchmarkDotNet artifacts
**/BenchmarkDotNet.Artifacts/
**/BenchmarkDotNet.Artifacts/

# Docusaurus artifacts
website/.docusaurus/
website/build/
website/node_modules/
website/static/api/**
!website/static/api
!website/static/api/.gitkeep
website/api-docs/dev/
website/api-docs/v*/
website/api-docs/versions.json
website/api-docs/**/.dfmg-version.json
.dfmg-metadata/
.dotnet-cli/
.dotnet/
.local/
.npm-cache/
.nuget/
1 change: 1 addition & 0 deletions .serena/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/cache
Loading
Loading