Resolves a package coordinate to the repository to analyse.
A batch request names packages (ecosystem, package, version); an
analysis needs a repository URL. Each scanner already knew how to find one
for its registry, but only inside a function that immediately analysed it,
so a batch job could not ask the question on its own (ADR-004).
Returns {:ok, url} or {:error, reason}, never a guess: an ecosystem this
does not know is refused rather than resolved by pattern.
Summary
Functions
The packages this one declares a dependency on, from the same registry
response resolve/3 already fetches (#263).
Blast radius is not health: a dead dependency in a test helper and a dead dependency on the request path score identically by metric counts, and only the second is worth waking up for. In-degree within a customer's own manifest is a proxy for that, and it needs these edges.
The request carries none — valid_dependency?/1 accepts only ecosystem,
package and version — so the graph has to come from somewhere. It comes from
a field we were already receiving and discarding, which is the same shape as
github_trending reading size and throwing away pushed_at.
{:ok, [names]} or {:error, reason}. An ecosystem whose registry shape is
not handled returns {:error, :unsupported} rather than an empty list:
"declares nothing" and "we did not look" must not read alike, or a package
whose edges we cannot see appears to have none and sorts as peripheral.
@spec describe(String.t(), String.t(), keyword()) :: {:ok, %{repository: String.t() | nil, dependencies: [String.t()] | nil}} | {:error, term()}
The repository URL and the declared dependencies, from one fetch.
resolve/3 and dependencies/3 each request the same registry document.
Calling both costs two round trips per package, which for a 400-package
manifest is 800 requests where 400 would do (#263).
Either field is nil when that part could not be read: a package with no
repository link still has dependencies worth knowing, and an ecosystem whose
dependency shape is unhandled still has a repository to analyse. Returning a
pair rather than failing on the first missing half keeps them independent.
@spec ecosystems() :: [String.t()]
The ecosystems resolve/3 understands.
Ask rather than assume: a caller that keeps its own list drifts the moment one is added here, and refuses an ecosystem the library supports.
@spec resolve(String.t(), String.t(), keyword()) :: {:ok, String.t()} | {:error, :not_found | :no_repository | {:unsupported_ecosystem, String.t()} | {:unreachable, term()} | {:status, integer()}}
Resolve package in ecosystem to an https repository URL.
Options:
:get- the HTTP GET, for tests. Defaults toHTTPoison.get/1throughLei.HTTP.Retry.:retry- retry options. Defaults to 3 attempts 2s apart: a registry that is down should fail a job quickly, not hold a worker for the retry module's default 5 attempts 15s apart.