diff --git a/.github/BuildHelpers/BuildHelpers.csproj b/.github/BuildHelpers/BuildHelpers.csproj
new file mode 100644
index 0000000..eab728f
--- /dev/null
+++ b/.github/BuildHelpers/BuildHelpers.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net10.0
+ enable
+ enable
+
+
+
+
+
+
+
+
diff --git a/.github/BuildHelpers/Targets.cs b/.github/BuildHelpers/Targets.cs
new file mode 100644
index 0000000..d0afdad
--- /dev/null
+++ b/.github/BuildHelpers/Targets.cs
@@ -0,0 +1,92 @@
+using static Bullseye.Targets;
+using static SimpleExec.Command;
+
+namespace BuildHelpers;
+
+///
+/// Registers build targets for per-lib build scripts.
+///
+public static class Targets
+{
+ private static readonly Lazy RepoRoot = new(FindRoot);
+
+ public const string Restore = "restore";
+ public const string Build = "build";
+ public const string Clean = "clean";
+ public const string Pack = "pack";
+ public const string Test = "test";
+
+ private const string Default = "default";
+
+ ///
+ /// Registers all shared targets parameterized by the lib's solution filter path.
+ ///
+ ///
+ /// Repo-relative path to the lib's solution filter
+ /// (e.g. signatures/signatures.slnf).
+ ///
+ ///
+ /// Repo-relative path to the lib's packable src project
+ /// (e.g. signatures/src/Http.HttpSignatures).
+ ///
+ public static void SharedTargets(string slnfPath, string packProjectPath)
+ {
+ ArgumentNullException.ThrowIfNull(slnfPath);
+ ArgumentNullException.ThrowIfNull(packProjectPath);
+
+ var libDir = slnfPath.Split('/')[0];
+
+ Target(Restore, () =>
+ RunAsync("dotnet", $"restore {slnfPath}", RepoRoot.Value));
+
+ Target(Build, dependsOn: [Restore], () =>
+ RunAsync("dotnet", $"build {slnfPath} --no-restore -c Release", RepoRoot.Value));
+
+ Target(Clean, () =>
+ RunAsync("dotnet", $"clean {slnfPath}", RepoRoot.Value));
+
+ Target(Pack, dependsOn: [Build], () =>
+ RunAsync(
+ "dotnet",
+ $"pack {packProjectPath} --no-build -c Release -o {libDir}/artifacts",
+ RepoRoot.Value));
+ }
+
+ ///
+ /// Registers a test target that runs dotnet test on a test project with standard options.
+ ///
+ /// The target name (e.g. "test").
+ ///
+ /// Repo-relative path to the test project
+ /// (e.g. "signatures/test/Http.HttpSignatures.Tests").
+ ///
+ public static void TestTarget(string targetName, string testProjectPath) =>
+ Target(targetName, dependsOn: [Restore], () =>
+ RunAsync(
+ "dotnet",
+ $"test --project {testProjectPath} -c Release --no-restore --report-xunit-trx " +
+ $"--report-xunit-trx-filename {testProjectPath.Replace('/', '-')}-tests.trx",
+ RepoRoot.Value));
+
+ public static void DefaultTarget(IEnumerable dependsOn) =>
+ Target(Default, dependsOn);
+
+ public static Task RunTargetsAndExitAsync(IEnumerable args) =>
+ Bullseye.Targets.RunTargetsAndExitAsync(args, messageOnly: ex => ex is SimpleExec.ExitCodeException);
+
+ private static string FindRoot()
+ {
+ var root = Directory.GetCurrentDirectory();
+
+ // Repositories have a .git folder, worktrees have a .git file, so check for both.
+ while (!Directory.Exists(Path.Combine(root, ".git")) && !File.Exists(Path.Combine(root, ".git")))
+ {
+ root = Directory.GetParent(root) is { } parent
+ ? parent.FullName
+ : throw new InvalidOperationException(
+ "Could not find repository root (no .git directory or file found)");
+ }
+
+ return root;
+ }
+}
diff --git a/.github/workflows/ci-template.yml b/.github/workflows/ci-template.yml
new file mode 100644
index 0000000..2154284
--- /dev/null
+++ b/.github/workflows/ci-template.yml
@@ -0,0 +1,71 @@
+name: ci-template
+
+on:
+ workflow_call:
+ inputs:
+ product:
+ description: "Lib folder name (e.g. signatures, structured-field-values)"
+ required: true
+ type: string
+ test-targets:
+ description: 'JSON array of build.cs test targets (e.g. ["test"]). Empty array or "[]" to skip.'
+ required: false
+ type: string
+ default: '["test"]'
+ os-matrix:
+ description: 'JSON array of OS runners for the test job. Defaults to ubuntu only.'
+ required: false
+ type: string
+ default: '["ubuntu-latest"]'
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
+jobs:
+ build:
+ name: Build
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+ - name: Build
+ run: dotnet run ${{ inputs.product }}/build.cs -- build
+
+ test:
+ name: "Test: ${{ matrix.target }} (${{ matrix.os }})"
+ if: inputs.test-targets != '[]'
+ runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
+ strategy:
+ matrix:
+ target: ${{ fromJson(inputs.test-targets) }}
+ os: ${{ fromJson(inputs.os-matrix) }}
+ fail-fast: false
+ timeout-minutes: 15
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+ - name: Test
+ run: dotnet run ${{ inputs.product }}/build.cs -- ${{ matrix.target }}
+ - name: Test report
+ if: success() || failure()
+ uses: phoenix-actions/test-reporting@f957cd93fc2d848d556fa0d03c57bc79127b6b5e
+ with:
+ name: "${{ inputs.product }}: ${{ matrix.target }} (${{ matrix.os }})"
+ path: "**/*-tests.trx"
+ reporter: dotnet-trx
+ output-to: step-summary
+ fail-on-error: true
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
deleted file mode 100644
index 6c1494e..0000000
--- a/.github/workflows/ci.yml
+++ /dev/null
@@ -1,108 +0,0 @@
-name: ci
-
-on:
- push:
- branches:
- - main
- - '**'
- pull_request:
- workflow_dispatch:
-
-jobs:
- build-and-test:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: '10.0.x'
-
- - name: Restore dependencies
- run: dotnet restore http-lib.slnx
-
- - name: Build
- run: dotnet build http-lib.slnx --no-restore --configuration Release
-
- - name: Run tests
- run: |
- dotnet run --project structured-field-values/test/Http.StructuredFieldValues.Tests/Http.StructuredFieldValues.Tests.csproj --no-build --configuration Release
- dotnet run --project signatures/test/Http.HttpSignatures.Tests/Http.HttpSignatures.Tests.csproj --no-build --configuration Release
- dotnet run --project hybrid-cache-handler/test/HttpHybridCacheHandler.Tests/HttpHybridCacheHandler.Tests.csproj --no-build --configuration Release
- dotnet run --project file-distributed-cache/test/FileDistributedCache.Tests/FileDistributedCache.Tests.csproj --no-build --configuration Release
-
- - name: Run benchmarks
- run: |
- cd hybrid-cache-handler/benchmarks/Benchmarks
- dotnet run --project Benchmarks.csproj --configuration Release --no-build -- --exporters json
- continue-on-error: true
-
- - name: Add benchmark results to summary
- if: always()
- run: |
- echo "## Benchmark Results" >> $GITHUB_STEP_SUMMARY
- if [ -f "hybrid-cache-handler/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks-report.json" ]; then
- echo "Benchmark completed successfully. Full results available in artifacts." >> $GITHUB_STEP_SUMMARY
- else
- echo "Benchmark execution completed with warnings or errors." >> $GITHUB_STEP_SUMMARY
- fi
-
- - name: Upload benchmark results
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: benchmark-results
- path: hybrid-cache-handler/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/
- if-no-files-found: ignore
-
- - name: Build NuGet packages
- run: |
- dotnet pack structured-field-values/src/Http.StructuredFieldValues/Http.StructuredFieldValues.csproj --no-build --configuration Release --output ./packages
- dotnet pack signatures/src/Http.HttpSignatures/Http.HttpSignatures.csproj --no-build --configuration Release --output ./packages
- dotnet pack hybrid-cache-handler/src/HttpHybridCacheHandler/HttpHybridCacheHandler.csproj --no-build --configuration Release --output ./packages
- dotnet pack file-distributed-cache/src/FileDistributedCache/FileDistributedCache.csproj --no-build --configuration Release --output ./packages
-
- - name: Upload NuGet packages
- uses: actions/upload-artifact@v4
- with:
- name: nuget-packages
- path: ./packages/*.nupkg
-
- - name: Add build summary
- if: always()
- run: |
- echo "## Build Summary" >> $GITHUB_STEP_SUMMARY
- echo "✅ Build completed" >> $GITHUB_STEP_SUMMARY
- echo "✅ Tests executed" >> $GITHUB_STEP_SUMMARY
- echo "✅ NuGet packages created" >> $GITHUB_STEP_SUMMARY
-
- cache-conformance:
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: '10.0.x'
-
- - name: Setup Node.js
- uses: actions/setup-node@v4
- with:
- node-version: '22'
-
- - name: Run RFC 9111 conformance suite
- run: ./hybrid-cache-handler/conformance/run-conformance.sh
-
- - name: Upload conformance results
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: cache-conformance-results
- path: hybrid-cache-handler/conformance/results.json
- if-no-files-found: ignore
diff --git a/.github/workflows/file-distributed-cache-ci.yml b/.github/workflows/file-distributed-cache-ci.yml
new file mode 100644
index 0000000..fc2a059
--- /dev/null
+++ b/.github/workflows/file-distributed-cache-ci.yml
@@ -0,0 +1,34 @@
+name: file-distributed-cache/ci
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ - '**'
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/file-distributed-cache-ci.yml
+ - .github/workflows/ci-template.yml
+ - file-distributed-cache/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+ pull_request:
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/file-distributed-cache-ci.yml
+ - .github/workflows/ci-template.yml
+ - file-distributed-cache/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+
+jobs:
+ build:
+ uses: ./.github/workflows/ci-template.yml
+ with:
+ product: file-distributed-cache
+ test-targets: '["test"]'
diff --git a/.github/workflows/file-distributed-cache-release.yml b/.github/workflows/file-distributed-cache-release.yml
new file mode 100644
index 0000000..2f3088d
--- /dev/null
+++ b/.github/workflows/file-distributed-cache-release.yml
@@ -0,0 +1,20 @@
+name: file-distributed-cache/release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version number (x.y.z or x.y.z-prerelease.1)'
+ required: true
+ type: string
+
+jobs:
+ release:
+ uses: ./.github/workflows/release-template.yml
+ with:
+ product: file-distributed-cache
+ package: DamianH.FileDistributedCache
+ tag-prefix: fdc-v
+ version: ${{ inputs.version }}
+ secrets:
+ NUGET_ORG_APIKEY: ${{ secrets.NUGET_ORG_APIKEY }}
diff --git a/.github/workflows/hybrid-cache-handler-checks.yml b/.github/workflows/hybrid-cache-handler-checks.yml
new file mode 100644
index 0000000..f9bf7dc
--- /dev/null
+++ b/.github/workflows/hybrid-cache-handler-checks.yml
@@ -0,0 +1,94 @@
+name: hybrid-cache-handler/checks
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ - '**'
+ paths:
+ - .github/workflows/hybrid-cache-handler-checks.yml
+ - hybrid-cache-handler/**
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+ pull_request:
+ paths:
+ - .github/workflows/hybrid-cache-handler-checks.yml
+ - hybrid-cache-handler/**
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
+jobs:
+ benchmarks:
+ name: Benchmarks
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+
+ - name: Run benchmarks
+ run: |
+ cd hybrid-cache-handler/benchmarks/Benchmarks
+ dotnet run --project Benchmarks.csproj --configuration Release -- --exporters json
+ continue-on-error: true
+
+ - name: Add benchmark results to summary
+ if: always()
+ run: |
+ echo "## Benchmark Results" >> $GITHUB_STEP_SUMMARY
+ if [ -f "hybrid-cache-handler/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks-report.json" ]; then
+ echo "Benchmark completed successfully. Full results available in artifacts." >> $GITHUB_STEP_SUMMARY
+ else
+ echo "Benchmark execution completed with warnings or errors." >> $GITHUB_STEP_SUMMARY
+ fi
+
+ - name: Upload benchmark results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: benchmark-results
+ path: hybrid-cache-handler/benchmarks/Benchmarks/BenchmarkDotNet.Artifacts/
+ if-no-files-found: ignore
+
+ cache-conformance:
+ name: RFC 9111 conformance
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+
+ - name: Run RFC 9111 conformance suite
+ run: ./hybrid-cache-handler/conformance/run-conformance.sh
+
+ - name: Upload conformance results
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: cache-conformance-results
+ path: hybrid-cache-handler/conformance/results.json
+ if-no-files-found: ignore
diff --git a/.github/workflows/hybrid-cache-handler-ci.yml b/.github/workflows/hybrid-cache-handler-ci.yml
new file mode 100644
index 0000000..40aecaa
--- /dev/null
+++ b/.github/workflows/hybrid-cache-handler-ci.yml
@@ -0,0 +1,34 @@
+name: hybrid-cache-handler/ci
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ - '**'
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/hybrid-cache-handler-ci.yml
+ - .github/workflows/ci-template.yml
+ - hybrid-cache-handler/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+ pull_request:
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/hybrid-cache-handler-ci.yml
+ - .github/workflows/ci-template.yml
+ - hybrid-cache-handler/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+
+jobs:
+ build:
+ uses: ./.github/workflows/ci-template.yml
+ with:
+ product: hybrid-cache-handler
+ test-targets: '["test"]'
diff --git a/.github/workflows/hybrid-cache-handler-release.yml b/.github/workflows/hybrid-cache-handler-release.yml
new file mode 100644
index 0000000..d7d73e4
--- /dev/null
+++ b/.github/workflows/hybrid-cache-handler-release.yml
@@ -0,0 +1,20 @@
+name: hybrid-cache-handler/release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version number (x.y.z or x.y.z-prerelease.1)'
+ required: true
+ type: string
+
+jobs:
+ release:
+ uses: ./.github/workflows/release-template.yml
+ with:
+ product: hybrid-cache-handler
+ package: DamianH.HttpHybridCacheHandler
+ tag-prefix: cache-v
+ version: ${{ inputs.version }}
+ secrets:
+ NUGET_ORG_APIKEY: ${{ secrets.NUGET_ORG_APIKEY }}
diff --git a/.github/workflows/release-template.yml b/.github/workflows/release-template.yml
new file mode 100644
index 0000000..dbe48ec
--- /dev/null
+++ b/.github/workflows/release-template.yml
@@ -0,0 +1,93 @@
+name: release-template
+
+on:
+ workflow_call:
+ inputs:
+ product:
+ description: "Lib folder name (e.g. signatures)"
+ required: true
+ type: string
+ package:
+ description: "NuGet package ID (e.g. DamianH.Http.HttpSignatures)"
+ required: true
+ type: string
+ tag-prefix:
+ description: "Git tag prefix (e.g. sig-v)"
+ required: true
+ type: string
+ version:
+ description: "Version number (x.y.z or x.y.z-prerelease.1)"
+ required: true
+ type: string
+ secrets:
+ NUGET_ORG_APIKEY:
+ required: true
+
+env:
+ DOTNET_NOLOGO: true
+ DOTNET_CLI_TELEMETRY_OPTOUT: true
+
+jobs:
+ release:
+ name: Release
+ runs-on: ubuntu-latest
+ environment: nuget.org
+ permissions:
+ contents: write
+ timeout-minutes: 30
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '10.0.x'
+
+ - name: Validate version format
+ run: |
+ if ! echo "${{ inputs.version }}" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?$'; then
+ echo "Invalid version format. Expected: x.y.z or x.y.z-prerelease.1"
+ exit 1
+ fi
+
+ - name: Remove existing tag if present
+ run: |
+ TAG="${{ inputs.tag-prefix }}${{ inputs.version }}"
+ if git rev-parse "$TAG" >/dev/null 2>&1; then
+ echo "Tag $TAG exists, removing it"
+ git tag -d "$TAG"
+ git push origin ":refs/tags/$TAG" || true
+ fi
+
+ - name: Create and push tag
+ run: |
+ TAG="${{ inputs.tag-prefix }}${{ inputs.version }}"
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git tag -a "$TAG" -m "Release ${{ inputs.package }} ${{ inputs.version }}"
+ git push origin "$TAG"
+
+ - name: Pack NuGet package
+ # Runs after tagging so MinVer derives the package version from the new tag.
+ run: dotnet run ${{ inputs.product }}/build.cs -- pack
+
+ - name: Push to NuGet.org
+ run: |
+ dotnet nuget push "${{ inputs.product }}/artifacts/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_ORG_APIKEY }} --skip-duplicate
+
+ - name: Upload packages as artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-packages-${{ inputs.package }}-${{ inputs.version }}
+ path: ${{ inputs.product }}/artifacts/*.nupkg
+
+ - name: Create GitHub Release
+ uses: softprops/action-gh-release@v2
+ with:
+ tag_name: ${{ inputs.tag-prefix }}${{ inputs.version }}
+ name: ${{ inputs.package }} ${{ inputs.version }}
+ draft: false
+ prerelease: ${{ contains(inputs.version, '-') }}
+ generate_release_notes: true
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index a02e0b8..0000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,110 +0,0 @@
-name: release
-
-on:
- workflow_dispatch:
- inputs:
- package:
- description: 'Package to release'
- required: true
- type: choice
- options:
- - DamianH.Http.StructuredFieldValues
- - DamianH.Http.HttpSignatures
- - DamianH.HttpHybridCacheHandler
- - DamianH.FileDistributedCache
- version:
- description: 'Version number (x.y.z or x.y.z-prerelease.1)'
- required: true
- type: string
-
-jobs:
- release:
- runs-on: ubuntu-latest
- environment: nuget.org
- permissions:
- contents: write
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Setup .NET
- uses: actions/setup-dotnet@v4
- with:
- dotnet-version: '10.0.x'
-
- - name: Determine tag prefix and project path
- id: package-info
- run: |
- case "${{ inputs.package }}" in
- "DamianH.Http.StructuredFieldValues")
- echo "tag_prefix=sfv-v" >> $GITHUB_OUTPUT
- echo "project_path=structured-field-values/src/Http.StructuredFieldValues/Http.StructuredFieldValues.csproj" >> $GITHUB_OUTPUT
- ;;
- "DamianH.Http.HttpSignatures")
- echo "tag_prefix=sig-v" >> $GITHUB_OUTPUT
- echo "project_path=signatures/src/Http.HttpSignatures/Http.HttpSignatures.csproj" >> $GITHUB_OUTPUT
- ;;
- "DamianH.HttpHybridCacheHandler")
- echo "tag_prefix=cache-v" >> $GITHUB_OUTPUT
- echo "project_path=hybrid-cache-handler/src/HttpHybridCacheHandler/HttpHybridCacheHandler.csproj" >> $GITHUB_OUTPUT
- ;;
- "DamianH.FileDistributedCache")
- echo "tag_prefix=fdc-v" >> $GITHUB_OUTPUT
- echo "project_path=file-distributed-cache/src/FileDistributedCache/FileDistributedCache.csproj" >> $GITHUB_OUTPUT
- ;;
- esac
-
- - name: Validate version format
- run: |
- if ! echo "${{ inputs.version }}" | grep -E '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?$'; then
- echo "Invalid version format. Expected: x.y.z or x.y.z-prerelease.1"
- exit 1
- fi
-
- - name: Remove existing tag if present
- run: |
- TAG="${{ steps.package-info.outputs.tag_prefix }}${{ inputs.version }}"
- if git rev-parse "$TAG" >/dev/null 2>&1; then
- echo "Tag $TAG exists, removing it"
- git tag -d "$TAG"
- git push origin ":refs/tags/$TAG" || true
- fi
-
- - name: Create and push tag
- run: |
- TAG="${{ steps.package-info.outputs.tag_prefix }}${{ inputs.version }}"
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
- git tag -a "$TAG" -m "Release ${{ inputs.package }} ${{ inputs.version }}"
- git push origin "$TAG"
-
- - name: Restore dependencies
- run: dotnet restore http-lib.slnx
-
- - name: Build
- run: dotnet build http-lib.slnx --configuration Release --no-restore
-
- - name: Pack NuGet package
- run: |
- dotnet pack ${{ steps.package-info.outputs.project_path }} --configuration Release --no-build --output ./packages
-
- - name: Push to NuGet.org
- run: |
- dotnet nuget push "./packages/*.nupkg" --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_ORG_APIKEY }} --skip-duplicate
-
- - name: Upload packages as artifacts
- uses: actions/upload-artifact@v4
- with:
- name: nuget-packages-${{ inputs.package }}-${{ inputs.version }}
- path: ./packages/*.nupkg
-
- - name: Create GitHub Release
- uses: softprops/action-gh-release@v2
- with:
- tag_name: ${{ steps.package-info.outputs.tag_prefix }}${{ inputs.version }}
- name: ${{ inputs.package }} ${{ inputs.version }}
- draft: false
- prerelease: ${{ contains(inputs.version, '-') }}
- generate_release_notes: true
diff --git a/.github/workflows/signatures-ci.yml b/.github/workflows/signatures-ci.yml
new file mode 100644
index 0000000..d3e8702
--- /dev/null
+++ b/.github/workflows/signatures-ci.yml
@@ -0,0 +1,34 @@
+name: signatures/ci
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ - '**'
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/signatures-ci.yml
+ - .github/workflows/ci-template.yml
+ - signatures/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+ pull_request:
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/signatures-ci.yml
+ - .github/workflows/ci-template.yml
+ - signatures/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+
+jobs:
+ build:
+ uses: ./.github/workflows/ci-template.yml
+ with:
+ product: signatures
+ test-targets: '["test"]'
diff --git a/.github/workflows/signatures-release.yml b/.github/workflows/signatures-release.yml
new file mode 100644
index 0000000..7fe5400
--- /dev/null
+++ b/.github/workflows/signatures-release.yml
@@ -0,0 +1,20 @@
+name: signatures/release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version number (x.y.z or x.y.z-prerelease.1)'
+ required: true
+ type: string
+
+jobs:
+ release:
+ uses: ./.github/workflows/release-template.yml
+ with:
+ product: signatures
+ package: DamianH.Http.HttpSignatures
+ tag-prefix: sig-v
+ version: ${{ inputs.version }}
+ secrets:
+ NUGET_ORG_APIKEY: ${{ secrets.NUGET_ORG_APIKEY }}
diff --git a/.github/workflows/structured-field-values-ci.yml b/.github/workflows/structured-field-values-ci.yml
new file mode 100644
index 0000000..2370bfb
--- /dev/null
+++ b/.github/workflows/structured-field-values-ci.yml
@@ -0,0 +1,34 @@
+name: structured-field-values/ci
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ - '**'
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/structured-field-values-ci.yml
+ - .github/workflows/ci-template.yml
+ - structured-field-values/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+ pull_request:
+ paths:
+ - .github/BuildHelpers/**
+ - .github/workflows/structured-field-values-ci.yml
+ - .github/workflows/ci-template.yml
+ - structured-field-values/**
+ - .editorconfig
+ - Directory.Build.props
+ - global.json
+ - http-lib.slnx
+
+jobs:
+ build:
+ uses: ./.github/workflows/ci-template.yml
+ with:
+ product: structured-field-values
+ test-targets: '["test"]'
diff --git a/.github/workflows/structured-field-values-release.yml b/.github/workflows/structured-field-values-release.yml
new file mode 100644
index 0000000..4fbf4ef
--- /dev/null
+++ b/.github/workflows/structured-field-values-release.yml
@@ -0,0 +1,20 @@
+name: structured-field-values/release
+
+on:
+ workflow_dispatch:
+ inputs:
+ version:
+ description: 'Version number (x.y.z or x.y.z-prerelease.1)'
+ required: true
+ type: string
+
+jobs:
+ release:
+ uses: ./.github/workflows/release-template.yml
+ with:
+ product: structured-field-values
+ package: DamianH.Http.StructuredFieldValues
+ tag-prefix: sfv-v
+ version: ${{ inputs.version }}
+ secrets:
+ NUGET_ORG_APIKEY: ${{ secrets.NUGET_ORG_APIKEY }}
diff --git a/README.md b/README.md
index 4c3ed41..1ca1972 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,9 @@
# http-libs
-[](https://github.com/damianh/http-hybridcache-handler/actions/workflows/ci.yml)
+[](https://github.com/damianh/http-libs/actions)
[](LICENSE)
[](https://dotnet.microsoft.com/)
-[](https://github.com/damianh/http-hybridcache-handler/stargazers)
+[](https://github.com/damianh/http-libs/stargazers)
A collection of .NET libraries for HTTP caching, structured field values, and message signatures.
@@ -30,14 +30,18 @@ signatures/ # RFC 9421 HTTP message signatures
## Building
+Each lib has its own build script and solution filter:
+
```bash
-dotnet build http-lib.slnx
+dotnet run signatures/build.cs -- build # or: structured-field-values, hybrid-cache-handler, file-distributed-cache
+dotnet build http-lib.slnx # everything
```
## Running Tests
```bash
-dotnet test http-lib.slnx
+dotnet run signatures/build.cs -- test # per lib
+dotnet test http-lib.slnx # everything
```
## License
diff --git a/file-distributed-cache/build.cs b/file-distributed-cache/build.cs
new file mode 100644
index 0000000..ddcd22c
--- /dev/null
+++ b/file-distributed-cache/build.cs
@@ -0,0 +1,17 @@
+#:project ../.github/BuildHelpers/BuildHelpers.csproj
+
+using static BuildHelpers.Targets;
+
+SharedTargets(
+ "file-distributed-cache/file-distributed-cache.slnf",
+ "file-distributed-cache/src/FileDistributedCache");
+
+TestTarget(Test, "file-distributed-cache/test/FileDistributedCache.Tests");
+
+DefaultTarget(dependsOn:
+[
+ Build,
+ Test,
+]);
+
+await RunTargetsAndExitAsync(args);
diff --git a/file-distributed-cache/file-distributed-cache.slnf b/file-distributed-cache/file-distributed-cache.slnf
new file mode 100644
index 0000000..a78dbfb
--- /dev/null
+++ b/file-distributed-cache/file-distributed-cache.slnf
@@ -0,0 +1,10 @@
+{
+ "solution": {
+ "path": "..\\http-lib.slnx",
+ "projects": [
+ "file-distributed-cache\\src\\FileDistributedCache\\FileDistributedCache.csproj",
+ "file-distributed-cache\\test\\FileDistributedCache.Tests\\FileDistributedCache.Tests.csproj",
+ "file-distributed-cache\\samples\\FileDistributedCacheSample\\FileDistributedCacheSample.csproj"
+ ]
+ }
+}
diff --git a/hybrid-cache-handler/build.cs b/hybrid-cache-handler/build.cs
new file mode 100644
index 0000000..067771e
--- /dev/null
+++ b/hybrid-cache-handler/build.cs
@@ -0,0 +1,17 @@
+#:project ../.github/BuildHelpers/BuildHelpers.csproj
+
+using static BuildHelpers.Targets;
+
+SharedTargets(
+ "hybrid-cache-handler/hybrid-cache-handler.slnf",
+ "hybrid-cache-handler/src/HttpHybridCacheHandler");
+
+TestTarget(Test, "hybrid-cache-handler/test/HttpHybridCacheHandler.Tests");
+
+DefaultTarget(dependsOn:
+[
+ Build,
+ Test,
+]);
+
+await RunTargetsAndExitAsync(args);
diff --git a/hybrid-cache-handler/hybrid-cache-handler.slnf b/hybrid-cache-handler/hybrid-cache-handler.slnf
new file mode 100644
index 0000000..38c825e
--- /dev/null
+++ b/hybrid-cache-handler/hybrid-cache-handler.slnf
@@ -0,0 +1,14 @@
+{
+ "solution": {
+ "path": "..\\http-lib.slnx",
+ "projects": [
+ "hybrid-cache-handler\\src\\HttpHybridCacheHandler\\HttpHybridCacheHandler.csproj",
+ "hybrid-cache-handler\\test\\HttpHybridCacheHandler.Tests\\HttpHybridCacheHandler.Tests.csproj",
+ "hybrid-cache-handler\\samples\\HttpClientFactorySample\\HttpClientFactorySample.csproj",
+ "hybrid-cache-handler\\samples\\FusionCacheSample\\FusionCacheSample.csproj",
+ "hybrid-cache-handler\\samples\\YarpCachingProxySample\\YarpCachingProxySample.csproj",
+ "hybrid-cache-handler\\benchmarks\\Benchmarks\\Benchmarks.csproj",
+ "hybrid-cache-handler\\conformance\\ConformanceProxy\\ConformanceProxy.csproj"
+ ]
+ }
+}
diff --git a/signatures/build.cs b/signatures/build.cs
new file mode 100644
index 0000000..734c43e
--- /dev/null
+++ b/signatures/build.cs
@@ -0,0 +1,17 @@
+#:project ../.github/BuildHelpers/BuildHelpers.csproj
+
+using static BuildHelpers.Targets;
+
+SharedTargets(
+ "signatures/signatures.slnf",
+ "signatures/src/Http.HttpSignatures");
+
+TestTarget(Test, "signatures/test/Http.HttpSignatures.Tests");
+
+DefaultTarget(dependsOn:
+[
+ Build,
+ Test,
+]);
+
+await RunTargetsAndExitAsync(args);
diff --git a/signatures/signatures.slnf b/signatures/signatures.slnf
new file mode 100644
index 0000000..32d10be
--- /dev/null
+++ b/signatures/signatures.slnf
@@ -0,0 +1,9 @@
+{
+ "solution": {
+ "path": "..\\http-lib.slnx",
+ "projects": [
+ "signatures\\src\\Http.HttpSignatures\\Http.HttpSignatures.csproj",
+ "signatures\\test\\Http.HttpSignatures.Tests\\Http.HttpSignatures.Tests.csproj"
+ ]
+ }
+}
diff --git a/structured-field-values/build.cs b/structured-field-values/build.cs
new file mode 100644
index 0000000..929df2c
--- /dev/null
+++ b/structured-field-values/build.cs
@@ -0,0 +1,17 @@
+#:project ../.github/BuildHelpers/BuildHelpers.csproj
+
+using static BuildHelpers.Targets;
+
+SharedTargets(
+ "structured-field-values/structured-field-values.slnf",
+ "structured-field-values/src/Http.StructuredFieldValues");
+
+TestTarget(Test, "structured-field-values/test/Http.StructuredFieldValues.Tests");
+
+DefaultTarget(dependsOn:
+[
+ Build,
+ Test,
+]);
+
+await RunTargetsAndExitAsync(args);
diff --git a/structured-field-values/structured-field-values.slnf b/structured-field-values/structured-field-values.slnf
new file mode 100644
index 0000000..20ee3cf
--- /dev/null
+++ b/structured-field-values/structured-field-values.slnf
@@ -0,0 +1,11 @@
+{
+ "solution": {
+ "path": "..\\http-lib.slnx",
+ "projects": [
+ "structured-field-values\\src\\Http.StructuredFieldValues\\Http.StructuredFieldValues.csproj",
+ "structured-field-values\\test\\Http.StructuredFieldValues.Tests\\Http.StructuredFieldValues.Tests.csproj",
+ "structured-field-values\\samples\\HttpClientSample\\HttpClientSample.csproj",
+ "structured-field-values\\samples\\AspNetCoreSample\\AspNetCoreSample.csproj"
+ ]
+ }
+}