fastlane-plugin-firebase_app_distribution 0.1.3 → 0.2.0.pre.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb +62 -53
 - data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_login.rb +58 -0
 - data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb +228 -0
 - data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb +91 -0
 - data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb +34 -0
 - data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb +24 -45
 - data/lib/fastlane/plugin/firebase_app_distribution/helper/upload_status_response.rb +37 -0
 - data/lib/fastlane/plugin/firebase_app_distribution/version.rb +1 -1
 - metadata +19 -10
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: b38b7e0efb3d03f71b9e4476b15304f9ffcddb75cd936acad55f16886222e458
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: '00228e7a41e3d55c4f0271ebbd5bd0aff4a699ec192b8e5e246a7dd271b9b5f3'
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 64478454789d88e5abdb0b8084e53b6e0d4320315073e59a3a5d789f2a7dde8adb34d79ca102148292977d763148645ad7b15ea2d8caeb21369a8932b9fd4f1e
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: ec77eb9cd77d44ebd4a3f3c6c89a5b3c37f3fc96a2c7bf8fa4ed227cbbca14b5c979583a75d555f2eba25912989d76672e9792eb0fe48cd18b63c56c7fc4b555
         
     | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb
    CHANGED
    
    | 
         @@ -1,39 +1,56 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            require 'tempfile'
         
     | 
| 
       2 
1 
     | 
    
         
             
            require 'fastlane/action'
         
     | 
| 
       3 
2 
     | 
    
         
             
            require 'open3'
         
     | 
| 
       4 
3 
     | 
    
         
             
            require 'shellwords'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'googleauth'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require_relative '../helper/upload_status_response'
         
     | 
| 
       5 
6 
     | 
    
         
             
            require_relative '../helper/firebase_app_distribution_helper'
         
     | 
| 
      
 7 
     | 
    
         
            +
            require_relative '../helper/firebase_app_distribution_error_message'
         
     | 
| 
      
 8 
     | 
    
         
            +
            require_relative '../client/firebase_app_distribution_api_client'
         
     | 
| 
      
 9 
     | 
    
         
            +
            require_relative '../helper/firebase_app_distribution_auth_client'
         
     | 
| 
       6 
10 
     | 
    
         | 
| 
       7 
11 
     | 
    
         
             
            ## TODO: should always use a file underneath? I think so.
         
     | 
| 
       8 
12 
     | 
    
         
             
            ## How should we document the usage of release notes?
         
     | 
| 
       9 
13 
     | 
    
         
             
            module Fastlane
         
     | 
| 
       10 
14 
     | 
    
         
             
              module Actions
         
     | 
| 
       11 
15 
     | 
    
         
             
                class FirebaseAppDistributionAction < Action
         
     | 
| 
       12 
     | 
    
         
            -
                  DEFAULT_FIREBASE_CLI_PATH = `which firebase`
         
     | 
| 
       13 
16 
     | 
    
         
             
                  FIREBASECMD_ACTION = "appdistribution:distribute".freeze
         
     | 
| 
       14 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
                  extend Auth::FirebaseAppDistributionAuthClient
         
     | 
| 
       15 
19 
     | 
    
         
             
                  extend Helper::FirebaseAppDistributionHelper
         
     | 
| 
       16 
20 
     | 
    
         | 
| 
       17 
21 
     | 
    
         
             
                  def self.run(params)
         
     | 
| 
       18 
22 
     | 
    
         
             
                    params.values # to validate all inputs before looking for the ipa/apk
         
     | 
| 
       19 
     | 
    
         
            -
                     
     | 
| 
       20 
     | 
    
         
            -
                     
     | 
| 
       21 
     | 
    
         
            -
                     
     | 
| 
       22 
     | 
    
         
            -
             
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                     
     | 
| 
       25 
     | 
    
         
            -
             
     | 
| 
       26 
     | 
    
         
            -
                     
     | 
| 
       27 
     | 
    
         
            -
             
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                       
     | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
                     
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                     
     | 
| 
      
 23 
     | 
    
         
            +
                    auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
         
     | 
| 
      
 24 
     | 
    
         
            +
                    binary_path = params[:ipa_path] || params[:apk_path]
         
     | 
| 
      
 25 
     | 
    
         
            +
                    platform = lane_platform || platform_from_path(binary_path)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, platform)
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                    if params[:app] # Set app_id if it is specified as a parameter
         
     | 
| 
      
 29 
     | 
    
         
            +
                      app_id = params[:app]
         
     | 
| 
      
 30 
     | 
    
         
            +
                    elsif platform == :ios
         
     | 
| 
      
 31 
     | 
    
         
            +
                      archive_path = Actions.lane_context[SharedValues::XCODEBUILD_ARCHIVE]
         
     | 
| 
      
 32 
     | 
    
         
            +
                      if archive_path
         
     | 
| 
      
 33 
     | 
    
         
            +
                        app_id = get_ios_app_id_from_archive(archive_path)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                    if app_id.nil?
         
     | 
| 
      
 38 
     | 
    
         
            +
                      UI.crash!(ErrorMessage::MISSING_APP_ID)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    release_id = fad_api_client.upload(app_id, binary_path, platform.to_s)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    if release_id.nil?
         
     | 
| 
      
 42 
     | 
    
         
            +
                      return
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                    release_notes = get_value_from_value_or_file(params[:release_notes], params[:release_notes_file])
         
     | 
| 
      
 46 
     | 
    
         
            +
                    fad_api_client.post_notes(app_id, release_id, release_notes)
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                    testers = get_value_from_value_or_file(params[:testers], params[:testers_file])
         
     | 
| 
      
 49 
     | 
    
         
            +
                    groups = get_value_from_value_or_file(params[:groups], params[:groups_file])
         
     | 
| 
      
 50 
     | 
    
         
            +
                    emails = string_to_array(testers)
         
     | 
| 
      
 51 
     | 
    
         
            +
                    group_ids = string_to_array(groups)
         
     | 
| 
      
 52 
     | 
    
         
            +
                    fad_api_client.enable_access(app_id, release_id, emails, group_ids)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    UI.success("🎉 App Distribution upload finished successfully.")
         
     | 
| 
       37 
54 
     | 
    
         
             
                  end
         
     | 
| 
       38 
55 
     | 
    
         | 
| 
       39 
56 
     | 
    
         
             
                  def self.description
         
     | 
| 
         @@ -41,7 +58,7 @@ module Fastlane 
     | 
|
| 
       41 
58 
     | 
    
         
             
                  end
         
     | 
| 
       42 
59 
     | 
    
         | 
| 
       43 
60 
     | 
    
         
             
                  def self.authors
         
     | 
| 
       44 
     | 
    
         
            -
                    ["Stefan Natchev"]
         
     | 
| 
      
 61 
     | 
    
         
            +
                    ["Stefan Natchev", "Manny Jimenez Github: mannyjimenez0810, Alonso Salas Infante Github: alonsosalasinfante"]
         
     | 
| 
       45 
62 
     | 
    
         
             
                  end
         
     | 
| 
       46 
63 
     | 
    
         | 
| 
       47 
64 
     | 
    
         
             
                  # supports markdown.
         
     | 
| 
         @@ -49,14 +66,26 @@ module Fastlane 
     | 
|
| 
       49 
66 
     | 
    
         
             
                    "Release your beta builds with Firebase App Distribution"
         
     | 
| 
       50 
67 
     | 
    
         
             
                  end
         
     | 
| 
       51 
68 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
                  def self. 
     | 
| 
       53 
     | 
    
         
            -
                     
     | 
| 
      
 69 
     | 
    
         
            +
                  def self.lane_platform
         
     | 
| 
      
 70 
     | 
    
         
            +
                    Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                  def self.platform_from_path(binary_path)
         
     | 
| 
      
 74 
     | 
    
         
            +
                    return nil unless binary_path
         
     | 
| 
      
 75 
     | 
    
         
            +
                    case binary_path.split('.').last
         
     | 
