a*.rb: move to strict Sorbet sigil.

Co-authored-by: Rylan Polster <rslpolster@gmail.com>
This commit is contained in:
Mike McQuaid 2025-06-16 17:33:24 +01:00
parent 95f0e76154
commit 7345607ca0
No known key found for this signature in database
8 changed files with 154 additions and 74 deletions

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "api/analytics" require "api/analytics"
@ -11,10 +11,10 @@ module Homebrew
module API module API
extend Cachable extend Cachable
HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname)
HOMEBREW_CACHE_API_SOURCE = (HOMEBREW_CACHE/"api-source").freeze HOMEBREW_CACHE_API_SOURCE = T.let((HOMEBREW_CACHE/"api-source").freeze, Pathname)
sig { params(endpoint: String).returns(Hash) } sig { params(endpoint: String).returns(T::Hash[String, T.untyped]) }
def self.fetch(endpoint) def self.fetch(endpoint)
return cache[endpoint] if cache.present? && cache.key?(endpoint) return cache[endpoint] if cache.present? && cache.key?(endpoint)
@ -33,7 +33,8 @@ module Homebrew
end end
sig { sig {
params(endpoint: String, target: Pathname, stale_seconds: Integer).returns([T.any(Array, Hash), T::Boolean]) params(endpoint: String, target: Pathname,
stale_seconds: Integer).returns([T.any(T::Array[T.untyped], T::Hash[String, T.untyped]), T::Boolean])
} }
def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint, def self.fetch_json_api_file(endpoint, target: HOMEBREW_CACHE_API/endpoint,
stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i) stale_seconds: Homebrew::EnvConfig.api_auto_update_secs.to_i)
@ -96,7 +97,8 @@ module Homebrew
mtime = insecure_download ? Time.new(1970, 1, 1) : Time.now mtime = insecure_download ? Time.new(1970, 1, 1) : Time.now
FileUtils.touch(target, mtime:) unless skip_download FileUtils.touch(target, mtime:) unless skip_download
JSON.parse(target.read(encoding: Encoding::UTF_8), freeze: true) # Can use `target.read` again when/if https://github.com/sorbet/sorbet/pull/8999 is merged/released.
JSON.parse(File.read(target, encoding: Encoding::UTF_8), freeze: true)
rescue JSON::ParserError rescue JSON::ParserError
target.unlink target.unlink
retry_count += 1 retry_count += 1
@ -122,8 +124,11 @@ module Homebrew
end end
end end
sig { params(json: Hash, bottle_tag: T.nilable(::Utils::Bottles::Tag)).returns(Hash) } sig {
def self.merge_variations(json, bottle_tag: nil) params(json: T::Hash[String, T.untyped],
bottle_tag: ::Utils::Bottles::Tag).returns(T::Hash[String, T.untyped])
}
def self.merge_variations(json, bottle_tag: T.unsafe(nil))
return json unless json.key?("variations") return json unless json.key?("variations")
bottle_tag ||= Homebrew::SimulateSystem.current_tag bottle_tag ||= Homebrew::SimulateSystem.current_tag
@ -147,7 +152,10 @@ module Homebrew
false false
end end
sig { params(json_data: Hash).returns([T::Boolean, T.any(String, Array, Hash)]) } sig {
params(json_data: T::Hash[String, T.untyped])
.returns([T::Boolean, T.any(String, T::Array[T.untyped], T::Hash[String, T.untyped])])
}
private_class_method def self.verify_and_parse_jws(json_data) private_class_method def self.verify_and_parse_jws(json_data)
signatures = json_data["signatures"] signatures = json_data["signatures"]
homebrew_signature = signatures&.find { |sig| sig.dig("header", "kid") == "homebrew-1" } homebrew_signature = signatures&.find { |sig| sig.dig("header", "kid") == "homebrew-1" }

View File

