Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ updates:
# Central package management means all NuGet versions live in
# Directory.Packages.props — Dependabot updates that file automatically,
# no per-project configuration needed.
# NOTE: Dependabot updates Directory.Packages.props but cannot rewrite packages.lock.json --
# the CentralTransitive entries have to be resolved by NuGet, not pattern-matched. Every bump
# here therefore arrives with stale lock files and fails `dotnet restore --locked-mode` (NU1004)
# until they are regenerated. Run the "Refresh lock files" workflow against the bump's branch;
# .github/workflows/refresh-lock-files.yml explains why that is a button and not automatic.
- package-ecosystem: "nuget"
directory: "/"
schedule:
Expand Down
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,16 @@ jobs:
8.0.x
10.0.x

# Named, so the failure can say what to do about it. NU1004 on a Dependabot branch means
# the lock files were not regenerated, which is a chore rather than a defect -- and the
# error NuGet prints for it does not mention the fix.
- name: Restore
run: dotnet restore --locked-mode
run: |
set -euo pipefail
if ! dotnet restore --locked-mode; then
echo "::error title=Lock files are out of step::dotnet restore --locked-mode failed. If this is a dependency bump, Dependabot cannot rewrite packages.lock.json under central package management. Run the 'Refresh lock files' workflow against ${{ github.head_ref || github.ref_name }}, then re-run these checks."
exit 1
fi

- name: Build
run: dotnet build --configuration Release --no-restore /p:ContinuousIntegrationBuild=true
Expand Down
81 changes: 81 additions & 0 deletions .github/workflows/refresh-lock-files.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Refresh lock files

# Dependabot cannot do this itself.
#
# Under central package management it edits Directory.Packages.props, but it does not rewrite
# packages.lock.json -- and it cannot, because the CentralTransitive entries have to be resolved by
# NuGet, not pattern-matched. So every NuGet bump arrives with lock files that still name the old
# versions, and `dotnet restore --locked-mode` fails before anything builds:
#
# error NU1004: Mistmatch between the requestedVersion of a lock file dependency marked as
# CentralTransitive and the version specified in the central package management file.
#
# Run this against the bump's branch and it fixes itself.
#
# Deliberately `workflow_dispatch` and not an automatic push on every Dependabot PR. A push made
# with GITHUB_TOKEN does not re-trigger workflows, so an automatic version would commit the fix and
# leave the required checks pinned to the superseded commit -- one stall traded for another. Making
# that work needs a PAT or a GitHub App key, and this repository stores no such credential on
# purpose. A human runs this, then re-runs the checks.

on:
workflow_dispatch:
inputs:
branch:
description: The branch to refresh, e.g. dependabot/nuget/microsoft-extensions-abc123
required: true
type: string

permissions:
contents: read

concurrency:
group: refresh-lock-files-${{ inputs.branch }}
cancel-in-progress: false

jobs:
refresh:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
# The one job in this repository that writes. Scoped here rather than at the top of the file
# so nothing else in this workflow inherits it.
contents: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ inputs.branch }}
persist-credentials: true

- uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: |
8.0.x
10.0.x

# --force-evaluate is the whole point: it re-resolves rather than asserting the lock is right.
- run: dotnet restore --force-evaluate

# Prove the result before committing it. If locked-mode cannot restore what force-evaluate
# just wrote, something is wrong that a commit would only bury.
- run: dotnet restore --locked-mode
- run: dotnet build --no-restore -c Release

- name: Commit the refreshed lock files
run: |
set -euo pipefail
# Named paths, never `git add -A`: this job can write, so it says exactly what it writes.
if [ -z "$(git status --porcelain -- '*packages.lock.json')" ]; then
echo "Lock files already match the manifests. Nothing to commit."
exit 0
fi
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add -- '*packages.lock.json'
git commit -m 'Refresh the lock files for this bump

Regenerated with `dotnet restore --force-evaluate`, then verified with
`--locked-mode` and a Release build. Dependabot cannot write these under
central package management; see .github/workflows/refresh-lock-files.yml.'
git push origin HEAD:'${{ inputs.branch }}'
echo "Pushed. Re-run the pull request's checks -- a GITHUB_TOKEN push does not."
12 changes: 12 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@
<PackageVersion Include="Testcontainers.PostgreSql" Version="4.13.0" />
<PackageVersion Include="Testcontainers.MsSql" Version="4.13.0" />
<PackageVersion Include="Testcontainers.CosmosDb" Version="4.13.0" />
<!--
Referenced directly by Healthie.Tests.Unit purely to raise it. Testcontainers 4.13.0 resolves
SSH.NET 2025.1.0, which is GHSA-q939-rpr3-3284 (high): ScpClient's recursive download writes
wherever a malicious server's filenames point. Patched in 2026.0.0, and 4.13.0 is
Testcontainers' own latest, so there is nothing upstream to wait for.