| 
      
 76 
     | 
    
         
            +
                    when 'ipa'
         
     | 
| 
      
 77 
     | 
    
         
            +
                      :ios
         
     | 
| 
      
 78 
     | 
    
         
            +
                    when 'apk'
         
     | 
| 
      
 79 
     | 
    
         
            +
                      :android
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
       54 
82 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
      
 83 
     | 
    
         
            +
                  def self.available_options
         
     | 
| 
      
 84 
     | 
    
         
            +
                    if lane_platform == :ios || lane_platform.nil?
         
     | 
| 
       56 
85 
     | 
    
         
             
                      ipa_path_default = Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last
         
     | 
| 
       57 
86 
     | 
    
         
             
                    end
         
     | 
| 
       58 
87 
     | 
    
         | 
| 
       59 
     | 
    
         
            -
                    if  
     | 
| 
      
 88 
     | 
    
         
            +
                    if lane_platform == :android
         
     | 
| 
       60 
89 
     | 
    
         
             
                      apk_path_default = Dir["*.apk"].last || Dir[File.join("app", "build", "outputs", "apk", "app-release.apk")].last
         
     | 
| 
       61 
90 
     | 
    
         
             
                    end
         
     | 
| 
       62 
91 
     | 
    
         | 
| 
         @@ -84,25 +113,13 @@ module Fastlane 
     | 
|
| 
       84 
113 
     | 
    
         
             
                      FastlaneCore::ConfigItem.new(key: :app,
         
     | 
| 
       85 
114 
     | 
    
         
             
                                                   env_name: "FIREBASEAPPDISTRO_APP",
         
     | 
| 
       86 
115 
     | 
    
         
             
                                                   description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
         
     | 
| 
       87 
     | 
    
         
            -
                                                   optional:  
     | 
| 
      
 116 
     | 
    
         
            +
                                                   optional: true,
         
     | 
| 
       88 
117 
     | 
    
         
             
                                                   type: String),
         
     | 
| 
       89 
118 
     | 
    
         
             
                      FastlaneCore::ConfigItem.new(key: :firebase_cli_path,
         
     | 
| 
      
 119 
     | 
    
         
            +
                                                   deprecated: "This plugin no longer uses the Firebase CLI",
         
     | 
| 
       90 
120 
     | 
    
         
             
                                                   env_name: "FIREBASEAPPDISTRO_FIREBASE_CLI_PATH",
         
     | 
| 
       91 
121 
     | 
    
         
             
                                                   description: "The absolute path of the firebase cli command",
         
     | 
| 
       92 
     | 
    
         
            -
                                                    
     | 
| 
       93 
     | 
    
         
            -
                                                   default_value_dynamic: true,
         
     | 
| 
       94 
     | 
    
         
            -
                                                   optional: false,
         
     | 
| 
       95 
     | 
    
         
            -
                                                   type: String,
         
     | 
| 
       96 
     | 
    
         
            -
                                                   verify_block: proc do |value|
         
     | 
| 
       97 
     | 
    
         
            -
                                                     value.chomp!
         
     | 
| 
       98 
     | 
    
         
            -
                                                     if value.to_s == "" || !File.exist?(value)
         
     | 
| 
       99 
     | 
    
         
            -
                                                       UI.user_error!("firebase_cli_path: missing path to firebase cli tool. Please install firebase in $PATH or specify path")
         
     | 
| 
       100 
     | 
    
         
            -
                                                     end
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
                                                     unless is_firebasecmd_supported?(value)
         
     | 
| 
       103 
     | 
    
         
            -
                                                       UI.user_error!("firebase_cli_path: `#{value}` does not support the `#{FIREBASECMD_ACTION}` command. Please download (https://appdistro.page.link/firebase-cli-download) or specify the path to the correct version of firebse")
         
     | 
| 
       104 
     | 
    
         
            -
                                                     end
         
     | 
| 
       105 
     | 
    
         
            -
                                                   end),
         
     | 
| 
      
 122 
     | 
    
         
            +
                                                   type: String),
         
     | 
| 
       106 
123 
     | 
    
         
             
                      FastlaneCore::ConfigItem.new(key: :groups,
         
     | 
| 
       107 
124 
     | 
    
         
             
                                                   env_name: "FIREBASEAPPDISTRO_GROUPS",
         
     | 
| 
       108 
125 
     | 
    
         
             
                                                   description: "The groups used for distribution, separated by commas",
         
     | 
| 
         @@ -143,7 +160,11 @@ module Fastlane 
     | 
|
| 
       143 
160 
     | 
    
         
             
                                                   description: "Print verbose debug output",
         
     | 
| 
       144 
161 
     | 
    
         
             
                                                   optional: true,
         
     | 
| 
       145 
162 
     | 
    
         
             
                                                   default_value: false,
         
     | 
| 
       146 
     | 
    
         
            -
                                                   is_string: false)
         
     | 
| 
      
 163 
     | 
    
         
            +
                                                   is_string: false),
         
     | 
| 
      
 164 
     | 
    
         
            +
                      FastlaneCore::ConfigItem.new(key: :service_credentials_file,
         
     | 
| 
      
 165 
     | 
    
         
            +
                                                   description: "Path to Google service account json",
         
     | 
| 
      
 166 
     | 
    
         
            +
                                                   optional: true,
         
     | 
| 
      
 167 
     | 
    
         
            +
                                                   type: String)
         
     | 
| 
       147 
168 
     | 
    
         
             
                    ]
         
     | 
| 
       148 
169 
     | 
    
         
             
                  end
         
     | 
| 
       149 
170 
     | 
    
         | 
| 
         @@ -165,18 +186,6 @@ module Fastlane 
     | 
|
| 
       165 
186 
     | 
    
         
             
                      CODE
         
     | 
| 
       166 
187 
     | 
    
         
             
                    ]
         
     | 
| 
       167 
188 
     | 
    
         
             
                  end
         
     | 
| 
       168 
     | 
    
         
            -
             
     | 
| 
       169 
     | 
    
         
            -
                  ## TODO: figure out if we can surpress color output.
         
     | 
| 
       170 
     | 
    
         
            -
                  def self.is_firebasecmd_supported?(cmd)
         
     | 
| 
       171 
     | 
    
         
            -
                    outerr, status = Open3.capture2e(cmd, "--non-interactive", FIREBASECMD_ACTION, "--help")
         
     | 
| 
       172 
     | 
    
         
            -
                    return false unless status.success?
         
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
       174 
     | 
    
         
            -
                    if outerr =~ /is not a Firebase command/
         
     | 
| 
       175 
     | 
    
         
            -
                      return false
         
     | 
| 
       176 
     | 
    
         
            -
                    end
         
     | 
| 
       177 
     | 
    
         
            -
             
     | 
| 
       178 
     | 
    
         
            -
                    true
         
     | 
| 
       179 
     | 
    
         
            -
                  end
         
     | 
| 
       180 
189 
     | 
    
         
             
                end
         
     | 
| 
       181 
190 
     | 
    
         
             
              end
         
     | 
| 
       182 
191 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,58 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'googleauth'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'googleauth/stores/file_token_store'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "google/apis/people_v1"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "fileutils"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Fastlane
         
     | 
| 
      
 7 
     | 
    
         
            +
              module Actions
         
     | 
| 
      
 8 
     | 
    
         
            +
                class FirebaseAppDistributionLoginAction < Action
         
     | 
| 
      
 9 
     | 
    
         
            +
                  OOB_URI = "urn:ietf:wg:oauth:2.0:oob"
         
     | 
| 
      
 10 
     | 
    
         
            +
                  SCOPE = "https://www.googleapis.com/auth/cloud-platform"
         
     | 
| 
      
 11 
     | 
    
         
            +
                  CLIENT_ID = "563584335869-fgrhgmd47bqnekij5i8b5pr03ho849e6.apps.googleusercontent.com"
         
     | 