@ -1,24 +1,26 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
# Used to substitute common paths with generic placeholders when generating JSON for the API. # Used to substitute common paths with generic placeholders when generating JSON for the API.
module APIHashable module APIHashable
sig { void }
def generating_hash! def generating_hash!
return if generating_hash? return if generating_hash?
# Apply monkeypatches for API generation # Apply monkeypatches for API generation
@old_homebrew_prefix = HOMEBREW_PREFIX @old_homebrew_prefix = T.let(HOMEBREW_PREFIX, T.nilable(Pathname))
@old_homebrew_cellar = HOMEBREW_CELLAR @old_homebrew_cellar = T.let(HOMEBREW_CELLAR, T.nilable(Pathname))
@old_home = Dir.home @old_home = T.let(Dir.home, T.nilable(String))
@old_git_config_global = ENV.fetch("GIT_CONFIG_GLOBAL", nil) @old_git_config_global = T.let(ENV.fetch("GIT_CONFIG_GLOBAL", nil), T.nilable(String))
Object.send(:remove_const, :HOMEBREW_PREFIX) Object.send(:remove_const, :HOMEBREW_PREFIX)
Object.const_set(:HOMEBREW_PREFIX, Pathname.new(HOMEBREW_PREFIX_PLACEHOLDER)) Object.const_set(:HOMEBREW_PREFIX, Pathname.new(HOMEBREW_PREFIX_PLACEHOLDER))
ENV["HOME"] = HOMEBREW_HOME_PLACEHOLDER ENV["HOME"] = HOMEBREW_HOME_PLACEHOLDER
ENV["GIT_CONFIG_GLOBAL"] = File.join(@old_home, ".gitconfig") ENV["GIT_CONFIG_GLOBAL"] = File.join(@old_home, ".gitconfig")
@generating_hash = true @generating_hash = T.let(true, T.nilable(T::Boolean))
end end
sig { void }
def generated_hash! def generated_hash!
return unless generating_hash? return unless generating_hash?
@ -31,6 +33,7 @@ module APIHashable
@generating_hash = false @generating_hash = false
end end
sig { returns(T::Boolean) }
def generating_hash? def generating_hash?
@generating_hash ||= false @generating_hash ||= false
@generating_hash == true @generating_hash == true

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "bundle/adder" require "bundle/adder"
@ -7,6 +7,7 @@ module Homebrew
module Bundle module Bundle
module Commands module Commands
module Add module Add
sig { params(args: String, type: Symbol, global: T::Boolean, file: T.nilable(String)).void }
def self.run(*args, type:, global:, file:) def self.run(*args, type:, global:, file:)
Homebrew::Bundle::Adder.add(*args, type:, global:, file:) Homebrew::Bundle::Adder.add(*args, type:, global:, file:)
end end

View File

@ -67,8 +67,8 @@ module Homebrew
end end
raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1 raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1
labels = if pr_url labels = if pr_url && (first_pr_url = pr_url.first)
pr = GitHub::API.open_rest(pr_url.first) pr = GitHub::API.open_rest(first_pr_url)
pr.fetch("labels").map { |l| l.fetch("name") } pr.fetch("labels").map { |l| l.fetch("name") }
else else
[] []

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "requirement" require "requirement"
@ -7,10 +7,14 @@ require "requirement"
class ArchRequirement < Requirement class ArchRequirement < Requirement
fatal true fatal true
@arch = T.let(nil, T.nilable(Symbol))
sig { returns(T.nilable(Symbol)) }
attr_reader :arch attr_reader :arch
sig { params(tags: T::Array[Symbol]).void }
def initialize(tags) def initialize(tags)
@arch = tags.shift @arch = T.let(tags.shift, T.nilable(Symbol))
super super
end end

View File

@ -132,7 +132,8 @@ module SystemConfig
out.puts "#{tap_name} branch: #{tap.git_branch || "(none)"}" if default_branches.exclude?(tap.git_branch) out.puts "#{tap_name} branch: #{tap.git_branch || "(none)"}" if default_branches.exclude?(tap.git_branch)
end end
if (json_file = Homebrew::API::HOMEBREW_CACHE_API/json_file_name) && json_file.exist? json_file = Homebrew::API::HOMEBREW_CACHE_API/json_file_name
if json_file.exist?
out.puts "#{tap_name} JSON: #{json_file.mtime.utc.strftime("%d %b %H:%M UTC")}" out.puts "#{tap_name} JSON: #{json_file.mtime.utc.strftime("%d %b %H:%M UTC")}"
elsif !tap.installed? elsif !tap.installed?
out.puts "#{tap_name}: N/A" out.puts "#{tap_name}: N/A"

