You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(validators): distinguish a missing package from a missing version on PyPI/NPM (#1411)
Fixes#553.
The PyPI and npm validators fetch the version-specific metadata endpoint
(`/pypi/{name}/{version}/json` and the npm equivalent) and report any
non-200 as `<pkg> not found`. That endpoint 404s for two different
reasons though: the package doesn't exist, or it exists and only the
*version* is missing (e.g. a release that hasn't propagated yet). So a
valid package reads as gone:
```diff
- PyPI package 'requests' not found (status: 404)
+ PyPI package 'requests' exists, but version '99.99.99' was not found (status: 404). A newly published release can take a moment to appear on PyPI. Wait and retry, or publish version '99.99.99' before registering it
```
On a version 404 it probes the package-level endpoint
(`/pypi/{name}/json`, `/{name}`) with a `HEAD` to tell the two apart,
and reports 429/5xx as transient rather than "not found". `HEAD` so it
reads a status without pulling the whole packument. The probe carries
its own 3s deadline: it only refines the error message, so a hung probe
must not stretch the validator past the ~10s-per-registry budget the
publish path assumes. Same probe-and-classify shape as the existing
cargo validator.
The fetch is split into `validatePyPIPackage` / `validateNPMPackage`
behind `export_test.go` so the branches are testable with `httptest`,
the same seam cargo uses.
### Testing
- [x] hermetic `httptest` for the status matrix (version-missing,
package-missing, 5xx, 429, inconclusive probe, positive path, scoped
npm); the mocks pin the expected method per endpoint (`GET` fetch,
`HEAD` probe)
- [x] hermetic deadline test: a hung probe is cut off at ~3s and
reported as inconclusive, not "not found"
- [x] existing live package tests still pass, now also hitting the HEAD
probe; on a live 429/5xx they now `t.Skip` as inconclusive instead of
flaking (deliberate: CI runs these against the real registries with no
short-mode gating)
- [x] `gofmt` / `go vet` / `golangci-lint` clean
Out of scope: auto-retry for the propagation race, and SSRF
redirect-pinning parity for the pypi/npm clients (pre-existing, shared
with nuget).
returnfmt.Errorf("NPM package '%s' exists, but version '%s' was not found (status: 404). A newly published release can take a moment to appear on the registry. Wait and retry, or publish version '%s' before registering it", pkg.Identifier, pkg.Version, pkg.Version)
142
+
casenpmPackageMissing:
143
+
returnfmt.Errorf("NPM package '%s' not found (status: 404)", pkg.Identifier)
144
+
casenpmPackageTransient:
145
+
returnfmt.Errorf("NPM could not confirm package '%s' version '%s' (version status: 404, package check inconclusive). Likely transient, retry later", pkg.Identifier, pkg.Version)
146
+
casenpmPackageUnknown:
147
+
// Probe returned an unclassifiable status, so fall through to the
148
+
// best-effort message below.
149
+
}
150
+
returnfmt.Errorf("NPM package '%s' version '%s' not found (status: 404)", pkg.Identifier, pkg.Version)
151
+
}
152
+
153
+
// probeNPMPackage checks whether a package exists on the NPM registry regardless
154
+
// of version, with a HEAD request to the package-level endpoint (/{name}). Only
155
+
// the status code is used, so HEAD avoids downloading the (large) packument.
0 commit comments