| 
      
 12 
     | 
    
         
            +
                  CLIENT_SECRET = "j9iVZfS8kkCEFUPaAeJV0sAi"
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  def self.run(params)
         
     | 
| 
      
 15 
     | 
    
         
            +
                    client_id = Google::Auth::ClientId.new(CLIENT_ID, CLIENT_SECRET)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    authorizer = Google::Auth::UserAuthorizer.new(client_id, SCOPE, nil)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    url = authorizer.get_authorization_url(base_url: OOB_URI)
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                    UI.message("Open the following address in your browser and sign in with your Google account:")
         
     | 
| 
      
 20 
     | 
    
         
            +
                    UI.message(url)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    UI.message("")
         
     | 
| 
      
 22 
     | 
    
         
            +
                    code = UI.input("Enter the resulting code here: ")
         
     | 
| 
      
 23 
     | 
    
         
            +
                    credentials = authorizer.get_credentials_from_code(code: code, base_url: OOB_URI)
         
     | 
| 
      
 24 
     | 
    
         
            +
                    UI.message("")
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                    UI.success("Set the refresh token as the FIREBASE_TOKEN environment variable")
         
     | 
| 
      
 27 
     | 
    
         
            +
                    UI.success("Refresh Token: #{credentials.refresh_token}")
         
     | 
| 
      
 28 
     | 
    
         
            +
                  rescue Signet::AuthorizationError
         
     | 
| 
      
 29 
     | 
    
         
            +
                    UI.error("The code you entered is invalid. Copy and paste the code and try again.")
         
     | 
| 
      
 30 
     | 
    
         
            +
                  rescue => error
         
     | 
| 
      
 31 
     | 
    
         
            +
                    UI.error(error.to_s)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    UI.crash!("An error has occured, please login again.")
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                  #####################################################
         
     | 
| 
      
 36 
     | 
    
         
            +
                  # @!group Documentation
         
     | 
| 
      
 37 
     | 
    
         
            +
                  #####################################################
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                  def self.description
         
     | 
| 
      
 40 
     | 
    
         
            +
                    "Authenticate with Firebase App Distribution using a Google account."
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def self.details
         
     | 
| 
      
 44 
     | 
    
         
            +
                    "Log in to Firebase App Distribution using a Google account to generate an authentication "\
         
     | 
| 
      
 45 
     | 
    
         
            +
                    "token. This token is stored within an environment variable and used to authenticate with your Firebase project. "\
         
     | 
| 
      
 46 
     | 
    
         
            +
                    "See https://firebase.google.com/docs/app-distribution/ios/distribute-fastlane for more information."
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def self.authors
         
     | 
| 
      
 50 
     | 
    
         
            +
                    ["Manny Jimenez Github: mannyjimenez0810, Alonso Salas Infante Github: alonsosalasinfante"]
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  def self.is_supported?(platform)
         
     | 
| 
      
 54 
     | 
    
         
            +
                    [:ios, :android].include?(platform)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  end
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
    ADDED
    
    | 
         @@ -0,0 +1,228 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fastlane_core/ui/ui'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require_relative '../actions/firebase_app_distribution_login'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Fastlane
         
     | 
| 
      
 5 
     | 
    
         
            +
              module Client
         
     | 
| 
      
 6 
     | 
    
         
            +
                class FirebaseAppDistributionApiClient
         
     | 
| 
      
 7 
     | 
    
         
            +
                  BASE_URL = "https://firebaseappdistribution.googleapis.com"
         
     | 
| 
      
 8 
     | 
    
         
            +
                  TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
         
     | 
| 
      
 9 
     | 
    
         
            +
                  MAX_POLLING_RETRIES = 60
         
     | 
| 
      
 10 
     | 
    
         
            +
                  POLLING_INTERVAL_SECONDS = 2
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                  def initialize(auth_token, platform)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    @auth_token = auth_token
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                    if platform.nil?
         
     | 
| 
      
 16 
     | 
    
         
            +
                      @binary_type = "IPA/APK"
         
     | 
| 
      
 17 
     | 
    
         
            +
                    elsif platform == :ios
         
     | 
| 
      
 18 
     | 
    
         
            +
                      @binary_type = "IPA"
         
     | 
| 
      
 19 
     | 
    
         
            +
                    else
         
     | 
| 
      
 20 
     | 
    
         
            +
                      @binary_type = "APK"
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  # Enables tester access to the specified app release. Skips this
         
     | 
| 
      
 25 
     | 
    
         
            +
                  # step if no testers are passed in (emails and group_ids are nil/empty).
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #
         
     | 
| 
      
 27 
     | 
    
         
            +
                  # args
         
     | 
| 
      
 28 
     | 
    
         
            +
                  #   app_id - Firebase App ID
         
     | 
| 
      
 29 
     | 
    
         
            +
                  #   release_id - App release ID, returned by upload_status endpoint
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #   emails - String array of app testers' email addresses
         
     | 
| 
      
 31 
     | 
    
         
            +
                  #   group_ids - String array of Firebase tester group IDs
         
     | 
| 
      
 32 
     | 
    
         
            +
                  #
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # Throws a user_error if app_id, emails, or group_ids are invalid
         
     | 
| 
      
 34 
     | 
    
         
            +
                  def enable_access(app_id, release_id, emails, group_ids)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    if (emails.nil? || emails.empty?) && (group_ids.nil? || group_ids.empty?)
         
     | 
| 
      
 36 
     | 
    
         
            +
                      UI.success("✅ No testers passed in. Skipping this step.")
         
     | 
| 
      
 37 
     | 
    
         
            +
                      return
         
     | 
| 
      
 38 
     | 
    
         
            +
                    end
         
     | 
| 
      
 39 
     | 
    
         
            +
                    payload = { emails: emails, groupIds: group_ids }
         
     | 
| 
      
 40 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 41 
     | 
    
         
            +
                      connection.post(enable_access_url(app_id, release_id), payload.to_json) do |request|
         
     | 
| 
      
 42 
     | 
    
         
            +
                        request.headers["Authorization"] = "Bearer " + @auth_token
         
     | 
| 
      
 43 
     | 
    
         
            +
                      end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    rescue Faraday::ResourceNotFound
         
     | 
| 
      
 45 
     | 
    
         
            +
                      UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
         
     | 
| 
      
 46 
     | 
    
         
            +
                    rescue Faraday::ClientError
         
     | 
| 
      
 47 
     | 
    
         
            +
                      UI.user_error!("#{ErrorMessage::INVALID_TESTERS} \nEmails: #{emails} \nGroups: #{group_ids}")
         
     | 
| 
      
 48 
     | 
    
         
            +
                    end
         
     | 
| 
      
 49 
     | 
    
         
            +
                    UI.success("✅ Added testers/groups.")
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  # Posts notes for the specified app release. Skips this
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # step if no notes are passed in (release_notes is nil/empty).
         
     | 
| 
      
 54 
     | 
    
         
            +
                  #
         
     | 
| 
      
 55 
     | 
    
         
            +
                  # args
         
     | 
| 
      
 56 
     | 
    
         
            +
                  #   app_id - Firebase App ID
         
     | 
| 
      
 57 
     | 
    
         
            +
                  #   release_id - App release ID, returned by upload_status endpoint
         
     | 
| 
      
 58 
     | 
    
         
            +
                  #   release_notes - String of notes for this release
         
     | 
| 
      
 59 
     | 
    
         
            +
                  #
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # Throws a user_error if app_id or release_id are invalid
         
     | 
| 
      
 61 
     | 
    
         
            +
                  def post_notes(app_id, release_id, release_notes)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    payload = { releaseNotes: { releaseNotes: release_notes } }
         
     | 
| 
      
 63 
     | 
    
         
            +
                    if release_notes.nil? || release_notes.empty?
         
     | 
| 
      
 64 
     | 
    
         
            +
                      UI.success("✅ No release notes passed in. Skipping this step.")
         
     | 
