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
140 changes: 136 additions & 4 deletions .docfx/BuildDocfxImage.ps1
Original file line number Diff line number Diff line change
@@ -1,4 +1,136 @@
$version = minver -i -t v -v w
docfx metadata docfx.json
docker buildx build -t savvyio-docfx:$version --platform linux/arm64,linux/amd64 --load -f Dockerfile.docfx .
get-childItem -recurse -path api -include *.yml, .manifest | remove-item
[CmdletBinding()]
param()

$ErrorActionPreference = 'Stop'

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

Push-Location -LiteralPath $PSScriptRoot
try {
$docfxRoot = $PSScriptRoot
$sourceRoot = [System.IO.Path]::GetFullPath((Join-Path $docfxRoot '..\src'))
$docfxConfig = Get-Content -LiteralPath (Join-Path $docfxRoot 'docfx.json') -Raw | ConvertFrom-Json
$version = minver -i -t v -v w
Assert-CommandSucceeded 'minver'

$metadataProjectPatterns = @(
foreach ($metadata in $docfxConfig.metadata) {
foreach ($source in $metadata.src) {
$metadataSourceRoot = if ($source.src) {
[System.IO.Path]::GetFullPath((Join-Path $docfxRoot $source.src))
} else {
$docfxRoot
}

foreach ($file in $source.files) {
(Join-Path $metadataSourceRoot $file).Replace('/', [System.IO.Path]::DirectorySeparatorChar)
}
}
}
)
$sourceProjects = @(Get-ChildItem -LiteralPath $sourceRoot -Recurse -File -Filter '*.csproj')
$metadataProjects = @(
$sourceProjects |
Where-Object {
$projectPath = $_.FullName
foreach ($pattern in $metadataProjectPatterns) {
if ($projectPath -like $pattern) {
return $true
}
}

return $false
}
)
$sourceProjectsHaveRestoreAssets = $sourceProjects.Count -gt 0
foreach ($project in $sourceProjects) {
$restoreAssetsPath = Join-Path $project.DirectoryName 'obj\project.assets.json'
if (-not (Test-Path -LiteralPath $restoreAssetsPath -PathType Leaf)) {
$sourceProjectsHaveRestoreAssets = $false
break
}
}

$useNoRestore = $metadataProjects.Count -gt 0 -and $sourceProjectsHaveRestoreAssets
$restoreInputNames = @('Directory.Build.props', 'Directory.Build.targets', 'Directory.Packages.props', 'NuGet.Config', 'nuget.config', 'global.json')

foreach ($project in $sourceProjects) {
$restoreAssetsPath = Join-Path $project.DirectoryName 'obj\project.assets.json'
if (-not (Test-Path -LiteralPath $restoreAssetsPath -PathType Leaf)) {
$useNoRestore = $false
break
}

$restoreAssetsLastWriteTime = (Get-Item -LiteralPath $restoreAssetsPath).LastWriteTimeUtc
$restoreInputPaths = [System.Collections.Generic.List[string]]::new()
$restoreInputPaths.Add($project.FullName)

$inputDirectory = $project.DirectoryName
while ($inputDirectory) {
foreach ($name in $restoreInputNames) {
$restoreInputPath = Join-Path $inputDirectory $name
if (Test-Path -LiteralPath $restoreInputPath -PathType Leaf) {
$restoreInputPaths.Add($restoreInputPath)
}
}

$parentDirectory = Split-Path -Parent $inputDirectory
if (-not $parentDirectory -or $parentDirectory -eq $inputDirectory) {
break
}

$inputDirectory = $parentDirectory
}

$lockFilePath = Join-Path $project.DirectoryName 'packages.lock.json'
if (Test-Path -LiteralPath $lockFilePath -PathType Leaf) {
$restoreInputPaths.Add($lockFilePath)
}

if ($env:APPDATA) {
$userNuGetConfigPath = Join-Path $env:APPDATA 'NuGet\NuGet.Config'
if (Test-Path -LiteralPath $userNuGetConfigPath -PathType Leaf) {
$restoreInputPaths.Add($userNuGetConfigPath)
}
}

foreach ($restoreInputPath in $restoreInputPaths | Sort-Object -Unique) {
if ((Get-Item -LiteralPath $restoreInputPath).LastWriteTimeUtc -gt $restoreAssetsLastWriteTime) {
$useNoRestore = $false
break
}
}

if (-not $useNoRestore) {
break
}
}

if (-not $useNoRestore) {
# Restore the source graph once so DocFX does not restore every project separately.
$restoreSolution = Join-Path ([System.IO.Path]::GetTempPath()) ("savvyio-docfx-{0}.slnx" -f [Guid]::NewGuid())
try {
$projectsXml = foreach ($project in $sourceProjects) {
' <Project Path="{0}" />' -f [Security.SecurityElement]::Escape($project.FullName)
}
@('<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 }
}
}

# Keep all metadata groups in one process; DocFX carries resolver state across groups.
docfx metadata docfx.json --noRestore
Assert-CommandSucceeded 'docfx metadata'

docker buildx build -t savvyio-docfx:$version --platform linux/arm64,linux/amd64 --load -f Dockerfile.docfx .
Assert-CommandSucceeded 'docker buildx build'

Get-ChildItem -Recurse -Path api -Include *.yml, .manifest | Remove-Item
} finally {
Pop-Location
}
11 changes: 5 additions & 6 deletions .docfx/Dockerfile.docfx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
ARG NGINX_VERSION=1.31-alpine
ARG NGINX_VERSION=1.31-alpine

FROM --platform=$BUILDPLATFORM nginx:${NGINX_VERSION} AS base
FROM --platform=$BUILDPLATFORM dhi.io/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
9 changes: 8 additions & 1 deletion .docfx/docfx.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
}
],
"dest": "api/dotnet",
"memberLayout": "separatePages",
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "net10.0"
Expand Down Expand Up @@ -57,6 +58,7 @@
}
],
"dest": "api/extensions/dotnet",
"memberLayout": "separatePages",
"filter": "filterConfig.yml",
"properties": {
"TargetFramework": "net10.0"
Expand Down Expand Up @@ -111,7 +113,8 @@
"template": [
"default",
"modern",
"templates/savvyio"
"templates/savvyio",
"templates/ms-style"
Comment thread
gimlichael marked this conversation as resolved.
Resolved
],
"overwrite": [
{
Expand Down Expand Up @@ -139,6 +142,10 @@
"keepFileLink": false,
"cleanupCacheHistory": false,
"disableGitFeatures": false,
"sitemap": {
"baseUrl": "https://docs.savvyio.net/",
"changefreq": "monthly"
},
"xrefService": [
"https://xref.docs.microsoft.com/query?uid={uid}" ]
}
Expand Down
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/savvyio/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
2 changes: 1 addition & 1 deletion .docfx/templates/savvyio/public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export default {
iconLinks: [
{
icon: 'github',
href: 'https://github.com/codebeltnet/classlib-savvyio',
href: 'https://github.com/codebeltnet/savvyio',
title: 'GitHub'
}
]
Expand Down
5 changes: 2 additions & 3 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Run tests one project at a time so a failing or hanging project is attributable.
$testProjects = Get-ChildItem test -Filter *.csproj -Recurse
$testProjects = $testProjects | Where-Object { $_.BaseName -notin @('Savvyio.Assets.Dapper.Tests', 'Savvyio.Assets.EfCore.Tests', 'Savvyio.Assets.Tests', 'Savvyio.Extensions.NATS.FunctionalTests', 'Savvyio.Extensions.QueueStorage.FunctionalTests', 'Savvyio.Extensions.RabbitMQ.FunctionalTests', 'Savvyio.Extensions.SimpleQueueService.FunctionalTests', 'Savvyio.FunctionalTests') }
foreach ($project in $testProjects) {
dotnet test $project.FullName --configuration Release --no-restore
dotnet test --project $project.FullName --configuration Release --no-restore
}
```

Expand All @@ -56,8 +56,7 @@ docker compose up -d
docker compose down
```

The normal CI test matrix excludes these eight projects:

The normal CI test matrix excludes these integration-dependent projects:
- `Savvyio.Assets.Dapper.Tests`
- `Savvyio.Assets.EfCore.Tests`
- `Savvyio.Assets.Tests`
Expand Down
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),

For more details, please refer to `PackageReleaseNotes.txt` on a per assembly basis in the `.nuget` folder.

## [5.0.13] - TBD

This is a patch release focused on DocFX publishing pipeline hardening, contributor test guidance corrections, and expanded test coverage for configured options, discovery, and JSON converters.

### Added

- Configured options verification across dependency injection extensions with shared assertions covering direct, Microsoft Options, and callback consumption forms,
- Automatic dispatcher, handler, and assembly context discovery coverage including duplicate-free results, ordinal filtering, transitive traversal, and caching semantics,
- JSON message converter coverage across Newtonsoft.Json and Text.Json including null writer guards and concrete CloudEvent resolution excluding abstract candidates.

### Changed

- DocFX publishing pipeline hardened with reliable restore and error handling, dhi.io nginx serving with dedicated config and lean build context, separate member pages, ms-style template, mobile navigation, corrected GitHub link, and sitemap for docs.savvyio.net,
- Contributor test guidance corrected to dotnet test --project syntax with integration-dependent exclusion wording.

## [5.0.12] - 2026-09-13

This is a patch release focused on test infrastructure enhancements for NATS and RabbitMQ messaging extensions, dependency updates to latest stable versions, and improved contributor documentation.
Expand Down Expand Up @@ -1120,6 +1135,7 @@ Noticeable highlights:
- QueryHandler class in the Savvyio.Queries namespace that defines a generic and consistent way of handling Query objects that implements the IQuery interface
- SavvyioOptionsExtensions class in the Savvyio.Queries namespace that consist of extension methods for the SavvyioOptions class: AddQueryHandler, AddQueryDispatcher

[5.0.13]: https://github.com/codebeltnet/savvyio/compare/v5.0.12...v5.0.13
[5.0.12]: https://github.com/codebeltnet/savvyio/compare/v5.0.11...v5.0.12
[5.0.11]: https://github.com/codebeltnet/savvyio/compare/v5.0.10...v5.0.11
[5.0.10]: https://github.com/codebeltnet/savvyio/compare/v5.0.9...v5.0.10
Expand Down
3 changes: 2 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,14 @@

<ItemGroup Condition="'$(IsTestProject)' == 'true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="Microsoft.Testing.Extensions.CodeCoverage" />
<PackageReference Include="Microsoft.Testing.Extensions.HangDump" />
<PackageReference Include="xunit.v3" />
<PackageReference Include="xunit.v3.runner.console" />
<PackageReference Include="xunit.runner.visualstudio">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Codebelt.Coverlet.MTP" />
<PackageReference Include="Codebelt.Extensions.Xunit.App" />
</ItemGroup>

Expand Down
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.5" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.12" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.10.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.CodeCoverage" Version="18.11.2" />
<PackageVersion Include="Codebelt.Coverlet.MTP" Version="10.1.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.HangDump" Version="2.4.1" />
<PackageVersion Include="MinVer" Version="8.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="NATS.Client.Core" Version="3.2.0" />
Expand Down
Loading
Loading