* feat(manager): pass extracted constraints to updateArtifacts `extractedConstraints` were collected during extraction but only ever read by `fetch.ts` for the lookup. At artifact time every manager had to derive the same tool constraints a second time, and when the updated package file no longer carried the constraint there was nothing to fall back to. `generateBranchConfig()` now merges the `extractedConstraints` of all upgrades of a branch onto the branch config, which reaches `updateArtifacts()` through `patchConfigForArtifactsUpdate()`. `UpdateArtifactsConfig` declares the field and documents what it contains. The new `resolveToolConstraint()` helper implements the ladder once: user `constraints` > an optional artifact-time derivation > the extracted value. The derivation is ranked above the extracted value on purpose: it reads the package files as the branch changed them, so poetry (python is an updatable dependency there), composer, pipenv (`pipenv` can be a dev package) and pep621 (`requires-python` is an updatable dependency) would otherwise regress to a stale constraint. pipenv, pep621 uv/pdm, poetry and composer are migrated to the helper and keep their derivation. Behaviour change: these managers now use the extract-time constraint as a last resort instead of running unconstrained, for example on lock file maintenance where pipenv deletes `Pipfile.lock` before deriving. `execute-post-upgrade-commands.ts` is unaffected: it reads `upgrade.constraints` per upgrade, and the extracted values are kept in a separate field rather than folded into `constraints`, so post-upgrade task tool constraints still come from user config only. Not migrated, because their constraints are never extracted, so the fallback would be dead code: bundler (`ruby`/`bundler`), pixi (`pixi`), pep621 uv (`required-version`) and npm's node ladder, whose last step already reads `package.json` engines. * refactor(manager): resolve tool constraints through resolveToolConstraint() Every manager now obtains the constraint of a tool it runs through `resolveToolConstraint()` instead of reading `config.constraints` itself, so the precedence "user config, then a value derived from the updated files, then the constraints collected during extraction" applies everywhere. The helper treats an empty string as not set at every step and accepts the `PostUpdateConfig` shape, whose `constraints` may be `null`. Behaviour changes that go beyond the new fallback to extracted constraints: - `mix` applies its hardcoded erlang default only after the extracted constraint, so an erlang version collected at extraction can win over it. - `kustomize` decides `HELM_EXPERIMENTAL_OCI` from the fully resolved helm constraint; `generateHelmEnvs()` takes that value instead of the config. - `npm` (yarn): an upgrade of yarn without a new value now consults the user constraint before deriving one from package.json or the lock file. - The "Using <tool> constraint from config" debug lines in bundler, composer, copier and pip-compile are gone, as the helper is the only reader of the config now. Helpers that read the config synchronously became async: bundler `getBundlerConstraint()`, copier `getPythonVersionConstraint()` and `getCopierVersionConstraint()`, pip-compile `get*VersionConstraint()` and `getToolVersionConstraint()`, gomod `deriveGoToolchainConstraints()` and `getUpdateImportPathCmds()`, mise `getMiseLockToolConstraints()`. composer `getPhpConstraint()` is removed. Each converted manager has a test showing the extracted constraint reaching the executed tool, and, where a derive callback exists, that the derived value still wins over it. * refactor(linters): add prefer-resolve-tool-constraint rule Reports member reads and destructuring of `constraints` and `extractedConstraints` from a manager config in `lib/modules/manager/**`, so that tool constraints are resolved through `resolveToolConstraint()` in `lib/modules/manager/util.ts` only. Writes and the same properties on package files, which extraction has to populate, are not reported. The helper itself and spec files are exempt in `.oxlintrc.json`. * refactor(manager): inline thin resolveToolConstraint wrappers Drop copier/utils.ts and the pip-compile getPipToolsVersionConstraint / getUvVersionConstraint helpers, which only forwarded to resolveToolConstraint(). Callers now call it directly. * docs(development): note lock file maintenance in tool constraints section * refactor(manager): address review on tool constraint resolution composer only runs extractConstraints() when a user constraint is missing, via Lazy. UpdateArtifactsConfig no longer re-declares constraints and extractedConstraints, which ToolConstraintsConfig already provides.
Use packageRules to control the behavior of the NuGet package manager.
The NuGet package manager supports these SDK-style files and formats:
.csproj.fsproj.sqlproj(SSDT SQL projects).vbproj.props.targetsglobal.jsondotnet-tools.json
MSBuild SDK dependencies are also extracted from:
<Sdk Name="..." Version="..." />elements<Import ... Sdk="..." Version="..." />elements<Project Sdk="name/version">attributesglobal.json→msbuild-sdks
.NET Core projects are supported by default.
For Renovate to work with .NET Framework projects, you need to update these files so they match the new SDK-style format:
.csproj.fsproj.vbproj.props.targets
You can also extract from the single code file projects (since .NET 10).
But, you need to add those files manually to the managerFilePatterns as they are not supported by default.
Disabling updates for pinned versions
In NuGet, when you use versions like Version="1.2.3" then it means "1.2.3 or greater" (an open-ended minimum, with no upper bound).
When you use versions like Version="[1.2.3]" then it means "exactly 1.2.3".
If you would like Renovate to disable updating of exact versions (warning: you might end up years out of date and not realize it) then here is an example configuration to achieve that:
{
"packageRules": [
{
"description": "Skip pinned versions",
"matchManagers": ["nuget"],
"matchCurrentValue": "/^\\[[^,]+\\]$/",
"enabled": false
}
]
}
Getting updates for non-pinned (bare) versions
Per NuGet's versioning rules, a bare version such as Version="1.2.3" is an open-ended minimum (1.2.3 or greater) — so it is NuGet, not Renovate, that treats this as a range rather than a single version.
The default rangeStrategy of auto resolves to replace for NuGet, and replace cannot produce an update for an open-ended minimum: every newer release already satisfies "1.2.3 or greater", so there is nothing to replace.
The result is that Renovate detects no updates for these dependencies and closes any existing update PRs.
To keep receiving updates for bare versions, set rangeStrategy to bump.
With bump, Renovate raises the minimum to the newest release (for example 1.2.3 to 1.5.0):
{
"packageRules": [
{
"description": "Bump bare NuGet versions to the latest release",
"matchManagers": ["nuget"],
"rangeStrategy": "bump"
}
]
}
!!! note
This is a change in behavior.
Earlier versions of Renovate anchored the "current version" directly to the value in your project file, so bare versions received updates under any rangeStrategy.
Since Renovate 43.208.2, Renovate no longer treats a range (including a bare NuGet version) as the current version, so the rangeStrategy now decides the outcome: use bump to restore the previous update behavior.
Workload restore
Sometimes you need to run dotnet workload restore to ensure that all required workloads are installed before restoring the project.
You can enable this behavior by adding dotnetWorkloadRestore to the postUpdateOptions in your Renovate configuration.
{
"postUpdateOptions": ["dotnetWorkloadRestore"]
}