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
5 changes: 5 additions & 0 deletions .docfx/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Local outputs and bookkeeping are not inputs to the container's DocFX build.
wwwroot/
obj/
bin/
*.ps1
175 changes: 171 additions & 4 deletions .docfx/BuildDocfxImage.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,171 @@
$version = minver -i -t v -v w
docfx metadata docfx.json
docker buildx build -t cuemon-docfx:$version --platform linux/arm64,linux/amd64 --load -f Dockerfile.docfx .
get-childItem -recurse -path api -include *.yml, .manifest | remove-item
[CmdletBinding()]
param(
# Regenerate metadata even when the inputs and generated files are unchanged.
[switch] $ForceMetadata
)

$ErrorActionPreference = 'Stop'

function Assert-CommandSucceeded([string] $Command) {
if ($LASTEXITCODE -ne 0) { throw "$Command failed with exit code $LASTEXITCODE." }
}

function Get-SourceFiles([string] $Directory) {
foreach ($item in Get-ChildItem -LiteralPath $Directory -Force) {
if ($item.PSIsContainer) {
if ($item.Name -notin @('bin', 'obj', '.git')) { Get-SourceFiles $item.FullName }
} else {
$item.FullName
}
}
}

function Get-Fingerprint([string[]] $Paths, [string[]] $Values = @()) {
$entries = @(
$Values
foreach ($path in $Paths | Sort-Object -Unique) {
if (Test-Path -LiteralPath $path -PathType Leaf) {
"$path=$((Get-FileHash -LiteralPath $path -Algorithm SHA256).Hash)"
} else {
"$path=<missing>"
}
}
)
$sha = [System.Security.Cryptography.SHA256]::Create()
try {
[BitConverter]::ToString($sha.ComputeHash([Text.Encoding]::UTF8.GetBytes(($entries -join [Environment]::NewLine))))
} finally {
$sha.Dispose()
}
}

function Get-MetadataFiles {
foreach ($directory in $metadataDirectories) {
if (Test-Path -LiteralPath $directory) {
Get-ChildItem -LiteralPath $directory -Recurse -Force -File |
Where-Object { $_.Extension -eq '.yml' -or $_.Name -eq '.manifest' } |
Select-Object -ExpandProperty FullName
}
}
}