View File

@ -1,9 +1,10 @@
# typed: true # rubocop:todo Sorbet/StrictSigil # typed: strict
# frozen_string_literal: true # frozen_string_literal: true
require "system_command" require "system_command"
module GitHub module GitHub
sig { params(scopes: T::Array[String]).returns(String) }
def self.pat_blurb(scopes = ALL_SCOPES) def self.pat_blurb(scopes = ALL_SCOPES)
require "utils/formatter" require "utils/formatter"
require "utils/shell" require "utils/shell"
@ -16,20 +17,21 @@ module GitHub
EOS EOS
end end
API_URL = "https://api.github.com" API_URL = T.let("https://api.github.com", String)
API_MAX_PAGES = 50 API_MAX_PAGES = T.let(50, Integer)
private_constant :API_MAX_PAGES private_constant :API_MAX_PAGES
API_MAX_ITEMS = 5000 API_MAX_ITEMS = T.let(5000, Integer)
private_constant :API_MAX_ITEMS private_constant :API_MAX_ITEMS
PAGINATE_RETRY_COUNT = 3 PAGINATE_RETRY_COUNT = T.let(3, Integer)
private_constant :PAGINATE_RETRY_COUNT private_constant :PAGINATE_RETRY_COUNT
CREATE_GIST_SCOPES = ["gist"].freeze CREATE_GIST_SCOPES = T.let(["gist"].freeze, T::Array[String])
CREATE_ISSUE_FORK_OR_PR_SCOPES = ["repo"].freeze CREATE_ISSUE_FORK_OR_PR_SCOPES = T.let(["repo"].freeze, T::Array[String])
CREATE_WORKFLOW_SCOPES = ["workflow"].freeze CREATE_WORKFLOW_SCOPES = T.let(["workflow"].freeze, T::Array[String])
ALL_SCOPES = (CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES + CREATE_WORKFLOW_SCOPES).freeze ALL_SCOPES = T.let((CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES + CREATE_WORKFLOW_SCOPES).freeze,
T::Array[String])
private_constant :ALL_SCOPES private_constant :ALL_SCOPES
GITHUB_PERSONAL_ACCESS_TOKEN_REGEX = /^(?:[a-f0-9]{40}|(?:gh[pousr]|github_pat)_\w{36,251})$/ GITHUB_PERSONAL_ACCESS_TOKEN_REGEX = T.let(/^(?:[a-f0-9]{40}|(?:gh[pousr]|github_pat)_\w{36,251})$/, Regexp)
private_constant :GITHUB_PERSONAL_ACCESS_TOKEN_REGEX private_constant :GITHUB_PERSONAL_ACCESS_TOKEN_REGEX
# Helper functions for accessing the GitHub API. # Helper functions for accessing the GitHub API.
@ -40,46 +42,60 @@ module GitHub
# Generic API error. # Generic API error.
class Error < RuntimeError class Error < RuntimeError
sig { returns(T.nilable(String)) }
attr_reader :github_message attr_reader :github_message
sig { params(message: T.nilable(String), github_message: String).void }
def initialize(message = nil, github_message = T.unsafe(nil))
@github_message = T.let(github_message, T.nilable(String))
super(message)
end
end end
# Error when the requested URL is not found. # Error when the requested URL is not found.
class HTTPNotFoundError < Error class HTTPNotFoundError < Error
sig { params(github_message: String).void }
def initialize(github_message) def initialize(github_message)
@github_message = github_message super(nil, github_message)
super
end end
end end
# Error when the API rate limit is exceeded. # Error when the API rate limit is exceeded.
class RateLimitExceededError < Error class RateLimitExceededError < Error
sig { params(reset: Integer, github_message: String).void }
def initialize(reset, github_message) def initialize(reset, github_message)
@github_message = github_message
new_pat_message = ", or:\n#{GitHub.pat_blurb}" if API.credentials.blank? new_pat_message = ", or:\n#{GitHub.pat_blurb}" if API.credentials.blank?
super <<~EOS message = <<~EOS
GitHub API Error: #{github_message} GitHub API Error: #{github_message}
Try again in #{pretty_ratelimit_reset(reset)}#{new_pat_message} Try again in #{pretty_ratelimit_reset(reset)}#{new_pat_message}
EOS EOS
super(message, github_message)
end end
sig { params(reset: Integer).returns(String) }
def pretty_ratelimit_reset(reset) def pretty_ratelimit_reset(reset)
pretty_duration(Time.at(reset) - Time.now) pretty_duration(Time.at(reset) - Time.now)
end end
end end
GITHUB_IP_ALLOWLIST_ERROR = Regexp.new("Although you appear to have the correct authorization credentials, " \ GITHUB_IP_ALLOWLIST_ERROR = T.let(
"the `(.+)` organization has an IP allow list enabled, " \ Regexp.new(
"and your IP address is not permitted to access this resource").freeze "Although you appear to have the correct authorization credentials, " \
"the `(.+)` organization has an IP allow list enabled, " \
"and your IP address is not permitted to access this resource",
).freeze,
Regexp,
)
NO_CREDENTIALS_MESSAGE = <<~MESSAGE.freeze NO_CREDENTIALS_MESSAGE = T.let <<~MESSAGE.freeze, String
No GitHub credentials found in macOS Keychain, GitHub CLI or the environment. No GitHub credentials found in macOS Keychain, GitHub CLI or the environment.
#{GitHub.pat_blurb} #{GitHub.pat_blurb}
MESSAGE MESSAGE
# Error when authentication fails. # Error when authentication fails.
class AuthenticationFailedError < Error class AuthenticationFailedError < Error
sig { params(credentials_type: Symbol, github_message: String).void }
def initialize(credentials_type, github_message) def initialize(credentials_type, github_message)
@github_message = github_message
message = "GitHub API Error: #{github_message}\n" message = "GitHub API Error: #{github_message}\n"
message << case credentials_type message << case credentials_type
when :github_cli_token when :github_cli_token
@ -103,12 +119,13 @@ module GitHub
when :none when :none
NO_CREDENTIALS_MESSAGE NO_CREDENTIALS_MESSAGE
end end
super message.freeze super message.freeze, github_message
end end
end end
# Error when the user has no GitHub API credentials set at all (macOS keychain, GitHub CLI or envvar). # Error when the user has no GitHub API credentials set at all (macOS keychain, GitHub CLI or envvar).
class MissingAuthenticationError < Error class MissingAuthenticationError < Error
sig { void }
def initialize def initialize
super NO_CREDENTIALS_MESSAGE super NO_CREDENTIALS_MESSAGE
end end
@ -116,24 +133,21 @@ module GitHub
# Error when the API returns a validation error. # Error when the API returns a validation error.
class ValidationFailedError < Error class ValidationFailedError < Error
sig { params(github_message: String, errors: T::Array[String]).void }
def initialize(github_message, errors) def initialize(github_message, errors)
@github_message = if errors.empty? github_message = "#{github_message}: #{errors}" unless errors.empty?
github_message
else
"#{github_message}: #{errors}"
end
super(@github_message) super(github_message, github_message)
end end
end end
ERRORS = [ ERRORS = T.let([
AuthenticationFailedError, AuthenticationFailedError,
HTTPNotFoundError, HTTPNotFoundError,
RateLimitExceededError, RateLimitExceededError,
Error, Error,
JSON::ParserError, JSON::ParserError,
].freeze ].freeze, T::Array[T.any(T.class_of(Error), T.class_of(JSON::ParserError))])
# Gets the token from the GitHub CLI for github.com. # Gets the token from the GitHub CLI for github.com.
sig { returns(T.nilable(String)) } sig { returns(T.nilable(String)) }
@ -151,7 +165,7 @@ module GitHub
print_stderr: false print_stderr: false
return unless result.success? return unless result.success?
gh_out.chomp gh_out.chomp.presence
end end
end end
@ -178,14 +192,16 @@ module GitHub
# https://github.com/Homebrew/brew/issues/6862#issuecomment-572610344 # https://github.com/Homebrew/brew/issues/6862#issuecomment-572610344
return unless GITHUB_PERSONAL_ACCESS_TOKEN_REGEX.match?(github_password) return unless GITHUB_PERSONAL_ACCESS_TOKEN_REGEX.match?(github_password)
github_password github_password.presence
end end
end end
sig { returns(T.nilable(String)) }
def self.credentials def self.credentials
@credentials ||= T.let(nil, T.nilable(String))
@credentials ||= Homebrew::EnvConfig.github_api_token.presence @credentials ||= Homebrew::EnvConfig.github_api_token.presence
@credentials ||= github_cli_token.presence @credentials ||= github_cli_token
@credentials ||= keychain_username_password.presence @credentials ||= keychain_username_password
end end
sig { returns(Symbol) } sig { returns(Symbol) }
@ -201,18 +217,19 @@ module GitHub
end end
end end
CREDENTIAL_NAMES = { CREDENTIAL_NAMES = T.let({
env_token: "HOMEBREW_GITHUB_API_TOKEN", env_token: "HOMEBREW_GITHUB_API_TOKEN",
github_cli_token: "GitHub CLI login", github_cli_token: "GitHub CLI login",
keychain_username_password: "macOS Keychain GitHub", keychain_username_password: "macOS Keychain GitHub",
}.freeze }.freeze, T::Hash[Symbol, String])
# Given an API response from GitHub, warn the user if their credentials # Given an API response from GitHub, warn the user if their credentials
# have insufficient permissions. # have insufficient permissions.
sig { params(response_headers: T::Hash[String, String], needed_scopes: T::Array[String]).void }
def self.credentials_error_message(response_headers, needed_scopes) def self.credentials_error_message(response_headers, needed_scopes)
return if response_headers.empty? return if response_headers.empty?
scopes = response_headers["x-accepted-oauth-scopes"].to_s.split(", ") scopes = response_headers["x-accepted-oauth-scopes"].to_s.split(", ").presence
needed_scopes = Set.new(scopes || needed_scopes) needed_scopes = Set.new(scopes || needed_scopes)
credentials_scopes = response_headers["x-oauth-scopes"] credentials_scopes = response_headers["x-oauth-scopes"]
return if needed_scopes.subset?(Set.new(credentials_scopes.to_s.split(", "))) return if needed_scopes.subset?(Set.new(credentials_scopes.to_s.split(", ")))
@ -222,17 +239,35 @@ module GitHub
credentials_scopes = "none" if credentials_scopes.blank? credentials_scopes = "none" if credentials_scopes.blank?
what = CREDENTIAL_NAMES.fetch(credentials_type) what = CREDENTIAL_NAMES.fetch(credentials_type)
@credentials_error_message ||= onoe <<~EOS @credentials_error_message ||= T.let(begin
Your #{what} credentials do not have sufficient scope! error_message = <<~EOS
Scopes required: #{needed_scopes} Your #{what} credentials do not have sufficient scope!
Scopes present: #{credentials_scopes} Scopes required: #{needed_scopes}
#{github_permission_link} Scopes present: #{credentials_scopes}
EOS #{github_permission_link}
EOS
onoe error_message
error_message
end, T.nilable(String))
end end
def self.open_rest( sig {
url, data: nil, data_binary_path: nil, request_method: nil, scopes: [].freeze, parse_json: true params(
) url: T.any(String, URI::Generic),
data: T::Hash[Symbol, T.untyped],
data_binary_path: String,
request_method: Symbol,
scopes: T::Array[String],
parse_json: T::Boolean,
_block: T.nilable(
T.proc
.params(data: T::Hash[String, T.untyped])
.returns(T.untyped),
),
).returns(T.untyped)
}
def self.open_rest(url, data: T.unsafe(nil), data_binary_path: T.unsafe(nil), request_method: T.unsafe(nil),
scopes: [].freeze, parse_json: true, &_block)
# This is a no-op if the user is opting out of using the GitHub API. # This is a no-op if the user is opting out of using the GitHub API.
return block_given? ? yield({}) : {} if Homebrew::EnvConfig.no_github_api? return block_given? ? yield({}) : {} if Homebrew::EnvConfig.no_github_api?
@ -289,7 +324,7 @@ module GitHub
begin begin
if !http_code.start_with?("2") || !result.status.success? if !http_code.start_with?("2") || !result.status.success?
raise_error(output, result.stderr, http_code, headers, scopes) raise_error(output, result.stderr, http_code, headers || "", scopes)
end end
return if http_code == "204" # No Content return if http_code == "204" # No Content
@ -305,7 +340,18 @@ module GitHub
end end
end end
def self.paginate_rest(url, additional_query_params: nil, per_page: 100, scopes: [].freeze) sig {
params(
url: T.any(String, URI::Generic),
additional_query_params: String,
per_page: Integer,
scopes: T::Array[String],
_block: T.proc
.params(result: T.untyped, page: Integer)
.returns(T.untyped),
).void
}
def self.paginate_rest(url, additional_query_params: T.unsafe(nil), per_page: 100, scopes: [].freeze, &_block)
(1..API_MAX_PAGES).each do |page| (1..API_MAX_PAGES).each do |page|
retry_count = 1 retry_count = 1
result = begin result = begin
@ -324,9 +370,17 @@ module GitHub
end end
end end
def self.open_graphql(query, variables: nil, scopes: [].freeze, raise_errors: true) sig {
params(
query: String,
variables: T::Hash[Symbol, T.untyped],
scopes: T::Array[String],
raise_errors: T::Boolean,
).returns(T.untyped)
}
def self.open_graphql(query, variables: T.unsafe(nil), scopes: [].freeze, raise_errors: true)
data = { query:, variables: } data = { query:, variables: }
result = open_rest("#{API_URL}/graphql", scopes:, data:, request_method: "POST") result = open_rest("#{API_URL}/graphql", scopes:, data:, request_method: :POST)
if raise_errors if raise_errors
raise Error, result["errors"].map { |e| e["message"] }.join("\n") if result["errors"].present? raise Error, result["errors"].map { |e| e["message"] }.join("\n") if result["errors"].present?
@ -340,13 +394,13 @@ module GitHub
sig { sig {
params( params(
query: String, query: String,
variables: T.nilable(T::Hash[Symbol, T.untyped]), variables: T::Hash[Symbol, T.untyped],
scopes: T::Array[String], scopes: T::Array[String],
raise_errors: T::Boolean, raise_errors: T::Boolean,
_block: T.proc.params(data: T::Hash[String, T.untyped]).returns(T::Hash[String, T.untyped]), _block: T.proc.params(data: T::Hash[String, T.untyped]).returns(T.untyped),
).void ).void
} }
def self.paginate_graphql(query, variables: nil, scopes: [].freeze, raise_errors: true, &_block) def self.paginate_graphql(query, variables: T.unsafe(nil), scopes: [].freeze, raise_errors: true, &_block)
result = API.open_graphql(query, variables:, scopes:, raise_errors:) result = API.open_graphql(query, variables:, scopes:, raise_errors:)
has_next_page = T.let(true, T::Boolean) has_next_page = T.let(true, T::Boolean)
@ -361,6 +415,15 @@ module GitHub
end end
end end
sig {
params(
output: String,
errors: String,
http_code: String,
headers: String,
scopes: T::Array[String],
).void
}
def self.raise_error(output, errors, http_code, headers, scopes) def self.raise_error(output, errors, http_code, headers, scopes)
json = begin json = begin
JSON.parse(output) JSON.parse(output)

View File

@ -11,11 +11,11 @@ module GitHub
# @param artifact_id [String] a value that uniquely identifies the downloaded artifact # @param artifact_id [String] a value that uniquely identifies the downloaded artifact
sig { params(url: String, artifact_id: String).void } sig { params(url: String, artifact_id: String).void }
def self.download_artifact(url, artifact_id) def self.download_artifact(url, artifact_id)
raise API::MissingAuthenticationError if API.credentials == :none token = API.credentials
raise API::MissingAuthenticationError if token.blank?
# We use a download strategy here to leverage the Homebrew cache # We use a download strategy here to leverage the Homebrew cache
# to avoid repeated downloads of (possibly large) bottles. # to avoid repeated downloads of (possibly large) bottles.
token = API.credentials
downloader = GitHubArtifactDownloadStrategy.new(url, artifact_id, token:) downloader = GitHubArtifactDownloadStrategy.new(url, artifact_id, token:)
downloader.fetch downloader.fetch
downloader.stage downloader.stage