| 
      
 65 
     | 
    
         
            +
                      return
         
     | 
| 
      
 66 
     | 
    
         
            +
                    end
         
     | 
| 
      
 67 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 68 
     | 
    
         
            +
                      connection.post(release_notes_create_url(app_id, release_id), payload.to_json) do |request|
         
     | 
| 
      
 69 
     | 
    
         
            +
                        request.headers["Authorization"] = "Bearer " + @auth_token
         
     | 
| 
      
 70 
     | 
    
         
            +
                      end
         
     | 
| 
      
 71 
     | 
    
         
            +
                    rescue Faraday::ResourceNotFound
         
     | 
| 
      
 72 
     | 
    
         
            +
                      UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
         
     | 
| 
      
 73 
     | 
    
         
            +
                      # rescue Faraday::ClientError
         
     | 
| 
      
 74 
     | 
    
         
            +
                      #   UI.user_error!("#{ErrorMessage::INVALID_RELEASE_ID}: #{release_id}")
         
     | 
| 
      
 75 
     | 
    
         
            +
                    end
         
     | 
| 
      
 76 
     | 
    
         
            +
                    UI.success("✅ Posted release notes.")
         
     | 
| 
      
 77 
     | 
    
         
            +
                  end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                  # Returns the url encoded upload token used for get_upload_status calls:
         
     | 
| 
      
 80 
     | 
    
         
            +
                  # projects/<project-number>/apps/<app-id>/releases/-/binaries/<binary-hash>
         
     | 
| 
      
 81 
     | 
    
         
            +
                  #
         
     | 
| 
      
 82 
     | 
    
         
            +
                  # args
         
     | 
| 
      
 83 
     | 
    
         
            +
                  #   app_id - Firebase App ID
         
     | 
| 
      
 84 
     | 
    
         
            +
                  #   binary_path - Absolute path to your app's apk/ipa file
         
     | 
| 
      
 85 
     | 
    
         
            +
                  #
         
     | 
| 
      
 86 
     | 
    
         
            +
                  # Throws a user_error if an invalid app id is passed in, the binary file does
         
     | 
| 
      
 87 
     | 
    
         
            +
                  # not exist, or invalid auth credentials are used (e.g. wrong project permissions)
         
     | 
| 
      
 88 
     | 
    
         
            +
                  def get_upload_token(app_id, binary_path)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    if binary_path.nil? || !File.exist?(binary_path)
         
     | 
| 
      
 90 
     | 
    
         
            +
                      UI.crash!("#{ErrorMessage.binary_not_found(@binary_type)}: #{binary_path}")
         
     | 
| 
      
 91 
     | 
    
         
            +
                    end
         
     | 
| 
      
 92 
     | 
    
         
            +
                    binary_hash = Digest::SHA256.hexdigest(File.open(binary_path).read)
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 95 
     | 
    
         
            +
                      response = connection.get(v1_apps_url(app_id)) do |request|
         
     | 
| 
      
 96 
     | 
    
         
            +
                        request.headers["Authorization"] = "Bearer " + @auth_token
         
     | 
| 
      
 97 
     | 
    
         
            +
                      end
         
     | 
| 
      
 98 
     | 
    
         
            +
                    rescue Faraday::ResourceNotFound
         
     | 
| 
      
 99 
     | 
    
         
            +
                      UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
                    contact_email = response.body[:contactEmail]
         
     | 
| 
      
 102 
     | 
    
         
            +
                    if contact_email.nil? || contact_email.strip.empty?
         
     | 
| 
      
 103 
     | 
    
         
            +
                      UI.user_error!(ErrorMessage::GET_APP_NO_CONTACT_EMAIL_ERROR)
         
     | 
| 
      
 104 
     | 
    
         
            +
                    end
         
     | 
| 
      
 105 
     | 
    
         
            +
                    return upload_token_format(response.body[:appId], response.body[:projectNumber], binary_hash)
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                  # Uploads the app binary to the Firebase API
         
     | 
| 
      
 109 
     | 
    
         
            +
                  #
         
     | 
| 
      
 110 
     | 
    
         
            +
                  # args
         
     | 
| 
      
 111 
     | 
    
         
            +
                  #   app_id - Firebase App ID
         
     | 
| 
      
 112 
     | 
    
         
            +
                  #   binary_path - Absolute path to your app's apk/ipa file
         
     | 
| 
      
 113 
     | 
    
         
            +
                  #   platform - 'android' or 'ios'
         
     | 
| 
      
 114 
     | 
    
         
            +
                  #
         
     | 
| 
      
 115 
     | 
    
         
            +
                  # Throws a user_error if an invalid app id is passed in, or if the binary file does not exist
         
     | 
| 
      
 116 
     | 
    
         
            +
                  def upload_binary(app_id, binary_path, platform)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    connection.post(binary_upload_url(app_id), File.open(binary_path).read) do |request|
         
     | 
| 
      
 118 
     | 
    
         
            +
                      request.headers["Authorization"] = "Bearer " + @auth_token
         
     | 
| 
      
 119 
     | 
    
         
            +
                      request.headers["X-APP-DISTRO-API-CLIENT-ID"] = "fastlane"
         
     | 
| 
      
 120 
     | 
    
         
            +
                      request.headers["X-APP-DISTRO-API-CLIENT-TYPE"] =  platform
         
     | 
| 
      
 121 
     | 
    
         
            +
                      request.headers["X-APP-DISTRO-API-CLIENT-VERSION"] = Fastlane::FirebaseAppDistribution::VERSION
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
                  rescue Faraday::ResourceNotFound
         
     | 
| 
      
 124 
     | 
    
         
            +
                    UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
         
     | 
| 
      
 125 
     | 
    
         
            +
                  rescue Errno::ENOENT # Raised when binary_path file does not exist
         
     | 
| 
      
 126 
     | 
    
         
            +
                    UI.user_error!("#{ErrorMessage.binary_not_found(@binary_type)}: #{binary_path}")
         
     | 
| 
      
 127 
     | 
    
         
            +
                  end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                  # Uploads the binary file if it has not already been uploaded
         
     | 
| 
      
 130 
     | 
    
         
            +
                  # Takes at least POLLING_INTERVAL_SECONDS between polling get_upload_status
         
     | 
| 
      
 131 
     | 
    
         
            +
                  #
         
     | 
| 
      
 132 
     | 
    
         
            +
                  # args
         
     | 
| 
      
 133 
     | 
    
         
            +
                  #   app_id - Firebase App ID
         
     | 
| 
      
 134 
     | 
    
         
            +
                  #   binary_path - Absolute path to your app's apk/ipa file
         
     | 
| 
      
 135 
     | 
    
         
            +
                  #
         
     | 
| 
      
 136 
     | 
    
         
            +
                  # Returns the release_id on a successful release, otherwise returns nil.
         
     | 
| 
      
 137 
     | 
    
         
            +
                  #
         
     | 
| 
      
 138 
     | 
    
         
            +
                  # Throws a UI error if the number of polling retries exceeds MAX_POLLING_RETRIES
         
     | 
| 
      
 139 
     | 
    
         
            +
                  # Crashes if not able to upload the binary
         
     | 
| 
      
 140 
     | 
    
         
            +
                  def upload(app_id, binary_path, platform)
         
     | 
| 
      
 141 
     | 
    
         
            +
                    upload_token = get_upload_token(app_id, binary_path)
         
     | 
| 
      
 142 
     | 
    
         
            +
                    upload_status_response = get_upload_status(app_id, upload_token)
         
     | 
| 
      
 143 
     | 
    
         
            +
                    if upload_status_response.success? || upload_status_response.already_uploaded?
         
     | 
| 
      
 144 
     | 
    
         
            +
                      UI.success("✅ This #{@binary_type} has been uploaded before. Skipping upload step.")
         
     | 
| 
      
 145 
     | 
    
         
            +
                    else
         
     | 
