a*.rb: move to strict Sorbet sigil.
Co-authored-by: Rylan Polster <rslpolster@gmail.com>
This commit is contained in:
		
							parent
							
								
									95f0e76154
								
							
						
					
					
						commit
						7345607ca0
					
				@ -1,4 +1,4 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "api/analytics"
 | 
			
		||||
@ -11,10 +11,10 @@ module Homebrew
 | 
			
		||||
  module API
 | 
			
		||||
    extend Cachable
 | 
			
		||||
 | 
			
		||||
    HOMEBREW_CACHE_API = (HOMEBREW_CACHE/"api").freeze
 | 
			
		||||
    HOMEBREW_CACHE_API_SOURCE = (HOMEBREW_CACHE/"api-source").freeze
 | 
			
		||||
    HOMEBREW_CACHE_API = T.let((HOMEBREW_CACHE/"api").freeze, Pathname)
 | 
			
		||||
    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)
 | 
			
		||||
      return cache[endpoint] if cache.present? && cache.key?(endpoint)
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,8 @@ module Homebrew
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    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,
 | 
			
		||||
                                 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
 | 
			
		||||
        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
 | 
			
		||||
        target.unlink
 | 
			
		||||
        retry_count += 1
 | 
			
		||||
@ -122,8 +124,11 @@ module Homebrew
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { params(json: Hash, bottle_tag: T.nilable(::Utils::Bottles::Tag)).returns(Hash) }
 | 
			
		||||
    def self.merge_variations(json, bottle_tag: nil)
 | 
			
		||||
    sig {
 | 
			
		||||
      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")
 | 
			
		||||
 | 
			
		||||
      bottle_tag ||= Homebrew::SimulateSystem.current_tag
 | 
			
		||||
@ -147,7 +152,10 @@ module Homebrew
 | 
			
		||||
      false
 | 
			
		||||
    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)
 | 
			
		||||
      signatures = json_data["signatures"]
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
# Used to substitute common paths with generic placeholders when generating JSON for the API.
 | 
			
		||||
module APIHashable
 | 
			
		||||
  sig { void }
 | 
			
		||||
  def generating_hash!
 | 
			
		||||
    return if generating_hash?
 | 
			
		||||
 | 
			
		||||
    # Apply monkeypatches for API generation
 | 
			
		||||
    @old_homebrew_prefix = HOMEBREW_PREFIX
 | 
			
		||||
    @old_homebrew_cellar = HOMEBREW_CELLAR
 | 
			
		||||
    @old_home = Dir.home
 | 
			
		||||
    @old_git_config_global = ENV.fetch("GIT_CONFIG_GLOBAL", nil)
 | 
			
		||||
    @old_homebrew_prefix = T.let(HOMEBREW_PREFIX, T.nilable(Pathname))
 | 
			
		||||
    @old_homebrew_cellar = T.let(HOMEBREW_CELLAR, T.nilable(Pathname))
 | 
			
		||||
    @old_home = T.let(Dir.home, T.nilable(String))
 | 
			
		||||
    @old_git_config_global = T.let(ENV.fetch("GIT_CONFIG_GLOBAL", nil), T.nilable(String))
 | 
			
		||||
    Object.send(:remove_const, :HOMEBREW_PREFIX)
 | 
			
		||||
    Object.const_set(:HOMEBREW_PREFIX, Pathname.new(HOMEBREW_PREFIX_PLACEHOLDER))
 | 
			
		||||
    ENV["HOME"] = HOMEBREW_HOME_PLACEHOLDER
 | 
			
		||||
    ENV["GIT_CONFIG_GLOBAL"] = File.join(@old_home, ".gitconfig")
 | 
			
		||||
 | 
			
		||||
    @generating_hash = true
 | 
			
		||||
    @generating_hash = T.let(true, T.nilable(T::Boolean))
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  sig { void }
 | 
			
		||||
  def generated_hash!
 | 
			
		||||
    return unless generating_hash?
 | 
			
		||||
 | 
			
		||||
