As part of future changes, we want to extract system package managers'
packages in `Dockerfile`s more generally.
Instead of an `apk`-specific file, we can introduce a shared file for
extracting commands from `RUN`s.
From here, it can then be used by `apk` and follow-up tools by providing
the `names` to look for.
Co-Authored-By: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
Now we have support for extracting `apk add`s in the Dockerfile manager,
we should also add a more in-depth indication of how to use this with
Wolfi or Chainguard Images.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
In the Wolfi undistribution, which Chainguard Images are built upon,
it is common to use a fuzzy constraint rather than exact versions[0]:
RUN apk update && apk add --no-cache libcurl-openssl4=~8.12.1
curl=~8.12.1 make
which we previously skipped as `unsupported-version` when we started
extracting `apk add` packages.
As the `apk` version resolves these, we should extract the constraint as
the `currentValue`, and let the lookup evaluate it.
A constraint of `~8.12.1` matches every `8.12.1-rN` version, so we don't
need to update these constraints.
However, when the `newVersion` is i.e. `8.13.0-r0`, the constraint is
rewritten to `curl=~8.13.0`.
If using an exact pin, the version is upgraded with the same precision
i.e. `curl=8.12.1` -> `curl=8.13.0`.
We still skip constraints we can't handle with i.e. `contains-variable`
or `unsupported-version`.
[0]: https://github.com/elastic/crawler/blob/bc15328fb808affd00dff0cd92527bbe402f5ded/Dockerfile.wolfi#L7
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
Some charts define the image reference and its version as sibling keys
of the same object, with the parent key unrelated to the image name:
cli:
image: ghcr.io/fluxcd/flux-cli
tag: v2.7.2
The walker previously extracted the `image` string via
matchesHelmValuesInlineImage but, without an embedded version, produced
a version-less dependency that could never be updated; the sibling
`tag`/`version` key was invisible because
matchesHelmValuesDockerHeuristic requires the keys to live inside an
object whose parent key ends in `image`.
When an inline reference carries no version and no digest, adopt a
sibling `tag` (preferred) or `version` string as the version, reusing
the same update mechanics as the repository/tag object form
(replaceString = version, docker versioning, `{{newValue}}` template).
Inline references with an embedded version or digest keep current
behavior, and repository/tag objects never reach the inline branch.
Discussion #46265 documents the layout with a real-world example.
Signed-off-by: Tyler Gillson <tyler.gillson@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(manager/dockerfile): extract `apk add` packages from `RUN` instructions
As noted in #43711, users pin APK packages in their Dockerfiles and want
Renovate to update them, which so far has needed a custom manager[0].
Now that we have the `apk` datasource, we can extract these ourselves,
and parse the `RUN` instruction more effectively than a regex can.
We can do this by:
- extracting `apk add` dependencies when processing a `Dockerfile`
- ignoring common flags to `apk` before we extract the dependency names,
which wasn't straightforward with a Custom Manager
- ignoring i.e. `--virtual .build-deps`
- handle example cases from [0] and other usages i.e. with a comment in
between lines
- ensure that `final` doesn't run against the final `apk` install
- removing the docs for how to do this with a Custom Manager, as that
complexity is no longer needed
- skipping `unspecified-version` and `contains-variable`
We inform the user that they will need to override the `registryUrls`,
as we already have a default on the `apk` datasource and can't override
it in a straightforward means.
[0]: https://www.jvt.me/posts/2025/03/24/renovate-regex-dockerfile-apk/
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
* fixup! feat(manager/dockerfile): extract `apk add` packages from `RUN` instructions
Co-Authored-By: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
* fixup! feat(manager/dockerfile): extract `apk add` packages from `RUN` instructions
Co-Authored-By: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
* fixup! feat(manager/dockerfile): extract `apk add` packages from `RUN` instructions
Find the command by name wherever it sits in the tokens, rather than
skipping an allowlist of shell keywords and variable assignments, so that
a wrapper program like `sudo`, `chroot /mnt` or `timeout 30` is handled
too.
Co-Authored-By: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
* fixup! feat(manager/dockerfile): extract `apk add` packages from `RUN` instructions
---------
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
* test(workers/repository): mark seven unreachable guards for v8
These are defensive paths that no input reaches, and the guards stay as
they are. What changes is that v8 now knows to skip them: the existing
`istanbul ignore` comments are inert under the v8 provider, so the
branches behind them were still being counted.
- `versioning/index`: a string input can only fail the schema with the
custom issue its own transform raises
- `versioning/conda`: pep440 always answers for a well-formed `==X.Y.*`
- `versioning/ruby`: callers only pass versions that have already parsed
- `repository/index`: only called when the repository logged a problem
- `config-migration/branch/rebase`: JSON5 output is always serializable
back to JSON
- `onboarding/pr/pr-list`: base branches are unique Record keys, so the
sort never compares one with itself
- `changelog/release-notes`: a matched release always carries its own url
- `changelog/source`: `getInRangeReleases` only returns null on paths it
already ignores
Two notes on the hints. They have to be a single-line comment directly
above the statement -- a two-line reason, or anything in between such as
an oxlint directive, breaks the association silently. And `v8 ignore
else` covers an `if` with no else, while `v8 ignore next` covers an
expression-level branch such as `??` or a ternary.
`versioning/index` also swaps `issue && issue.code` for `issue?.code`,
which is what the oxlint directive sitting in the way was suppressing.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the last five pr worker guards
None of these needed an ignore comment; each has a real input:
- `updatePrDebugData` gets a describe of its own. It is exported but was
only reached through a pr whose debug data already carried labels, so
the case where an existing pr has none went untaken.
- a `not-pending` branch whose status is no longer yellow
- a changelog error that is not a missing GitHub token
- an existing pr updated because a rebase was requested
- a platform that answers `createPr` with null, so assignees and
reviewers are skipped
With these, `update/pr/index.ts` is fully covered.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the last four branch update guards
- `bump-versions` reads the current version off disk when the pending
change for that file carries no contents. All three guards involved are
covered by one case where the file is pending in both the package file
and the artifact list with no contents on either entry. An earlier
attempt at this looked like it failed: the bump does happen, but the
result is appended to `updatedPackageFiles` rather than
`updatedArtifacts`, because the file is already pending there.
- `get-updated` keeps a package file another upgrade already wrote, for a
manager that is asked for a lockfile-only update but has no
`updateLockedDependency`.
With these, `lib/workers/repository` is fully covered.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* ci: pin the branch coverage threshold at 100
A full run on this branch reports 100% on all four metrics: statements
(49435/49435), branches (25727/25727), functions (7414/7414) and lines
(48924/48924).
Branches joins the other three at 100 rather than taking another
rounding-down step. There is no longer a margin to leave: any new
uncovered branch is a regression, and the threshold should say so.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
---------
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the dependency dashboard guards
- a repository with no dashboard issue yet, so nothing is read back into
the config
- a dashboard rendered with an empty header and with abandonment
reporting turned off
- two vulnerabilities for the same package, which are grouped under one
entry rather than recorded twice
With these, `dependency-dashboard.ts` is fully covered.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover a locked update that reports no files
`updateLockedDependency` can answer `updated` without handing back any
file contents. Every test supplied files alongside that status, so the
guard that skips merging them went untaken.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover a changelog release read from cache
Every changelog test started from an empty package cache, so the branch
that reuses a cached release rather than fetching it went untaken.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* ci: ratchet the branch coverage threshold
A full run on this branch reports 100% statements (49441/49441), 100%
functions (7414/7414), 100% lines (48930/48930) and 99.92% branches
(25723/25742), up from 99.89% on the branch below. The threshold moves to
99.90, keeping the same rounding-down margin as before.
Statements, functions and lines are already pinned at 100 and stay
there.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
---------
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover five pr body and warning guards
- an upgrade with no release notes of its own, which gets no summary
title while the branch as a whole has notes
- an upgrade with no homepage, source or dependency url, whose name is
therefore left unlinked
- an upgrade that defines no pr body columns
- an assignee list that filtering empties out
- a second warned dependency in a package file that is already listed,
so the file is not recorded twice
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover four changelog guards
- `massageName` gets a describe of its own: it is exported but was only
ever reached through a release that carried a tag, so the no-version
path and the empty-result path went untaken
- a lone in-range release with no earlier one to prepend
- a bitbucket tree holding exactly one changelog candidate, so nothing is
logged about choosing between several
- a bitbucket-server source url that parses but matches neither the git
nor the web repository pattern
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover both extract failure paths
- a manager whose `extractAllPackageFiles` returns null, so there is
nothing to massage dep names in
- a file whose `extractPackageFile` returns nothing, so it contributes no
package file
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* ci: ratchet the branch coverage threshold
A full run on this branch reports 100% statements (49491/49491), 100%
functions (7384/7384), 100% lines (48981/48981) and 99.86% branches
(25726/25761), up from 99.82% on the branch below. The threshold moves to
99.84, keeping the same rounding-down margin as before.
Statements, functions and lines are already pinned at 100 and stay
there.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover four process guards
- a libyear update released before the version currently in use, which
yields a negative age and is therefore not counted
- a pr that is closed, and one raised from the onboarding branch, neither
of which counts towards the concurrent pr limit
- a second base branch whose package files do not displace the first
one's, on a platform that cannot sync a fork with its upstream
The fork case redefines `syncForkWithUpstream` rather than deleting it,
the same way the migration branch spec handles `refreshPr`.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the extract cache guards
- a cached extract whose manager fingerprints all still match, so the
cache is reusable; the test reads the expected fingerprint out of
`hashMap` rather than hard-coding a hash that regenerates
- an extract that is told not to overwrite the cache
- a cached scan for a branch that is still a configured base branch, so
it survives the clean-up that drops the others
With these, `lib/workers/repository/process` is fully covered.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the branchPrefixOld miss
`prAlreadyExisted` retries the lookup under the old branch prefix, but
every test either hit on the first lookup or on the second, so the case
where both miss went untaken.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* ci: ratchet the branch coverage threshold to 99.87
The tests added after the earlier bump in this branch close eight more
branches. A full run now reports 100% statements (49491/49491), 100%
functions (7384/7384), 100% lines (48981/48981) and 99.89% branches
(25734/25761). The threshold moves from 99.84 to 99.87, keeping the same
rounding-down margin as before.
Statements, functions and lines are already pinned at 100 and stay
there.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover three changelog embedding guards
The pr worker copies release notes off each upgrade's changelog result.
Three of its guards were never taken because every test supplied a
complete result for a distinct repository:
- a changelog with no project, so no repo name is recorded
- a changelog with a project but no versions, so no releases are copied
- a second upgrade for a repository whose notes were already committed,
which is skipped rather than repeated
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
---------
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the pr note and closed-pr guards
`handle-existing.ts` had no spec of its own and was only reached through
the branch worker, so none of its early exits were exercised. Give it
one, covering an open pr, a suppressed notification, a branch that is
already gone, each of the three ignore messages, and both dry-run paths
in each function.
Also cover a pr body with no notes at all, extra notes where none of the
three conditions hold, and a config warning issue raised from an error
that carries no validation details and whose issue is neither created
nor updated.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover five config migration guards
- a `package.json` that carries no `renovate` key, so nothing is deleted
from it before it is rewritten
- a platform with no `refreshPr`, which the migration branch check has to
tolerate
- an old migration branch that is already gone, so there is nothing to
delete before the new one is created
- an editorconfig that sets no maximum line length
- a migration pr with no footer configured
The platform case redefines `refreshPr` rather than deleting it: the
mocked module object refuses deletion but allows redefinition.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the onboarding and reconfigure caches
- deleting and updating the onboarding cache when there is no cache
- an onboarding pr whose closing comment is suppressed
- a reconfigure cache written together with an extract result
Also drop the `if (extractResult)` in the reconfigure check: the catch
above it returns, so the variable is always set by the time the comment
is ensured, and the compiler narrows it on its own.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/repository): cover the update flattening guards
- a config whose `semanticCommits` is already decided, so no detection
runs while flattening
- a semantic commit with no scope configured
Also drop the first `packagePath.length > 0` check while flattening:
`String.split` always yields at least one element, so there is always a
file name to drop. The check after the splice stays, since that one can
leave the array empty.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* ci: ratchet the branch coverage threshold
A full run on this branch reports 100% statements (49468/49468), 100%
functions (7381/7381), 100% lines (48958/48958) and 99.82% branches
(25709/25755), up from 99.73% on the branch below. The threshold moves to
99.80, keeping the same rounding-down margin as before.
Statements, functions and lines are already pinned at 100 and stay
there.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* Apply suggestion from `@secustor`
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
---------
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
Co-authored-by: Sebastian Poxhofer <secustor@users.noreply.github.com>
* feat(config): add minimumReleaseAgeBuffer option
Adds a new config option which extends the time Renovate waits before
suggesting an update by a percentage of minimumReleaseAge. This gives
companion packages that are published after the main package (e.g.
platform-specific binaries) time to pass the package manager's own
release age cooldown, avoiding lock file artifact errors.
* feat(config): make minimumReleaseAgeBuffer a duration with 10 minutes default
* feat(config): raise minimumReleaseAgeBuffer default to 30 minutes
fix(github): attempt direct merge before falling back to the merge queue
PR automerge against a merge-queue-protected base branch could no longer
merge directly when the authenticated actor is allowed to bypass the
queue: mergePr() enqueues the PR unconditionally once the base branch has
a merge queue, and the enqueuePullRequest mutation evaluates queue-entry
requirements (e.g. required code-owner review) without honoring ruleset
bypass, leaving bypass-enabled installs unable to merge at all.
Attempt the direct REST merge first and enqueue only when that merge is
refused, mirroring the try-first design #45724 shipped for branch
automerge. Repositories without a bypass actor pay one refused REST merge
call per automerge attempt before the queue fallback, and up to four when
an unrecognized refusal message triggers method guessing.
Docs: describe the direct-merge-first behavior in the GitHub merge queue
section of the automerge documentation.
In #46201, we found that although `forgejo>` support was added to
Shareable Config Presets, the `renovate-config` manager didn't actually
implement the lookup, meaning we didn't previously propose updates we
should have.
We can validate this is done by making sure that the `{platform}-tags`
datasource is used to look up a preset.
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
feat(terragrunt): detect forgejo-tags datasource for git-tag sources
detectGitTagDatasource() in the terragrunt manager only handled
gitlab, bitbucket and gitea platforms, so Forgejo-hosted modules
(e.g. codeberg.org) fell through to the generic git-tags datasource
and lost release timestamps, sourceUrl and changelog data. Add a
'forgejo' case mapping to ForgejoTagsDatasource, mirroring the
existing pattern in the github-actions manager's detectDatasource().
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Migrate pub, gleam, helmfile, pixi, mise, poetry, pep621/pdm and
pep621/uv onto `updateLockFile()`, so each manager keeps only its
command and `ExecOptions` construction.
Behaviour changes:
- Artifact error text now comes from `artifactErrorMessageFromExecError`,
i.e. a non-blank `err.stderr`, else a non-blank `err.stdout`, else
`err.message`. Previously pixi used `${err}` ("Error: " prefix), mise
joined stdout, stderr and message, and poetry rendered
"<stdout>\n<stderr>" (literally "undefined\nundefined" when the error
carried neither).
- helmfile, pixi, poetry, pdm and uv now return null instead of an
addition when the lock file is missing or empty after the run, which
is what gleam and mise already did.
- Unchanged/missing lock file debug messages are now uniformly
"<lockFileName> is unchanged" / "No <lockFileName> found". pub gains
such a message, mise loses "Returning updated mise lock file" and
poetry loses "Returning updated <lockFileName>".
Migrate apm, bun, cargo, cocoapods, composer, conan, copier, deno,
devbox, gomod, helmv3, jsonnet-bundler, kustomize, mix, nuget and vendir
onto `fileAddition()`, `artifactError()` and `artifactErrorResult()`.
copier's private `artifactError()` is replaced by the shared one.
Behaviour change: artifact error text now comes from
`artifactErrorMessageFromExecError` everywhere, i.e. a non-blank
`err.stderr`, else a non-blank `err.stdout`, else `err.message`.
Previously apm used `${err}` ("Error: " prefix), cocoapods used a
nullish coalesce that kept an empty stderr, nuget preferred stdout over
stderr, and jsonnet-bundler emitted an undefined stderr with no
fallback. The rest used `err.message` only and are unchanged for errors
without output.
Add `fileAddition()`, `artifactError()`, `artifactErrorResult()` and an
`updateLockFile()` skeleton to `lib/modules/manager/util.ts`. The
skeleton rewrites the package file, optionally drops the lock file for
lock file maintenance, runs the manager's own command and returns the
lock file when its content changed.
No manager uses them yet; the adoptions follow in separate changes.
Add `collectFileChanges()` next to the other repository helpers, plus a
thin `fileChangesToArtifactResults()` in `lib/modules/manager/util.ts`,
and migrate the managers which re-implemented the same status loops:
apm, cocoapods, composer, copier, gomod, helmv3, jsonnet-bundler,
kustomize and vendir.
Each site keeps its own status bucket selection and path filter, so the
emitted artifacts are unchanged. Two dead pieces of logic are dropped:
vendir's `vendorDir || isFileInVendorDir` condition, which was always
true, and kustomize's optional chaining on a non-nullable status, which
only ever mattered for an unset test mock (now set explicitly).
While looking at the fix for `actions/setup-java`, we noticed that
`erlef/setup-beam` may also be affected, as it notes:
> values like `22.x`, or even `>22`, are also accepted, and we attempt
> to resolve them according to semantic versioning rules
As these fall back to `semver-coerced`, Renovate then raises a PR
rewriting the range into a full version, which isn't the intent.
We can use `npm` versioning for the Elixir, Gleam and rebar3
dependencies.
This deliberately leaves `otp-version` alone: Erlang/OTP releases
4-component versions (e.g. `26.2.5.3`) which aren't valid semver, so
`npm` versioning would discard those releases entirely. Resolving that
needs a versioning which handles both the 4-component versions and the
ranges, which is left as a follow-up.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`golangci/golangci-lint-action` may also be affected, as it notes for
its default `binary` install mode:
> the value can be v2.3, v2.3.4, or `latest` to use the latest version.
As the value falls back to `semver-coerced`, `v2.3` is treated as a
pinned version, so Renovate then raises a PR to bump to a full version,
which isn't the intent.
Unlike the other Actions looked at here, the partial versions are
`v`-prefixed, so we need to widen the partial-version check to allow
that. `semver-partial` versioning preserves the prefix, so `v2.5` is
bumped to `v2.6` rather than to a full `v2.6.0`.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`crystal-lang/install-crystal` may also be affected, as it notes for
both its `crystal` and `shards` inputs:
> Install a particular release of Crystal (if the full version is
> specified), or the latest patch version of a release series.
As these fall back to `semver-coerced`, a partial version such as `1.21`
is treated as a pinned version, so Renovate then raises a PR to bump to
a full version, which isn't the intent.
We can reuse the same `semver-partial` handling to keep the value's
precision.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`swift-actions/setup-swift` may also be affected, as it notes:
> YAML interprets eg. 5.0 as a float, this action will then interpret
> that as 5 which will result in eg. Swift 5.5 being resolved.
As the value falls back to `semver-coerced`, a partial `swift-version`
is treated as a pinned version, so Renovate then raises a PR to bump to
a full version, which isn't the intent.
Unlike the other Actions looked at here, no ranges are documented, so
`semver-partial` versioning is enough to keep a partial version's
precision.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`GitTools/actions/gitversion/setup` may also be affected, as it
documents `versionSpec` as:
> Required version in the form of 6.8.x or exact version like 6.0.0.
with its own examples using the `6.8.x` form throughout.
As the value falls back to `semver-coerced`, Renovate then raises a PR
rewriting `6.8.x` into a full version, which isn't the intent.
We can use `npm` versioning here too.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`subosito/flutter-action` may also be affected. It doesn't describe
`flutter-version` in prose, but its examples use x-ranges to pick the
latest release of a line:
> flutter-version: 1.22.x
> flutter-version: 3.x
As these fall back to `semver-coerced`, Renovate then raises a PR
rewriting `3.24.x` into a full version, which isn't the intent.
We can use `npm` versioning here too.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`jwlawson/actions-setup-cmake` may also be affected, as it notes:
> This can be a fully specified verison `3.3.0`, partly specified `3.2`,
> a wildcard version `3.2.x`.
As the value falls back to `semver-coerced`, Renovate then raises a PR
rewriting `3.2.x` into a full version, which isn't the intent.
We can use `npm` versioning here too, which understands both the partly
specified and the wildcard forms.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`hashicorp/setup-terraform` and `opentofu/setup-opentofu` may also be
affected, as both note:
> Instead of a full version string, you can also specify a constraint
> string (see Semver Ranges for available range specifications).
As these fall back to `semver-coerced`, which coerces `~1.1.0` to
`1.1.0`, Renovate then raises a PR rewriting the constraint into a full
version, which isn't the intent.
We can use `npm` versioning, which handles the constraint strings the
Actions document, and leaves `latest` alone as a value we have no
version to bump it to.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`julia-actions/setup-julia` may also be affected, as it notes:
> Internally the action uses node's semver package to resolve version
> ranges.
Its documented values therefore include `1.0`, which "matches the
highest available version starting with that prefix", as well as
`^1.3.0-rc1` and `~1.3.0-rc1`.
As these fall back to `semver-coerced`, Renovate then raises a PR to
bump `1.10` to the full-precision tag `v1.11.7`, which isn't the intent.
We can instead use `npm` versioning, as we already do for
`goreleaser/goreleaser-action` and `j178/prek-action`.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`actions/setup-dotnet` may also be affected, as it notes:
> - **A.B** or **A.B.x** installs the latest patch version of .NET SDK
> on the channel
> - **A** or **A.x** installs the latest minor version of the specified
> major tag
alongside `A.B.Cxx` for "the latest version of the specific SDK release"
and `latest` for "the highest active .NET SDK version".
As these fall back to `semver-coerced`, which happily coerces `8.0.x` to
`8.0.0`, Renovate then raises a PR to bump to a full version such as
`9.0.100`, which isn't the intent.
Rather than the `semver-partial` versioning used for the other Actions,
here we can use `npm` versioning, which understands the x-range form
too, and safely rejects `8.0.4xx`/`latest` as values we have no version
to bump them to.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that
`dart-lang/setup-dart` may also be affected, as it notes:
> An SDK release version - e.g. `2.19` or `3.1`. This will install the
> latest patch release for that specific release version.
As the `sdk` value falls back to `semver-coerced`, `3.5` is treated as a
pinned version, so Renovate then raises a PR to bump to a full version
such as `3.9.5`, which isn't the intent.
We can reuse the same `semver-partial` handling to keep the value's
precision.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
While looking at the fix for `actions/setup-java`, we noticed that the
`ruby/setup-ruby` may also be affected, as it notes:
> short version like '2.6', automatically using the latest release
> matching that version
As Ruby versioning treats `3.4` as a pinned version rather than a range,
we'd propose a patch upgrade rewriting it to `3.4.7`, losing the rolling
behaviour the workflow asked for.
We can reuse the `semver-partial` handling as a `partialValSchema`
helper, so short versions keep their precision and only exact versions
are looked up as pinned.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
As noted in #46249, `actions/setup-java` documents `java-version` as:
> Java version to install. Supports whole versions, semver ranges,
> early-access versions, and latest.
When the Java version is parsed using the versioning in
`workarounds:javaLTSVersions`, this leads to `11` looking older than
`11.0.32+101`, so Renovate then raises a PR to bump to a full version,
which isn't the intent.
Similar to the fix for mise in
5f5e58b822, we can use `semver-partial`
versioning for whole versions, so `11` stays `11` and only a new major
bumps it to `25`, and exempt `github-actions` from the preset rule that
would otherwise force the Java regex versioning back on.
The remaining documented forms (`21.x`, `>=21`, `21-ea`, `latest`) have
no version we could bump them to, so we skip them as
`unsupported-version` rather than mangling them.
`graalvm/setup-graalvm` reads the same datasource from its own
`java-version` input, so it gets the same treatment.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
Add 'forgejo' to supportedPresetSources mapping to the forgejo-tags
datasource, so pinned forgejo> preset references (e.g.
"forgejo>org/repo#1.2.3") can be extracted and updated, matching the
existing support for gitea> presets.
Closes discussion #40153.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* test(versioning): cover four uncovered guards
- `cargo`: a converted range whose operators are already attached to
their versions, so `npm2cargo` leaves the elements alone
- `hashicorp`: a widen on a complex range ending in `>=`, which the
underlying semver implementation refuses and returns null for
- `poetry`: a version list holding an unparseable entry, for both
`getSatisfyingVersion` and `minSatisfyingVersion`, plus the same widen
refusal, which falls back to the current value
- `swift`: an open-ended range whose bound is not a valid semver
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(versioning): drop two guards that cannot be false
- `hermit`: `major` is the only mandatory group in the version regex, so
the `if (major)` around the first release segment always holds; the
sibling parser already pushes it unconditionally
- `rpm`: after `c1 === c2` is ruled out, one of `c1 > c2` and `c1 < c2`
must hold, so the second comparison becomes a ternary
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(versioning/gradle): cover three uncovered guards
- `getMajor` on a version whose leading token is not a number
- `getSatisfyingVersion` with a later match that is not greater than the
one already held
- `minSatisfyingVersion` with a later match that is not smaller than the
one already held
Two branches in `compare.ts` are still open and are looked at next.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(versioning/ivy): cover a later match that is not the greatest
`getSatisfyingVersion` kept every matching version it walked past, so the
guard that holds on to the one already found was never taken.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(versioning): drop three guards that cannot be false
- `gradle`: `tokenCmp` compares two tokens whose only types are `Number`
and `String`, so once both-numbers and both-strings are handled
exactly one side is a number; the chain becomes a ternary and the
unreachable `return 0` goes
- `ivy`: `tokenize` always yields at least one token for a non-empty
version, so the length check around the last one always holds
- `ivy`: `rangeToStr` only returns null for a null range, which the
`length === 1` check above it already rules out
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(versioning/nuget): cover a later match that is not the greatest
Both loops in `getSatisfyingVersion` walked version lists that only ever
climbed, so the guard that holds on to the candidate already found was
never taken -- once for a bracket range, once for a bare version acting
as a minimum.
Also pin down that a floating range needs a major component: `-*` alone
does not parse.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(versioning): drop three more guards that cannot be false
- `maven`: the interval picked for auto-extension is only picked when the
new value is at or past its upper bound, and that upper bound is the
lower one incremented, so the new value never sorts below the lower
bound
- `maven`: an interval never has both bounds open, because the parser
only nulls one side when the other is a version
- `nuget`: a floating range always carries a major component, so neither
the parser's NaN check nor the serializer's undefined check can fail
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(versioning/pep440): drop three unreachable range paths
`updateRangeValue` handles `!=`, a `.*` prefix, `~=`, `==` and `<=`
itself, `handleUpperBound` handles `<`, and `parseCurrentRange` rejects
`===`. Only `>` and `>=` can reach `handleLowerBound`, so its operator
check always holds and it never returns null. Making it total removes
the null check at the call site as well.
This does drop the `logger.error` that followed, which was guarded by a
comment saying it could not happen unless pep440 itself changed.
`isLessThanRange` classifies the same fixed operator set, so the last
check there is the only one left and its fallback was dead.
`divideCompatibleReleaseRange` only ever sees a `~=` version, and pep440
requires at least two release components after that operator, so there
is always a component to drop. A new test pins down that `~=7` is not a
valid range.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(versioning): cover three more uncovered guards
- `elm`: a widen where the new version sits below the lower bound, so
the upper bound is kept rather than extended
- `hex`: a widen on a complex range ending in `>=`, which the underlying
semver implementation refuses and returns null for
- `rust-release-channel`: a concrete current version paired with a
channel name as the new version, which falls through to the new
version unchanged
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(versioning): drop two more guards that cannot be false
- `composer`: `getNewValue` falls back to the new version whenever
nothing else matched, so it never returns null and the recursive call
in the or-range path always has a value to work with
- `docker`: a version's prerelease is always a string, so once an empty
one on either side is handled, the remaining pair is both non-empty
and unequal
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(config,datasource): cover two more uncovered guards
- `config/validation`: `hostRules[].allowInternal` nested inside a
`packageRules[]` entry, which reports a plain configuration error
rather than a security one, because nested host rules are never applied
- `datasource/docker`: a non-list manifest whose response carries no
`docker-content-digest` header, so the digest falls back to the hash of
the manifest body
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(config,datasource): unwind three branches v8 could not count
- `config/decrypt` and `datasource/docker/common` each picked a response
with an `await` inside a ternary. v8 gives the branch that follows such
a ternary a negative hit count, which the reporters read as uncovered
even though both paths run. Spelling the choice as an if/else makes the
count come out right; behaviour is unchanged.
- `datasource/index` and `datasource/npm/npmrc` each ended an exhaustive
chain with an `else if` whose implicit else v8 counts but nothing can
reach: `registryStrategy` is one of three literals, and `npmrc` starts
as an object and is only ever reassigned to one.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/global): cover seven uncovered guards
The config parsers rewrite legacy boolean spellings of `dryRun`,
`requireConfig` and `platformCommit`, but nothing passed a value that was
already current, so the fall-through of each chain went untaken. Cover it
from both the CLI and the environment.
Also cover a datasource env var that names no auth field, one whose value
is undefined, a default config file that cannot be read, and the error
`isLimitReached` throws when asked for a concurrent limit without a
branch config. That last one had an ignore hint saying it should not
happen; the error message documents the contract, so it is now tested and
the hint is gone.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(workers/global): drop a guard that cannot be false
`getConfig` in the additional-config-file parser returns early unless
`RENOVATE_ADDITIONAL_CONFIG_FILE` is set, so by the time a parse error is
being classified the variable is always present.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(workers/repository): drop a dead changelog file lookup
The gitea, gitlab and forgejo changelog sources each declared `files` as
an empty array and then guarded the only assignment to it with
`if (!files.length)`, which is always true one line after the
declaration. Collapse it to the filter itself.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* ci: ratchet the branch coverage threshold
A full run on this branch reports 100% statements (49471/49471), 100%
functions (7381/7381), 100% lines (48961/48961) and 99.73% branches
(25690/25759), up from 99.53% before this branch's work. The threshold
moves to 99.71, keeping the same rounding-down margin as before.
Statements, functions and lines are already pinned at 100 and stay
there.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(config,datasource): say why the ternary was unwound
Both sites now carry a comment explaining that the if/else is deliberate:
v8 gives the branch following an `await` inside a ternary a negative hit
count, which the coverage reporters read as uncovered.
`apiCheckResponse` also gets an explicit `HttpResponse<unknown>` type,
which is what the two branches have in common and what
`isECRMaxResultsResponse` already expects.
Also fix a type error in the new config file spec: the `pathExists` spy
needs the same `as never` cast the surrounding tests use.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* refactor(versioning/pep440): use a template literal in handleLowerBound
The exclude-style return is the one line this branch rewrote in that
function, so it now builds its string with a template literal instead of
the `+` operator.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
* test(workers/global): drop the cast from the limits throw test
Calling the overload with a non-null assertion on the config argument
says the same thing without retyping the whole function.
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
---------
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
fix: treat forgejo like gitea for onboarding branch markdown/html-comment support
Forgejo's platform implementation reuses Gitea's massageMarkdown and
supports the same HTML-comment rendering, but three onboarding-branch
checks only allowlisted 'github', 'gitea' and 'gitlab', causing
Forgejo to skip onboarding-branch rebase, force unconditional PR
update requests, and bypass config-hash change detection.
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>