From ce13400d18e96bfa061a085fd093fd2153eb5b39 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Fri, 7 Nov 2025 10:25:10 -0700 Subject: [PATCH 1/2] 0.2.0 Release Prep: Upgrade dependencies, bump version, fix Swagger docs, remove JetBrains Annotations --- .github/workflows/ci_build.yml | 31 +++++++-------- .github/workflows/publish.yml | 38 +++++++++++++------ .../F23.Kernel.AspNetCore.csproj | 9 ++--- .../Core/ResultsEndpoints.cs | 28 ++++++++------ .../F23.Kernel.Examples.AspNetCore.csproj | 3 +- .../Infrastructure/ResultsController.cs | 8 +++- src/F23.Kernel.Examples.AspNetCore/Program.cs | 22 +++++++++-- src/F23.Kernel.Tests/F23.Kernel.Tests.csproj | 17 ++++++--- src/F23.Kernel/EventSourcing/IApplyEvent.cs | 1 - src/F23.Kernel/F23.Kernel.csproj | 3 +- src/F23.Kernel/GlobalUsings.cs | 1 - 11 files changed, 101 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml index 6bae9ce..5eb273c 100644 --- a/.github/workflows/ci_build.yml +++ b/.github/workflows/ci_build.yml @@ -10,14 +10,11 @@ on: - 'releases/**' pull_request: branches: [ main ] - paths: - - '**/*.cs' - - '**/*.csproj' jobs: build: - name: build-${{matrix.os}}-${{matrix.dotnet}} + name: build-${{matrix.os}} runs-on: ${{ matrix.os }} defaults: run: @@ -25,24 +22,29 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macOS-latest] - dotnet: ['8.x'] # Just a single target for now steps: - uses: actions/checkout@v3 - - name: Setup .NET Core + + - name: Setup .NET 8 uses: actions/setup-dotnet@v3 with: - dotnet-version: ${{ matrix.dotnet }} + dotnet-version: 8.x + + - name: Setup .NET 9 + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 9.x - name: Install dependencies run: dotnet restore - + - name: Build run: dotnet build --configuration Release --no-restore - name: Test - run: dotnet test --no-restore --verbosity normal --logger trx --collect:"XPlat Code Coverage" - + run: dotnet test --no-build --configuration Release --verbosity normal --logger trx --collect:"XPlat Code Coverage" + - name: Combine Coverage Reports # This is because one report is produced per project, and we want one result for all of them. uses: danielpalme/ReportGenerator-GitHub-Action@5.2.4 if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' # Skip if running locally with act @@ -55,7 +57,7 @@ jobs: tag: "${{ github.run_number }}_${{ github.run_id }}" # Optional tag or build version. customSettings: "" # Optional custom settings (separated by semicolon). See: https://github.com/danielpalme/ReportGenerator/wiki/Settings. toolpath: "reportgeneratortool" # Default directory for installing the dotnet tool. - + - name: Upload Combined Coverage XML uses: actions/upload-artifact@v4 if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' # Skip if running locally with act @@ -63,7 +65,7 @@ jobs: name: coverage path: ${{ github.workspace }}/Cobertura.xml retention-days: 5 - + - name: Publish Code Coverage Report uses: irongut/CodeCoverageSummary@v1.3.0 if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' # Skip if running locally with act @@ -77,7 +79,7 @@ jobs: indicators: true output: both thresholds: "10 30" # Red, Yellow - + - name: Add Coverage PR Comment uses: marocchino/sticky-pull-request-comment@v2 if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' && github.event_name == 'pull_request' # Skip if running locally with act, or if not a pull request @@ -92,10 +94,9 @@ jobs: name: test-results path: ${{ github.workspace }}/**/TestResults/**/* retention-days: 5 - + - name: Publish Test Results uses: EnricoMi/publish-unit-test-result-action@v2.16.1 if: github.actor != 'nektos/act' && matrix.os == 'ubuntu-latest' && always() # Skip if running locally with act with: trx_files: "${{ github.workspace }}/**/*.trx" - \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ee96bcf..79ee29b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,13 +4,6 @@ name: publish on: workflow_dispatch: # Allow running the workflow manually from the GitHub UI - push: - branches: - - main # Run the workflow when pushing to the main branch - - 'releases/**' # Run the workflow when pushing to a release branch - pull_request: - branches: - - '*' # Run the workflow for all pull requests release: types: - published # Run the workflow when a new GitHub release is published @@ -32,8 +25,15 @@ jobs: with: fetch-depth: 0 # Get all history to allow automatic versioning using MinVer - - name: Setup .NET + - name: Setup .NET 8 uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Setup .NET 9 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.x - run: dotnet pack --configuration Release --output ${{ env.NuGetDirectory }} @@ -52,8 +52,17 @@ jobs: working-directory: src steps: - uses: actions/checkout@v3 - - name: Setup .NET + + - name: Setup .NET 8 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Setup .NET 9 uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.x + - name: Run tests run: dotnet test --configuration Release @@ -74,8 +83,15 @@ jobs: name: nuget path: ${{ env.NuGetDirectory }} - - name: Setup .NET Core + - name: Setup .NET 8 uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.x + + - name: Setup .NET 9 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 9.x # Publish all NuGet packages to NuGet.org # Use --skip-duplicate to prevent errors if a package with the same version already exists. @@ -84,4 +100,4 @@ jobs: run: | foreach($file in (Get-ChildItem "${{ env.NuGetDirectory }}" -Recurse -Include *.nupkg)) { dotnet nuget push $file --api-key "${{ secrets.NUGET_APIKEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - } \ No newline at end of file + } diff --git a/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj b/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj index d6a6073..c8b4f5d 100644 --- a/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj +++ b/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.1.0 + 0.2.0 feature[23] feature[23] https://github.com/feature23/kernel @@ -23,18 +23,17 @@ - + - + - - + diff --git a/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs b/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs index 57d8c44..109328e 100644 --- a/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs +++ b/src/F23.Kernel.Examples.AspNetCore/Core/ResultsEndpoints.cs @@ -1,8 +1,6 @@ -using F23.Kernel; using F23.Kernel.AspNetCore; using F23.Kernel.Results; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Mvc; namespace F23.Kernel.Examples.AspNetCore.Core; @@ -18,43 +16,49 @@ public static class ResultsEndpoints public static void MapResultsEndpoints(this WebApplication app) { var group = app.MapGroup("/minimal-apis/results") - .WithTags("Results - Minimal APIs") - .WithOpenApi(); + .WithTags("Results - Minimal APIs"); group.MapGet("/success-no-value", SuccessNoValue) .WithName("MinimalApiSuccessNoValue") .WithSummary("Demonstrates a successful result with no value") - .WithDescription("Returns 204 No Content"); + .WithDescription("Returns 204 No Content") + .Produces(StatusCodes.Status204NoContent); group.MapGet("/success-with-value", SuccessWithValue) .WithName("MinimalApiSuccessWithValue") .WithSummary("Demonstrates a successful result with a value") - .WithDescription("Returns 200 OK with the value in the response body"); + .WithDescription("Returns 200 OK with the value in the response body") + .Produces(StatusCodes.Status200OK); group.MapGet("/validation-failed", ValidationFailed) .WithName("MinimalApiValidationFailed") .WithSummary("Demonstrates a validation failed result") - .WithDescription("Returns 400 Bad Request with validation errors"); + .WithDescription("Returns 400 Bad Request with validation errors") + .Produces(StatusCodes.Status400BadRequest); group.MapGet("/unauthorized", Unauthorized) .WithName("MinimalApiUnauthorized") .WithSummary("Demonstrates an unauthorized result") - .WithDescription("Returns 403 Forbidden"); + .WithDescription("Returns 403 Forbidden") + .Produces(StatusCodes.Status403Forbidden); group.MapGet("/not-found", NotFound) .WithName("MinimalApiNotFound") .WithSummary("Demonstrates a precondition failed result (not found)") - .WithDescription("Returns 404 Not Found"); + .WithDescription("Returns 404 Not Found") + .Produces(StatusCodes.Status404NotFound); group.MapGet("/concurrency-mismatch", ConcurrencyMismatch) .WithName("MinimalApiConcurrencyMismatch") .WithSummary("Demonstrates a precondition failed result (concurrency mismatch)") - .WithDescription("Returns 412 Precondition Failed"); + .WithDescription("Returns 412 Precondition Failed") + .Produces(StatusCodes.Status412PreconditionFailed); group.MapGet("/conflict", Conflict) .WithName("MinimalApiConflict") .WithSummary("Demonstrates a precondition failed result (conflict)") - .WithDescription("Returns 409 Conflict"); + .WithDescription("Returns 409 Conflict") + .Produces(StatusCodes.Status409Conflict); } private static IResult SuccessNoValue() diff --git a/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj b/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj index c9353ea..d11c080 100644 --- a/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj +++ b/src/F23.Kernel.Examples.AspNetCore/F23.Kernel.Examples.AspNetCore.csproj @@ -7,8 +7,7 @@ - - + diff --git a/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs b/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs index 85b80f7..c082d04 100644 --- a/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs +++ b/src/F23.Kernel.Examples.AspNetCore/Infrastructure/ResultsController.cs @@ -1,4 +1,3 @@ -using F23.Kernel; using F23.Kernel.AspNetCore; using F23.Kernel.Results; using Microsoft.AspNetCore.Mvc; @@ -18,6 +17,7 @@ public class ResultsController : ControllerBase /// /// 204 No Content [HttpGet("success-no-value")] + [ProducesResponseType(StatusCodes.Status204NoContent)] public IActionResult SuccessNoValue() { var result = Result.Success(); @@ -29,6 +29,7 @@ public IActionResult SuccessNoValue() /// /// 200 OK with the value in the response body [HttpGet("success-with-value")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult SuccessWithValue() { var data = new { message = "Operation completed successfully", timestamp = DateTime.UtcNow }; @@ -41,6 +42,7 @@ public IActionResult SuccessWithValue() /// /// 400 Bad Request with validation errors [HttpGet("validation-failed")] + [ProducesResponseType(StatusCodes.Status400BadRequest)] public IActionResult ValidationFailed() { var errors = new[] @@ -58,6 +60,7 @@ public IActionResult ValidationFailed() /// /// 403 Forbidden [HttpGet("unauthorized")] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public IActionResult UnauthorizedDemo() { var result = Result.Unauthorized("User does not have permission to access this resource"); @@ -69,6 +72,7 @@ public IActionResult UnauthorizedDemo() /// /// 404 Not Found [HttpGet("not-found")] + [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult NotFoundDemo() { var result = Result.PreconditionFailed( @@ -83,6 +87,7 @@ public IActionResult NotFoundDemo() /// /// 412 Precondition Failed [HttpGet("concurrency-mismatch")] + [ProducesResponseType(StatusCodes.Status412PreconditionFailed)] public IActionResult ConcurrencyMismatch() { var result = Result.PreconditionFailed( @@ -97,6 +102,7 @@ public IActionResult ConcurrencyMismatch() /// /// 409 Conflict [HttpGet("conflict")] + [ProducesResponseType(StatusCodes.Status409Conflict)] public IActionResult ConflictDemo() { var result = Result.PreconditionFailed( diff --git a/src/F23.Kernel.Examples.AspNetCore/Program.cs b/src/F23.Kernel.Examples.AspNetCore/Program.cs index 2f13e86..fae616a 100644 --- a/src/F23.Kernel.Examples.AspNetCore/Program.cs +++ b/src/F23.Kernel.Examples.AspNetCore/Program.cs @@ -1,16 +1,28 @@ +using System.Reflection; using F23.Kernel; using F23.Kernel.AspNetCore; using F23.Kernel.Examples.AspNetCore.Core; using F23.Kernel.Examples.AspNetCore.Infrastructure; using F23.Kernel.Examples.AspNetCore.UseCases.GetWeatherForecast; +using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); +var version = typeof(Result).Assembly.GetCustomAttribute()?.InformationalVersion + ?? "0.0.0"; + // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddSwaggerGen(); +builder.Services.AddSwaggerGen(options => +{ + options.SwaggerDoc($"v{version}", new OpenApiInfo + { + Title = "F23.Kernel Examples", + Version = $"v{version}", + }); +}); builder.Services.AddSingleton(); builder.Services.RegisterQueryHandler(); @@ -21,7 +33,10 @@ if (app.Environment.IsDevelopment()) { app.UseSwagger(); - app.UseSwaggerUI(); + app.UseSwaggerUI(options => + { + options.SwaggerEndpoint($"/swagger/v{version}/swagger.json", $"F23.Kernel Examples v{version}"); + }); } app.UseHttpsRedirection(); @@ -34,8 +49,7 @@ return result.ToMinimalApiResult(); }) - .WithName("GetWeatherForecast") - .WithOpenApi(); + .WithName("GetWeatherForecast"); app.MapResultsEndpoints(); diff --git a/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj b/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj index 7fe333b..e61b2ae 100644 --- a/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj +++ b/src/F23.Kernel.Tests/F23.Kernel.Tests.csproj @@ -1,19 +1,24 @@ - net8.0 + net8.0;net9.0 enable enable - false true - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/F23.Kernel/EventSourcing/IApplyEvent.cs b/src/F23.Kernel/EventSourcing/IApplyEvent.cs index 985f0e0..9d3bbb0 100644 --- a/src/F23.Kernel/EventSourcing/IApplyEvent.cs +++ b/src/F23.Kernel/EventSourcing/IApplyEvent.cs @@ -21,6 +21,5 @@ public interface IApplyEvent /// Applies the specified event to the aggregate root and updates its state accordingly. /// /// The event to be applied to the aggregate root. - [UsedImplicitly] TSnapshot Apply(TEvent e); } diff --git a/src/F23.Kernel/F23.Kernel.csproj b/src/F23.Kernel/F23.Kernel.csproj index 36f791b..e3e72aa 100644 --- a/src/F23.Kernel/F23.Kernel.csproj +++ b/src/F23.Kernel/F23.Kernel.csproj @@ -4,7 +4,7 @@ net8.0 enable enable - 0.1.0 + 0.2.0 feature[23] feature[23] https://github.com/feature23/kernel @@ -25,7 +25,6 @@ - diff --git a/src/F23.Kernel/GlobalUsings.cs b/src/F23.Kernel/GlobalUsings.cs index de79f00..be9ca72 100644 --- a/src/F23.Kernel/GlobalUsings.cs +++ b/src/F23.Kernel/GlobalUsings.cs @@ -1,2 +1 @@ -global using JetBrains.Annotations; global using Microsoft.Extensions.DependencyInjection; From 4583143d96845dfdacda17ab6892aefc3e0642ba Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Fri, 7 Nov 2025 10:31:39 -0700 Subject: [PATCH 2/2] Suppress nullability warning with HypermediaResponse --- src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj | 1 + src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs | 2 +- src/F23.Kernel.AspNetCore/MvcResultExtensions.cs | 2 +- src/F23.Kernel/F23.Kernel.csproj | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj b/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj index c8b4f5d..666262b 100644 --- a/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj +++ b/src/F23.Kernel.AspNetCore/F23.Kernel.AspNetCore.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + true 0.2.0 feature[23] feature[23] diff --git a/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs b/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs index 000e300..c5c8853 100644 --- a/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs +++ b/src/F23.Kernel.AspNetCore/MinimalApiResultExtensions.cs @@ -97,7 +97,7 @@ public static IResult ToMinimalApiResult(this Result result, Func success when successMap != null => successMap(success.Value), SuccessResult success - => HttpResults.Ok(new HypermediaResponse(success.Value)), + => HttpResults.Ok(new HypermediaResponse(success.Value!)), // [!]: TODO: Fix nullability issue in F23.Hateoas PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when useProblemDetails => result.ToProblemHttpResult(HttpStatusCode.NotFound), PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when !useProblemDetails diff --git a/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs b/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs index adc8010..21e6ea3 100644 --- a/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs +++ b/src/F23.Kernel.AspNetCore/MvcResultExtensions.cs @@ -104,7 +104,7 @@ public static IActionResult ToActionResult(this Result result, Func success when successMap != null => successMap(success.Value), SuccessResult success - => new OkObjectResult(new HypermediaResponse(success.Value)), + => new OkObjectResult(new HypermediaResponse(success.Value!)), // [!]: TODO: Fix nullability issue in F23.Hateoas PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when useProblemDetails => result.ToProblemDetailsResult(HttpStatusCode.NotFound), PreconditionFailedResult { Reason: PreconditionFailedReason.NotFound } when !useProblemDetails diff --git a/src/F23.Kernel/F23.Kernel.csproj b/src/F23.Kernel/F23.Kernel.csproj index e3e72aa..be984fa 100644 --- a/src/F23.Kernel/F23.Kernel.csproj +++ b/src/F23.Kernel/F23.Kernel.csproj @@ -4,6 +4,7 @@ net8.0 enable enable + true 0.2.0 feature[23] feature[23]