Commit Graph

3705 Commits

Author SHA1 Message Date
Michael Kriese f711fd46e2 test: reach 100% branch coverage (#46284)
* 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>
2026-09-17 07:08:48 +00:00
Michael Kriese f1ace2dfdc test(workers/repository): close more branch coverage gaps (#46269)
* 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>
2026-09-17 07:08:47 +00:00
Michael Kriese f7e8104511 test(workers/repository): close more branch coverage gaps (#46259)
* 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>
2026-09-17 07:08:46 +00:00
Michael Kriese 7960e5d077 test(workers/repository): close more branch coverage gaps (#46254)
* 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>
2026-09-17 07:08:46 +00:00
Sebastian Poxhofer 4215f35059 feat(config): add minimumReleaseAgeBuffer option (#45577)
* 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
2026-09-17 07:08:00 +00:00
Michael Kriese 59852479bf test: close branch coverage gaps outside the manager tree (#46252)
* 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>
2026-09-16 12:03:23 +00:00
Skyler Mäntysaari f6bbc9f579 fix(workers/onboarding): treat forgejo like gitea for onboarding-branch rebase and config-hash detection (#46200)
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>
2026-09-16 11:56:33 +00:00
Jamie Tanna 5ddaf0a30e fix(changelog): prefer precise tags over floating ones for compare links (#46210)
* fix(changelog): prefer precise tags over floating ones for compare links

As noticed in [0], if Renovate upgrades v7.0.0 → v7.0.1 on a repository
with "floating" tags, the "floating" tag takes precedence over the
non-floating tag.

While the changelog is rendered correctly, the "Compare Source" link is
broken.

We should instead look for the most precise version (the version with
the most `.` separators) that exists.

Co-Authored-By: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>

[0]: https://github.com/astral-sh/uv/pull/21654#issuecomment-5669818744

* fixup! fix(changelog): prefer precise tags over floating ones for compare links

Add a test where a precise tag precedes a floating one that resolves
to the same version, to cover the `reduce` branch that keeps the
current most-precise candidate. Codecov flagged line 284 of source.ts
as uncovered on PR #46210.

Co-Authored-By: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>

---------

Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
2026-09-16 11:02:52 +00:00
Jamie Tanna 54c7e5e77b perf(workers/repository): don't log all matched files at DEBUG (#46228)
On a repository with many hundreds (or thousands) of files matched by a
Manager, this log line can be significantly large.

For users who still want this information, we can relegate it to TRACE,
but keep the high-level info as a DEBUG that's still useful to know.
2026-09-16 07:03:57 +00:00
Gregor Zeitlinger 2e951d2254 feat(mise): support selector lockfile updates (#44615)
* mise: support selector lockfile updates

* fix(mise): handle empty lockfile AST

* test(mise): use valid empty TOML AST fixture

* fix(mise): support current regex utility

* fix: clarify lockfile-only lookup handling

* fix(mise): handle selector lockfile updates correctly

* test(mise): cover selector lockfile review fixes

* fix(mise): scope selector artifact refresh

* chore: retrigger CI

* fix(mise): remove unreachable lockfile guard

* refactor(lookup): clarify lockfile-only value handling

* refactor(mise): use shared TOML utilities

* fix(mise): handle selector lockfile edge cases

* fix(mise): satisfy selector CI coverage

* fix(mise): require dependency names

* fix(mise): preserve configured dependency names

* fix(mise): address selector lockfile review feedback

* test(mise): cover major selector lockfile updates

* chore(static-data): auto-update Java LTS versions

So we don't have a hardcoded set of data in the mise manager.

Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>

* fix(mise): address selector lockfile feedback

* test(manager/mise): cover nonnumeric current version prefix

formatLockedVersion()'s currentPrefix fallback to '' only runs when
the current version has no leading numeric match, which no existing
test reached.

Co-Authored-By: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>

---------

Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
2026-09-14 15:57:22 +00:00
Michael Kriese 7aedcb24d2 test(workers/lookup): cover untested branches under lookup/ (#45930)
* test(workers/lookup): cover the remaining generateUpdate branches

Adds a unit spec for `generateUpdate()` exercising the branches that the
surrounding lookup tests never reached: a release carrying `checksumUrl`,
`downloadUrl` and `newDigest`, a versioning api whose `getNewValue`
throws, and an update with no current version.

With those covered, the last istanbul ignores in the file can go, so
`generate.ts` now reports full statement and branch coverage with
nothing suppressed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(workers/lookup): cover two untested lookup branches

Adds tests for two paths flagged by the `TODO: add test #40625` markers
that no existing test reached:

- `filterInternalChecks()` with a release that has no `releaseTimestamp`
  while `minimumReleaseAgeBehaviour` is unset. The existing tests only
  cover `timestamp-required` and `timestamp-optional`, so the release is
  now checked to be accepted without being reported under either.
- `lookupUpdates()` when no current version can be resolved but a
  `lockedVersion` is set, which must return without marking the
  dependency `invalid-value`.

The ignore comments are left in place, so these branches stay excluded
from the coverage report; each test instead asserts an outcome that only
holds on the intended path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(workers/lookup): cover error paths in bucket, current and rollback

Adds unit specs for three exported helpers whose guard clauses no test
reached: `getBucket()` when the versioning api cannot determine a major
or a minor, `getCurrentVersion()` when the current value is not a string,
and `getRollbackUpdate()` when the versioning api lacks
`isLessThanRange()`, when `isLessThanRange()` throws, and when there is
nothing older to roll back to.

The ignore comments on those branches are left in place, so the coverage
report is unchanged; each test asserts the return value that is only
produced by the guard it targets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(workers/lookup): cover filter and rollback guards

Covers three more guards that no test reached: `filterVersions()` with
no current version, and `filterByMaxMajorIncrement()` when the versioning
api cannot determine the current major or a release's major. Adds a
`lookupUpdates()` test for the case where rollbacks are enabled but
nothing older exists to roll back to, which warns instead of updating.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(workers/lookup): cover the vulnerability alert range strategy fallback

A vulnerability alert configured with `rangeStrategy=update-lockfile` but
no locked version falls back to `bump`, which no test covered. The new
test asserts the range is widened and the update is marked as a bump,
which only happens once the fallback has been applied.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(workers/lookup): explain the remaining coverage ignores

Each ignore left under `lookup/` now records why the branch it hides is
not reached, instead of pointing at a TODO with no detail. No ignore is
added or removed, and the coverage report is unchanged.

The explanation goes above the hint rather than after it: a hint only
applies to the statement on the line immediately below, so trailing
comment lines silently stop it from suppressing anything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(workers/lookup): cover three more unreached lookup branches

Covers the guards behind three of the remaining ignore hints:

- `getRollbackUpdate()` when the release it falls back to carries an
  empty version, so there is nothing to roll back to after all
- `filterVersions()` coercing a release version that the configured
  versioning accepts but semver cannot parse, which happens once
  `allowedVersions` falls back to semver range syntax
- `lookupUpdates()` when the datasource rejects every candidate release
  during postprocessing, leaving a bucket with no release to update to

The ignore comments stay in place, so the coverage report is unchanged;
each test asserts an outcome that only holds on the intended path.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

* docs(workers/lookup): correct the ignore reasons this branch disproves

Three of the reasons added alongside the previous batch of tests claimed
their branch was unreachable, which the tests in this branch disprove:
`getRollbackUpdate()` can pop a versionless release, `filterVersions()`
does coerce a version semver cannot parse, and `filterInternalChecks()`
yields no release when a datasource rejects every candidate. Each now
describes what actually reaches it.

Also sharpens two of the reasons that still stand. The `getBucket()`
else contradicts the `isVersion()` filter every release has already
passed, so it reads as unreachable rather than untested; and a lookup
whose releases are all unversioned tags never reaches the empty-version
block, because a configured digest routes it to the digest-only path
first.

No ignore is added or removed, and the coverage report is unchanged.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
2026-09-14 12:29:39 +00:00
Michael Kriese ec4ba7b381 refactor(workers/lookup): drop stale istanbul ignores in generateUpdate (#45929)
Vitest uses the v8 coverage provider, and v8-to-istanbul still honours
istanbul hints, so these comments suppress real branch coverage rather
than being inert. The `releaseTimestamp` and `registryUrl` branches are
both exercised by the existing lookup tests, so their ignores only hid
coverage that was already there.

The remaining ignores in this file stay: no datasource used in these
tests reports a checksum url, and the `getNewValue` catch block and the
missing-currentVersion guard are still unreachable.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 12:29:38 +00:00
Sebastian Poxhofer 4b4d14ec00 feat(manager): pass extracted constraints to updateArtifacts (#45826)
* 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.
2026-09-14 11:09:07 +00:00
MFietze 96e72f6a93 fix(platform/gitlab): skip projects marked for deletion (#45928) 2026-09-14 10:40:41 +00:00
Michael Kriese 874060f7ee refactor(lookup): remove redundant currentValue check (#45933)
* refactor(lookup): remove redundant currentValue check

The inner `if (config.currentValue)` was already guaranteed by the
enclosing condition, so the `v8 ignore else` comment was hiding an
unreachable branch rather than an untested one. Drop both; the
`invalid-value` path remains fully covered by existing tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Update lib/workers/repository/process/lookup/index.ts

Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>

* refactor(lookup): use safeStringify for invalid currentValue log

Template interpolation of a non-string `currentValue` yields
`[object Object]`, which hides the value that made it invalid. Use
`safeStringify` so objects and arrays are logged readably.

Co-Authored-By: Claude Opus 5 <michael.kriese+claude-code@mend.io>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
Co-authored-by: Claude Opus 5 <michael.kriese+claude-code@mend.io>
2026-09-14 09:59:37 +00:00
Sebastian Poxhofer 44160755f9 feat(platform): validate gitea responses with schemas and share the implementation with forgejo (#45825)
* refactor(platform): share one implementation between gitea and forgejo

The `forgejo` platform was copied from `gitea` in #37075 and the two have
drifted since: fixes landed on one side only (zod validation #43900, empty
email #44068, null issues #44048, team reviewers #39533, merge-style fallback
#37768 vs #38040), and lint sweeps had to be applied to both copies by hand.

Both platforms now build on `lib/modules/platform/gitea-common/`, which holds
the zod schemas, API helpers, utils, PR cache and the platform object itself.
`createPlatform()` closes over its own state, so `gitea` and `forgejo` keep
separate config, caches and host types. `gitea/index.ts` and `forgejo/index.ts`
are thin wrappers that supply the platform id, default endpoint, http client
and the version gates that genuinely differ.

Reconciliations, forgejo's behaviour adopted for gitea:

- Responses are validated with zod instead of `getJsonUnchecked` casts. Gitea
  therefore tolerates empty emails, null PRs in list responses and lenient
  repo/issue payloads the same way Forgejo already did.
- `getRepoContents` returns a discriminated union, so `getRawFile` returns null
  for dir/symlink/submodule entries instead of reading a missing field. The
  gitea preset resolver now throws PRESET_INVALID for non-file entries, like
  the forgejo one.
- An unknown `default_merge_style` (e.g. `manually-merged`) is caught by the
  schema and skipped, instead of aborting the repository with
  REPOSITORY_BLOCKED from `isAllowed()`.
- `initRepo()` resolves whether the repo owner is an org and only fetches org
  labels when it is, adding one `/orgs/{owner}` request and an `orgs` entry in
  the gitea repository cache.
- Reviewers prefixed with `team:` are sent as `team_reviewers`. Gitea keeps its
  `v1.14.0` guard for requesting reviewers at all.
- `getBranchStatusCheck()` maps unknown commit status values through the
  schema's `unknown` fallback rather than warning and returning yellow.

Gitea's behaviour is kept where it is deliberately different: the Forgejo
instance detection and its deprecation notice, the `1.24.0` (or `10.0.0` for a
detected Forgejo) native automerge gate, and the reviewer version guard are
passed in as options.

Two shared log messages became static so they can be grouped in metrics:
`{platform}-native automerge: fail` is now `Platform-native automerge: fail`
and `{Platform} getRepos() error` is now `Error auto-discovering repositories`,
both with the platform id in the metadata. The dead `defaults.hostType` field
is gone.

* refactor(platform): keep the shared gitea implementation under gitea/

Drop the `gitea-common/` folder. The zod schemas, API helpers, utils, PR
cache and `createPlatform()` live in `lib/modules/platform/gitea/` under
their original file names, and `forgejo/` imports them from there.

The preset resolvers pass `giteaHttp` and `forgejoHttp` to
`getRepoContents()` directly, which removes the two bound wrappers. The
gitea changelog source uses the shared `RepoContents` type like the
forgejo one already does.

* refactor(http): derive ForgejoHttp from GiteaHttp

The two clients were identical apart from the default `hostType`.
`ForgejoHttp` now extends `GiteaHttp` and only keeps its own base url,
so the pagination logic exists once. The platform helpers accept
`GiteaHttp` directly instead of the `HttpBase` alias.

Also covers the `updatePr()` branch where the updated pull request
cannot be converted and therefore is not cached.

* refactor(platform): create the gitea and forgejo http clients in index.ts

`forgejo-helper.ts` only held the `ForgejoHttp` instance, so it is gone
and `forgejo/index.ts` creates the client next to the platform. Gitea
does the same for symmetry, which leaves `gitea-helper.ts` with only the
shared API helpers.

* refactor(platform): fetch gitea labels and issues with await

`getLabelList()` and `getIssueList()` still memoize the in-flight
promise, but build it from small async functions instead of `.then()`
and `.catch()` chains.

* refactor(platform): keep declaration order of gitea/index.ts

Order the module the way it was before `createPlatform()` so the diff
against main aligns: platform id and http client after the repo config
interface, pure helpers before the factory, and `maxBodyLength()` after
the platform object.

* refactor(platform): move gitea label fetching to labels.ts

`fetchLabelList()` takes the http client and the repository details
instead of closing over the platform state, and gets its own spec. The
factory only keeps the memoizing `getLabelList()`.

* refactor(platform): move gitea label lookup to labels.ts

* refactor(platform): create the gitea pr cache per platform

`GiteaPrCache` is now created once per platform with its http client and
platform id, and `initRepo()` records the repository. The per-repository
state moves to a private `RepoPrCache` which is bound to the repository
cache on first use and reused until the next `initRepo()`, instead of
being rebuilt on every lookup.
2026-09-14 08:45:04 +00:00
Adam Setch 65f803630e fix(dashboard): keep abandoned note outside the <details> block (#45902)
fix(dashboard): keep abandoned note outside the <details> block

GitHub only renders alert callouts at the top level of a body, not
inside collapsible <details> sections. Move the abandoned-dependencies
note above the details block so it renders as a styled note.
2026-09-12 14:51:51 +00:00
Matthew Nitschke ac22678812 feat(datasource/npm): integrity and tarball extraction (#44315)
* added support for extracting integrity and tarball from npm datasource

* test(datasource/npm): keep integrity and tarball in packument shape assertions

The packument-trimming tests on main pinned a cached shape that dropped
`dist.tarball`, so they broke once the schema started retaining
`integrity` and `tarball`. Update both expectations to the new shape and
cover `integrity` alongside `tarball`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(workers/lookup): expect integrity and tarball on npm updates

`generateUpdate` copies `downloadUrl` and `newDigest` from the release
onto the update, so every npm lookup in these tests now carries the
tarball URL and, where the packument fixture has one, the integrity
hash. Pin and rollback updates are built without those release fields,
so their expectations are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Michael Kriese <michael.kriese@mend.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-11 10:09:59 +00:00
Sebastian Poxhofer e5ef25bbba feat(datasource/gitlab-tags): replace getJsonUnchecked with validated getJson (#44144)
* fix(datasource/gitlab-tags): replace getJsonUnchecked with validated getJson

The go datasource embeds GitlabTagsDatasource, so its test mocks are
updated to satisfy the stricter GitlabTag schema (commit.id/created_at).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test: fixup tests

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-11 08:54:01 +00:00
Jamie Tanna 238e15a498 feat(config): add inheritConfigTrusted to trust inherited-config hostRules
Renovate deployments that are deployed across multiple organisations may
take advantage of Inherited Config as a means to decentralise some
decisions and/or preferences that their organisations would like to use
for some self-hosted configuration.

Similarly, the decision of which hosts to allowlist may be something
that a self-hosted administrator /may/ want to allow their organisation
admins to decide.

We can make this an opt-in capability to indicate that the self-hosted
administrator trusts the Inherited Config moreso, and allows someone who
can write to it the ability to allowlist access to internal hosts that
may not be intended.

If a self-hosted administrator sets `allowInternal: false`, this takes
precedence, allowing a "veto", and a self-hosted admin's `headers` are
still preferred, too.

It's not possible for Inherited Config to set grants for `extends`, as
config resolution has already occurred, so this still needs to be
performed at the self-hosted config level.

This trust is transitive: `hostRules` defined in presets the
inherited config `extends` are registered at the same inherited
tier once resolved, so this option must only be enabled when those
preset sources are trusted too.

Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-10 14:52:01 +01:00
Jamie Tanna 270a3b7719 fix(workers/repository/init): surface inherited-config validation errors
Indirectly related to our changes to Host Rules, it was noticed that we
previously did not provide as helpful an error when a config validation
error occurs with Inherited Config.

Instead of throwing a `CONFIG_VALIDATION`, without a `validationSource`,
`validationError` or `validationMessage`, we can instead throw it
accordingly.

Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-10 14:52:01 +01:00
Jamie Tanna acb46bc706 fix(config): surface blocked hosts more clearly when resolving config
With the new hardening on blocking internal hosts with outbound HTTP
calls, we should build on top of this to ensure that the UX is better
when a preset is blocked.

We can do this by making sure that when `HOST_BLOCKED`, we report a more
targeted config validation error.

We can also make sure to pass around our `internalGrants`, similar to
how we currently allow an admin's `headers` are handled.

Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-10 14:52:01 +01:00
Jamie Tanna 09d2da2f1b feat(host-rules): add allowInternal
As part of follow-up changes, we're looking to tighten the way that
Renovate handles outgoing HTTP traffic.

Before we do this, we can introduce a new config option,
`hostRules[].allowInternal`, that allows a self-hosted administrator to
mark an internal host as "trusted" for access.

This wires in the new global-only config option, `allowInternal`, which
implies a new internal field `internalHostGrant`.

We'll add full documentation in a future commit, when the full
enforcement is in place, as there are some wider changes to enforce it
fully.

Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-10 14:52:01 +01:00
Dharma Teja Thoutam 5ec380f402 fix(changelog): log external host errors below the error level (#45798)
* fix(changelog): log external host errors below the error level

* fix: restore index.spec.ts to upstream state

* test: cover changelog external host error log level
2026-09-10 12:29:59 +00:00
Adam Setch 4afe14fb19 refactor: emojify pr body notes (#45646)
* refactor: emojify pr body notes

Signed-off-by: Adam Setch <adam.setch@outlook.com>

* refactor: emojify pr body notes

Signed-off-by: Adam Setch <adam.setch@outlook.com>

* refactor: emojify pr body notes

Signed-off-by: Adam Setch <adam.setch@outlook.com>

---------

Signed-off-by: Adam Setch <adam.setch@outlook.com>
2026-09-10 12:17:28 +00:00
RahulGautamSingh 9cecb86eca feat(config): add experimental exitCodeForErrors option (#45752)
* feat(config): add experimental exitCodeForErrors option

* feat(config): give unknown-error its own exit code

* docs: restore exit codes 1 and 2 to the table
2026-09-10 11:07:26 +00:00
Sebastian Poxhofer 4573aa6ac8 feat(automerge): attempt branch automerge before falling back to a PR on merge queue rejection (#45724)
* feat(automerge): attempt branch automerge before falling back to a PR on merge queue rejection

* refactor(automerge): ask the platform instead of parsing merge queue rejection messages
2026-09-09 15:21:47 +00:00
Sebastian Poxhofer 7b25e97ba3 feat(github): add support for using merge queues without auto merge (#45721)
* feat(github): add experimental support for using merge queues without auto merge

* test(github): use vi.stubEnv for merge queue env flag

* fix(github): do not re-enqueue PRs already waiting in the merge queue

* refactor(github): reuse merge queue detection from #45252 for isBranchMergeQueueEnabled

* refactor(github): read the base branch from the PR instead of extending MergePRConfig

* feat(github): enable merge queue support without the RENOVATE_X_GITHUB_MERGE_QUEUE flag

* Apply suggestion from @secustor
2026-09-09 15:21:45 +00:00
Sebastian Poxhofer b2d3b64c48 refactor(lint): enforce err key for errors in logger metadata (#45759)
* refactor(lint): enforce err key for errors in logger metadata

* refactor(lint): flag error message and stack strings in logger-err-key
2026-09-09 13:55:14 +00:00
renovate[bot] 9d74625d93 build(deps): update dependency prettier to v3.9.6 (main) (#45815)
* build(deps): update dependency prettier to v3.9.6

| datasource | package  | from  | to    |
| ---------- | -------- | ----- | ----- |
| npm        | prettier | 3.8.1 | 3.9.6 |

* chore: prettier fixes

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Kriese <michael.kriese@mend.io>
2026-09-09 13:50:03 +00:00
renovate[bot] 0e5b2287e1 build(deps): update dependency markdown-it to v15 (main) (#45796)
* build(deps): update dependency markdown-it to v15

| datasource | package     | from   | to     |
| ---------- | ----------- | ------ | ------ |
| npm        | markdown-it | 14.3.1 | 15.0.1 |

* fix: migration

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Michael Kriese <michael.kriese@mend.io>
2026-09-09 12:32:23 +00:00
Jamie Tanna a806d2f735 feat(onboarding): note that vulnerabilityAlerts can be rate limited (#45686)
For instance, when a repository is onboarding with many dependency
updates (some of which have many security releases), it would be
useful to let the user know that they're not unlimited, but that the
rate limit is taken into account.

Now we have the capability to specify these, we should:

- take them into account when determining how many PR(s) may be raised
  at a given time
- point users to the documentation when they may want to limit them, if
  the number of security PRs is more than the
  `prConcurrentLimit`/`branchConcurrentLimit`

Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
2026-09-04 20:05:08 +00:00
Sebastian Poxhofer 05e1271376 test: use expect().resolves instead of awaiting inside expect (#45672)
Enables the vitest/prefer-expect-resolves oxlint rule and applies its
autofix across the spec suite.
2026-09-04 10:40:46 +00:00
Sebastian Poxhofer df8c4051ff test(core): replace file snapshots with explicit assertions (#45660)
* test(core): replace file snapshots with explicit assertions

* test(core): replace inline snapshots with explicit assertions

* test(core): assert on relevant sections instead of full bodies

* test(core): drop assertions implied by full equality

* test(core): address review feedback

* test(core): use resolves instead of awaiting inside expect
2026-09-04 10:28:55 +00:00
Gauke Teijema e10ef79236 fix(bump-versions): collect bumpVersions from all upgrades, not just upgrades[0] (#44809)
* fix(bump-versions): collect bumpVersions from all upgrades, not just upgrades[0]

* chore: add test for duplicate bumpversion configs

* chore: use suggested safeStringify

* chore: use coerceArray
2026-09-04 10:09:28 +00:00
Sergei Zharinov 09ff8d22b6 test: Validate minimumReleaseAge is ignored for security updates (#44272)
* test: validate minimumReleaseAge does not apply to security updates

* test: add end-to-end coverage for security-update minimumReleaseAge bypass

Drive the real vulnerability flow (appendVulnerabilityPackageRules) through the
real lookupUpdates, instead of only simulating the generated packageRule. Also
reword the inline comment and use luxon/asTimestamp for timestamps.

* test: fix type-check on e2e lookup config

Cast the spread config to satisfy partial() overload resolution, matching the
existing `getConfig() as never` idiom in the lookup spec.

* test: drop redundant inline lookup test in favor of end-to-end coverage

The simulated lookup test hand-built the security packageRule; the end-to-end
test plus the generated-rule assertion in vulnerabilities.spec.ts cover the same
behavior against the real flow, so the inline test added no unique coverage.

* test(vulnerability): align lookup config types
2026-09-04 09:31:58 +00:00
Jamie Tanna 4530b9e8c2 fix(workers/repository): ensure internal checks are applied when a rebase is requested
Previously, if we ticked the checkbox for a PR that's open, but has
pending updates, we recreate the branch with the latest version,
regardless of whether it's `pending` or not.

This can lead to cases where we accidentally bring an update that's not
passed `minimumReleaseAge` into the branch.

To prevent this, we can make sure that we only treat a Dependency
Dashboard checkbox/PR rebase request as consent for an update when it is
the new `unpend` check, or when using `checkedBranches`.

For visibility, we both log an INFO and `ensureComment` on the PR to
indicate why this branch isn't being updated past the version we're
currently proposing (which may change from the rebase update, but won't
include a pending version).

The comment is removed once a requested rebase is applied, and is only
removed when rebases are requested, avoiding unnecessary platform
requests on non-rebase updates.

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 16:03:26 +01:00
Jamie Tanna bb8c9cbc7f refactor(workers/repository): add a separate check for "don't make this branch as pending"
As part of follow-up changes, we're looking to correct behaviour when
rebasing a given open PR, where the PR includes a set of pending
changes.

Before we do this, we can refactor the dependency dashboard to add a new
`unpend` check type, to say "create a branch, even though I know the
branch is pending status checks".

Previously, this section would share `approvePr` alongside other checks,
which may not make sense.

As `approvePr` currently forces PR creation, we need to treat `unpend`
similarly, so we don't create the branch, but not the PR when i.e.
`prCreation=approval`.

As the Dashboard is being re-rendered, a checkbox that was previously
checked (before this version of Renovate) will need re-checking.

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 16:03:26 +01:00
Jamie Tanna f1c39b202a refactor(workers/repository): extract a type for the Dependency Dashboard checks
As part of future changes, we're adding a new type of checkbox for the
Dependency Dashboard.

Before we do this, we can extract an enum for the known types.

We don't touch `dependencyDashboardChecks` itself, as that also includes
`configMigrationCheckboxState`, which isn't related to the per-branch
checks.

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 16:03:26 +01:00
oliverpool 0bfd189a2e fix: skip empty post upgrade task (#45227)
* tests: empty postUpgradeTasks should be skipped

* fix: skip empty post-upgrade task

* check: prettier
2026-09-03 14:39:12 +00:00
Birger Johan Nordølum 19e4cdc47f fix(lookup): preserve current compatibility (#45171)
* fix: preserve current compatibility after lookup

* fix: expose current compatibility in lookup result

* test: preserve current compatibility after lookup

* test: preserve compatibility through fetch

* test: isolate current compatibility regression

* test: isolate current compatibility regression

* test: isolate current compatibility regression

* test: isolate current compatibility regression
2026-09-03 14:29:15 +00:00
Marius b22f58045e fix(manager): Handle variables with default values in registry aliases (#36293)
* fix(manager): handle registryAliases with shell variable defaults

Allows `registryAliases` keys to contain shell-style variables with
empty (or other) defaults, e.g. `${DEPENDENCY_PROXY:-}` or
`${CI_REGISTRY:-}`. The same image string is then resolved both
online (variable expanded) and offline (variable empty).

Changes in `dockerfile/extract.ts` (used by `gitlabci` too):
* Match the alias key as either `${name}/...` (existing) or `${name}...`
  when `name` already ends in a non-identifier boundary character such
  as `}`. Bare-identifier keys like `$CI_REGISTRY` continue to require
  the slash boundary so they no longer overmatch keys that begin with
  the same prefix (e.g. `$CI_REGISTRY` vs `$CI_REGISTRY_IMAGE`).
* Use `ensureTrailingSlash` to handle alias values that already include
  a trailing slash.
* Preserve the original `name` and path structure in `dep.depName`
  instead of substringing only after the alias `value`. This keeps the
  variable in the depName so it survives a `splitImageParts` round-trip
  and the resulting branch name comes out clean.

Adds `sanitizeDepName` regex passes in
`lib/workers/repository/updates/flatten.ts` so any leftover
`${VAR}`/`${VAR:-...}` placeholder is stripped from the depName before
it becomes a branch name (otherwise we'd get
`renovate/${dependency_proxy}python-3.x` instead of
`renovate/python-3.x`).

Tests cover:
* `${VAR:-}/image` (with slash) and `${VAR:-}image` (no slash, requires
  the boundary form).
* Overlapping prefixes: `$CI_REGISTRY` vs `$CI_REGISTRY_IMAGE`.
* Registry alias with port (`localhost:5000/repo`).
* `sanitizeDepName` strips `${VAR}` / `${VAR:-}` / bare `$VAR`.

Reproducer: https://github.com/Wayneoween/renovate-36293-repro

* revert(manager/gitlabci): keep regex group name as 'prefix'

Reverting the unrelated `prefix` -> `rawPrefix` rename in `depProxyRe`.
Per review on #36293, this rename is out of scope for the registry-alias
fix and just adds review surface.

* refactor(manager/dockerfile): narrow alias boundary check to '}'

Per review on #36293. The previous regex `/[^A-Za-z0-9_]$/` matched any
non-word ending, which is broader than the documented case (alias keys
ending in `}` from `${VAR}` / `${VAR:-...}`). Replace with a plain
`name.endsWith('}')` check, which also avoids re-building a regex on
every iteration of the alias loop.

Existing tests (`'variables with default and slash'`,
`'variables with default no slash'`, `'alias with digest only'`)
continue to exercise the `}`-boundary path; the `'overlapping prefixes'`
case continues to exercise the slash-only path.

* chore(manager/dockerfile): trim comment and TODO library/ strip edge

Per review on #36293:
* Drop the verbose explanation block above the depName-restore branch;
  the spec rows already document the expected shape.
* Add a TODO above the `dep.depName?.startsWith(valueWithSlash)` guard
  noting a pre-existing edge case: when the inner `getDep` strips a
  `library/` prefix (or similar) the depName no longer starts with
  `valueWithSlash`, so the alias-rooted depName is not restored. Out of
  scope for this PR.

* fix(manager/dockerfile): handle empty registry alias value

- strip trailing slash after ${VAR} placeholders in sanitizeDepName
- treat an empty registry alias value as Docker Hub instead of '/'
- clarify registry alias docs wording

* refactor(manager/dockerfile): drop redundant alias boundary guard

- `matchedAtBoundary` is only read behind `!matchedWithSlash`
- add docker-compose test for `${VAR:-}` registry aliases

* test(manager/dockerfile): pin special-prefix registry alias behavior

`getDep` strips `library/`, `amd64/` and `arm64/` as special prefixes, so
an alias whose value is one of those loses its alias-rooted `depName`.
Both the slash form and the variable form behave this way on `main`.
2026-09-03 12:43:47 +00:00
Sebastian Poxhofer 3412de84ce refactor(linters): enable oxlint rule prefer-named-capture-group (#44714)
* refactor(lint): enable oxlint prefer-named-capture-group at error

Numbered capture groups (m[1], $1) break silently when a regex is
edited and group order shifts. Named groups (?<name>...) make intent
explicit and accesses (m.groups.name) resilient to reordering.

This enables the built-in eslint rule prefer-named-capture-group,
surfacing 321 pre-existing unnamed-group violations across ~121
files to be burned down incrementally in follow-up commits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(versioning): name capture groups for prefer-named-capture-group

Burns down all prefer-named-capture-group violations in
lib/modules/versioning/. Unused capture groups (only reachable via
.test() or whole-match) become non-capturing (?:...); groups whose
value is read become named (?<name>...), with accesses rewritten to
match.groups!.<name> (or destructured from .groups!) instead of
positional match[n]/$n so a future edit that reorders groups cannot
silently break the reader.

Two cases needed extra care to preserve behavior:
- semver/common.ts and poetry/patterns.ts split() on a regex whose
  capture group is relied upon by String.prototype.split to keep the
  separator in the output array; the group is kept capturing and
  named, not converted to non-capturing.
- hashicorp/convertor.ts's semverRegex is only ever embedded via
  .source into a named `(?<version>...)` group elsewhere and never
  matched directly, so its internal groups are safe to make
  non-capturing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(datasource): name capture groups for prefer-named-capture-group

Burns down all prefer-named-capture-group violations under
lib/modules/datasource/. Same treatment as the versioning module:
unused groups become non-capturing, used groups get names, and
positional match[n] reads move to match.groups!.<name>.

docker/ecr.ts and pypi/common.ts keep their positional destructuring
(`const [, region] = ...`, `!!m &&`) working unchanged since a named
capture group is still addressable by its numeric index in
JavaScript — only pypi/common.ts's access was rewritten to
m.groups!.owner for clarity since it read a single group directly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(platform): name capture groups for prefer-named-capture-group

Burns down all prefer-named-capture-group violations under
lib/modules/platform/. Uses the repo's established idiom of
`match.groups?.<name>` after an optional-chained exec()/match() (see
bitbucket/index.ts hostnameWithoutApiPrefix, gerrit/utils.ts
extractSourceBranch) and `$<name>` in String.replace replacement
strings. Unused groups (markdown/HTML massaging regexes matched only
to be stripped) become non-capturing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(manager): name capture groups in ansible-galaxy for prefer-named-capture-group

Burns down prefer-named-capture-group violations in
lib/modules/manager/ansible-galaxy/. newBlockRegEx and blockLineRegEx
dropped their unused outer wrapping group (only the inner key/value
groups were ever read) and named the key/value groups; nameMatchRegex's
remaining unused groups (protocol/user-info/port alternatives) become
non-capturing since only source/hostname/depName/version are read via
.groups. Accesses in collections.ts and roles.ts move from
lineMatch[2]/lineMatch[3] to lineMatch.groups!.key/value.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(manager): name capture groups for prefer-named-capture-group (batch 1)

Burns down prefer-named-capture-group violations across the first
alphabetical slice of lib/modules/manager/ (ansible through mix).
Used groups get names with accesses rewritten to match.groups!.<name>
(or $<name> in replacement strings); groups whose value is never read
become non-capturing (?:...). Two String.split() call sites
(git-submodules/extract.ts, meteor/extract.ts) keep the established
"named group still readable positionally from the split() result
array" idiom instead of restructuring the destructuring.

bazel/rules/go.ts and buildpacks/extract.ts dockerRef verified
byte-for-byte equivalent before/after via a quick node script since
several nested groups collapsed to non-capturing at once.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(manager): name capture groups for prefer-named-capture-group (batch 2)

Burns down prefer-named-capture-group violations across the remaining
alphabetical slice of lib/modules/manager/ (npm through terragrunt).
Same treatment as batch 1: used groups get names with accesses moved
to match.groups!.<name> or $<name> replacement strings; unused groups
(wrapping alternations, quantifier repetition groups only consumed via
.test(), or values never read from a match) become non-capturing.

terraform/extractors/others/modules.ts and terragrunt/modules.ts share
near-identical URL-parsing regexes (github/bitbucket/git/azure-devops
ref matchers) — the same set of always-unused `depth=`/`&depth=`
query-string groups is converted to non-capturing in both files.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(config): name capture groups for prefer-named-capture-group

Burns down prefer-named-capture-group violations in lib/config/.
Used groups get names with replacement strings switched from $1/$2 to
$<name>; the unused separator group in schedule-migration.ts's
afterBeforeRegex becomes non-capturing. presets/parse.ts keeps
assigning through the already-declared `repo` variable rather than
array-destructuring the exec() result, matching this file's existing
style for the other named-group regexes just above it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(util): name capture groups for prefer-named-capture-group

Burns down prefer-named-capture-group violations across lib/util/ and
lib/logger/. Same treatment as previous batches: named groups for
values that are read (via $<name> replacement strings, .groups!.<name>,
or destructured from .groups), non-capturing groups where nothing reads
the captured value (e.g. semantic.ts's angular-commit .test()-only
regex). www-authenticate.ts's tokenizer entries each get their own
named group but keep accessing the match generically via match[1],
since a positionally-indexed named group is still readable by index.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(workers): name capture groups for prefer-named-capture-group

Burns down prefer-named-capture-group violations in lib/workers/.
Used groups get names with $<name> replacement strings; unused
wrapper/alternation groups (e.g. semantic-commit-message.ts's optional
scope/issue wrappers, release-notes.ts's date-heading and
horizontal-rule matchers that are only probed with .search()/replaced
wholesale) become non-capturing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* refactor(test,tools): name capture groups for prefer-named-capture-group

Finishes the prefer-named-capture-group burndown: test/, tools/, and
vitest.config.mts. Groups whose value is read get names ($<name>
replacement strings or .groups.<name> access); groups only used via
.test()/.search() or that feed a .match(/…/g) call (whose return value
is the array of whole matches, not captured groups) become
non-capturing.

This is the last remaining slice of pre-existing violations, so the
rule (already enabled at error repo-wide) now passes with zero
findings across the whole codebase.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017RxHFNsSworLcv23xXk9Hk

* fix prettier failure

* refactor(lint): use a named capture group in `v8-ignore-no-count`

New rule from main trips the newly enabled `prefer-named-capture-group` oxlint rule.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-09-03 09:54:40 +00:00
Jamie Tanna 4732da7d2b fix(util/host-rules): apply the admin's headers over a repository's
As part of changes we've recently made to the merging of
`hostRules[].headers`, it highlighted that a repository - or a preset
it extends - could lead to a header an admin has configured for one host
to be sent to another.

This happened due to the way we additively merged i.e.
`Object.assign({}, ...matchedHeaders)`.

This doesn't require the repository to know a credential is being
set (for a specific host), or to act maliciously - any header of their
own for that host is enough to bring the administrator's masked one
back, with nothing logged or failing to show it happened.

To fix this, we can:

- apply admin headers last, as they are "trusted"
- stop repositories from being able to unset an admin-set header
- retain the existing logic for the "most specific rule" being used
- make sure that `trusted` isn't a field that could be injected by a
  repository, and make sure we explicitly only set it where we know
  we're using self-hosted administrator config
- make `filterAllowedHeaders` drop `headers` if every header name was
  denied, so `{}` isn't returned and used to suppress headers when
  merged

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 08:01:41 +01:00
Jamie Tanna c98f4f71c8 fix(util/host-rules): enforce allowedHeaders within add()
We previously enforced `allowedHeaders` by making sure that each
`hostRules.add()` call had `filterAllowedHeaders`.

This isn't super future proofed, and although we could add a linting
rule, we should instead enforce this by making sure that
`hostRules.add()` applies the filtering.

This also catches cases like host rules from `.npmrc` which wasn't
currently being filtered - albeit there was no means to bypass
`allowedHeaders`.

Moving `filterAllowedHeaders` into `lib/util` removes a dependency from
the global bootstrap and `config-validator` importing from
`lib/workers/repository/init/`.

While doing so, we can also make sure that:

- we log only one WARN for global `hostRules` being invalid, rather than
  once per `repositories[]`
- `renovate-config-validator` validates a global config file's
  `hostRules` against that file's own `allowedHeaders`, like a real run
  would after parsing it

Co-authored-by: Claude Fable 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 08:01:41 +01:00
Jamie Tanna 3d56c38cf6 fix(util/host-rules): merge headers across matching rules
Previously, if there was a `hostRule[].headers` value for a given host
set in both global self-hosted configuration and repo config, the repo
config would overwrite /all/ headers set by the admin.

To resolve this, we can now correctly merge the keys, with the repo
config taking priority.

This still only takes into account the `allowedHeaders`, so a repository
cannot bypass admin settings.

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 08:01:41 +01:00
Jamie Tanna 432bb00cd2 fix(workers/global): enforce allowedHeaders for the admin's own hostRules
For some time, admin configuration for `hostRules[].headers` was
enforced through `allowedHeaders`, but no validation feedback was given
to administrators, as it was silently ignored when `applyHostRule` runs.

To provide visibility to administrators who were setting headers that
were silently being ignored, we can instead provide validation at the
time we set our global host rules.

In a future change, we'll align the behaviour of `allowedHeaders` and
`allowedEnv` to make it so admin-controlled `hostRules` aren't bound by
the `allowedHeaders` flag.

We can also make it clear in the documentation that this is the
behaviour.

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 08:01:41 +01:00
Jamie Tanna 2382888bc7 fix(workers/repository): validate resolved presets
As a further level of defence-in-depth for fixes on top of `allowedEnv`
and `allowedHeaders` enforcement, we can also make sure that we're
performing full config validation for the resolved configuration.

This allows us to report any configuration errors/warnings when we
include the full resolved configuration, without necessarily blocking
the full Renovate run, unless using `configValidationError`.

Co-authored-by: Claude Opus 4.8 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 08:01:41 +01:00
Jamie Tanna cf491cc1cd test(workers/repository): don't mock more than we need to
This change was broken out of another commit, but it is unclear which AI
model was used to make the change, so we need to note both.

Co-authored-by: Claude Opus 4.8 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 08:01:41 +01:00
Jamie Tanna 223e6efd4e fix(config): enforce allowedHeaders when applying headers
As an additional defence-in-depth, we can make sure that when applying
the `headers` configuration (from repo config, or presets) we
re-validate it.

We allow any admin-supplied configuration regardless, and then any
user-supplied settings are re-filtered according to `allowedHeaders`.

Due to the existing way that header merging incorrectly merges the repo
(or preset) `headers` over the admin config, we don't currently handle
this explicitly, unlike `filterAllowedEnv`.

If a user-supplied setting is filtered, we log a WARN message to inform
the user - and the admin - that this is ignored.

At this point, we don't try and explain which preset(s) this may have
come from, only that there is an issue.

Co-authored-by: Claude Opus 4.8 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-03 08:01:41 +01:00