@ -31,6 +33,7 @@ module APIHashable
 | 
			
		||||
    @generating_hash = false
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  sig { returns(T::Boolean) }
 | 
			
		||||
  def generating_hash?
 | 
			
		||||
    @generating_hash ||= false
 | 
			
		||||
    @generating_hash == true
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "bundle/adder"
 | 
			
		||||
@ -7,6 +7,7 @@ module Homebrew
 | 
			
		||||
  module Bundle
 | 
			
		||||
    module Commands
 | 
			
		||||
      module Add
 | 
			
		||||
        sig { params(args: String, type: Symbol, global: T::Boolean, file: T.nilable(String)).void }
 | 
			
		||||
        def self.run(*args, type:, global:, file:)
 | 
			
		||||
          Homebrew::Bundle::Adder.add(*args, type:, global:, file:)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
@ -67,8 +67,8 @@ module Homebrew
 | 
			
		||||
        end
 | 
			
		||||
        raise UsageError, "Only one url can be specified" if pr_url&.count&.> 1
 | 
			
		||||
 | 
			
		||||
        labels = if pr_url
 | 
			
		||||
          pr = GitHub::API.open_rest(pr_url.first)
 | 
			
		||||
        labels = if pr_url && (first_pr_url = pr_url.first)
 | 
			
		||||
          pr = GitHub::API.open_rest(first_pr_url)
 | 
			
		||||
          pr.fetch("labels").map { |l| l.fetch("name") }
 | 
			
		||||
        else
 | 
			
		||||
          []
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "requirement"
 | 
			
		||||
@ -7,10 +7,14 @@ require "requirement"
 | 
			
		||||
class ArchRequirement < Requirement
 | 
			
		||||
  fatal true
 | 
			
		||||
 | 
			
		||||
  @arch = T.let(nil, T.nilable(Symbol))
 | 
			
		||||
 | 
			
		||||
  sig { returns(T.nilable(Symbol)) }
 | 
			
		||||
  attr_reader :arch
 | 
			
		||||
 | 
			
		||||
  sig { params(tags: T::Array[Symbol]).void }
 | 
			
		||||
  def initialize(tags)
 | 
			
		||||
    @arch = tags.shift
 | 
			
		||||
    @arch = T.let(tags.shift, T.nilable(Symbol))
 | 
			
		||||
    super
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -132,7 +132,8 @@ module SystemConfig
 | 
			
		||||
        out.puts "#{tap_name} branch: #{tap.git_branch || "(none)"}" if default_branches.exclude?(tap.git_branch)
 | 
			
		||||
      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")}"
 | 
			
		||||
      elsif !tap.installed?
 | 
			
		||||
        out.puts "#{tap_name}: N/A"
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,10 @@
 | 
			
		||||
# typed: true # rubocop:todo Sorbet/StrictSigil
 | 
			
		||||
# typed: strict
 | 
			
		||||
# frozen_string_literal: true
 | 
			
		||||
 | 
			
		||||
require "system_command"
 | 
			
		||||
 | 
			
		||||
module GitHub
 | 
			
		||||
  sig { params(scopes: T::Array[String]).returns(String) }
 | 
			
		||||
  def self.pat_blurb(scopes = ALL_SCOPES)
 | 
			
		||||
    require "utils/formatter"
 | 
			
		||||
    require "utils/shell"
 | 
			
		||||
