Module: StillActive::EcosystemsClient
- Extended by:
- EcosystemsClient
- Included in:
- EcosystemsClient
- Defined in:
- lib/still_active/ecosystems_client.rb
Overview
Tokenless repo signals (archived?, last-commit date) for github.com-hosted gems, sourced from ecosyste.ms instead of the GitHub API. Used as the fallback when no GitHub token is configured, so an unauthenticated run isn't capped at GitHub's 60 req/hr (ecosyste.ms allows 5000 anonymous) -- the difference between "works on a large Gemfile" and "dies after a handful".
GitHub-only by design: ecosyste.ms's repos service doesn't populate commit recency for GitLab/Codeberg (its repo crawler is GitHub-centric), so those hosts keep their own live clients.
Data is CC-BY-SA 4.0 (https://creativecommons.org/licenses/by-sa/4.0/); still_active queries it live (no redistribution) and attributes ecosyste.ms in the README data sources.
Constant Summary collapse
- BASE_URI =
URI("https://repos.ecosyste.ms/")
- PACKAGES_BASE_URI =
The packages service is a distinct host from the repos service above; it carries per-version declared dependency constraints (the poison-pill input).
URI("https://packages.ecosyste.ms/")
- USER_AGENT =
ecosyste.ms asks consumers to identify themselves for its "polite pool".
"still_active/#{StillActive::VERSION} (+https://github.com/SeanLF/still_active)"- RUNTIME_KINDS =
ecosyste.ms dependency-kind labels that ship at RUNTIME (so a cap on one holds the consumer's tree hostage). It is registry-specific vocabulary: rubygems/npm/pypi say "runtime", but cargo says "normal" (its "dev"/"build" kinds are excluded). An ALLOWLIST, not a denylist: an unrecognised kind is dropped rather than risk flagging a dev/build dep and breaking the FP discipline. Compared case-insensitively (rubygems's dev kind is "Development").
["runtime", "normal"].freeze
Instance Method Summary collapse
-
#declared_dependencies(name:, version:, registry: "rubygems.org") ⇒ Object
The runtime dependency constraints a package version declares: an array of { package_name:, requirements: } for runtime-shipped deps only (see RUNTIME_KINDS).
-
#repo_signals(owner:, name:) ⇒ Object
archived + last-commit date from a single repository call.
Instance Method Details
#declared_dependencies(name:, version:, registry: "rubygems.org") ⇒ Object
The runtime dependency constraints a package version declares: an array of
{ package_name:, requirements: } for runtime-shipped deps only (see
RUNTIME_KINDS). Dev/build/test deps are dropped -- they don't cap the
consumer's tree, so they can't be a poison-pill. requirements is the raw
constraint string ("< 5.0, >= 4.0.1") ConstraintHelper reads. registry is
the ecosyste.ms registry name (rubygems.org, pypi.org, npmjs.org,
crates.io), defaulting to Ruby.
Returns [] whenever the version can't be read (unindexed, 404, timeout, schema drift), so a caller degrades to "no constraints known" rather than crashing the per-gem audit.
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
# File 'lib/still_active/ecosystems_client.rb', line 72 def declared_dependencies(name:, version:, registry: "rubygems.org") return [] if name.nil? || version.nil? path = "/api/v1/registries/#{encode(registry)}/packages/#{encode(name)}/versions/#{encode(version)}" body = HttpHelper.get_json(PACKAGES_BASE_URI, path, headers: { "User-Agent" => USER_AGENT }, params: politeness_params) return [] unless body.is_a?(Hash) dependencies = body["dependencies"] return [] unless dependencies.is_a?(Array) dependencies.filter_map do |dep| next unless dep.is_a?(Hash) && RUNTIME_KINDS.include?(dep["kind"].to_s.downcase) package_name = dep["package_name"] requirements = dep["requirements"] next if package_name.nil? || requirements.nil? { package_name: package_name, requirements: requirements } end end |
#repo_signals(owner:, name:) ⇒ Object
archived + last-commit date from a single repository call. ecosyste.ms's pushed_at mirrors GitHub's, so this returns the same shape as GithubClient. Returns {} when the repo can't be read, so the caller leaves both blank.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/still_active/ecosystems_client.rb', line 34 def repo_signals(owner:, name:) return {} if owner.nil? || name.nil? path = "/api/v1/hosts/GitHub/repositories/#{encode_repo(owner, name)}" body = HttpHelper.get_json(BASE_URI, path, headers: { "User-Agent" => USER_AGENT }, params: politeness_params) # A non-Hash 200 body (error envelope rendered as an array, schema drift) # would otherwise raise on indexing and vanish the gem from the audit via # the workflow's rescue; degrade to "no signal" like any other read failure. return {} unless body.is_a?(Hash) signals = { last_commit_date: parse_time(body["pushed_at"], owner, name) } # Only assert archived when the field is actually present. A missing field # must read as unknown, not be invented as false -- otherwise a partial # crawl could silently mask the most actionable verdict (gem is archived). signals[:archived] = body["archived"] == true if body.key?("archived") signals end |