Featured image of post Automated NuGet package version range updates in .NET projects using Renovate

Automated NuGet package version range updates in .NET projects using Renovate

Renovate does not handle NuGet version ranges by default, let's configure a custom Renovate Regex manager to support them.

In my previous post about how to locally test and validate Renovate configuration files, we saw how Renovate can be helpful in keeping our dependencies up-to-date. It recommended updates for the Microsoft.Extensions.Hosting package from 7.0.0 to 7.0.1 (minor) or 8.0.0 (major).

By default, the way Renovate handles NuGet package updates in .NET projects is suitable for the majority of cases. According to the Renovate NuGet manager documentation (a manager is a module that manages updates for a type of dependency), the following files are analyzed:

  • Project files .csproj, .fsproj, and .vbproj,
  • MSBuild files .props and .targets, including Directory.Build.props, Directory.Build.targets, and Directory.Packages.props,
  • .NET tool installation manifests dotnet-tools.json,
  • .NET SDK version definition files global.json.

Any reference to a NuGet package or .NET SDK in these files will be analyzed by Renovate, and if a new version is available, a pull request will potentially be created to update it.

What is not specified in the Renovate NuGet manager documentation page is that it can only process packages referenced with the basic package version notation, for example:

<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.0" />

However, specifying a package version in a .NET project can be much more advanced than that. Indeed, it’s possible to specify a version range:

NotationApplied ruleDescription
1.0x ≥ 1.0Minimum version, inclusive
[1.0,)x ≥ 1.0Minimum version, inclusive
(1.0,)x > 1.0Minimum version, exclusive
[1.0]x == 1.0Exact version match
(,1.0]x ≤ 1.0Maximum version, inclusive
(,1.0)x < 1.0Maximum version, exclusive
[1.0,2.0]1.0 ≤ x ≤ 2.0Exact range, inclusive
(1.0,2.0)1.0 < x < 2.0Exact range, exclusive
[1.0,2.0)1.0 ≤ x < 2.0Mixed inclusive minimum and exclusive maximum version

Take, for example, the library OpenTelemetry.Instrumentation.Hangfire, which imports the Hangfire.Core library:

<PackageReference Include="Hangfire.Core" Version="[1.7.0,1.9.0)" />

Unfortunately, Renovate does not support NuGet version ranges by default. However, it is possible to extend its capabilities with regular expressions.

It’s required to extract the dependency name and the current version. In my previous example, the dependency name is Hangfire.Core, and I will consider the current version to be the left side of the range, which is 1.7.0. You are free to decide which part of the range you wish to update.

Here’s the regular expression that allows extracting the dependency name and the current version:

<PackageReference\s+Include="(?<depName>[^"]+)"\s+Version="\[(?<currentValue>[^,]+),.*"

depName and currentValue are mandatory group names that can be interpreted by Renovate to perform the update.

Let’s update our Renovate file to include the custom regex manager with our regular expression. I’ve omitted any content from the file that’s not relevant for this article.

{
  "$schema": "https://docs.renovatebot.com/renovate-schema.json",
  "extends": [
    "config:best-practices"
  ],
  "enabledManagers": [
    "nuget",
    "custom.regex"
  ],
  "customManagers": [
    {
      "customType": "regex",
      "fileMatch": [
        "\\.csproj$"
      ],
      "matchStrings": [
        "<PackageReference\\s+Include=\"(?<depName>[^\"]+)\"\\s+Version=\"\\[(?<currentValue>[^,]+),.*\""
      ],
      "datasourceTemplate": "nuget",
      "versioningTemplate": "nuget"
    }
  ]
}

If we execute Renovate locally with our new configuration, we should see that Renovate is capable of identifying the 1.7.0 version of Hangfire.Core and proposes an update:

DEBUG: packageFiles with updates (repository=local)
       "config": {
         "regex": [
           {
             "deps": [
               {
                 "depName": "Hangfire.Core",
                 "currentValue": "1.7.0",
                 "datasource": "nuget",
                 "versioning": "nuget",
                 "replaceString": "<PackageReference Include=\"Hangfire.Core\" Version=\"[1.7.0,1.9.0)\"",
                 "updates": [
                   {
                     "bucket": "non-major",
                     "newVersion": "1.8.11",
                     "newValue": "1.8.11",
                     "releaseTimestamp": "2024-02-23T11:56:41.437Z",
                     "newMajor": 1,
                     "newMinor": 8,
                     "updateType": "minor",
                     "branchName": "renovate/hangfire-monorepo"
                   }
                 ],
                 "packageName": "Hangfire.Core",
                 "warnings": [],
                 "sourceUrl": "https://github.com/HangfireIO/Hangfire",
                 "registryUrl": "https://api.nuget.org/v3/index.json",
                 "homepage": "https://www.hangfire.io/",
                 "currentVersion": "1.7.0",
                 "isSingleVersion": true,
                 "fixedVersion": "1.7.0"
               }
             ],
             "matchStrings": [
               "<PackageReference\\s+Include=\"(?<depName>[^\"]+)\"\\s+Version=\"\\[(?<currentValue>[^,]+),.*\""
             ],
             "datasourceTemplate": "nuget",
             "versioningTemplate": "nuget",
             "packageFile": "Project.csproj"
           }
         ]
       }

The Renovate Regex manager has many other parameters, so be sure to check out the documentation.

# References

Licensed under CC BY 4.0
Ko-fi donations Buy me a coffee