@ -16,20 +17,21 @@ module GitHub
 | 
			
		||||
    EOS
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  API_URL = "https://api.github.com"
 | 
			
		||||
  API_MAX_PAGES = 50
 | 
			
		||||
  API_URL = T.let("https://api.github.com", String)
 | 
			
		||||
  API_MAX_PAGES = T.let(50, Integer)
 | 
			
		||||
  private_constant :API_MAX_PAGES
 | 
			
		||||
  API_MAX_ITEMS = 5000
 | 
			
		||||
  API_MAX_ITEMS = T.let(5000, Integer)
 | 
			
		||||
  private_constant :API_MAX_ITEMS
 | 
			
		||||
  PAGINATE_RETRY_COUNT = 3
 | 
			
		||||
  PAGINATE_RETRY_COUNT = T.let(3, Integer)
 | 
			
		||||
  private_constant :PAGINATE_RETRY_COUNT
 | 
			
		||||
 | 
			
		||||
  CREATE_GIST_SCOPES = ["gist"].freeze
 | 
			
		||||
  CREATE_ISSUE_FORK_OR_PR_SCOPES = ["repo"].freeze
 | 
			
		||||
  CREATE_WORKFLOW_SCOPES = ["workflow"].freeze
 | 
			
		||||
  ALL_SCOPES = (CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES + CREATE_WORKFLOW_SCOPES).freeze
 | 
			
		||||
  CREATE_GIST_SCOPES = T.let(["gist"].freeze, T::Array[String])
 | 
			
		||||
  CREATE_ISSUE_FORK_OR_PR_SCOPES = T.let(["repo"].freeze, T::Array[String])
 | 
			
		||||
  CREATE_WORKFLOW_SCOPES = T.let(["workflow"].freeze, T::Array[String])
 | 
			
		||||
  ALL_SCOPES = T.let((CREATE_GIST_SCOPES + CREATE_ISSUE_FORK_OR_PR_SCOPES + CREATE_WORKFLOW_SCOPES).freeze,
 | 
			
		||||
                     T::Array[String])
 | 
			
		||||
  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
 | 
			
		||||
 | 
			
		||||
  # Helper functions for accessing the GitHub API.
 | 
			
		||||
