diff --git a/.github/workflows/pull-request-ci.yml b/.github/workflows/pull-request-ci.yml
index 6eb8aa6..c356a2b 100644
--- a/.github/workflows/pull-request-ci.yml
+++ b/.github/workflows/pull-request-ci.yml
@@ -40,7 +40,7 @@ jobs:
if: >-
vars.SONAR_CI_ENABLED == 'true'
&& github.event.pull_request.head.repo.full_name == github.repository
- && github.actor != 'dependabot[bot]'
+ && github.event.pull_request.user.login != 'dependabot[bot]'
uses: ./.github/workflows/sonar.yml
secrets:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml
index 1b9a4b2..3474a27 100644
--- a/.github/workflows/sonar.yml
+++ b/.github/workflows/sonar.yml
@@ -11,14 +11,10 @@ on:
description: SonarQube Cloud organization key.
type: string
default: cheatenginenet
- wait-quality-gate:
- description: Fail the job when the quality gate fails.
- type: boolean
- default: false
secrets:
SONAR_TOKEN:
- description: SonarQube Cloud token. The analysis is skipped when it is empty.
- required: false
+ description: SonarQube Cloud token for protected, same-repository analysis.
+ required: true
permissions:
contents: read
@@ -38,22 +34,21 @@ jobs:
name: Analyze
if: github.event_name != 'merge_group'
runs-on: windows-latest
- timeout-minutes: 30
+ timeout-minutes: 40
env:
- SONAR_ENABLED: ${{ secrets.SONAR_TOKEN != '' }}
SONAR_PROJECT_KEY: ${{ inputs.project-key }}
SONAR_ORGANIZATION: ${{ inputs.organization }}
- SONAR_WAIT_QUALITY_GATE: ${{ inputs.wait-quality-gate }}
steps:
- - name: Skip without token
- if: env.SONAR_ENABLED != 'true'
- run: >-
- Write-Host '::notice title=Sonar analysis skipped::SONAR_TOKEN is empty. Fork and Dependabot pull requests
- receive no repository secret; configure SONAR_TOKEN to enable analysis.'
+ - name: Require Sonar token
+ env:
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
+ run: |
+ if ([string]::IsNullOrWhiteSpace($env:SONAR_TOKEN)) {
+ throw 'SONAR_TOKEN is required when SONAR_CI_ENABLED is true.'
+ }
- name: Check analysis method
- if: env.SONAR_ENABLED == 'true'
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
@@ -63,8 +58,7 @@ jobs:
-TimeoutSec 30 -MaximumRetryCount 2 -RetryIntervalSec 3
}
catch {
- Write-Host "::warning::Could not read the analysis method of $env:SONAR_PROJECT_KEY. $($_.Exception.Message)"
- return
+ throw "Could not verify the analysis method of $env:SONAR_PROJECT_KEY. $($_.Exception.Message)"
}
if (@($response.settings | Where-Object { $_.key -eq 'sonar.autoscan.enabled' -and $_.value -eq 'true' })) {
@@ -73,21 +67,18 @@ jobs:
}
- name: Checkout
- if: env.SONAR_ENABLED == 'true'
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Set up JDK 21
- if: env.SONAR_ENABLED == 'true'
uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1
with:
distribution: zulu
java-version: '21'
- name: Install pinned .NET SDK
- if: env.SONAR_ENABLED == 'true'
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
global-json-file: global.json
@@ -97,17 +88,14 @@ jobs:
Directory.Packages.props
- name: Install scanner
- if: env.SONAR_ENABLED == 'true'
run: >-
dotnet tool install dotnet-sonarscanner --tool-path "$env:RUNNER_TEMP/sonar-scanner"
--version $env:SONAR_SCANNER_VERSION
- name: Restore locked dependency graph
- if: env.SONAR_ENABLED == 'true'
run: dotnet restore CheatEngine.Client.slnx --locked-mode
- name: Begin analysis
- if: env.SONAR_ENABLED == 'true'
env:
SONARQUBE_SCANNER_PARAMS: '{"sonar.token":"${{ secrets.SONAR_TOKEN }}"}'
run: |
@@ -138,8 +126,10 @@ jobs:
"/k:$env:SONAR_PROJECT_KEY"
"/o:$env:SONAR_ORGANIZATION"
'/d:sonar.exclusions=artifacts/**,tests/CheatEngine.Client.Benchmarks/**'
+ '/d:sonar.cs.cobertura.reportsPaths=artifacts/sonar-test-results/*/coverage.cobertura.xml'
"/d:sonar.issue.ignore.multicriteria=$(($ignoredIssues.Key) -join ',')"
- "/d:sonar.qualitygate.wait=$env:SONAR_WAIT_QUALITY_GATE"
+ '/d:sonar.qualitygate.wait=true'
+ '/d:sonar.qualitygate.timeout=300'
)
foreach ($issue in $ignoredIssues) {
$arguments += "/d:sonar.issue.ignore.multicriteria.$($issue.Key).ruleKey=$($issue.Rule)"
@@ -148,13 +138,73 @@ jobs:
& "$env:RUNNER_TEMP/sonar-scanner/dotnet-sonarscanner.exe" begin @arguments
- name: Build
- if: env.SONAR_ENABLED == 'true'
run: >-
dotnet build CheatEngine.Client.slnx --configuration Release --no-restore --no-incremental
--disable-build-servers --warnaserror
+ - name: Run tests with Microsoft Testing Platform coverage
+ run: |
+ $projects = @(Get-ChildItem -Path tests -Filter '*.Tests.csproj' -Recurse -File | Sort-Object FullName)
+ if ($projects.Count -eq 0) {
+ throw 'No *.Tests.csproj project found under tests.'
+ }
+
+ foreach ($project in $projects) {
+ $results = Join-Path 'artifacts/sonar-test-results' $project.BaseName
+ $coverage = [IO.Path]::GetFullPath((Join-Path $results 'coverage.cobertura.xml'))
+ $options = @(
+ '--project', $project.FullName,
+ '--configuration', 'Release',
+ '--no-build', '--no-restore',
+ '--report-trx',
+ '--results-directory', $results,
+ '--coverage',
+ '--coverage-output', $coverage,
+ '--coverage-output-format', 'cobertura',
+ '--fail-skips', 'on'
+ )
+ dotnet test @options
+ if ($LASTEXITCODE -ne 0) {
+ throw "Microsoft Testing Platform failed for '$($project.FullName)' with exit code $LASTEXITCODE."
+ }
+ }
+
+ - name: Verify coverage reports
+ run: |
+ $projects = @(Get-ChildItem -Path tests -Filter '*.Tests.csproj' -Recurse -File)
+ $reports = @($projects | ForEach-Object {
+ Join-Path (Join-Path 'artifacts/sonar-test-results' $_.BaseName) 'coverage.cobertura.xml'
+ })
+ $missingReports = @($reports | Where-Object { -not (Test-Path -LiteralPath $_ -PathType Leaf) })
+ if ($missingReports.Count -gt 0) {
+ throw "Microsoft Testing Platform did not produce Cobertura coverage report(s): $($missingReports -join ', ')."
+ }
+
+ foreach ($report in $reports) {
+ try {
+ $coverage = [xml](Get-Content -LiteralPath $report -Raw)
+ }
+ catch {
+ throw "Coverage report '$report' is not valid XML. $($_.Exception.Message)"
+ }
+
+ if ((Get-Item -LiteralPath $report).Length -eq 0 -or $coverage.DocumentElement.Name -ne 'coverage') {
+ throw "Coverage report '$report' is not a Cobertura report."
+ }
+ }
+
+ Write-Host "Verified $($reports.Count) Cobertura coverage report(s)."
+
- name: End analysis
- if: env.SONAR_ENABLED == 'true'
env:
SONARQUBE_SCANNER_PARAMS: '{"sonar.token":"${{ secrets.SONAR_TOKEN }}"}'
run: '& "$env:RUNNER_TEMP/sonar-scanner/dotnet-sonarscanner.exe" end'
+
+ - name: Upload Sonar coverage reports
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: sonar-coverage
+ path: artifacts/sonar-test-results/*/coverage.cobertura.xml
+ if-no-files-found: error
+ retention-days: 14
diff --git a/Directory.Packages.props b/Directory.Packages.props
index ab4e128..cd094fa 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -19,6 +19,7 @@
+
diff --git a/eng/Tests.props b/eng/Tests.props
index f20f859..b66e29e 100644
--- a/eng/Tests.props
+++ b/eng/Tests.props
@@ -14,6 +14,7 @@
+
diff --git a/tests/CheatEngine.Client.Abstractions.Tests/packages.lock.json b/tests/CheatEngine.Client.Abstractions.Tests/packages.lock.json
index 8c00db6..6cf7ac7 100644
--- a/tests/CheatEngine.Client.Abstractions.Tests/packages.lock.json
+++ b/tests/CheatEngine.Client.Abstractions.Tests/packages.lock.json
@@ -13,6 +13,17 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.11.2, )",
+ "resolved": "18.11.2",
+ "contentHash": "bT6awBEUR+fjPpeLAN++4qx5q0sgA9SeJt6QfijnFFPxNSr68BYsYFMQH8L8kY6vMJd3fuvFAFBht4JtWQcWtQ==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Testing.Platform": "2.4.0"
+ }
+ },
"Microsoft.Testing.Extensions.TrxReport": {
"type": "Direct",
"requested": "[2.4.1, )",
@@ -52,6 +63,16 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.10",
+ "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA=="
+ },
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "10.0.401",
diff --git a/tests/CheatEngine.Client.Core.Tests/packages.lock.json b/tests/CheatEngine.Client.Core.Tests/packages.lock.json
index d9d0a56..ac542c5 100644
--- a/tests/CheatEngine.Client.Core.Tests/packages.lock.json
+++ b/tests/CheatEngine.Client.Core.Tests/packages.lock.json
@@ -13,6 +13,17 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.11.2, )",
+ "resolved": "18.11.2",
+ "contentHash": "bT6awBEUR+fjPpeLAN++4qx5q0sgA9SeJt6QfijnFFPxNSr68BYsYFMQH8L8kY6vMJd3fuvFAFBht4JtWQcWtQ==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Testing.Platform": "2.4.0"
+ }
+ },
"Microsoft.Testing.Extensions.TrxReport": {
"type": "Direct",
"requested": "[2.4.1, )",
@@ -52,6 +63,16 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.10",
+ "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA=="
+ },
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "10.0.401",
diff --git a/tests/CheatEngine.Client.Extensions.DependencyInjection.Tests/packages.lock.json b/tests/CheatEngine.Client.Extensions.DependencyInjection.Tests/packages.lock.json
index 0f9f765..f242f60 100644
--- a/tests/CheatEngine.Client.Extensions.DependencyInjection.Tests/packages.lock.json
+++ b/tests/CheatEngine.Client.Extensions.DependencyInjection.Tests/packages.lock.json
@@ -13,6 +13,17 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.11.2, )",
+ "resolved": "18.11.2",
+ "contentHash": "bT6awBEUR+fjPpeLAN++4qx5q0sgA9SeJt6QfijnFFPxNSr68BYsYFMQH8L8kY6vMJd3fuvFAFBht4JtWQcWtQ==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Testing.Platform": "2.4.0"
+ }
+ },
"Microsoft.Testing.Extensions.TrxReport": {
"type": "Direct",
"requested": "[2.4.1, )",
@@ -52,6 +63,11 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.10",
+ "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA=="
+ },
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "10.0.12",
@@ -61,6 +77,11 @@
"Microsoft.Extensions.Primitives": "10.0.12"
}
},
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "10.0.12",
diff --git a/tests/CheatEngine.Client.Fluent.Tests/packages.lock.json b/tests/CheatEngine.Client.Fluent.Tests/packages.lock.json
index e036eed..1aaafd6 100644
--- a/tests/CheatEngine.Client.Fluent.Tests/packages.lock.json
+++ b/tests/CheatEngine.Client.Fluent.Tests/packages.lock.json
@@ -13,6 +13,17 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.11.2, )",
+ "resolved": "18.11.2",
+ "contentHash": "bT6awBEUR+fjPpeLAN++4qx5q0sgA9SeJt6QfijnFFPxNSr68BYsYFMQH8L8kY6vMJd3fuvFAFBht4JtWQcWtQ==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Testing.Platform": "2.4.0"
+ }
+ },
"Microsoft.Testing.Extensions.TrxReport": {
"type": "Direct",
"requested": "[2.4.1, )",
@@ -52,6 +63,16 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.10",
+ "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA=="
+ },
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
"Microsoft.SourceLink.Common": {
"type": "Transitive",
"resolved": "10.0.401",
diff --git a/tests/CheatEngine.Client.Hosting.Tests/packages.lock.json b/tests/CheatEngine.Client.Hosting.Tests/packages.lock.json
index 9610c29..60d1d51 100644
--- a/tests/CheatEngine.Client.Hosting.Tests/packages.lock.json
+++ b/tests/CheatEngine.Client.Hosting.Tests/packages.lock.json
@@ -13,6 +13,17 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.11.2, )",
+ "resolved": "18.11.2",
+ "contentHash": "bT6awBEUR+fjPpeLAN++4qx5q0sgA9SeJt6QfijnFFPxNSr68BYsYFMQH8L8kY6vMJd3fuvFAFBht4JtWQcWtQ==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Testing.Platform": "2.4.0"
+ }
+ },
"Microsoft.Testing.Extensions.TrxReport": {
"type": "Direct",
"requested": "[2.4.1, )",
@@ -52,6 +63,11 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.10",
+ "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA=="
+ },
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "10.0.12",
@@ -61,6 +77,11 @@
"Microsoft.Extensions.Primitives": "10.0.12"
}
},
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "10.0.12",
diff --git a/tests/CheatEngine.Client.SourceGenerators.Lua.Tests/packages.lock.json b/tests/CheatEngine.Client.SourceGenerators.Lua.Tests/packages.lock.json
index fa761cf..260ecf1 100644
--- a/tests/CheatEngine.Client.SourceGenerators.Lua.Tests/packages.lock.json
+++ b/tests/CheatEngine.Client.SourceGenerators.Lua.Tests/packages.lock.json
@@ -23,6 +23,17 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.11.2, )",
+ "resolved": "18.11.2",
+ "contentHash": "bT6awBEUR+fjPpeLAN++4qx5q0sgA9SeJt6QfijnFFPxNSr68BYsYFMQH8L8kY6vMJd3fuvFAFBht4JtWQcWtQ==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Testing.Platform": "2.4.0"
+ }
+ },
"Microsoft.Testing.Extensions.TrxReport": {
"type": "Direct",
"requested": "[2.4.1, )",
@@ -70,6 +81,11 @@
"Microsoft.CodeAnalysis.Analyzers": "5.9.0-1.26328.17"
}
},
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.10",
+ "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA=="
+ },
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "10.0.12",
@@ -79,6 +95,11 @@
"Microsoft.Extensions.Primitives": "10.0.12"
}
},
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "10.0.12",
diff --git a/tests/CheatEngine.Client.Tests/packages.lock.json b/tests/CheatEngine.Client.Tests/packages.lock.json
index e1916b1..5a92361 100644
--- a/tests/CheatEngine.Client.Tests/packages.lock.json
+++ b/tests/CheatEngine.Client.Tests/packages.lock.json
@@ -13,6 +13,17 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.Testing.Extensions.CodeCoverage": {
+ "type": "Direct",
+ "requested": "[18.11.2, )",
+ "resolved": "18.11.2",
+ "contentHash": "bT6awBEUR+fjPpeLAN++4qx5q0sgA9SeJt6QfijnFFPxNSr68BYsYFMQH8L8kY6vMJd3fuvFAFBht4JtWQcWtQ==",
+ "dependencies": {
+ "Microsoft.DiaSymReader": "2.2.10",
+ "Microsoft.Extensions.DependencyModel": "10.0.10",
+ "Microsoft.Testing.Platform": "2.4.0"
+ }
+ },
"Microsoft.Testing.Extensions.TrxReport": {
"type": "Direct",
"requested": "[2.4.1, )",
@@ -52,6 +63,11 @@
"System.IO.Hashing": "10.0.12"
}
},
+ "Microsoft.DiaSymReader": {
+ "type": "Transitive",
+ "resolved": "2.2.10",
+ "contentHash": "zmGsm6b2y3STDa/Of7rdkkfTDV8VuGB8aCqIkLoJIQh5tL78K3zJ8OUyFKnfsaORXmGr9iOKwDkZfUjeXi+CwA=="
+ },
"Microsoft.Extensions.Configuration": {
"type": "Transitive",
"resolved": "10.0.12",
@@ -61,6 +77,11 @@
"Microsoft.Extensions.Primitives": "10.0.12"
}
},
+ "Microsoft.Extensions.DependencyModel": {
+ "type": "Transitive",
+ "resolved": "10.0.10",
+ "contentHash": "rfZA1RjR021RPqSmIPovfz2aOd79TGqJ9BengbjnzIISOVwjLmuSDnhCMmiY/1c6iYvGolQ1iNGzkav0u11XEA=="
+ },
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "10.0.12",