• v1.8.0 d813d2b805

    Ghost released this 2026-07-12 15:24:34 -04:00 | 51 commits to main since this release

    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:

    - 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

    • 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)
    • hermetic deadline test: a hung probe is cut off at ~3s and
      reported as inconclusive, not "not found"
    • 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)
    • 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).

    Downloads