$totalTimer = [Diagnostics.Stopwatch]::StartNew()
Push-Location $PSScriptRoot
try {
$docfxRoot = $PSScriptRoot
$repoRoot = Split-Path -Parent $docfxRoot
$sourceRoot = Join-Path $repoRoot 'src'
$docfxConfig = Get-Content -LiteralPath (Join-Path $docfxRoot 'docfx.json') -Raw | ConvertFrom-Json
$version = minver -i -t v -v w
Assert-CommandSucceeded 'minver'
$docfxVersion = docfx --version
Assert-CommandSucceeded 'docfx --version'
$sdkVersion = dotnet --version
Assert-CommandSucceeded 'dotnet --version'
$revision = git rev-parse HEAD
Assert-CommandSucceeded 'git rev-parse HEAD'
$branch = git rev-parse --abbrev-ref HEAD
Assert-CommandSucceeded 'git rev-parse --abbrev-ref HEAD'

# Only these generated destinations may be cleaned; preserve authored Markdown.
$apiRoot = [IO.Path]::GetFullPath((Join-Path $docfxRoot 'api')) + [IO.Path]::DirectorySeparatorChar
$metadataDirectories = @(
foreach ($metadata in $docfxConfig.metadata) {
$destination = [IO.Path]::GetFullPath((Join-Path $docfxRoot $metadata.dest))
if (-not $destination.StartsWith($apiRoot, [StringComparison]::OrdinalIgnoreCase)) {
throw "Metadata destination must be beneath $apiRoot : $destination"
}
$destination
}
)
$sourceFiles = @(Get-SourceFiles $sourceRoot)
$sourceProjects = @($sourceFiles | Where-Object { [IO.Path]::GetExtension($_) -eq '.csproj' })
$inputPaths = @(
$PSCommandPath
$sourceFiles
Get-ChildItem -LiteralPath $repoRoot -Force -File | Select-Object -ExpandProperty FullName
foreach ($metadata in $docfxConfig.metadata) {
if ($metadata.filter) { Join-Path $docfxRoot $metadata.filter }
}
$inputDirectory = $repoRoot
while ($inputDirectory) {
foreach ($name in @('Directory.Build.props', 'Directory.Build.targets', 'Directory.Packages.props', 'NuGet.Config', 'global.json')) {
Join-Path $inputDirectory $name
}
$inputDirectory = Split-Path -Parent $inputDirectory
}
if ($env:APPDATA) { Join-Path $env:APPDATA 'NuGet/NuGet.Config' }
foreach ($project in $sourceProjects) {
$objDirectory = Join-Path (Split-Path -Parent $project) 'obj'
Join-Path $objDirectory 'project.assets.json'
Join-Path $objDirectory ((Split-Path -Leaf $project) + '.nuget.g.props')
Join-Path $objDirectory ((Split-Path -Leaf $project) + '.nuget.g.targets')
}
)
$inputValues = @(
$version; $docfxVersion; $sdkVersion; $revision; $branch
Comment on lines +105 to +106

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Metadata cache misses every commit

The fingerprint includes the current Git revision and branch, while metadata is reused only when the fingerprint remains identical. Every new commit therefore forces another full restore and metadata-generation run even when no documentation inputs changed, defeating the intended incremental-build optimization.

Suggested change
$inputValues = @(
$version; $docfxVersion; $sdkVersion; $revision; $branch
$inputValues = @(
$version; $docfxVersion; $sdkVersion
Prompt To Fix With AI
This is a comment left during a code review.
Path: .docfx/BuildDocfxImage.ps1
Line: 105-106

Comment:
**Metadata cache misses every commit**

The fingerprint includes the current Git revision and branch, while metadata is reused only when the fingerprint remains identical. Every new commit therefore forces another full restore and metadata-generation run even when no documentation inputs changed, defeating the intended incremental-build optimization.

```suggestion
    $inputValues = @(
        $version; $docfxVersion; $sdkVersion
```

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

$docfxConfig.metadata | ConvertTo-Json -Depth 100 -Compress
foreach ($name in @('CI', 'EMAIL', 'Configuration', 'MSBuildSDKsPath', 'DOTNET_ROOT', 'NUGET_PACKAGES', 'GITHUB_RUN_NUMBER')) {
"$name=$([Environment]::GetEnvironmentVariable($name))"
}
)
$cachePath = Join-Path $docfxRoot 'obj/metadata-cache.json'
$inputFingerprint = Get-Fingerprint $inputPaths $inputValues
$metadataFiles = @(Get-MetadataFiles)
$cache = $null
if (Test-Path -LiteralPath $cachePath) {
try {
$cache = Get-Content -LiteralPath $cachePath -Raw | ConvertFrom-Json
} catch {
Write-Warning 'Metadata cache could not be read; regenerating it.'
}
}
$reuseMetadata = -not $ForceMetadata -and $cache -and
$cache.inputs -eq $inputFingerprint -and $metadataFiles.Count -gt 0 -and
$cache.outputs -eq (Get-Fingerprint $metadataFiles)

if ($reuseMetadata) {
Write-Host 'Metadata unchanged; reusing verified generated files.'
} else {
$metadataTimer = [Diagnostics.Stopwatch]::StartNew()
# One restore graph replaces DocFX's separate restore for each project.
$restoreSolution = Join-Path ([IO.Path]::GetTempPath()) ("cuemon-docfx-{0}.slnx" -f [Guid]::NewGuid())
try {
$projectsXml = foreach ($project in $sourceProjects) {
' <Project Path="{0}" />' -f [Security.SecurityElement]::Escape($project)
}
@('<Solution>') + $projectsXml + @('</Solution>') | Set-Content -LiteralPath $restoreSolution -Encoding utf8
dotnet restore $restoreSolution --verbosity quiet
Assert-CommandSucceeded 'dotnet restore'
} finally {
if (Test-Path -LiteralPath $restoreSolution) { Remove-Item -LiteralPath $restoreSolution }
}

# Invalidate before generation so an interrupted or failed run cannot be reused.
if (Test-Path -LiteralPath $cachePath) { Remove-Item -LiteralPath $cachePath }
foreach ($file in $metadataFiles) { Remove-Item -LiteralPath $file }
$generationFingerprint = Get-Fingerprint $inputPaths $inputValues
# Keep all groups in one process; DocFX carries resolver state across groups.
docfx metadata docfx.json --noRestore
Assert-CommandSucceeded 'docfx metadata'
$metadataFiles = @(Get-MetadataFiles)
if ($metadataFiles.Count -eq 0) { throw 'DocFX did not generate metadata.' }
if ((Get-Fingerprint $inputPaths $inputValues) -ne $generationFingerprint) {
throw 'Metadata inputs changed during generation; rerun the build.'
}
$cache = @{
inputs = $generationFingerprint
outputs = Get-Fingerprint $metadataFiles
}
New-Item -ItemType Directory -Path (Split-Path -Parent $cachePath) -Force | Out-Null
$cache | ConvertTo-Json | Set-Content -LiteralPath $cachePath -Encoding utf8
Write-Host ('Metadata and restore: {0:N2}s' -f $metadataTimer.Elapsed.TotalSeconds)
}

$imageTimer = [Diagnostics.Stopwatch]::StartNew()
docker buildx build -t cuemon-docfx:$version --platform linux/arm64,linux/amd64 --load -f Dockerfile.docfx .
Assert-CommandSucceeded 'docker buildx build'
Write-Host ('Docker image: {0:N2}s; total: {1:N2}s' -f $imageTimer.Elapsed.TotalSeconds, $totalTimer.Elapsed.TotalSeconds)
} finally {
Pop-Location
}
7 changes: 3 additions & 4 deletions .docfx/Dockerfile.docfx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,14 @@
FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base
RUN rm -rf /usr/share/nginx/html/*

FROM --platform=$BUILDPLATFORM codebeltnet/docfx:2.78.5 AS build
FROM --platform=$BUILDPLATFORM codebeltnet/docfx:2 AS build

ADD [".", "docfx"]

RUN cd docfx; \
docfx build

FROM nginx:${NGINX_VERSION} AS final
FROM dhi.io/nginx:${NGINX_VERSION} AS final
WORKDIR /usr/share/nginx/html
COPY --from=build /build/docfx/wwwroot /usr/share/nginx/html

ENTRYPOINT ["nginx", "-g", "daemon off;"]
COPY nginx.conf /etc/nginx/conf.d/default.conf
76 changes: 0 additions & 76 deletions .docfx/api/extensions/index.md

This file was deleted.

16 changes: 12 additions & 4 deletions .docfx/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
}
],
"dest": "api/dotnet",
"memberLayout": "separatePages",
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "net10.0"
Expand Down Expand Up @@ -53,6 +54,7 @@
}
],
"dest": "api/extensions/dotnet",
"memberLayout": "separatePages",
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "net10.0"
Expand All @@ -71,6 +73,7 @@
}
],
"dest": "api/aspnet",
"memberLayout": "separatePages",
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "net10.0"
Expand All @@ -93,6 +96,7 @@
}
],
"dest": "api/extensions/aspnet",
"memberLayout": "separatePages",
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "net10.0"
Expand Down Expand Up @@ -135,12 +139,11 @@
"_appLogoPath": "images/50x50.png",
"_appFaviconPath": "images/favicon.ico",
"_googleAnalyticsTagId": "UA-126254455-1",
"_disableBreadcrumb": true,
"_enableSearch": true,
"_disableContribution": false,
"_gitContribute": {
"repo": "https://github.com/codebeltnet/cuemon",
"branch": "development"
"branch": "main"
},
"_gitUrlPattern": "github",
"_lang": "en"
Expand All @@ -151,7 +154,8 @@
"template": [
"default",
"modern",
"templates/cuemon"
"templates/cuemon",
"templates/ms-style"
Comment thread
gimlichael marked this conversation as resolved.
],
"overwrite": [
{
Expand All @@ -170,6 +174,10 @@
"noLangKeyword": false,
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false
"disableGitFeatures": false,
"sitemap": {
"baseUrl": "https://docs.cuemon.net/",
"changefreq": "monthly"
}
}
}
31 changes: 31 additions & 0 deletions .docfx/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
server {
listen 8080;
server_name _;
# The ingress terminates TLS; keep redirects from exposing this container port.
absolute_redirect off;

root /usr/share/nginx/html;
index index.html;

# DocFX fingerprints these generated dependencies in their filenames. They can be
# cached permanently because changed content is published under a different URL.
location ~ "^/public/.+-[A-Z0-9]{8}(?:-[A-Z0-9]{8})?(?:\.min)?\.(?:(?:css|js)(?:\.map)?|woff2?)$" {
try_files $uri =404;
add_header Cache-Control "public, max-age=31536000, immutable" always;
add_header Vary "Accept-Encoding" always;
}

# Pages, navigation data, images, and DocFX entry assets keep stable URLs. Do not
# retain them across deployments, and ignore validators cached under an old policy.
location / {
# Nginx derives ETags from modification time and file size. Those values can be
# reused by files copied from different image layers, producing a false 304.
etag off;
if_modified_since off;
try_files $uri $uri/ =404;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Expires "0" always;
add_header Pragma "no-cache" always;
add_header Vary "Accept-Encoding" always;
}
}
14 changes: 14 additions & 0 deletions .docfx/templates/cuemon/layout/_master.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@

<div class="content">

<div class="actionbar">
{{^_disableToc}}
<button class="btn btn-lg border-0 d-md-none"
type="button" data-bs-toggle="offcanvas" data-bs-target="#tocOffcanvas"
aria-controls="tocOffcanvas" aria-expanded="false" aria-label="Show table of contents">
<i class="bi bi-list"></i>
</button>
{{/_disableToc}}

{{^_disableBreadcrumb}}
<nav id="breadcrumb"></nav>
{{/_disableBreadcrumb}}
</div>

<article data-uid="{{uid}}">
{{!body}}
</article>
Expand Down
Loading
Loading