A direct reference rather than CentralPackageTransitivePinningEnabled: that switch forces one
version of every transitive package across the whole solution, which collides head-on with the
per-target-framework Microsoft.Extensions.* versions above and fails restore with NU1109.
Drop this when Testcontainers resolves 2026.0.0 itself.
-->
<PackageVersion Include="SSH.NET" Version="2026.0.0" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions tests/Healthie.Tests.Unit/Healthie.Tests.Unit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
<PackageReference Include="Testcontainers.PostgreSql" />
<PackageReference Include="Testcontainers.MsSql" />
<PackageReference Include="Testcontainers.CosmosDb" />
<PackageReference Include="SSH.NET" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>

Expand Down
42 changes: 22 additions & 20 deletions tests/Healthie.Tests.Unit/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@
"resolved": "13.0.4",
"contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A=="
},
"SSH.NET": {
"type": "Direct",
"requested": "[2026.0.0, )",
"resolved": "2026.0.0",
"contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": {
"BouncyCastle.Cryptography": "2.7.0"
}
},
"Testcontainers.CosmosDb": {
"type": "Direct",
"requested": "[4.13.0, )",
Expand Down Expand Up @@ -116,8 +125,8 @@
},
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.6.2",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w=="
"resolved": "2.7.0",
"contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
},
"Docker.DotNet.Enhanced": {
"type": "Transitive",
Expand Down Expand Up @@ -399,14 +408,6 @@
"SQLitePCLRaw.core": "2.1.12"
}
},
"SSH.NET": {
"type": "Transitive",
"resolved": "2025.1.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==",
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2"
}
},
"System.ClientModel": {
"type": "Transitive",
"resolved": "1.1.0",
Expand Down Expand Up @@ -831,6 +832,15 @@
"resolved": "13.0.4",
"contentHash": "pdgNNMai3zv51W5aq268sujXUyx7SNdE2bj1wZcWjAQrKMFZV260lbqYop1d2GM67JI1huLRwxo9ZqnfF/lC6A=="
},
"SSH.NET": {
"type": "Direct",
"requested": "[2026.0.0, )",
"resolved": "2026.0.0",
"contentHash": "Yu9dirPq8l3oaat0+OQ7K0nUf5MmYltpia5UGqsApTG4zTPvBC1cxbNnC3NERij26dUST0A3Ef1QdHSn5ArbWQ==",
"dependencies": {
"BouncyCastle.Cryptography": "2.7.0"
}
},
"Testcontainers.CosmosDb": {
"type": "Direct",
"requested": "[4.13.0, )",
Expand Down Expand Up @@ -916,8 +926,8 @@
},
"BouncyCastle.Cryptography": {
"type": "Transitive",
"resolved": "2.6.2",
"contentHash": "7oWOcvnntmMKNzDLsdxAYqApt+AjpRpP2CShjMfIa3umZ42UQMvH0tl1qAliYPNYO6vTdcGMqnRrCPmsfzTI1w=="
"resolved": "2.7.0",
"contentHash": "U+12df8UEWHgBi04YVf/Lgi2dy3SItlIYvHjjEVa/BngCQIzDCDRBk50DDByCfDvSbe5pRNFr3b7UrVK2kMcLw=="
},
"Docker.DotNet.Enhanced": {
"type": "Transitive",
Expand Down Expand Up @@ -1312,14 +1322,6 @@
"SQLitePCLRaw.core": "2.1.12"
}
},
"SSH.NET": {
"type": "Transitive",
"resolved": "2025.1.0",
"contentHash": "jrnbtf0ItVaXAe6jE8X/kSLa6uC+0C+7W1vepcnRQB/rD88qy4IxG7Lf1FIbWmkoc4iVXv0pKrz+Wc6J4ngmHw==",
"dependencies": {
"BouncyCastle.Cryptography": "2.6.2"
}
},
"System.ClientModel": {
"type": "Transitive",
"resolved": "1.1.0",
Expand Down
Loading