| 
      
 146 
     | 
    
         
            +
                      unless upload_status_response.in_progress?
         
     | 
| 
      
 147 
     | 
    
         
            +
                        UI.message("⌛ Uploading the #{@binary_type}.")
         
     | 
| 
      
 148 
     | 
    
         
            +
                        upload_binary(app_id, binary_path, platform)
         
     | 
| 
      
 149 
     | 
    
         
            +
                      end
         
     | 
| 
      
 150 
     | 
    
         
            +
                      MAX_POLLING_RETRIES.times do
         
     | 
| 
      
 151 
     | 
    
         
            +
                        upload_status_response = get_upload_status(app_id, upload_token)
         
     | 
| 
      
 152 
     | 
    
         
            +
                        if upload_status_response.success? || upload_status_response.already_uploaded?
         
     | 
| 
      
 153 
     | 
    
         
            +
                          UI.success("✅ Uploaded the #{@binary_type}.")
         
     | 
| 
      
 154 
     | 
    
         
            +
                          break
         
     | 
| 
      
 155 
     | 
    
         
            +
                        elsif upload_status_response.in_progress?
         
     | 
| 
      
 156 
     | 
    
         
            +
                          sleep(POLLING_INTERVAL_SECONDS)
         
     | 
| 
      
 157 
     | 
    
         
            +
                        else
         
     | 
| 
      
 158 
     | 
    
         
            +
                          if !upload_status_response.message.nil?
         
     | 
| 
      
 159 
     | 
    
         
            +
                            UI.user_error!("#{ErrorMessage.upload_binary_error(@binary_type)}: #{upload_status_response.message}")
         
     | 
| 
      
 160 
     | 
    
         
            +
                          else
         
     | 
| 
      
 161 
     | 
    
         
            +
                            UI.user_error!(ErrorMessage.upload_binary_error(@binary_type))
         
     | 
| 
      
 162 
     | 
    
         
            +
                          end
         
     | 
| 
      
 163 
     | 
    
         
            +
                        end
         
     | 
| 
      
 164 
     | 
    
         
            +
                      end
         
     | 
| 
      
 165 
     | 
    
         
            +
                      unless upload_status_response.success?
         
     | 
| 
      
 166 
     | 
    
         
            +
                        UI.error("It took longer than expected to process your #{@binary_type}, please try again.")
         
     | 
| 
      
 167 
     | 
    
         
            +
                        return nil
         
     | 
| 
      
 168 
     | 
    
         
            +
                      end
         
     | 
| 
      
 169 
     | 
    
         
            +
                    end
         
     | 
| 
      
 170 
     | 
    
         
            +
                    upload_status_response.release_id
         
     | 
| 
      
 171 
     | 
    
         
            +
                  end
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  # Fetches the status of an uploaded binary
         
     | 
| 
      
 174 
     | 
    
         
            +
                  #
         
     | 
| 
      
 175 
     | 
    
         
            +
                  # args
         
     | 
| 
      
 176 
     | 
    
         
            +
                  #   app_id - Firebase App ID
         
     | 
| 
      
 177 
     | 
    
         
            +
                  #   upload_token - URL encoded upload token
         
     | 
| 
      
 178 
     | 
    
         
            +
                  #
         
     | 
| 
      
 179 
     | 
    
         
            +
                  # Returns the release ID on a successful release, otherwise returns nil.
         
     | 
| 
      
 180 
     | 
    
         
            +
                  #
         
     | 
| 
      
 181 
     | 
    
         
            +
                  # Throws a user_error if an invalid app_id is passed in
         
     | 
| 
      
 182 
     | 
    
         
            +
                  def get_upload_status(app_id, upload_token)
         
     | 
| 
      
 183 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 184 
     | 
    
         
            +
                      response = connection.get(upload_status_url(app_id, upload_token)) do |request|
         
     | 
| 
      
 185 
     | 
    
         
            +
                        request.headers["Authorization"] = "Bearer " + @auth_token
         
     | 
| 
      
 186 
     | 
    
         
            +
                      end
         
     | 
| 
      
 187 
     | 
    
         
            +
                    rescue Faraday::ResourceNotFound
         
     | 
| 
      
 188 
     | 
    
         
            +
                      UI.user_error!("#{ErrorMessage::INVALID_APP_ID}: #{app_id}")
         
     | 
| 
      
 189 
     | 
    
         
            +
                    end
         
     | 
| 
      
 190 
     | 
    
         
            +
                    return UploadStatusResponse.new(response.body)
         
     | 
| 
      
 191 
     | 
    
         
            +
                  end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                  private
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                  def v1_apps_url(app_id)
         
     | 
| 
      
 196 
     | 
    
         
            +
                    "/v1alpha/apps/#{app_id}"
         
     | 
| 
      
 197 
     | 
    
         
            +
                  end
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                  def release_notes_create_url(app_id, release_id)
         
     | 
| 
      
 200 
     | 
    
         
            +
                    "#{v1_apps_url(app_id)}/releases/#{release_id}/notes"
         
     | 
| 
      
 201 
     | 
    
         
            +
                  end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                  def enable_access_url(app_id, release_id)
         
     | 
| 
      
 204 
     | 
    
         
            +
                    "#{v1_apps_url(app_id)}/releases/#{release_id}/enable_access"
         
     | 
| 
      
 205 
     | 
    
         
            +
                  end
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                  def binary_upload_url(app_id)
         
     | 
| 
      
 208 
     | 
    
         
            +
                    "/app-binary-uploads?app_id=#{app_id}"
         
     | 
| 
      
 209 
     | 
    
         
            +
                  end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                  def upload_status_url(app_id, app_token)
         
     | 
| 
      
 212 
     | 
    
         
            +
                    "#{v1_apps_url(app_id)}/upload_status/#{app_token}"
         
     | 
| 
      
 213 
     | 
    
         
            +
                  end
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                  def upload_token_format(app_id, project_number, binary_hash)
         
     | 
| 
      
 216 
     | 
    
         
            +
                    CGI.escape("projects/#{project_number}/apps/#{app_id}/releases/-/binaries/#{binary_hash}")
         
     | 
| 
      
 217 
     | 
    
         
            +
                  end
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                  def connection
         
     | 
| 
      
 220 
     | 
    
         
            +
                    @connection ||= Faraday.new(url: BASE_URL) do |conn|
         
     | 
| 
      
 221 
     | 
    
         
            +
                      conn.response(:json, parser_options: { symbolize_names: true })
         
     | 
| 
      
 222 
     | 
    
         
            +
                      conn.response(:raise_error) # raise_error middleware will run before the json middleware
         
     | 
| 
      
 223 
     | 
    
         
            +
                      conn.adapter(Faraday.default_adapter)
         
     | 
| 
      
 224 
     | 
    
         
            +
                    end
         
     | 
| 
      
 225 
     | 
    
         
            +
                  end
         
     | 
| 
      
 226 
     | 
    
         
            +
                end
         
     | 
| 
      
 227 
     | 
    
         
            +
              end
         
     | 
| 
      
 228 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb
    ADDED
    
    | 
         @@ -0,0 +1,91 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fastlane_core/ui/ui'
         
     | 
| 
      
 2 
     | 
    
         
            +
            module Fastlane
         
     | 
| 
      
 3 
     | 
    
         
            +
              UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Auth
         
     | 
| 
      
 5 
     | 
    
         
            +
                module FirebaseAppDistributionAuthClient
         
     | 
| 
      
 6 
     | 
    
         
            +
                  TOKEN_CREDENTIAL_URI = "https://oauth2.googleapis.com/token"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                  # Returns the auth token for any of the auth methods (Firebase CLI token,
         
     | 
| 
      
 9 
     | 
    
         
            +
                  # Google service account, firebase-tools). To ensure that a specific
         
     | 
| 
      
 10 
     | 
    
         
            +
                  # auth method is used, unset all other auth variables/parameters to nil/empty
         
     | 
