* test: replace manual process.env mutation with vi.stubEnv
Enable Vitest's `unstubEnvs` so env stubs are reverted automatically
before every test, and convert the manual `process.env.X = ...` /
`delete process.env.X` pairs in specs to `vi.stubEnv()`.
This removes the hand-written `afterEach` cleanup blocks and the
`const OLD_ENV = process.env` / `process.env = { ...OLD_ENV }`
save-restore dance, which silently leaked state between tests when a
test set a variable without a matching delete.
Left as-is (incompatible with `unstubEnvs`, which unstubs before each
test, not after):
- module top-level env writes (cocoapods, gomod, post-update, pip-compile
and bundler artifacts specs)
- `beforeAll` env writes -- the two in `util/http` moved to `beforeEach`
* test: stop asserting on ambient environment variables
Three specs asserted on values that the surrounding environment can
override, so they only passed on a machine that happened not to set them:
- `util/exec/env`: `getChildProcessEnv()` forwards every entry of
`basicEnvVars`, but the spec only seeded a subset. Any other forwarded
variable that was set ambiently (e.g. a lowercase `http_proxy`) leaked
into the `toEqual` assertion. Clear all of them first.
- `util/git/index`: `process.env.GIT_SSH_COMMAND` deliberately overrides
the default that the two env-inheritance tests assert on. Clear it.
- `logger/pretty-stdout`: the colorized level strings are built at import
time, so the `beforeEach` setting `FORCE_COLOR` could never affect them
-- it was dead code, and the test silently assumed a terminal without
colour support. Derive the expectation the same way the module does.
* test(datasource/nuget): use a throwaway cache dir for nupkg downloads
The `determine source URL from nupkg` tests do not mock the filesystem,
so they really wrote downloaded packages into a fixed `/tmp/cache` path.
That fails wherever the system tmpdir is not writable, and two checkouts
running the suite at once would share the same directory.
Use `tmp.dir({ unsafeCleanup: true })` and clean it up afterwards, which
is the pattern the other specs that touch disk already use.
* feat(lint): add renovate/prefer-stub-env rule
Enforces in specs what the migration to `vi.stubEnv` established:
- assigning to or deleting `process.env.X` inside `beforeEach`, `afterEach`,
`it` or `test` is reported, with a fixer that rewrites it to `vi.stubEnv()`
- module scope, `describe` bodies and `beforeAll` / `afterAll` are left alone:
`unstubEnvs` reverts stubs *before* each test, so a stub created there is
gone by the time the first test runs, and those must keep assigning directly
- replacing the `process.env` object is reported everywhere and never fixed.
`vi.stubEnv()` captures the original object when the worker starts, so after
a reassignment its deletes silently target the old object
The rule found 28 further violations in specs the earlier sweep missed, since
that one was anchored on `delete process.env`. Twelve were auto-fixed; the
rest replaced `process.env` wholesale and are now `clearEnv()` from test/util.
`util/exec/index.spec.ts` opts out with a comment: it asserts on the whole
child env, and `vi.stubEnv()` cannot express that because it maps `PROD`,
`DEV` and `SSR` onto '' instead of deleting them.
* feat(pypi): add support for JSON-based Simple API (PEP 691)
* Fix some stuff
* Clean some things up
* Fix coverage
* Simplify JSON parse
* Remove useless tests
* Align terminology
* Incorporate some changes from PR #44182
Co-authored-by: Andreas Hippler <andreas.hippler@goto.com>
* Use isNonEmptyString to filter requires_python
* Address review feedback
* Warn once actually
* fixup! review comments
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
* fixup! fixup! review comments
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
* fixup! review comments
As the list of status codes we retry on was based on an unverified
claim that Artifactory, Nexus and devpi answer a rejected `Accept`
header with `400`/`403`/`415`, we should only retry on `406`, which is
what RFC 9110 specifies for a request whose `Accept` cannot be
satisfied.
Our `Accept` header ends with `text/html; q=0.01`, so any conforming
registry can satisfy it, `415` refers to the request body's
`Content-Type` rather than `Accept`, and retrying on `403` doubles the
requests for a registry we're not authenticated against.
The unwrapping of `ExternalHostError` is unaffected, as that's needed
for the `abortOnError` case regardless of which status codes we retry.
Co-Authored-By: Claude Opus 5 <claude-code@jamietanna.co.uk>
* fixup! review comments
As the note about `upload-time` sat in the paragraph about the Simple
API, it read as if `minimumReleaseAge` needs a registry which supports
the JSON-based Simple API (PEP 691).
That's only true of the Simple API. The JSON API has always exposed
`upload_time`, and as the default registry of `https://pypi.org/pypi/`
doesn't end in `/simple/`, that's the lookup most users get, so
`minimumReleaseAge` already works for them.
We can split the two apart, so that the guarantee for each API is
stated separately.
Co-Authored-By: Claude Opus 5 <claude-code@jamietanna.co.uk>
* reword
* fixup! review comments
As the JSON-based Simple API's release timestamps were only covered by
hand-written responses, we didn't have the equivalent of the real world
coverage added for the JSON API in
e942788539.
We can add a `numpy` fixture from PyPI's JSON-based Simple API, trimmed
to 1.5.0 and 1.5.1, so we assert against a response a registry has
actually served. 1.5.1 is the interesting case, as it was released in
2010 but gained a wheel in 2014 which PyPI lists first.
This also covers the `upload-time` format the Simple API uses, of
`2010-11-18T14:16:58.801332Z`, rather than the JSON API's
`2010-11-18T14:16:58`, so that we know `asTimestamp` normalises both.
Unlike the JSON API, there's no previous behaviour to characterise
first, as before these changes the Simple API exposed no timestamps at
all.
Co-Authored-By: Claude Opus 5 <claude-code@jamietanna.co.uk>
* fixup! fixup! review comments
* fixups
* fixups
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
* fixup! comments
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
---------
Co-authored-by: Andreas Hippler <andreas.hippler@goto.com>
Co-authored-by: Jamie Tanna <jamie.tanna@mend.io>
Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
Co-authored-by: Claude Opus 5 <claude-code@jamietanna.co.uk>
* test(datasource/pypi): cover which `upload_time` a version reports
As part of future changes to how we calculate the `releaseTimestamp`, it
was clear we don't have as much coverage of this logic.
To make this more explicit, we can add new test coverage.
Additionally, we can introduce a new example from a package to guide
this.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
* fix(datasource/pypi): use the earliest `upload_time` of a version
When calculating the `releaseTimestamp` for the PyPI datasource, we've
been looking at the first entry in the response, which isn't always
the first time this package existed.
In the case that a Wheel is uploaded after the package first was
released - for instance, to support a new version of Python, at an
existing pinned version of the package - this would lead to a
potentially incorrect calculation for the `releaseTimestamp`.
This can lead to Renovate seeing a package as newer than it is, which
can mean that i.e. `minimumReleaseAge` can show as pending for a package
that shouldn't be.
As part of upstream changes[0], this should be less frequent in the
ecosystem, but existing packages still exist with this behaviour.
To fix this, we can make sure that we filter a given Release's
to find the earliest `upload_time`.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
[0]: https://blog.pypi.org/posts/2026-07-22-releases-now-reject-new-files-after-14-days/
---------
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
test(datasource/pypi): add regression
As per changes suggested by Claude Opus 5 in a commit in #44222, a
regression could be triggered with our handling of
`constraintsFiltering=strict`, breaking a key piece of functionality.
To prevent this, we can add a regression test for this functionality.
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
Add regression tests for the fix that allows a null or missing
home_page in PyPI JSON responses (#43814). Covers both the schema
parser and the datasource, asserting that homepage is left unset and
sourceUrl/changelogUrl fall back to project_urls when home_page is null.
* fix(datasource): replace getJsonUnchecked with validated getJson in npm/Node/PyPI/Go
Replace all getJsonUnchecked calls in npm, Node.js, PyPI, and Go datasources
with Zod-validated getJson calls:
- datasource/npm: add NpmResponseSchema to schema.ts (shared with presets)
- config/presets/npm: migrate to NpmResponseSchema with passthrough version fields
- datasource/node-version: add NodeReleasesSchema using LooseArray
- datasource/pypi: add PypiJSONSchema (nullable+catch for lenient empty-body handling)
- datasource/go: add VersionInfoSchema for both versionInfo and getLatestVersion calls
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* some manual cleaning up
* removing schema level catch
* removing some more types
* refactor: rename schema
* fix linter errors
* remove schema suffix which hasn't been catched by linter
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(datasource/pypi): sanitize GAR userinfo for authenticated lookups
Strip URL userinfo from Google Artifact Registry PyPI lookup URLs only when
Google auth is being applied. This avoids got-derived Basic auth conflicts for
uv-style index URLs while keeping the change narrowly scoped to authenticated
datasource requests.
* Test getAuthHeaders with invalid URL
* Parse the URL once
* Remove cast and ignore error
* Remove test and add v8 ignore comment
---------
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Make GitHub project URL sniffing to check user/organization part for
`sponsors` only. There are a few PyPI projects for which the previous
implementation currently produces false positives.
https://pypi.org/search/?q=sponsors
Adds new option `constraintsFiltering`.
Closes#19979
BREAKING CHANGE: PyPI releases will no longer be filtered by default based on `constraints.python` compatibility. To retain existing functionality, set
`constraintsFiltering=strict`.
* fix(datasource/pypi): use pypi api instead of the simple variant
* fix(datasource/pypi): use pypi api instead of the simple variant
* fix(datasource/pypi): use pypi api instead of the simple variant
* fix(datasource/pypi): use pypi api instead of the simple variant
* Update lib/modules/datasource/pypi/index.ts
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Moves datasource, manager, platform and versioning code from lib/ into new lib/modules/
BREAKING CHANGE: External tools must update paths to datasource, manager, platform and versioning