@ -40,46 +42,60 @@ module GitHub
 | 
			
		||||
 | 
			
		||||
    # Generic API error.
 | 
			
		||||
    class Error < RuntimeError
 | 
			
		||||
      sig { returns(T.nilable(String)) }
 | 
			
		||||
      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
 | 
			
		||||
 | 
			
		||||
    # Error when the requested URL is not found.
 | 
			
		||||
    class HTTPNotFoundError < Error
 | 
			
		||||
      sig { params(github_message: String).void }
 | 
			
		||||
      def initialize(github_message)
 | 
			
		||||
        @github_message = github_message
 | 
			
		||||
        super
 | 
			
		||||
        super(nil, github_message)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Error when the API rate limit is exceeded.
 | 
			
		||||
    class RateLimitExceededError < Error
 | 
			
		||||
      sig { params(reset: Integer, github_message: String).void }
 | 
			
		||||
      def initialize(reset, github_message)
 | 
			
		||||
        @github_message = github_message
 | 
			
		||||
        new_pat_message = ", or:\n#{GitHub.pat_blurb}" if API.credentials.blank?
 | 
			
		||||
        super <<~EOS
 | 
			
		||||
        message = <<~EOS
 | 
			
		||||
          GitHub API Error: #{github_message}
 | 
			
		||||
          Try again in #{pretty_ratelimit_reset(reset)}#{new_pat_message}
 | 
			
		||||
        EOS
 | 
			
		||||
        super(message, github_message)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      sig { params(reset: Integer).returns(String) }
 | 
			
		||||
      def pretty_ratelimit_reset(reset)
 | 
			
		||||
        pretty_duration(Time.at(reset) - Time.now)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    GITHUB_IP_ALLOWLIST_ERROR = Regexp.new("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
 | 
			
		||||
    GITHUB_IP_ALLOWLIST_ERROR = T.let(
 | 
			
		||||
      Regexp.new(
 | 
			
		||||
        "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.
 | 
			
		||||
      #{GitHub.pat_blurb}
 | 
			
		||||
    MESSAGE
 | 
			
		||||
 | 
			
		||||
    # Error when authentication fails.
 | 
			
		||||
    class AuthenticationFailedError < Error
 | 
			
		||||
      sig { params(credentials_type: Symbol, github_message: String).void }
 | 
			
		||||
      def initialize(credentials_type, github_message)
 | 
			
		||||
        @github_message = github_message
 | 
			
		||||
        message = "GitHub API Error: #{github_message}\n"
 | 
			
		||||
        message << case credentials_type
 | 
			
		||||
        when :github_cli_token
 | 
			
		||||
@ -103,12 +119,13 @@ module GitHub
 | 
			
		||||
        when :none
 | 
			
		||||
          NO_CREDENTIALS_MESSAGE
 | 
			
		||||
        end
 | 
			
		||||
        super message.freeze
 | 
			
		||||
        super message.freeze, github_message
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    # Error when the user has no GitHub API credentials set at all (macOS keychain, GitHub CLI or envvar).
 | 
			
		||||
    class MissingAuthenticationError < Error
 | 
			
		||||
      sig { void }
 | 
			
		||||
      def initialize
 | 
			
		||||
        super NO_CREDENTIALS_MESSAGE
 | 
			
		||||
      end
 | 
			
		||||
@ -116,24 +133,21 @@ module GitHub
 | 
			
		||||
 | 
			
		||||
    # Error when the API returns a validation error.
 | 
			
		||||
    class ValidationFailedError < Error
 | 
			
		||||
      sig { params(github_message: String, errors: T::Array[String]).void }
 | 
			
		||||
      def initialize(github_message, errors)
 | 
			
		||||
        @github_message = if errors.empty?
 | 
			
		||||
          github_message
 | 
			
		||||
        else
 | 
			
		||||
          "#{github_message}: #{errors}"
 | 
			
		||||
        end
 | 
			
		||||
        github_message = "#{github_message}: #{errors}" unless errors.empty?
 | 
			
		||||
 | 
			
		||||
        super(@github_message)
 | 
			
		||||
        super(github_message, github_message)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    ERRORS = [
 | 
			
		||||
    ERRORS = T.let([
 | 
			
		||||
      AuthenticationFailedError,
 | 
			
		||||
      HTTPNotFoundError,
 | 
			
		||||
      RateLimitExceededError,
 | 
			
		||||
      Error,
 | 
			
		||||
      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.
 | 
			
		||||
    sig { returns(T.nilable(String)) }
 | 
			
		||||
@ -151,7 +165,7 @@ module GitHub
 | 
			
		||||
                                           print_stderr: false
 | 
			
		||||
        return unless result.success?
 | 
			
		||||
 | 
			
		||||
        gh_out.chomp
 | 
			
		||||
        gh_out.chomp.presence
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
@ -178,14 +192,16 @@ module GitHub
 | 
			
		||||
        #   https://github.com/Homebrew/brew/issues/6862#issuecomment-572610344
 | 
			
		||||
        return unless GITHUB_PERSONAL_ACCESS_TOKEN_REGEX.match?(github_password)
 | 
			
		||||
 | 
			
		||||
        github_password
 | 
			
		||||
        github_password.presence
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { returns(T.nilable(String)) }
 | 
			
		||||
    def self.credentials
 | 
			
		||||
      @credentials ||= T.let(nil, T.nilable(String))
 | 
			
		||||
      @credentials ||= Homebrew::EnvConfig.github_api_token.presence
 | 
			
		||||
      @credentials ||= github_cli_token.presence
 | 
			
		||||
      @credentials ||= keychain_username_password.presence
 | 
			
		||||
      @credentials ||= github_cli_token
 | 
			
		||||
      @credentials ||= keychain_username_password
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { returns(Symbol) }
 | 
			
		||||
@ -201,18 +217,19 @@ module GitHub
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    CREDENTIAL_NAMES = {
 | 
			
		||||
    CREDENTIAL_NAMES = T.let({
 | 
			
		||||
      env_token:                  "HOMEBREW_GITHUB_API_TOKEN",
 | 
			
		||||
      github_cli_token:           "GitHub CLI login",
 | 
			
		||||
      keychain_username_password: "macOS Keychain GitHub",
 | 
			
		||||
    }.freeze
 | 
			
		||||
    }.freeze, T::Hash[Symbol, String])
 | 
			
		||||
 | 
			
		||||
    # Given an API response from GitHub, warn the user if their credentials
 | 
			
		||||
    # 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)
 | 
			
		||||
      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)
 | 
			
		||||
      credentials_scopes = response_headers["x-oauth-scopes"]
 | 
			
		||||
      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?
 | 
			
		||||
 | 
			
		||||
      what = CREDENTIAL_NAMES.fetch(credentials_type)
 | 
			
		||||
      @credentials_error_message ||= onoe <<~EOS
 | 
			
		||||
        Your #{what} credentials do not have sufficient scope!
 | 
			
		||||
        Scopes required: #{needed_scopes}
 | 
			
		||||
        Scopes present:  #{credentials_scopes}
 | 
			
		||||
        #{github_permission_link}
 | 
			
		||||
      EOS
 | 
			
		||||
      @credentials_error_message ||= T.let(begin
 | 
			
		||||
        error_message = <<~EOS
 | 
			
		||||
          Your #{what} credentials do not have sufficient scope!
 | 
			
		||||
          Scopes required: #{needed_scopes}
 | 
			
		||||
          Scopes present:  #{credentials_scopes}
 | 
			
		||||
          #{github_permission_link}
 | 
			
		||||
        EOS
 | 
			
		||||
        onoe error_message
 | 
			
		||||
        error_message
 | 
			
		||||
      end, T.nilable(String))
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def self.open_rest(
 | 
			
		||||
      url, data: nil, data_binary_path: nil, request_method: nil, scopes: [].freeze, parse_json: true
 | 
			
		||||
    )
 | 
			
		||||
    sig {
 | 
			
		||||
      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.
 | 
			
		||||
      return block_given? ? yield({}) : {} if Homebrew::EnvConfig.no_github_api?
 | 
			
		||||
 | 
			
		||||
@ -289,7 +324,7 @@ module GitHub
 | 
			
		||||
 | 
			
		||||
      begin
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
        return if http_code == "204" # No Content
 | 
			
		||||
@ -305,7 +340,18 @@ module GitHub
 | 
			
		||||
      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|
 | 
			
		||||
        retry_count = 1
 | 
			
		||||
        result = begin
 | 
			
		||||
@ -324,9 +370,17 @@ module GitHub
 | 
			
		||||
      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: }
 | 
			
		||||
      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
 | 
			
		||||
        raise Error, result["errors"].map { |e| e["message"] }.join("\n") if result["errors"].present?
 | 
			
		||||
@ -340,13 +394,13 @@ module GitHub
 | 
			
		||||
    sig {
 | 
			
		||||
      params(
 | 
			
		||||
        query:        String,
 | 
			
		||||
        variables:    T.nilable(T::Hash[Symbol, T.untyped]),
 | 
			
		||||
        variables:    T::Hash[Symbol, T.untyped],
 | 
			
		||||
        scopes:       T::Array[String],
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
    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:)
 | 
			
		||||
 | 
			
		||||
      has_next_page = T.let(true, T::Boolean)
 | 
			
		||||
@ -361,6 +415,15 @@ module GitHub
 | 
			
		||||
      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)
 | 
			
		||||
      json = begin
 | 
			
		||||
        JSON.parse(output)
 | 
			
		||||
 | 
			
		||||
@ -11,11 +11,11 @@ module GitHub
 | 
			
		||||
  # @param artifact_id [String] a value that uniquely identifies the downloaded artifact
 | 
			
		||||
  sig { params(url: String, artifact_id: String).void }
 | 
			
		||||
  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
 | 
			
		||||
    # to avoid repeated downloads of (possibly large) bottles.
 | 
			
		||||
    token = API.credentials
 | 
			
		||||
    downloader = GitHubArtifactDownloadStrategy.new(url, artifact_id, token:)
 | 
			
		||||
    downloader.fetch
 | 
			
		||||
    downloader.stage
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user