| 
      
 11 
     | 
    
         
            +
                  #
         
     | 
| 
      
 12 
     | 
    
         
            +
                  # args
         
     | 
| 
      
 13 
     | 
    
         
            +
                  #   google_service_path - Absolute path to the Google service account file
         
     | 
| 
      
 14 
     | 
    
         
            +
                  #   firebase_cli_token - Firebase CLI refresh token from login action or
         
     | 
| 
      
 15 
     | 
    
         
            +
                  #                        CI environment
         
     | 
| 
      
 16 
     | 
    
         
            +
                  #
         
     | 
| 
      
 17 
     | 
    
         
            +
                  # env variables
         
     | 
| 
      
 18 
     | 
    
         
            +
                  #   GOOGLE_APPLICATION_CREDENTIALS - see google_service_path
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #   FIREBASE_TOKEN - see firebase_cli_token
         
     | 
| 
      
 20 
     | 
    
         
            +
                  #
         
     | 
| 
      
 21 
     | 
    
         
            +
                  # Crashes if given invalid or missing credentials
         
     | 
| 
      
 22 
     | 
    
         
            +
                  def fetch_auth_token(google_service_path, firebase_cli_token)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    if !google_service_path.nil? && !google_service_path.empty?
         
     | 
| 
      
 24 
     | 
    
         
            +
                      UI.message("Authenticating with --service_credentials_file path parameter: #{google_service_path}")
         
     | 
| 
      
 25 
     | 
    
         
            +
                      token = service_account(google_service_path)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    elsif !firebase_cli_token.nil? && !firebase_cli_token.empty?
         
     | 
| 
      
 27 
     | 
    
         
            +
                      UI.message("Authenticating with --firebase_cli_token parameter")
         
     | 
| 
      
 28 
     | 
    
         
            +
                      token = firebase_token(firebase_cli_token)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    elsif !ENV["FIREBASE_TOKEN"].nil? && !ENV["FIREBASE_TOKEN"].empty?
         
     | 
| 
      
 30 
     | 
    
         
            +
                      UI.message("Authenticating with FIREBASE_TOKEN environment variable")
         
     | 
| 
      
 31 
     | 
    
         
            +
                      token = firebase_token(ENV["FIREBASE_TOKEN"])
         
     | 
| 
      
 32 
     | 
    
         
            +
                    elsif !ENV["GOOGLE_APPLICATION_CREDENTIALS"].nil? && !ENV["GOOGLE_APPLICATION_CREDENTIALS"].empty?
         
     | 
| 
      
 33 
     | 
    
         
            +
                      UI.message("Authenticating with GOOGLE_APPLICATION_CREDENTIALS environment variable: #{ENV['GOOGLE_APPLICATION_CREDENTIALS']}")
         
     | 
| 
      
 34 
     | 
    
         
            +
                      token = service_account(ENV["GOOGLE_APPLICATION_CREDENTIALS"])
         
     | 
| 
      
 35 
     | 
    
         
            +
                    elsif (refresh_token = refresh_token_from_firebase_tools)
         
     | 
| 
      
 36 
     | 
    
         
            +
                      UI.message("No authentication method specified. Using cached Firebase CLI credentials.")
         
     | 
| 
      
 37 
     | 
    
         
            +
                      token = firebase_token(refresh_token)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    else
         
     | 
| 
      
 39 
     | 
    
         
            +
                      UI.user_error!(ErrorMessage::MISSING_CREDENTIALS)
         
     | 
| 
      
 40 
     | 
    
         
            +
                    end
         
     | 
| 
      
 41 
     | 
    
         
            +
                    UI.success("🔐 Authenticated successfully.")
         
     | 
| 
      
 42 
     | 
    
         
            +
                    token
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                  private
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                  def refresh_token_from_firebase_tools
         
     | 
| 
      
 48 
     | 
    
         
            +
                    if ENV["XDG_CONFIG_HOME"].nil? || ENV["XDG_CONFIG_HOME"].empty?
         
     | 
| 
      
 49 
     | 
    
         
            +
                      config_path = File.expand_path(".config/configstore/firebase-tools.json", "~")
         
     | 
| 
      
 50 
     | 
    
         
            +
                    else
         
     | 
| 
      
 51 
     | 
    
         
            +
                      config_path = File.expand_path("configstore/firebase-tools.json", ENV["XDG_CONFIG_HOME"])
         
     | 
| 
      
 52 
     | 
    
         
            +
                    end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                    if File.exist?(config_path)
         
     | 
| 
      
 55 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 56 
     | 
    
         
            +
                        refresh_token = JSON.parse(File.read(config_path))['tokens']['refresh_token']
         
     | 
| 
      
 57 
     | 
    
         
            +
                        refresh_token unless refresh_token.nil? || refresh_token.empty?
         
     | 
| 
      
 58 
     | 
    
         
            +
                      # TODO: Catch parser errors, improve error handling here
         
     | 
| 
      
 59 
     | 
    
         
            +
                      # Returns nil when there is an empty "tokens" field in the firebase-tools json
         
     | 
| 
      
 60 
     | 
    
         
            +
                      rescue NoMethodError
         
     | 
| 
      
 61 
     | 
    
         
            +
                      end
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                  def firebase_token(refresh_token)
         
     | 
| 
      
 66 
     | 
    
         
            +
                    client = Signet::OAuth2::Client.new(
         
     | 
| 
      
 67 
     | 
    
         
            +
                      token_credential_uri: TOKEN_CREDENTIAL_URI,
         
     | 
| 
      
 68 
     | 
    
         
            +
                      client_id: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_ID,
         
     | 
| 
      
 69 
     | 
    
         
            +
                      client_secret: Fastlane::Actions::FirebaseAppDistributionLoginAction::CLIENT_SECRET,
         
     | 
| 
      
 70 
     | 
    
         
            +
                      refresh_token: refresh_token
         
     | 
| 
      
 71 
     | 
    
         
            +
                    )
         
     | 
| 
      
 72 
     | 
    
         
            +
                    client.fetch_access_token!
         
     | 
| 
      
 73 
     | 
    
         
            +
                    client.access_token
         
     | 
| 
      
 74 
     | 
    
         
            +
                  rescue Signet::AuthorizationError
         
     | 
| 
      
 75 
     | 
    
         
            +
                    UI.user_error!(ErrorMessage::REFRESH_TOKEN_ERROR)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                  def service_account(google_service_path)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    service_account_credentials = Google::Auth::ServiceAccountCredentials.make_creds(
         
     | 
| 
      
 80 
     | 
    
         
            +
                      json_key_io: File.open(google_service_path),
         
     | 
| 
      
 81 
     | 
    
         
            +
                      scope: Fastlane::Actions::FirebaseAppDistributionLoginAction::SCOPE
         
     | 
| 
      
 82 
     | 
    
         
            +
                    )
         
     | 
| 
      
 83 
     | 
    
         
            +
                    service_account_credentials.fetch_access_token!["access_token"]
         
     | 
| 
      
 84 
     | 
    
         
            +
                  rescue Errno::ENOENT
         
     | 
| 
      
 85 
     | 
    
         
            +
                    UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_NOT_FOUND}: #{google_service_path}")
         
     | 
| 
      
 86 
     | 
    
         
            +
                  rescue Signet::AuthorizationError
         
     | 
| 
      
 87 
     | 
    
         
            +
                    UI.user_error!("#{ErrorMessage::SERVICE_CREDENTIALS_ERROR}: #{google_service_path}")
         
     | 
| 
      
 88 
     | 
    
         
            +
                  end
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb
    ADDED
    
    | 
         @@ -0,0 +1,34 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module ErrorMessage
         
     | 
| 
      
 2 
     | 
    
         
            +
              MISSING_CREDENTIALS = "Missing authentication credentials. Check that your Firebase refresh token is set or that your service account file path is correct and try again."
         
     | 
