Lei.PackageRepository (LowEndInsight v0.13.1)

Copy Markdown View Source

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).

The repository URL and the declared dependencies, from one fetch.

The ecosystems resolve/3 understands.

Resolve package in ecosystem to an https repository URL.

Functions

dependencies(ecosystem, package, opts \\ [])

@spec dependencies(String.t(), String.t(), keyword()) ::
  {:ok, [String.t()]} | {:error, term()}

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.

describe(ecosystem, package, opts \\ [])

@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.

ecosystems()

@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.

resolve(ecosystem, package, opts \\ [])

@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 to HTTPoison.get/1 through Lei.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.