From 73b15fb8b973c6e791db10c0afa8440605dae405 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Fri, 24 Jul 2026 14:20:44 +1200 Subject: [PATCH 1/2] build: Fallout CI, trusted-publishing & spec tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modernizes the repo's build/release/test infrastructure to match the TVDB sibling repo (Fallout-generated CI, OIDC trusted publishing, spec tests). Library code and dependency pins are untouched — Scrutor-major/TFM bumps are tracked separately as modernization. CI (Fallout, C#): - build/ Fallout project (Build.cs) with Test + Pack targets; .github/workflows/ build.yml is GENERATED from the [GitHubActions] attribute (never hand-edited). - Polyglot build.cmd/build.sh/build.ps1 bootstrappers; .fallout root marker; .config/dotnet-tools.json (Fallout.GlobalTool + GitVersion, both 10.3.49/6.3.0). - GitVersion.yml, tag-driven: MAJOR tracks compatible Scrutor major, MINOR.PATCH is our internal counter. Removed the old publish-nuget.yml (brandedoutcast). Publishing (hand-written publish.yml, documented exception): - Tag-driven: nuget.org via Trusted Publishing (OIDC, no stored key) + GitHub Packages + a GitHub Release with the .nupkg attached and label-categorized notes (.github/release.yml). Tests (spec-style, no CPM): - tests/Scrutor.Extensions.HttpClient.Specs: 7 xUnit/FluentAssertions specs proving .AsHttpClient()/.AsHttpClient(name) register scanned classes as typed clients (factory wired, named-client config + handler pipeline flow through, transient lifetime, unnamed overload) + a PublicApiGenerator+Verify public-surface baseline. CLAUDE.md documents all of the above. Co-Authored-By: Claude Opus 4.8 --- .config/dotnet-tools.json | 20 ++++ .fallout/build.schema.json | 105 ++++++++++++++++ .fallout/parameters.json | 3 + .github/release.yml | 19 +++ .github/workflows/build.yml | 43 +++++++ .github/workflows/publish-nuget.yml | 57 --------- .github/workflows/publish.yml | 75 ++++++++++++ .gitignore | 4 + CLAUDE.md | 91 ++++++++++++++ Directory.Build.props | 29 +++++ GitVersion.yml | 36 ++++++ Scrutor.Extensions.HttpClient.sln | 48 ++++++++ .../Scrutor.Extensions.HttpClient.csproj | 27 ++--- build.cmd | 6 + build.ps1 | 11 ++ build.sh | 3 + build/Build.cs | 52 ++++++++ build/_build.csproj | 20 ++++ global.json | 6 + tests/Directory.Build.props | 30 +++++ .../AsHttpClientSpecs.cs | 112 ++++++++++++++++++ ...c_api_surface_has_not_changed.verified.txt | 11 ++ .../PublicApiSpecs.cs | 29 +++++ ...Scrutor.Extensions.HttpClient.Specs.csproj | 13 ++ .../TestClients.cs | 54 +++++++++ 25 files changed, 830 insertions(+), 74 deletions(-) create mode 100644 .config/dotnet-tools.json create mode 100644 .fallout/build.schema.json create mode 100644 .fallout/parameters.json create mode 100644 .github/release.yml create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/publish-nuget.yml create mode 100644 .github/workflows/publish.yml create mode 100644 CLAUDE.md create mode 100644 Directory.Build.props create mode 100644 GitVersion.yml create mode 100755 build.cmd create mode 100755 build.ps1 create mode 100755 build.sh create mode 100644 build/Build.cs create mode 100644 build/_build.csproj create mode 100644 global.json create mode 100644 tests/Directory.Build.props create mode 100644 tests/Scrutor.Extensions.HttpClient.Specs/AsHttpClientSpecs.cs create mode 100644 tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.Public_api_surface_has_not_changed.verified.txt create mode 100644 tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.cs create mode 100644 tests/Scrutor.Extensions.HttpClient.Specs/Scrutor.Extensions.HttpClient.Specs.csproj create mode 100644 tests/Scrutor.Extensions.HttpClient.Specs/TestClients.cs diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json new file mode 100644 index 0000000..fb36ceb --- /dev/null +++ b/.config/dotnet-tools.json @@ -0,0 +1,20 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "GitVersion.Tool": { + "version": "6.3.0", + "commands": [ + "dotnet-gitversion" + ], + "rollForward": true + }, + "fallout.globaltool": { + "version": "10.3.49", + "commands": [ + "fallout" + ], + "rollForward": true + } + } +} diff --git a/.fallout/build.schema.json b/.fallout/build.schema.json new file mode 100644 index 0000000..f453028 --- /dev/null +++ b/.fallout/build.schema.json @@ -0,0 +1,105 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "definitions": { + "Host": { + "type": "string", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitbucket", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "ExecutableTarget": { + "type": "string", + "enum": [ + "Pack", + "Test" + ] + }, + "Verbosity": { + "type": "string", + "description": "", + "enum": [ + "Verbose", + "Normal", + "Minimal", + "Quiet" + ] + }, + "FalloutBuild": { + "properties": { + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "description": "Host for execution. Default is 'automatic'", + "$ref": "#/definitions/Host" + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "$ref": "#/definitions/ExecutableTarget" + } + }, + "Verbosity": { + "description": "Logging verbosity during build execution. Default is 'Normal'", + "$ref": "#/definitions/Verbosity" + } + } + } + }, + "allOf": [ + {}, + { + "$ref": "#/definitions/FalloutBuild" + } + ] +} diff --git a/.fallout/parameters.json b/.fallout/parameters.json new file mode 100644 index 0000000..84e6d1a --- /dev/null +++ b/.fallout/parameters.json @@ -0,0 +1,3 @@ +{ + "Solution": "Scrutor.Extensions.HttpClient.sln" +} diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..7e4edfa --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,19 @@ +# Categorises the auto-generated GitHub Release notes by PR label +# (used by `generate_release_notes` in publish.yml). +changelog: + exclude: + labels: + - duplicate + - invalid + - wontfix + categories: + - title: ✨ Features & Enhancements + labels: [enhancement, feature] + - title: 🐛 Fixes + labels: [bug, fix] + - title: 📝 Documentation + labels: [documentation] + - title: 🔧 Maintenance & CI + labels: [chore, ci, cleanup, dependencies, iac] + - title: Other Changes + labels: ["*"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c0bb2af --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,43 @@ +# ------------------------------------------------------------------------------ +# +# +# This code was generated. +# +# - To turn off auto-generation set: +# +# [GitHubActions (AutoGenerate = false)] +# +# - To trigger manual generation invoke: +# +# fallout --generate-configuration GitHubActions_build --host GitHubActions +# +# +# ------------------------------------------------------------------------------ + +name: build + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + ubuntu-latest: + name: ubuntu-latest + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: 'Cache: .fallout/temp, ~/.nuget/packages' + uses: actions/cache@v4 + with: + path: | + .fallout/temp + ~/.nuget/packages + key: ${{ runner.os }}-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props') }} + - name: 'Run: Test, Pack' + run: ./build.cmd Test Pack diff --git a/.github/workflows/publish-nuget.yml b/.github/workflows/publish-nuget.yml deleted file mode 100644 index 8b03a9c..0000000 --- a/.github/workflows/publish-nuget.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: publish to nuget -on: - push: - branches: - - main # Default release branch, may also be named 'master' or 'develop' -jobs: - publish: - name: build, pack & publish - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Setup dotnet - uses: actions/setup-dotnet@v1 - with: - dotnet-version: 9.0.x - - # Publish - - name: publish on version change - id: publish_nuget - uses: brandedoutcast/publish-nuget@v2 - with: - # Filepath of the project to be packaged, relative to root of repository - PROJECT_FILE_PATH: Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj - - # Configuration to build and package - # BUILD_CONFIGURATION: Release - - # Platform target to compile (default is empty/AnyCPU) - # BUILD_PLATFORM: x64 - - # NuGet package id, used for version detection & defaults to project name - # PACKAGE_NAME: Core - - # Filepath with version info, relative to root of repository & defaults to PROJECT_FILE_PATH - # VERSION_FILE_PATH: Directory.Build.props - - # Regex pattern to extract version info in a capturing group - # VERSION_REGEX: ^\s*(.*)<\/Version>\s*$ - - # Useful with external providers like Nerdbank.GitVersioning, ignores VERSION_FILE_PATH & VERSION_REGEX - # VERSION_STATIC: 1.0.0 - - # Flag to toggle git tagging, enabled by default - # TAG_COMMIT: true - - # Format of the git tag, [*] gets replaced with actual version - # TAG_FORMAT: v* - - # API key to authenticate with NuGet server - NUGET_KEY: ${{secrets.NUGET_API_KEY}} - - # NuGet server uri hosting the packages, defaults to https://api.nuget.org - # NUGET_SOURCE: https://api.nuget.org - - # Flag to toggle pushing symbols along with nuget package to the server, disabled by default - # INCLUDE_SYMBOLS: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..9b9c2dd --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,75 @@ +# Publish on a version tag: +# 1. NuGet.org via Trusted Publishing (OIDC) — no stored API key. +# 2. GitHub Packages (uses the built-in GITHUB_TOKEN). +# 3. A GitHub Release with the .nupkg file attached and auto-generated, +# label-categorized release notes (see .github/release.yml). +# +# HAND-WRITTEN, documented exception: Fallout owns .github/workflows/build.yml +# (generated from the [GitHubActions] attribute — never hand-edit it), but the +# publish flow (OIDC trusted publishing + GitHub Packages + Release) is not +# expressible via the pinned Fallout 10.3.49 attribute API, so this workflow is +# maintained by hand. See CLAUDE.md. +# +# One-time nuget.org setup: a Trusted Publisher policy for the package +# (Scrutor.Extensions.HttpClient): owner Chrison-dev, repo +# Scrutor.Extensions.HttpClient, workflow publish.yml, environment nuget.org. +# Repo variable NUGET_USER = your nuget.org username. +# +# Triggered by pushing a version tag, e.g. `git tag v5.1.0 && git push origin v5.1.0`. +name: publish + +on: + push: + tags: ['v*'] + workflow_dispatch: + +permissions: + id-token: write # OIDC trusted publishing (nuget.org) + contents: write # create the GitHub Release + packages: write # push to GitHub Packages + +jobs: + publish: + runs-on: ubuntu-latest + environment: nuget.org + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # GitVersion needs full history + + - uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + - name: 'Restore: dotnet tools' + run: dotnet tool restore + + - name: 'Pack' + run: dotnet run --project build/_build.csproj -- Pack + + - name: 'NuGet.org: trusted-publishing login (OIDC → short-lived key)' + uses: NuGet/login@v1 + id: login + with: + user: ${{ vars.NUGET_USER }} + + - name: 'NuGet.org: push' + run: > + dotnet nuget push "artifacts/packages/*.nupkg" + --source https://api.nuget.org/v3/index.json + --api-key ${{ steps.login.outputs.NUGET_API_KEY }} + --skip-duplicate + + - name: 'GitHub Packages: push' + run: > + dotnet nuget push "artifacts/packages/*.nupkg" + --source https://nuget.pkg.github.com/Chrison-dev/index.json + --api-key ${{ secrets.GITHUB_TOKEN }} + --skip-duplicate + + - name: 'GitHub Release: create with packages + generated notes' + if: startsWith(github.ref, 'refs/tags/') + uses: softprops/action-gh-release@v2 + with: + files: artifacts/packages/*.nupkg + generate_release_notes: true diff --git a/.gitignore b/.gitignore index a4fe18b..2007dce 100644 --- a/.gitignore +++ b/.gitignore @@ -398,3 +398,7 @@ FodyWeavers.xsd # JetBrains Rider *.sln.iml + +# Fallout build system +.fallout/temp/ +artifacts/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..30fef89 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,91 @@ +# CLAUDE.md + +Guidance for Claude Code when working in this repository. + +## What this is + +`Scrutor.Extensions.HttpClient` is a tiny (single public class) companion library for +[Scrutor](https://github.com/khellang/Scrutor). It adds `.AsHttpClient()` / +`.AsHttpClient(name)` registration selectors to Scrutor's assembly-scan pipeline so that +each scanned class is registered as a **typed `HttpClient`** (via `IHttpClientFactory` / +`AddHttpClient()`) instead of a plain service. + +It exists to solve [Scrutor issue #180](https://github.com/khellang/Scrutor/issues/180), +which the Scrutor maintainer declined to ship natively (it would force a +`Microsoft.Extensions.Http` dependency and needs `MakeGenericMethod` reflection). As of +Scrutor 7.0.0 / .NET 10 nothing upstream fills this gap, so the library still earns its keep. + +```csharp +services.AddHttpClient("MyClient", c => c.BaseAddress = new Uri("https://api.example.com")); +services.Scan(scan => scan + .FromAssemblyOf() + .AddClasses(c => c.AssignableTo()) + .AsMatchingInterface() + .AsHttpClient("MyClient")); // ← all scanned clients become typed clients of "MyClient" +``` + +## Build & CI — Fallout owns the workflows (never hand-edit YAML) + +CI is defined in C# via [Fallout](https://www.nuget.org/packages/Fallout.GlobalTool/) (a NUKE +fork), not hand-written pipelines. The build lives in `build/Build.cs`. + +- `.github/workflows/build.yml` is **GENERATED** from the `[GitHubActions]` attribute on the + `Build` class. **Never hand-edit it.** Regenerate by running any build (`./build.cmd`) or: + `dotnet fallout --generate-configuration GitHubActions_build --host GitHubActions`. +- Run the build locally with the polyglot bootstrapper (bat/sh in one file — works on the + Linux CI runner and locally): + ```bash + ./build.cmd Test # run the *.Specs suite + ./build.cmd Pack # Test + pack the NuGet package into artifacts/packages + ./build.cmd # default target (Pack) + ``` +- Fallout tooling notes (learned the hard way on the TVDB sibling repo): the tool package + `Fallout.GlobalTool` is singular and **lags the libraries at 10.3.49** — keep `Fallout.Common` + / `Fallout.Components` pinned to `10.3.49` to match. `.fallout/parameters.json` marks the repo + root; `.fallout/build.schema.json` is generated. + +### Publishing — hand-written `publish.yml` (documented exception) + +`.github/workflows/publish.yml` is the **one hand-maintained workflow**. The publish flow +(OIDC trusted publishing + GitHub Packages + GitHub Release) is not expressible via the pinned +Fallout 10.3.49 attribute API, so it is written by hand — this is deliberate, not an oversight. + +Publishing is **tag-driven**. Pushing a `v*` tag: +1. Packs via the Fallout `Pack` target. +2. Pushes to **nuget.org via Trusted Publishing (OIDC)** — no stored API key. +3. Pushes to **GitHub Packages** (`nuget.pkg.github.com/Chrison-dev`) with `GITHUB_TOKEN`. +4. Creates a **GitHub Release** with the `.nupkg` attached and label-categorized notes + (`.github/release.yml`). + +One-time nuget.org setup: a Trusted Publisher policy for `Scrutor.Extensions.HttpClient` +(owner `Chrison-dev`, repo `Scrutor.Extensions.HttpClient`, workflow `publish.yml`, environment +`nuget.org`) + repo variable `NUGET_USER`. + +## Versioning — GitVersion, tag-driven + +`GitVersion.yml` drives the version from `v*` tags. **Convention: MAJOR tracks the compatible +Scrutor major** (v5.x → Scrutor 5.x; a move to Scrutor 7.x makes this package 7.x). MINOR.PATCH +is our own internal release counter. Cut a release by tagging, e.g. `git tag v5.1.0 && git push +origin v5.1.0`. Untagged builds fall back to the static version in `Directory.Build.props`. + +## Tests — spec-style, no Central Package Management + +Tests live in `tests/*.Specs` and follow the Fallout/TVDB convention: + +- The test stack (xUnit + FluentAssertions + Verify + PublicApiGenerator) is injected via + `tests/Directory.Build.props` with **per-package versions — do NOT introduce Central Package + Management** (deliberate preference). +- `PublicApiSpecs` snapshots the public surface with PublicApiGenerator + Verify. The surface is + in the `Microsoft.Extensions.DependencyInjection` namespace, so it must be allow-listed past + PublicApiGenerator's default `System`/`Microsoft` deny-list (`AllowNamespacePrefixes`). Accept + intentional API changes by updating the `*.verified.txt` baseline. +- Functional specs exercise the real DI container + a stub `HttpMessageHandler` (no Mockly / + NetArchTest here — nothing to mock or architect in a single-class library). + +## Library conventions + +- Targets **net8.0** (Scrutor 5.x era). TFM/Scrutor-major bumps are **modernization**, tracked + separately — not folded into routine changes. +- Scrutor pinned `[5.1.2, 6.0.0)` and `Microsoft.Extensions.Http` `[9.0.0, 10.0.0)` on purpose + (6.x Scrutor has a breaking change). Root `Directory.Build.props` centralizes TFM + GitVersion; + the library csproj only carries package metadata. diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..3afb018 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,29 @@ + + + + + net8.0 + enable + enable + + + $([System.DateTime]::Now.ToString("yyyy")) + + + + + true + + 5.1.0 + 5.1.0.0 + 5.1.0.0 + 5.1.0 + + $(GitVersion_FullSemVer) + $(GitVersion_AssemblySemVer) + $(GitVersion_AssemblySemVer) + $(GitVersion_InformationalVersion) + + diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..8dee64b --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1,36 @@ +mode: ContinuousDelivery + +# Versioning scheme: the MAJOR tracks the major version of Scrutor this package is +# compatible with (v5.x → Scrutor 5.x; when we move to Scrutor 7.x the package +# becomes 7.x). MINOR.PATCH is our own internal release counter. Releases are cut +# by tagging `v..` (e.g. `git tag v5.1.0`) — GitVersion reads +# the tag (tag-prefix 'v'). Untagged builds get a prerelease suffix. + +branches: + main: + mode: ContinuousDelivery + increment: None + develop: + mode: ContinuousDeployment + label: 'alpha' + increment: None + release: + mode: ContinuousDelivery + label: 'beta' + increment: None + feature: + mode: ContinuousDeployment + label: 'feature' + increment: None + hotfix: + mode: ContinuousDeployment + label: 'hotfix' + increment: None + +assembly-versioning-scheme: MajorMinorPatch +assembly-file-versioning-scheme: MajorMinorPatchTag + +tag-prefix: 'v' + +# Keep major.minor.patch static by not incrementing any version component; +# the tag drives the release version, prerelease labels provide uniqueness. diff --git a/Scrutor.Extensions.HttpClient.sln b/Scrutor.Extensions.HttpClient.sln index e1f6b23..7921ca8 100644 --- a/Scrutor.Extensions.HttpClient.sln +++ b/Scrutor.Extensions.HttpClient.sln @@ -13,20 +13,68 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scrutor.Extensions.HttpClient", "Scrutor.Extensions.HttpClient\Scrutor.Extensions.HttpClient.csproj", "{9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{DEE5DD87-39C1-BF34-B639-A387DCCF972B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "_build", "build\_build.csproj", "{517BC9BD-296E-4F6B-AE15-6999EC71A74A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Scrutor.Extensions.HttpClient.Specs", "tests\Scrutor.Extensions.HttpClient.Specs\Scrutor.Extensions.HttpClient.Specs.csproj", "{5366CD2A-4383-45F5-BDEE-2F5D4A07A280}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Debug|x64.ActiveCfg = Debug|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Debug|x64.Build.0 = Debug|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Debug|x86.ActiveCfg = Debug|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Debug|x86.Build.0 = Debug|Any CPU {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Release|Any CPU.ActiveCfg = Release|Any CPU {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Release|Any CPU.Build.0 = Release|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Release|x64.ActiveCfg = Release|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Release|x64.Build.0 = Release|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Release|x86.ActiveCfg = Release|Any CPU + {9BAB1F5C-57D9-CE14-2C82-0CFEB033CBE6}.Release|x86.Build.0 = Release|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Debug|x64.ActiveCfg = Debug|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Debug|x64.Build.0 = Debug|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Debug|x86.ActiveCfg = Debug|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Debug|x86.Build.0 = Debug|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Release|Any CPU.Build.0 = Release|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Release|x64.ActiveCfg = Release|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Release|x64.Build.0 = Release|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Release|x86.ActiveCfg = Release|Any CPU + {517BC9BD-296E-4F6B-AE15-6999EC71A74A}.Release|x86.Build.0 = Release|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Debug|x64.ActiveCfg = Debug|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Debug|x64.Build.0 = Debug|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Debug|x86.ActiveCfg = Debug|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Debug|x86.Build.0 = Debug|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Release|Any CPU.Build.0 = Release|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Release|x64.ActiveCfg = Release|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Release|x64.Build.0 = Release|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Release|x86.ActiveCfg = Release|Any CPU + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {517BC9BD-296E-4F6B-AE15-6999EC71A74A} = {DEE5DD87-39C1-BF34-B639-A387DCCF972B} + {5366CD2A-4383-45F5-BDEE-2F5D4A07A280} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D2B49AAE-7B4F-4F57-8FF4-0759FE6E73BA} EndGlobalSection diff --git a/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj b/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj index c99a36b..c878246 100644 --- a/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj +++ b/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj @@ -1,29 +1,17 @@ - - - - net8.0 - enable - enable - + - True Scrutor HttpClient Extension Chrison Simtian - $([System.DateTime]::Now.ToString(yyyy)) - $(Company) + $(CurrentYear) - $(Company) Allows you to register HttpClients via Scrutor - https://github.com/ChrisonSimtian/Scrutor.Extensions.HttpClient - https://github.com/ChrisonSimtian/Scrutor.Extensions.HttpClient.git + https://github.com/Chrison-dev/Scrutor.Extensions.HttpClient + https://github.com/Chrison-dev/Scrutor.Extensions.HttpClient.git git LICENSE True False README.md - - - - - 5.1.2-alpha.3 True @@ -42,8 +30,13 @@ - + + + + + diff --git a/build.cmd b/build.cmd new file mode 100755 index 0000000..61a428a --- /dev/null +++ b/build.cmd @@ -0,0 +1,6 @@ +:; set -eo pipefail +:; SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd) +:; exec "$SCRIPT_DIR/build.sh" "$@" + +@echo off +powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0build.ps1" %* diff --git a/build.ps1 b/build.ps1 new file mode 100755 index 0000000..5fd597f --- /dev/null +++ b/build.ps1 @@ -0,0 +1,11 @@ +#!/usr/bin/env pwsh +# Fallout build bootstrapper. Runs the build project directly (no global tool needed). +# ./build.ps1 Test # run the spec suite +# ./build.ps1 Pack # test + pack the NuGet package +# ./build.ps1 # default target (Pack) +[CmdletBinding()] +Param([Parameter(ValueFromRemainingArguments = $true)] [string[]] $BuildArguments) + +$ErrorActionPreference = 'Stop' +dotnet run --project "$PSScriptRoot/build/_build.csproj" -- @BuildArguments +exit $LASTEXITCODE diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..2c9a9b9 --- /dev/null +++ b/build.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +set -eo pipefail +dotnet run --project "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/build/_build.csproj" -- "$@" diff --git a/build/Build.cs b/build/Build.cs new file mode 100644 index 0000000..dbde913 --- /dev/null +++ b/build/Build.cs @@ -0,0 +1,52 @@ +using Fallout.Common; +using Fallout.Common.CI.GitHubActions; +using Fallout.Common.IO; +using Fallout.Common.Tools.DotNet; +using static Fallout.Common.Tools.DotNet.DotNetTasks; + +/// +/// Fallout build for Scrutor.Extensions.HttpClient. +/// +/// CI (build.yml, auto-generated from the [GitHubActions] attribute) runs Test + Pack +/// on pushes/PRs to main. Publishing is a separate, hand-written workflow (publish.yml, +/// a documented exception) driven by version tags — see CLAUDE.md. +/// +// Workflows are GENERATED from this attribute (see CLAUDE.md: never hand-edit +// .github/workflows/*.yml). Regenerate with `./build.cmd` or: +// dotnet fallout --generate-configuration GitHubActions_build --host GitHubActions +[GitHubActions( + "build", + GitHubActionsImage.UbuntuLatest, + FetchDepth = 0, + OnPushBranches = new[] { "main" }, + OnPullRequestBranches = new[] { "main" }, + InvokedTargets = new[] { nameof(Test), nameof(Pack) })] +partial class Build : FalloutBuild +{ + public static int Main() => Execute(x => x.Pack); + + const string PackageProject = "Scrutor.Extensions.HttpClient"; + + AbsolutePath ArtifactsDirectory => RootDirectory / "artifacts"; + AbsolutePath PackagesDirectory => ArtifactsDirectory / "packages"; + AbsolutePath SpecsProject => RootDirectory / "tests" / "Scrutor.Extensions.HttpClient.Specs" / "Scrutor.Extensions.HttpClient.Specs.csproj"; + AbsolutePath PackableProject => RootDirectory / PackageProject / $"{PackageProject}.csproj"; + + Target Test => _ => _ + .Description("Run the *.Specs test suite") + .Executes(() => DotNetTest(_ => _ + .SetProjectFile(SpecsProject) + .SetConfiguration("Release"))); + + Target Pack => _ => _ + .Description("Pack the NuGet package into artifacts/packages") + .DependsOn(Test) + .Executes(() => + { + PackagesDirectory.CreateOrCleanDirectory(); + DotNetPack(_ => _ + .SetProject(PackableProject) + .SetConfiguration("Release") + .SetOutputDirectory(PackagesDirectory)); + }); +} diff --git a/build/_build.csproj b/build/_build.csproj new file mode 100644 index 0000000..2bd51d3 --- /dev/null +++ b/build/_build.csproj @@ -0,0 +1,20 @@ + + + + Exe + net10.0 + enable + enable + false + false + _build + $(MSBuildProjectDirectory)/.. + CS0649;CS0169;CA1050;CS8618 + + + + + + + + diff --git a/global.json b/global.json new file mode 100644 index 0000000..1e7fdfa --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestMinor" + } +} diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..d5f6b41 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,30 @@ + + + + + + net10.0 + true + false + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + diff --git a/tests/Scrutor.Extensions.HttpClient.Specs/AsHttpClientSpecs.cs b/tests/Scrutor.Extensions.HttpClient.Specs/AsHttpClientSpecs.cs new file mode 100644 index 0000000..c95edb8 --- /dev/null +++ b/tests/Scrutor.Extensions.HttpClient.Specs/AsHttpClientSpecs.cs @@ -0,0 +1,112 @@ +using FluentAssertions; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Scrutor.Extensions.HttpClient.Specs; + +/// +/// End-to-end specs for the .AsHttpClient() / .AsHttpClient(name) Scrutor +/// registration selectors: they must register every scanned class as a typed +/// HttpClient (via ), not a plain service — bridging +/// Scrutor's assembly scanning with Microsoft.Extensions.Http. +/// +public class AsHttpClientSpecs +{ + private const string ClientName = "test-client"; + + private static IServiceCollection ScanClientsAsNamed(IServiceCollection services) => + services.Scan(scan => scan + .FromAssemblyOf() + .AddClasses(classes => classes.AssignableTo()) + .AsMatchingInterface() + .AsHttpClient(ClientName)); + + [Fact] + public void AsHttpClient_named_registers_every_scanned_client_against_its_matching_interface() + { + var services = new ServiceCollection(); + services.AddHttpClient(ClientName, c => c.BaseAddress = new Uri("https://api.example.com/")); + + ScanClientsAsNamed(services); + var provider = services.BuildServiceProvider(); + + provider.GetService().Should().BeOfType(); + provider.GetService().Should().BeOfType(); + } + + [Fact] + public void AsHttpClient_named_wires_up_the_http_client_factory() + { + var services = new ServiceCollection(); + services.AddHttpClient(ClientName, c => c.BaseAddress = new Uri("https://api.example.com/")); + + ScanClientsAsNamed(services); + var provider = services.BuildServiceProvider(); + + provider.GetService().Should().NotBeNull( + "registering a typed client must pull in IHttpClientFactory"); + } + + [Fact] + public void Named_client_configuration_flows_into_the_injected_http_client() + { + var baseAddress = new Uri("https://api.example.com/"); + var services = new ServiceCollection(); + services.AddHttpClient(ClientName, c => c.BaseAddress = baseAddress); + + ScanClientsAsNamed(services); + var provider = services.BuildServiceProvider(); + + provider.GetRequiredService().Http.BaseAddress.Should().Be(baseAddress); + provider.GetRequiredService().Http.BaseAddress.Should().Be(baseAddress); + } + + [Fact] + public void Typed_clients_are_registered_with_transient_lifetime() + { + var services = new ServiceCollection(); + services.AddHttpClient(ClientName, c => c.BaseAddress = new Uri("https://api.example.com/")); + + ScanClientsAsNamed(services); + var provider = services.BuildServiceProvider(); + + provider.GetRequiredService() + .Should().NotBeSameAs(provider.GetRequiredService(), + "typed HttpClients are registered transient"); + } + + [Fact] + public async Task Injected_client_flows_through_the_named_clients_handler_pipeline() + { + var services = new ServiceCollection(); + services + .AddHttpClient(ClientName, c => c.BaseAddress = new Uri("http://stub/")) + .AddHttpMessageHandler(() => new StubHandler("pong")); + + ScanClientsAsNamed(services); + var provider = services.BuildServiceProvider(); + + var weather = provider.GetRequiredService(); + + (await weather.PingAsync()).Should().Be("pong", + "the scanned client must be a genuine factory client whose configured handlers run"); + } + + [Fact] + public void AsHttpClient_without_a_name_registers_scanned_clients_as_typed_clients() + { + var services = new ServiceCollection(); + services.Scan(scan => scan + .FromAssemblyOf() + .AddClasses(classes => classes.AssignableTo()) + .AsMatchingInterface() + .AsHttpClient()); + + var provider = services.BuildServiceProvider(); + + provider.GetService().Should().BeOfType(); + provider.GetService().Should().BeOfType(); + provider.GetService().Should().NotBeNull(); + provider.GetRequiredService().Http.Should().NotBeNull(); + } +} diff --git a/tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.Public_api_surface_has_not_changed.verified.txt b/tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.Public_api_surface_has_not_changed.verified.txt new file mode 100644 index 0000000..60496c9 --- /dev/null +++ b/tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.Public_api_surface_has_not_changed.verified.txt @@ -0,0 +1,11 @@ +[assembly: System.Reflection.AssemblyMetadata("RepositoryUrl", "https://github.com/Chrison-dev/Scrutor.Extensions.HttpClient.git")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Scrutor.Extensions.HttpClient.Specs")] +[assembly: System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v8.0", FrameworkDisplayName=".NET 8.0")] +namespace Microsoft.Extensions.DependencyInjection +{ + public static class HttpClientExtensions + { + public static Scrutor.IServiceTypeSelector AsHttpClient(this Scrutor.IServiceTypeSelector selector) { } + public static Scrutor.IServiceTypeSelector AsHttpClient(this Scrutor.IServiceTypeSelector selector, string name = "") { } + } +} \ No newline at end of file diff --git a/tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.cs b/tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.cs new file mode 100644 index 0000000..2ee714e --- /dev/null +++ b/tests/Scrutor.Extensions.HttpClient.Specs/PublicApiSpecs.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using PublicApiGenerator; +using VerifyXunit; +using Xunit; + +namespace Scrutor.Extensions.HttpClient.Specs; + +/// +/// Roslyn-based public-API baseline (PublicApiGenerator + Verify), mirroring the +/// approach used in Fallout/TVDB. Any unintended change to the shipped public +/// surface fails this spec; intended changes are accepted by updating the +/// *.verified.txt snapshot. +/// +public class PublicApiSpecs +{ + [Fact] + public Task Public_api_surface_has_not_changed() + { + // The public surface lives in the Microsoft.Extensions.DependencyInjection + // namespace (by design — it extends Scrutor's selector). PublicApiGenerator + // denies "Microsoft"/"System" prefixes by default, so allow-list ours back in. + var publicApi = typeof(HttpClientExtensions).Assembly.GeneratePublicApi(new ApiGeneratorOptions + { + AllowNamespacePrefixes = new[] { "Microsoft.Extensions.DependencyInjection" }, + }); + return Verifier.Verify(publicApi); + } +} diff --git a/tests/Scrutor.Extensions.HttpClient.Specs/Scrutor.Extensions.HttpClient.Specs.csproj b/tests/Scrutor.Extensions.HttpClient.Specs/Scrutor.Extensions.HttpClient.Specs.csproj new file mode 100644 index 0000000..b996cdb --- /dev/null +++ b/tests/Scrutor.Extensions.HttpClient.Specs/Scrutor.Extensions.HttpClient.Specs.csproj @@ -0,0 +1,13 @@ + + + + enable + enable + CS1591 + + + + + + + diff --git a/tests/Scrutor.Extensions.HttpClient.Specs/TestClients.cs b/tests/Scrutor.Extensions.HttpClient.Specs/TestClients.cs new file mode 100644 index 0000000..8b971fd --- /dev/null +++ b/tests/Scrutor.Extensions.HttpClient.Specs/TestClients.cs @@ -0,0 +1,54 @@ +using System.Net; +using System.Text; + +namespace Scrutor.Extensions.HttpClient.Specs; + +/// +/// Fixtures for the registration specs: a common marker interface plus two API +/// clients, each with its own matching interface (so AsMatchingInterface() +/// maps WeatherClientIWeatherClient, etc.) and an injected +/// . +/// +public interface ITestApiClient; + +public interface IWeatherClient : ITestApiClient +{ + System.Net.Http.HttpClient Http { get; } + + Task PingAsync(CancellationToken ct = default); +} + +public sealed class WeatherClient(System.Net.Http.HttpClient http) : IWeatherClient +{ + public System.Net.Http.HttpClient Http => http; + + public async Task PingAsync(CancellationToken ct = default) + { + var response = await http.GetAsync("ping", ct); + return await response.Content.ReadAsStringAsync(ct); + } +} + +public interface IStockClient : ITestApiClient +{ + System.Net.Http.HttpClient Http { get; } +} + +public sealed class StockClient(System.Net.Http.HttpClient http) : IStockClient +{ + public System.Net.Http.HttpClient Http => http; +} + +/// +/// Terminating that returns a canned body without an +/// inner handler — used to prove the injected client flows through the named client's +/// configured message-handler pipeline (i.e. it really is an IHttpClientFactory client). +/// +public sealed class StubHandler(string body) : DelegatingHandler +{ + protected override Task SendAsync(HttpRequestMessage request, CancellationToken ct) + => Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(body, Encoding.UTF8, "text/plain"), + }); +} From b882c72377dc822a60c98ebe52d8455c69fd192b Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Fri, 24 Jul 2026 14:34:55 +1200 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20bump=20to=20Scrutor=207.x=20?= =?UTF-8?q?=E2=80=94=20package=20major=20aligns=20(7.x=20=E2=86=94=20Scrut?= =?UTF-8?q?or=207.x)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `.AsHttpClient()` bridge sits entirely on Scrutor's stable RegistrationStrategy seam, so the 5→7 bump is clean: dependency change only, zero code changes, all 7 specs (incl. the PublicApiGenerator baseline) pass unchanged against Scrutor 7.0.0. - Scrutor `[5.1.2,6.0.0)` → `[7.0.0,8.0.0)`; Microsoft.Extensions.Http upper bound → `<11.0.0` (Scrutor 7 pulls DI abstractions 10.x). - Version convention now 7.x per GitVersion.yml (MAJOR = compatible Scrutor major; 6.x was skipped/never adopted). Fallback version + docs updated. Deferred modernization (discuss first): multi-target TFMs; source-generator rewrite to drop the MakeGenericMethod reflection (the only non-trim/AOT-friendly bit). Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 24 ++++++++++++------- Directory.Build.props | 15 ++++++------ GitVersion.yml | 8 +++---- .../Scrutor.Extensions.HttpClient.csproj | 8 +++---- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 30fef89..159c049 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -64,9 +64,13 @@ One-time nuget.org setup: a Trusted Publisher policy for `Scrutor.Extensions.Htt ## Versioning — GitVersion, tag-driven `GitVersion.yml` drives the version from `v*` tags. **Convention: MAJOR tracks the compatible -Scrutor major** (v5.x → Scrutor 5.x; a move to Scrutor 7.x makes this package 7.x). MINOR.PATCH -is our own internal release counter. Cut a release by tagging, e.g. `git tag v5.1.0 && git push -origin v5.1.0`. Untagged builds fall back to the static version in `Directory.Build.props`. +Scrutor major** — currently **7.x ↔ Scrutor 7.x** (the old 5.x line was Scrutor 5.x; 6.x was +skipped/never adopted). MINOR.PATCH is our own internal release counter. Cut a release by +tagging, e.g. `git tag v7.0.0 && git push origin v7.0.0`. Untagged builds fall back to the +static version in `Directory.Build.props`. + +> **Note:** GitVersion only honours a version tag on `main` (feature branches get branch-name +> prerelease labels). Cut releases/prereleases by tagging `main` after the PR merges. ## Tests — spec-style, no Central Package Management @@ -84,8 +88,12 @@ Tests live in `tests/*.Specs` and follow the Fallout/TVDB convention: ## Library conventions -- Targets **net8.0** (Scrutor 5.x era). TFM/Scrutor-major bumps are **modernization**, tracked - separately — not folded into routine changes. -- Scrutor pinned `[5.1.2, 6.0.0)` and `Microsoft.Extensions.Http` `[9.0.0, 10.0.0)` on purpose - (6.x Scrutor has a breaking change). Root `Directory.Build.props` centralizes TFM + GitVersion; - the library csproj only carries package metadata. +- Targets **net8.0** (Scrutor 7.x supports net8.0+). Multi-targeting is a separate modernization + concern, tracked apart from routine changes. +- Scrutor pinned `[7.0.0, 8.0.0)` (aligned with the package major) and `Microsoft.Extensions.Http` + `[9.0.0, 11.0.0)`. The 5→7 bump was clean — the `.AsHttpClient()` bridge sits on Scrutor's + stable `RegistrationStrategy` seam, so no code/test changes were needed. Root + `Directory.Build.props` centralizes TFM + GitVersion; the library csproj only carries metadata. +- Still-open modernization (deliberately deferred, discuss before doing): multi-target TFMs, and a + source-generator rewrite to replace the `MakeGenericMethod` reflection (the one axis that isn't + trim/Native-AOT friendly). diff --git a/Directory.Build.props b/Directory.Build.props index 3afb018..79b23b8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - + net8.0 enable enable @@ -15,11 +15,12 @@ true - 5.1.0 - 5.1.0.0 - 5.1.0.0 - 5.1.0 + MAJOR tracks the compatible Scrutor major (7.x ↔ Scrutor 7.x); MINOR.PATCH + is our internal counter. --> + 7.0.0 + 7.0.0.0 + 7.0.0.0 + 7.0.0 $(GitVersion_FullSemVer) $(GitVersion_AssemblySemVer) diff --git a/GitVersion.yml b/GitVersion.yml index 8dee64b..f87589e 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,10 +1,10 @@ mode: ContinuousDelivery # Versioning scheme: the MAJOR tracks the major version of Scrutor this package is -# compatible with (v5.x → Scrutor 5.x; when we move to Scrutor 7.x the package -# becomes 7.x). MINOR.PATCH is our own internal release counter. Releases are cut -# by tagging `v..` (e.g. `git tag v5.1.0`) — GitVersion reads -# the tag (tag-prefix 'v'). Untagged builds get a prerelease suffix. +# compatible with (currently v7.x → Scrutor 7.x; the old 5.x line was Scrutor 5.x — +# 6.x was skipped, never adopted). MINOR.PATCH is our own internal release counter. +# Releases are cut by tagging `v..` (e.g. `git tag v7.0.0`) — +# GitVersion reads the tag (tag-prefix 'v'). Untagged builds get a prerelease suffix. branches: main: diff --git a/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj b/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj index c878246..25aad83 100644 --- a/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj +++ b/Scrutor.Extensions.HttpClient/Scrutor.Extensions.HttpClient.csproj @@ -27,12 +27,10 @@ - - + - - + +