| 
      
 3 
     | 
    
         
            +
              MISSING_APP_ID = "Missing app id. Please check that the app parameter is set and try again"
         
     | 
| 
      
 4 
     | 
    
         
            +
              SERVICE_CREDENTIALS_NOT_FOUND = "Service credentials file does not exist. Please check the service credentials path and try again"
         
     | 
| 
      
 5 
     | 
    
         
            +
              PARSE_SERVICE_CREDENTIALS_ERROR = "Failed to extract service account information from the service credentials file"
         
     | 
| 
      
 6 
     | 
    
         
            +
              UPLOAD_RELEASE_NOTES_ERROR = "App Distribution halted because it had a problem uploading release notes"
         
     | 
| 
      
 7 
     | 
    
         
            +
              UPLOAD_TESTERS_ERROR = "App Distribution halted because it had a problem adding testers/groups"
         
     | 
| 
      
 8 
     | 
    
         
            +
              GET_RELEASE_TIMEOUT = "App Distribution failed to fetch release information"
         
     | 
| 
      
 9 
     | 
    
         
            +
              REFRESH_TOKEN_ERROR = "App Distribution could not generate credentials from the refresh token specified."
         
     | 
| 
      
 10 
     | 
    
         
            +
              GET_APP_ERROR = "App Distribution failed to fetch app information"
         
     | 
| 
      
 11 
     | 
    
         
            +
              APP_NOT_ONBOARDED_ERROR = "App Distribution not onboarded"
         
     | 
| 
      
 12 
     | 
    
         
            +
              GET_APP_NO_CONTACT_EMAIL_ERROR = "App Distribution could not find a contact email associated with this app. Contact Email"
         
     | 
| 
      
 13 
     | 
    
         
            +
              INVALID_APP_ID = "App Distribution could not find your app. Make sure to onboard your app by pressing the \"Get started\" button on the App Distribution page in the Firebase console: https://console.firebase.google.com/project/_/appdistribution. App ID"
         
     | 
| 
      
 14 
     | 
    
         
            +
              INVALID_PATH = "Could not read content from"
         
     | 
| 
      
 15 
     | 
    
         
            +
              INVALID_TESTERS = "Could not enable access for testers. Check that the groups exist and the tester emails are formatted correctly"
         
     | 
| 
      
 16 
     | 
    
         
            +
              INVALID_RELEASE_ID = "App distribution failed to fetch release with id"
         
     | 
| 
      
 17 
     | 
    
         
            +
              SERVICE_CREDENTIALS_ERROR = "App Distribution could not generate credentials from the service credentials file specified. Service Account Path"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def self.binary_not_found(binary_type)
         
     | 
| 
      
 20 
     | 
    
         
            +
                "Could not find the #{binary_type}. Make sure you set the #{binary_type} path parameter to point to your #{binary_type}"
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def self.parse_binary_metadata_error(binary_type)
         
     | 
| 
      
 24 
     | 
    
         
            +
                "Failed to extract #{binary_type} metadata from the #{binary_type} path"
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def self.upload_binary_error(binary_type)
         
     | 
| 
      
 28 
     | 
    
         
            +
                "App Distribution halted because it had a problem uploading the #{binary_type}"
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              def self.binary_processing_error(binary_type)
         
     | 
| 
      
 32 
     | 
    
         
            +
                "App Distribution failed to process the #{binary_type}"
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb
    CHANGED
    
    | 
         @@ -1,60 +1,39 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            require 'fastlane_core/ui/ui'
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
      
 2 
     | 
    
         
            +
            require 'cfpropertylist'
         
     | 
| 
       3 
3 
     | 
    
         
             
            module Fastlane
         
     | 
| 
       4 
4 
     | 
    
         
             
              UI = FastlaneCore::UI unless Fastlane.const_defined?("UI")
         
     | 
| 
       5 
     | 
    
         
            -
             
     | 
| 
       6 
5 
     | 
    
         
             
              module Helper
         
     | 
| 
       7 
6 
     | 
    
         
             
                module FirebaseAppDistributionHelper
         
     | 
| 
       8 
     | 
    
         
            -
                  def  
     | 
| 
       9 
     | 
    
         
            -
                     
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
       11 
     | 
    
         
            -
             
     | 
| 
       12 
     | 
    
         
            -
             
     | 
| 
       13 
     | 
    
         
            -
             
     | 
| 
       14 
     | 
    
         
            -
             
     | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
       16 
     | 
    
         
            -
                  def release_notes_flag(params)
         
     | 
| 
       17 
     | 
    
         
            -
                    file_flag_if_supplied("--release-notes-file", "release_notes", params)
         
     | 
| 
       18 
     | 
    
         
            -
                  end
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
     | 
    
         
            -
                  def file_flag_if_supplied(flag, param_name, params)
         
     | 
| 
       21 
     | 
    
         
            -
                    file = params["#{param_name}_file".to_sym]
         
     | 
| 
       22 
     | 
    
         
            -
                    file ||= file_for_contents(param_name.to_sym, params)
         
     | 
| 
       23 
     | 
    
         
            -
             
     | 
| 
       24 
     | 
    
         
            -
                    if file
         
     | 
| 
       25 
     | 
    
         
            -
                      return "#{flag} #{file}"
         
     | 
| 
      
 7 
     | 
    
         
            +
                  def get_value_from_value_or_file(value, path)
         
     | 
| 
      
 8 
     | 
    
         
            +
                    if (value.nil? || value.empty?) && !path.nil?
         
     | 
| 
      
 9 
     | 
    
         
            +
                      begin
         
     | 
| 
      
 10 
     | 
    
         
            +
                        return File.open(path).read
         
     | 
| 
      
 11 
     | 
    
         
            +
                      rescue Errno::ENOENT
         
     | 
| 
      
 12 
     | 
    
         
            +
                        UI.crash!("#{ErrorMessage::INVALID_PATH}: #{path}")
         
     | 
| 
      
 13 
     | 
    
         
            +
                      end
         
     | 
| 
       26 
14 
     | 
    
         
             
                    end
         
     | 
| 
      
 15 
     | 
    
         
            +
                    value
         
     | 
| 
       27 
16 
     | 
    
         
             
                  end
         
     | 
| 
       28 
17 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                   
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
       31 
     | 
    
         
            -
                   
     | 
| 
       32 
     | 
    
         
            -
             
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                     
     | 
| 
      
 18 
     | 
    
         
            +
                  # Returns the array representation of a string with comma seperated values.
         
     | 
| 
      
 19 
     | 
    
         
            +
                  #
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # Does not work with strings whose individual values have spaces. EX "Hello World" the space will be removed to "HelloWorld"
         
     | 
| 
      
 21 
     | 
    
         
            +
                  def string_to_array(string)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    return nil if string.nil? || string.empty?
         
     | 
| 
      
 23 
     | 
    
         
            +
                    string_array = string.gsub(/\s+/, '').split(",")
         
     | 
| 
      
 24 
     | 
    
         
            +
                    return string_array
         
     | 
| 
       35 
25 
     | 
    
         
             
                  end
         
     | 
| 
       36 
26 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                   
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
                  def file_for_contents(parameter_name, params)
         
     | 
| 
       40 
     | 
    
         
            -
                    if @tempfiles.nil?
         
     | 
| 
       41 
     | 
    
         
            -
                      @tempfiles = []
         
     | 
| 
       42 
     | 
    
         
            -
                    end
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
                    contents = params[parameter_name]
         
     | 
| 
       45 
     | 
    
         
            -
                    return nil if contents.nil?
         
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
                    file = Tempfile.new(parameter_name.to_s)
         
     | 
| 
       48 
     | 
    
         
            -
                    file.write(contents)
         
     | 
| 
       49 
     | 
    
         
            -
                    file.close
         
     | 
| 
       50 
     | 
    
         
            -
                    @tempfiles << file
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                    file.path
         
     | 
