diff --git a/.github/version-picker-template.html b/.github/version-picker-template.html deleted file mode 100644 index 39a25942..00000000 --- a/.github/version-picker-template.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - {{TITLE}} - - - -

{{TITLE}}

-

Select a documentation version:

- - - diff --git a/.github/workflows/build-all-versions.yaml b/.github/workflows/build-all-versions.yaml index 890b5ae2..8c1e211f 100644 --- a/.github/workflows/build-all-versions.yaml +++ b/.github/workflows/build-all-versions.yaml @@ -119,20 +119,20 @@ jobs: try { # Attempt dotnet restore + build; failures are non-fatal because # DocFX can still extract metadata from source files. - $slnFile = Get-ChildItem -Filter '*.sln' -ErrorAction SilentlyContinue | + $slnFile = Get-ChildItem -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in '.sln','.slnx' } | Select-Object -First 1 if ($slnFile) { Write-Host "Restoring $($slnFile.Name)..." - dotnet restore $slnFile.FullName 2>&1 | Write-Host + dotnet restore $slnFile.FullName 2>&1 | Out-Host Write-Host "Building $($slnFile.Name)..." - dotnet build $slnFile.FullName --configuration Release --no-restore 2>&1 | Write-Host + dotnet build $slnFile.FullName --configuration Release --no-restore 2>&1 | Out-Host } Write-Host "Running docfx metadata..." - docfx metadata docfx_project/docfx.json 2>&1 | Write-Host + docfx metadata docfx_project/docfx.json 2>&1 | Out-Host Write-Host "Running docfx build..." - docfx build docfx_project/docfx.json 2>&1 | Write-Host + docfx build docfx_project/docfx.json 2>&1 | Out-Host if (Test-Path 'docfx_project/_site') { $dest = Join-Path $outDir 'versions' $version @@ -207,7 +207,7 @@ jobs: Push-Location $latestWorkDir try { - $slnFile = Get-ChildItem -Filter '*.sln' -ErrorAction SilentlyContinue | + $slnFile = Get-ChildItem -File -ErrorAction SilentlyContinue | Where-Object { $_.Extension -in '.sln','.slnx' } | Select-Object -First 1 if ($slnFile) { Write-Host "Restoring $($slnFile.Name)..." @@ -285,9 +285,22 @@ jobs: Sort-Object -Property Major, Minor, Patch, Stable -Descending | Select-Object -ExpandProperty Tag + # Only emit version-picker entries for tags whose docs were actually + # built and copied under $outDir/versions//. Skipped tags (worktree + # add failed, DocFX produced no _site, etc.) would otherwise appear in + # versions.json as links to 404s on gh-pages. + $versionsDir = Join-Path $outDir 'versions' + $builtTags = if (Test-Path $versionsDir) { + Get-ChildItem -Path $versionsDir -Directory | Select-Object -ExpandProperty Name + } else { @() } + [array]$versions = @([PSCustomObject]@{ version = 'latest'; url = "${base}versions/latest/" }) foreach ($t in $orderedTags) { - $versions += [PSCustomObject]@{ version = $t; url = "${base}versions/$t/" } + if ($builtTags -contains $t) { + $versions += [PSCustomObject]@{ version = $t; url = "${base}versions/$t/" } + } else { + Write-Host "::notice::Skipping versions.json entry for $t — no built docs under versions/$t/" + } } $versionsJson = ConvertTo-Json -InputObject $versions -Depth 3 @@ -299,10 +312,13 @@ jobs: Write-Host "Generated versions.json with $($versions.Count) entries: $($versions | ForEach-Object { $_.version })" - - name: Generate root index.html - # Builds index.html from the shared template (.github/version-picker-template.html) - # so the page layout is maintained in one place and both this workflow and - # docfx.yaml produce identical markup. + - name: Generate root index.html (meta-refresh → versions/latest/) + # Inline the redirect HTML directly. Only the page title (repo name) + # is dynamic; the in-page version picker + # (docfx_project/public/version-picker.js) handles version switching + # from any docs page, so the root no longer needs a clickable list. + # Both this workflow and docfx.yaml use the same inline pattern, so + # the produced root index.html is identical regardless of trigger. shell: pwsh env: GITHUB_REPOSITORY: ${{ github.repository }} @@ -312,33 +328,45 @@ jobs: $repoName = ($env:GITHUB_REPOSITORY -split '/')[-1] $title = if ($repoName) { "$repoName Documentation" } else { "Documentation" } - $versionsJsonPath = Join-Path $outDir 'versions' 'latest' 'versions.json' - $versions = Get-Content $versionsJsonPath -Raw | ConvertFrom-Json - - $listItems = foreach ($v in $versions) { - $liClass = if ($v.version -eq 'latest') { ' class="latest"' } else { '' } - $label = if ($v.version -eq 'latest') { 'latest (stable)' } else { $v.version } - " $label" - } - $listHtml = $listItems -join "`n" - - $templatePath = '.github/version-picker-template.html' - if (-not (Test-Path $templatePath)) { - Write-Error "Template file '$templatePath' not found. This file is required." - exit 1 - } - $template = Get-Content $templatePath -Raw - $html = $template -replace '\{\{TITLE\}\}', $title ` - -replace '\{\{VERSION_LIST\}\}', $listHtml - + # Build the HTML as a string array joined with LF. Avoids a + # PowerShell here-string whose unindented inner lines would + # break the surrounding YAML literal-block scalar (they'd + # drop below the block's required indentation and terminate + # the scalar prematurely). Use a relative `versions/latest/` + # link so it resolves under the GitHub Pages project path + # `//`. + $htmlLines = @( + '', + '', + '', + ' ', + ' ', + " $title", + ' ', + ' ', + ' ', + " ", + '', + '', + '

Redirecting to the latest documentation…

', + ' ', + '', + '' + ) + $html = $htmlLines -join "`n" $html | Set-Content -Path (Join-Path $outDir 'index.html') -Encoding utf8NoBOM - Write-Host "Generated index.html with $($versions.Count) version link(s)." + Write-Host "Generated root index.html (meta-refresh → versions/latest/)." - name: Deploy all versioned docs to gh-pages at root # Publishes the entire all_version_docs/ output directory to gh-pages # at the site root. keep_files: true preserves any other content # already present on the branch (CNAME, root assets, etc.). - uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0 + uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ${{ runner.temp }}/all_version_docs diff --git a/.github/workflows/docfx.yaml b/.github/workflows/docfx.yaml index 02aecc6b..9018545f 100644 --- a/.github/workflows/docfx.yaml +++ b/.github/workflows/docfx.yaml @@ -21,6 +21,11 @@ on: required: false type: boolean default: true + overlay_canonical_docs_assets: + description: 'Before docfx build, overlay docfx_project/{public,versions.json,logo.svg,docfx.json} from origin/main onto the checked-out ref. Lets you backfill the canonical version-picker UI onto older tags whose original docfx_project predated it. Leave on for old-tag rebuilds; harmless no-op when running from main.' + required: false + type: boolean + default: true # Manual trigger for ad-hoc builds or dry-runs. # Leave 'version' blank to use the selected branch or tag name as the destination. workflow_dispatch: @@ -37,6 +42,10 @@ on: description: 'Also deploy to the site root (/) and versions/latest/ (uncheck when rebuilding older versions)' type: boolean default: true + overlay_canonical_docs_assets: + description: 'Before docfx build, overlay docfx_project/{public,versions.json,logo.svg,docfx.json} from origin/main onto the checked-out ref. Lets you backfill the canonical version-picker UI onto older tags whose original docfx_project predated it. Leave on for old-tag rebuilds; harmless no-op when running from main.' + type: boolean + default: true permissions: contents: read # Default to read-only; the build-and-deploy job overrides with write @@ -56,6 +65,67 @@ jobs: fetch-depth: 0 # Full history needed to enumerate all v* tags persist-credentials: false + # When backfilling docs for a tag that predates the canonical + # version-picker assets, the checked-out tag has no + # docfx_project/public/version-picker.js and its docfx.json + # has no picker-bootstrap ", + '', + '', + '

Redirecting to the latest documentation…

', + ' ', + '', + '' + ) + $html = $htmlLines -join "`n" $html | Set-Content -Path "$siteDir/index.html" -Encoding utf8NoBOM - Write-Host "Generated version-picker index.html with $($versions.Count) version link(s)." + Write-Host "Generated root index.html (meta-refresh → versions/latest/)." - # Deploy version picker + shared assets to site root + # Deploy site-root assets (root index.html + picker JS at /public/, etc.) Copy-Item -Path "$siteDir/*" -Destination $WORK_DIR -Recurse -Force - Write-Host "✅ Copied version picker to site root" + Write-Host "✅ Copied site-root assets" } - # Single commit and push + # Single commit and push. + # `git diff --cached --quiet` exits 0 (no changes), 1 (changes + # exist), or >1 (error — e.g. cannot read index). Treat the + # three cases explicitly: commit only when there are changes + # (exit 1); on >1 abort the deploy with a clear error; on 0 + # report "nothing to deploy" and exit cleanly. git -C $WORK_DIR add -A git -C $WORK_DIR diff --cached --quiet - if ($LASTEXITCODE -ne 0) { + $diffExit = $LASTEXITCODE + if ($diffExit -eq 1) { $msg = if ($env:DEPLOY_AS_LATEST -eq 'true') { "docs: deploy $($env:VERSION_DIR) and update latest" } else { "docs: deploy $($env:VERSION_DIR)" } git -C $WORK_DIR commit -m $msg + if ($LASTEXITCODE -ne 0) { + Write-Error "❌ git commit failed with exit code $LASTEXITCODE — aborting deploy." + throw "git commit failed" + } git -C $WORK_DIR push origin HEAD:gh-pages + if ($LASTEXITCODE -ne 0) { + Write-Error "❌ git push origin HEAD:gh-pages failed with exit code $LASTEXITCODE — deploy did not land." + throw "git push failed" + } Write-Host "✅ Documentation deployed in a single commit." - } else { + } elseif ($diffExit -eq 0) { Write-Host "ℹ️ No documentation changes to deploy." + } else { + Write-Error "❌ git diff --cached --quiet exited with $diffExit (error reading the staged index) — aborting deploy." + throw "git diff failed" } # Capture the deploy outcome before the finally block runs cleanup diff --git a/docs/DOCFX-VERSION-PICKER.md b/docs/DOCFX-VERSION-PICKER.md new file mode 100644 index 00000000..8713cbed --- /dev/null +++ b/docs/DOCFX-VERSION-PICKER.md @@ -0,0 +1,256 @@ +# DocFX Version Picker + +The docs site shows an in-page version picker (a `` between the app title and the theme toggle, populated from `versions.json` and pre-selecting whichever version the current URL is under. | +| Pick a different version in the dropdown | Browser navigates to `//versions//` | + +The "latest" alias is filtered out of the dropdown (redundant — the +highest-numbered `v*` row already represents latest); `versions.json` +still includes it so external links / scripts can resolve it. + +--- + +## The four moving parts + +### 1. `docfx_project/public/version-picker.js` + +Browser-side picker (~160 lines). On `DOMContentLoaded`: + +- Detects whether the host is `*.github.io` and computes the repo + prefix accordingly — same file works on github.io, on + `docfx build --serve` localhost, and on CNAME-served custom domains. +- Fetches `versions.json` from the site root (default browser cache + policy — the file changes infrequently). +- Builds a themed `