mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00
Merge pull request #20121 from Homebrew/sorbet_strict_a
a*.rb: move to strict Sorbet sigil.
This commit is contained in:
commit
d7d8c61f00
@ -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" }
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
[]
|
[]
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user