| 
      
 27 
     | 
    
         
            +
                  def parse_plist(path)
         
     | 
| 
      
 28 
     | 
    
         
            +
                    CFPropertyList.native_types(CFPropertyList::List.new(file: path).value)
         
     | 
| 
       53 
29 
     | 
    
         
             
                  end
         
     | 
| 
       54 
30 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
                  def  
     | 
| 
       56 
     | 
    
         
            -
                     
     | 
| 
       57 
     | 
    
         
            -
                     
     | 
| 
      
 31 
     | 
    
         
            +
                  def get_ios_app_id_from_archive(path)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    app_path = parse_plist("#{path}/Info.plist")["ApplicationProperties"]["ApplicationPath"]
         
     | 
| 
      
 33 
     | 
    
         
            +
                    UI.shell_error!("can't extract application path from Info.plist at #{path}") if app_path.empty?
         
     | 
| 
      
 34 
     | 
    
         
            +
                    identifier = parse_plist("#{path}/Products/#{app_path}/GoogleService-Info.plist")["GOOGLE_APP_ID"]
         
     | 
| 
      
 35 
     | 
    
         
            +
                    UI.shell_error!("can't extract GOOGLE_APP_ID") if identifier.empty?
         
     | 
| 
      
 36 
     | 
    
         
            +
                    return identifier
         
     | 
| 
       58 
37 
     | 
    
         
             
                  end
         
     | 
| 
       59 
38 
     | 
    
         
             
                end
         
     | 
| 
       60 
39 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class UploadStatusResponse
         
     | 
| 
      
 2 
     | 
    
         
            +
              def initialize(response_json_hash)
         
     | 
| 
      
 3 
     | 
    
         
            +
                @response_json_hash = response_json_hash
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def status
         
     | 
| 
      
 7 
     | 
    
         
            +
                @response_json_hash[:status]
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def success?
         
     | 
| 
      
 11 
     | 
    
         
            +
                status == 'SUCCESS'
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
              def in_progress?
         
     | 
| 
      
 15 
     | 
    
         
            +
                status == 'IN_PROGRESS'
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              def error?
         
     | 
| 
      
 19 
     | 
    
         
            +
                status == 'ERROR'
         
     | 
| 
      
 20 
     | 
    
         
            +
              end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              def already_uploaded?
         
     | 
| 
      
 23 
     | 
    
         
            +
                status == 'ALREADY_UPLOADED'
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              def release_hash
         
     | 
| 
      
 27 
     | 
    
         
            +
                @response_json_hash[:release]
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              def release_id
         
     | 
| 
      
 31 
     | 
    
         
            +
                release_hash ? release_hash[:id] : nil
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              def message
         
     | 
| 
      
 35 
     | 
    
         
            +
                @response_json_hash[:message]
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,16 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: fastlane-plugin-firebase_app_distribution
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.2.0.pre.4
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Stefan Natchev
         
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
      
 8 
     | 
    
         
            +
            - Manny Jimenez
         
     | 
| 
      
 9 
     | 
    
         
            +
            - Alonso Salas Infante
         
     | 
| 
      
 10 
     | 
    
         
            +
            autorequire:
         
     | 
| 
       9 
11 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
12 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date:  
     | 
| 
      
 13 
     | 
    
         
            +
            date: 2020-09-01 00:00:00.000000000 Z
         
     | 
| 
       12 
14 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
15 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
16 
     | 
    
         
             
              name: pry
         
     | 
| 
         @@ -136,9 +138,11 @@ dependencies: 
     | 
|
| 
       136 
138 
     | 
    
         
             
                - - ">="
         
     | 
| 
       137 
139 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       138 
140 
     | 
    
         
             
                    version: 2.127.1
         
     | 
| 
       139 
     | 
    
         
            -
            description: 
     | 
| 
      
 141 
     | 
    
         
            +
            description:
         
     | 
| 
       140 
142 
     | 
    
         
             
            email:
         
     | 
| 
       141 
143 
     | 
    
         
             
            - snatchev@google.com
         
     | 
| 
      
 144 
     | 
    
         
            +
            - mannyjimenez@google.com
         
     | 
| 
      
 145 
     | 
    
         
            +
            - alonsosi@google.com
         
     | 
| 
       142 
146 
     | 
    
         
             
            executables: []
         
     | 
| 
       143 
147 
     | 
    
         
             
            extensions: []
         
     | 
| 
       144 
148 
     | 
    
         
             
            extra_rdoc_files: []
         
     | 
| 
         @@ -147,13 +151,18 @@ files: 
     | 
|
| 
       147 
151 
     | 
    
         
             
            - README.md
         
     | 
| 
       148 
152 
     | 
    
         
             
            - lib/fastlane/plugin/firebase_app_distribution.rb
         
     | 
| 
       149 
153 
     | 
    
         
             
            - lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_action.rb
         
     | 
| 
      
 154 
     | 
    
         
            +
            - lib/fastlane/plugin/firebase_app_distribution/actions/firebase_app_distribution_login.rb
         
     | 
| 
      
 155 
     | 
    
         
            +
            - lib/fastlane/plugin/firebase_app_distribution/client/firebase_app_distribution_api_client.rb
         
     | 
| 
      
 156 
     | 
    
         
            +
            - lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_auth_client.rb
         
     | 
| 
      
 157 
     | 
    
         
            +
            - lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_error_message.rb
         
     | 
| 
       150 
158 
     | 
    
         
             
            - lib/fastlane/plugin/firebase_app_distribution/helper/firebase_app_distribution_helper.rb
         
     | 
| 
      
 159 
     | 
    
         
            +
            - lib/fastlane/plugin/firebase_app_distribution/helper/upload_status_response.rb
         
     | 
| 
       151 
160 
     | 
    
         
             
            - lib/fastlane/plugin/firebase_app_distribution/version.rb
         
     | 
| 
       152 
     | 
    
         
            -
            homepage: https://github.com/fastlane 
     | 
| 
      
 161 
     | 
    
         
            +
            homepage: https://github.com/fastlane/fastlane-plugin-firebase_app_distribution
         
     | 
| 
       153 
162 
     | 
    
         
             
            licenses:
         
     | 
| 
       154 
163 
     | 
    
         
             
            - MIT
         
     | 
| 
       155 
164 
     | 
    
         
             
            metadata: {}
         
     | 
| 
       156 
     | 
    
         
            -
            post_install_message: 
     | 
| 
      
 165 
     | 
    
         
            +
            post_install_message:
         
     | 
| 
       157 
166 
     | 
    
         
             
            rdoc_options: []
         
     | 
| 
       158 
167 
     | 
    
         
             
            require_paths:
         
     | 
| 
       159 
168 
     | 
    
         
             
            - lib
         
     | 
| 
         @@ -164,12 +173,12 @@ required_ruby_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       164 
173 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       165 
174 
     | 
    
         
             
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
       166 
175 
     | 
    
         
             
              requirements:
         
     | 
| 
       167 
     | 
    
         
            -
              - - " 
     | 
| 
      
 176 
     | 
    
         
            +
              - - ">"
         
     | 
| 
       168 
177 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       169 
     | 
    
         
            -
                  version:  
     | 
| 
      
 178 
     | 
    
         
            +
                  version: 1.3.1
         
     | 
| 
       170 
179 
     | 
    
         
             
            requirements: []
         
     | 
| 
       171 
     | 
    
         
            -
            rubygems_version: 3. 
     | 
| 
       172 
     | 
    
         
            -
            signing_key: 
     | 
| 
      
 180 
     | 
    
         
            +
            rubygems_version: 3.1.2
         
     | 
| 
      
 181 
     | 
    
         
            +
            signing_key:
         
     | 
| 
       173 
182 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       174 
183 
     | 
    
         
             
            summary: Release your beta builds to Firebase App Distribution. https://firebase.google.com/docs/app-distribution
         
     | 
| 
       175 
184 
     | 
    
         
             
            test_files: []
         
     |