fastlane-plugin-firebase_app_distribution 0.2.5 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1415793a828ae696b67f1568c6393a9ae68eb6dbf17fc2a379904e3256201c1b
4
- data.tar.gz: b1e08e8e78359f62c2c76c868c8e80e90bdf3d463951072ecf77aaa46a232ad1
3
+ metadata.gz: 745bfb3dfc4c6c48f389c8e4d75b50026cf37e872ce26e7510aefef049280329
4
+ data.tar.gz: 8122801b30f95422216c9c5343d102bc6b11fbc16308f268515e7a3405bbf070
5
5
  SHA512:
6
- metadata.gz: a3c2f313a6401ac052c48329d9f21228c084983c6f6a3c252e22c494c4216b57ee68882668e1f3f6f0e5f639b3389b0701d97dad3c3542289099450aea2f1952
7
- data.tar.gz: 29c7622c069712c0012429c26e3ad7e306499c6a29d7a7d837185a008837831dba795787a16930817dbbdbb3aabb0f38f37c06094208d2e1376cff030c79733d
6
+ metadata.gz: 6ac42a57fdbda601b6ef2656a7e0d1b38304f022a26c970a75f1e0a43d01f2607f993c5500bceaf8ffb5b24ada6fbcbcb1ccf84d7dc8d87dc7d62049561d7886
7
+ data.tar.gz: ae12197e0149e187b02716e28a71207cd5ce67d14ea331bb9c230e41a53237e1c3e4359b1178f09bf059ac2d9697cb4b667d358a1d4751a3b47006efcaf131bb
data/fad-icon.png ADDED
Binary file
@@ -8,40 +8,93 @@ require_relative '../helper/firebase_app_distribution_error_message'
8
8
  require_relative '../client/firebase_app_distribution_api_client'
9
9
  require_relative '../helper/firebase_app_distribution_auth_client'
10
10
 
11
- ## TODO: should always use a file underneath? I think so.
12
11
  ## How should we document the usage of release notes?
13
12
  module Fastlane
14
13
  module Actions
15
- class FirebaseAppDistributionAction < Action
16
- FIREBASECMD_ACTION = "appdistribution:distribute".freeze
14
+ module SharedValues
15
+ FIREBASE_APP_DISTRO_RELEASE ||= :FIREBASE_APP_DISTRO_RELEASE
16
+ end
17
17
 
18
+ class FirebaseAppDistributionAction < Action
18
19
  extend Auth::FirebaseAppDistributionAuthClient
19
20
  extend Helper::FirebaseAppDistributionHelper
20
21
 
22
+ DEFAULT_UPLOAD_TIMEOUT_SECONDS = 300
23
+
21
24
  def self.run(params)
22
- params.values # to validate all inputs before looking for the ipa/apk
25
+ params.values # to validate all inputs before looking for the ipa/apk/aab
26
+
27
+ if params[:debug]
28
+ UI.important("Warning: Debug logging enabled. Output may include sensitive information.")
29
+ end
23
30
 
24
31
  app_id = app_id_from_params(params)
32
+ app_name = app_name_from_app_id(app_id)
25
33
  platform = lane_platform || platform_from_app_id(app_id)
26
- binary_path = binary_path_from_platform(platform, params[:ipa_path], params[:apk_path])
27
34
 
28
- auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
29
- fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, platform, params[:debug])
35
+ binary_path = get_binary_path(platform, params)
36
+ UI.user_error!("Couldn't find binary") if binary_path.nil?
37
+ UI.user_error!("Couldn't find binary at path #{binary_path}") unless File.exist?(binary_path)
38
+ binary_type = binary_type_from_path(binary_path)
39
+
40
+ auth_token = fetch_auth_token(
41
+ params[:service_credentials_file], params[:firebase_cli_token], params[:debug]
42
+ )
43
+ fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
44
+
45
+ # If binary is an AAB, get the AAB info for this app, which includes the integration state and certificate data
46
+ if binary_type == :AAB
47
+ aab_info = fad_api_client.get_aab_info(app_name)
48
+ validate_aab_setup!(aab_info)
49
+ end
50
+
51
+ upload_timeout = get_upload_timeout(params)
52
+
53
+ upload_status_response = fad_api_client.upload(app_name, binary_path, platform.to_s, upload_timeout)
54
+ release_name = upload_status_response.release_name
55
+ release = upload_status_response.release
30
56
 
31
- release_id = fad_api_client.upload(app_id, binary_path, platform.to_s)
32
- if release_id.nil?
33
- return
57
+ if binary_type == :AAB && aab_info && !aab_info.certs_provided?
58
+ updated_aab_info = fad_api_client.get_aab_info(app_name)
59
+ if updated_aab_info.certs_provided?
60
+ UI.message("After you upload an AAB for the first time, App Distribution " \
61
+ "generates a new test certificate. All AAB uploads are re-signed with this test " \
62
+ "certificate. Use the certificate fingerprints below to register your app " \
63
+ "signing key with API providers, such as Google Sign-In and Google Maps.\n" \
64
+ "MD-1 certificate fingerprint: #{updated_aab_info.md5_certificate_hash}\n" \
65
+ "SHA-1 certificate fingerprint: #{updated_aab_info.sha1_certificate_hash}\n" \
66
+ "SHA-256 certificate fingerprint: #{updated_aab_info.sha256_certificate_hash}")
67
+ end
34
68
  end
35
69
 
36
- release_notes = get_value_from_value_or_file(params[:release_notes], params[:release_notes_file])
37
- fad_api_client.post_notes(app_id, release_id, release_notes)
70
+ release_notes = release_notes(params)
71
+ if release_notes.nil? || release_notes.empty?
72
+ UI.message("⏩ No release notes passed in. Skipping this step.")
73
+ else
74
+ release = fad_api_client.update_release_notes(release_name, release_notes)
75
+ end
38
76
 
39
77
  testers = get_value_from_value_or_file(params[:testers], params[:testers_file])
40
78
  groups = get_value_from_value_or_file(params[:groups], params[:groups_file])
41
79
  emails = string_to_array(testers)
42
- group_ids = string_to_array(groups)
43
- fad_api_client.enable_access(app_id, release_id, emails, group_ids)
44
- UI.success("🎉 App Distribution upload finished successfully.")
80
+ group_aliases = string_to_array(groups)
81
+ fad_api_client.distribute(release_name, emails, group_aliases)
82
+ UI.success("🎉 App Distribution upload finished successfully. Setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE] to the uploaded release.")
83
+
84
+ if upload_status_response.firebase_console_uri
85
+ UI.message("🔗 View this release in the Firebase console: #{upload_status_response.firebase_console_uri}")
86
+ end
87
+
88
+ if upload_status_response.testing_uri
89
+ UI.message("🔗 Share this release with testers who have access: #{upload_status_response.testing_uri}")
90
+ end
91
+
92
+ if upload_status_response.binary_download_uri
93
+ UI.message("🔗 Download the release binary (link expires in 1 hour): #{upload_status_response.binary_download_uri}")
94
+ end
95
+
96
+ Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_RELEASE] = release
97
+ release
45
98
  end
46
99
 
47
100
  def self.description
@@ -90,37 +143,66 @@ module Fastlane
90
143
  end
91
144
  end
92
145
 
93
- def self.binary_path_from_platform(platform, ipa_path, apk_path)
94
- case platform
95
- when :ios
96
- ipa_path
97
- when :android
98
- apk_path
99
- else
100
- ipa_path || apk_path
146
+ def self.get_binary_path(platform, params)
147
+ if platform == :ios
148
+ return params[:ipa_path] ||
149
+ Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] ||
150
+ Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last
151
+ end
152
+
153
+ if platform == :android
154
+ return params[:apk_path] || params[:android_artifact_path] if params[:apk_path] || params[:android_artifact_path]
155
+
156
+ if params[:android_artifact_type] == 'AAB'
157
+ return Actions.lane_context[SharedValues::GRADLE_AAB_OUTPUT_PATH] ||
158
+ Dir["*.aab"].last ||
159
+ Dir[File.join("app", "build", "outputs", "bundle", "release", "app-release.aab")].last
160
+ end
161
+
162
+ return Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH] ||
163
+ Dir["*.apk"].last ||
164
+ Dir[File.join("app", "build", "outputs", "apk", "release", "app-release.apk")].last
101
165
  end
102
166
  end
103
167
 
104
- def self.available_options
105
- if lane_platform == :ios || lane_platform.nil?
106
- ipa_path_default = Dir["*.ipa"].sort_by { |x| File.mtime(x) }.last
168
+ def self.get_upload_timeout(params)
169
+ if params[:upload_timeout]
170
+ return params[:upload_timeout]
171
+ else
172
+ return DEFAULT_UPLOAD_TIMEOUT_SECONDS
107
173
  end
174
+ end
108
175
 
109
- if lane_platform == :android
110
- apk_path_default = Dir["*.apk"].last || Dir[File.join("app", "build", "outputs", "apk", "app-release.apk")].last
176
+ def self.validate_aab_setup!(aab_info)
177
+ if aab_info && aab_info.integration_state != AabInfo::AabState::INTEGRATED && aab_info.integration_state != AabInfo::AabState::UNAVAILABLE
178
+ case aab_info.integration_state
179
+ when AabInfo::AabState::PLAY_ACCOUNT_NOT_LINKED
180
+ UI.user_error!(ErrorMessage::PLAY_ACCOUNT_NOT_LINKED)
181
+ when AabInfo::AabState::APP_NOT_PUBLISHED
182
+ UI.user_error!(ErrorMessage::APP_NOT_PUBLISHED)
183
+ when AabInfo::AabState::NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT
184
+ UI.user_error!(ErrorMessage::NO_APP_WITH_GIVEN_BUNDLE_ID_IN_PLAY_ACCOUNT)
185
+ when AabInfo::AabState::PLAY_IAS_TERMS_NOT_ACCEPTED
186
+ UI.user_error!(ErrorMessage::PLAY_IAS_TERMS_NOT_ACCEPTED)
187
+ else
188
+ UI.user_error!(ErrorMessage.aab_upload_error(aab_info.integration_state))
189
+ end
111
190
  end
191
+ end
192
+
193
+ def self.release_notes(params)
194
+ release_notes_param =
195
+ get_value_from_value_or_file(params[:release_notes], params[:release_notes_file])
196
+ release_notes_param || Actions.lane_context[SharedValues::FL_CHANGELOG]
197
+ end
112
198
 
199
+ def self.available_options
113
200
  [
114
201
  # iOS Specific
115
202
  FastlaneCore::ConfigItem.new(key: :ipa_path,
116
203
  env_name: "FIREBASEAPPDISTRO_IPA_PATH",
117
204
  description: "Path to your IPA file. Optional if you use the _gym_ or _xcodebuild_ action",
118
- default_value: Actions.lane_context[SharedValues::IPA_OUTPUT_PATH] || ipa_path_default,
119
- default_value_dynamic: true,
120
- optional: true,
121
- verify_block: proc do |value|
122
- UI.user_error!("firebase_app_distribution: Couldn't find ipa file at path '#{value}'") unless File.exist?(value)
123
- end),
205
+ optional: true),
124
206
  FastlaneCore::ConfigItem.new(key: :googleservice_info_plist_path,
125
207
  env_name: "GOOGLESERVICE_INFO_PLIST_PATH",
126
208
  description: "Path to your GoogleService-Info.plist file, relative to the archived product path",
@@ -131,12 +213,21 @@ module Fastlane
131
213
  FastlaneCore::ConfigItem.new(key: :apk_path,
132
214
  env_name: "FIREBASEAPPDISTRO_APK_PATH",
133
215
  description: "Path to your APK file",
134
- default_value: Actions.lane_context[SharedValues::GRADLE_APK_OUTPUT_PATH] || apk_path_default,
216
+ optional: true),
217
+ FastlaneCore::ConfigItem.new(key: :android_artifact_path,
218
+ env_name: "FIREBASEAPPDISTRO_ANDROID_ARTIFACT_PATH",
219
+ description: "Path to your APK or AAB file",
220
+ optional: true),
221
+ FastlaneCore::ConfigItem.new(key: :android_artifact_type,
222
+ env_name: "FIREBASEAPPDISTRO_ANDROID_ARTIFACT_TYPE",
223
+ description: "Android artifact type. Set to 'APK' or 'AAB'. Defaults to 'APK' if not set",
224
+ default_value: "APK",
135
225
  default_value_dynamic: true,
136
226
  optional: true,
137
227
  verify_block: proc do |value|
138
- UI.user_error!("firebase_app_distribution: Couldn't find apk file at path '#{value}'") unless File.exist?(value)
228
+ UI.user_error!("firebase_app_distribution: '#{value}' is not a valid value for android_artifact_type. Should be 'APK' or 'AAB'") unless ['APK', 'AAB'].include?(value)
139
229
  end),
230
+ # Generic
140
231
  FastlaneCore::ConfigItem.new(key: :app,
141
232
  env_name: "FIREBASEAPPDISTRO_APP",
142
233
  description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
@@ -149,12 +240,12 @@ module Fastlane
149
240
  type: String),
150
241
  FastlaneCore::ConfigItem.new(key: :groups,
151
242
  env_name: "FIREBASEAPPDISTRO_GROUPS",
152
- description: "The groups used for distribution, separated by commas",
243
+ description: "The group aliases used for distribution, separated by commas",
153
244
  optional: true,
154
245
  type: String),
155
246
  FastlaneCore::ConfigItem.new(key: :groups_file,
156
247
  env_name: "FIREBASEAPPDISTRO_GROUPS_FILE",
157
- description: "The groups used for distribution, separated by commas",
248
+ description: "The group aliases used for distribution, separated by commas",
158
249
  optional: true,
159
250
  type: String),
160
251
  FastlaneCore::ConfigItem.new(key: :testers,
@@ -170,8 +261,6 @@ module Fastlane
170
261
  FastlaneCore::ConfigItem.new(key: :release_notes,
171
262
  env_name: "FIREBASEAPPDISTRO_RELEASE_NOTES",
172
263
  description: "Release notes for this build",
173
- default_value: Actions.lane_context[SharedValues::FL_CHANGELOG],
174
- default_value_dynamic: true,
175
264
  optional: true,
176
265
  type: String),
177
266
  FastlaneCore::ConfigItem.new(key: :release_notes_file,
@@ -180,7 +269,7 @@ module Fastlane
180
269
  optional: true,
181
270
  type: String),
182
271
  FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
183
- description: "Auth token for firebase cli",
272
+ description: "Auth token generated using the Firebase CLI's login:ci command",
184
273
  optional: true,
185
274
  type: String),
186
275
  FastlaneCore::ConfigItem.new(key: :debug,
@@ -191,7 +280,12 @@ module Fastlane
191
280
  FastlaneCore::ConfigItem.new(key: :service_credentials_file,
192
281
  description: "Path to Google service account json",
193
282
  optional: true,
194
- type: String)
283
+ type: String),
284
+ FastlaneCore::ConfigItem.new(key: :upload_timeout,
285
+ description: "The amount of seconds before the upload will timeout, if not completed",
286
+ optional: true,
287
+ default_value: DEFAULT_UPLOAD_TIMEOUT_SECONDS,
288
+ type: Integer)
195
289
  ]
196
290
  end
197
291
 
@@ -207,12 +301,18 @@ module Fastlane
207
301
  [
208
302
  <<-CODE
209
303
  firebase_app_distribution(
210
- app: "1:1234567890:ios:0a1b2c3d4e5f67890",
304
+ app: "<your Firebase app ID>",
211
305
  testers: "snatchev@google.com, rebeccahe@google.com"
212
306
  )
213
307
  CODE
214
308
  ]
215
309
  end
310
+
311
+ def self.output
312
+ [
313
+ ['FIREBASE_APP_DISTRO_RELEASE', 'A hash representing the uploaded release created in Firebase App Distribution']
314
+ ]
315
+ end
216
316
  end
217
317
  end
218
318
  end
@@ -0,0 +1,90 @@
1
+ require 'fastlane/action'
2
+ require 'fastlane_core/ui/ui'
3
+
4
+ require_relative '../helper/firebase_app_distribution_helper'
5
+ require_relative '../helper/firebase_app_distribution_auth_client'
6
+
7
+ module Fastlane
8
+ module Actions
9
+ class FirebaseAppDistributionAddTestersAction < Action
10
+ extend Auth::FirebaseAppDistributionAuthClient
11
+ extend Helper::FirebaseAppDistributionHelper
12
+
13
+ def self.run(params)
14
+ auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
15
+ fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
16
+
17
+ if blank?(params[:emails]) && blank?(params[:file])
18
+ UI.user_error!("Must specify `emails` or `file`.")
19
+ end
20
+
21
+ emails = string_to_array(get_value_from_value_or_file(params[:emails], params[:file]))
22
+
23
+ UI.user_error!("Must pass at least one email") if blank?(emails)
24
+
25
+ if emails.count > 1000
26
+ UI.user_error!("A maximum of 1000 testers can be added at a time.")
27
+ end
28
+
29
+ UI.message("⏳ Adding #{emails.count} testers to project #{params[:project_number]}...")
30
+
31
+ fad_api_client.add_testers(params[:project_number], emails)
32
+
33
+ # The add_testers response lists all the testers from the request
34
+ # regardless of whether or not they were created or if they already
35
+ # exists so can't get an accurate count of the number of newly created testers
36
+ UI.success("✅ Tester(s) successfully added.")
37
+ end
38
+
39
+ def self.description
40
+ "Create testers in bulk from a comma-separated list or a file"
41
+ end
42
+
43
+ def self.authors
44
+ ["Tunde Agboola"]
45
+ end
46
+
47
+ # supports markdown.
48
+ def self.details
49
+ "Create testers in bulk from a comma-separated list or a file"
50
+ end
51
+
52
+ def self.available_options
53
+ [
54
+ FastlaneCore::ConfigItem.new(key: :project_number,
55
+ env_name: "FIREBASEAPPDISTRO_PROJECT_NUMBER",
56
+ description: "Your Firebase project number. You can find the project number in the Firebase console, on the General Settings page",
57
+ type: Integer,
58
+ optional: false),
59
+ FastlaneCore::ConfigItem.new(key: :emails,
60
+ env_name: "FIREBASEAPPDISTRO_ADD_TESTERS_EMAILS",
61
+ description: "Comma separated list of tester emails to be created. A maximum of 1000 testers can be created at a time",
62
+ optional: true,
63
+ type: String),
64
+ FastlaneCore::ConfigItem.new(key: :file,
65
+ env_name: "FIREBASEAPPDISTRO_ADD_TESTERS_FILE",
66
+ description: "Path to a file containing a comma separated list of tester emails to be created. A maximum of 1000 testers can be deleted at a time",
67
+ optional: true,
68
+ type: String),
69
+ FastlaneCore::ConfigItem.new(key: :service_credentials_file,
70
+ description: "Path to Google service credentials file",
71
+ optional: true,
72
+ type: String),
73
+ FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
74
+ description: "Auth token generated using the Firebase CLI's login:ci command",
75
+ optional: true,
76
+ type: String),
77
+ FastlaneCore::ConfigItem.new(key: :debug,
78
+ description: "Print verbose debug output",
79
+ optional: true,
80
+ default_value: false,
81
+ is_string: false)
82
+ ]
83
+ end
84
+
85
+ def self.is_supported?(platform)
86
+ true
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,114 @@
1
+ require 'fastlane/action'
2
+ require_relative '../client/firebase_app_distribution_api_client'
3
+ require_relative '../helper/firebase_app_distribution_auth_client'
4
+ require_relative '../helper/firebase_app_distribution_helper'
5
+
6
+ module Fastlane
7
+ module Actions
8
+ module SharedValues
9
+ FIREBASE_APP_DISTRO_LATEST_RELEASE ||= :FIREBASE_APP_DISTRO_LATEST_RELEASE
10
+ end
11
+ class FirebaseAppDistributionGetLatestReleaseAction < Action
12
+ extend Auth::FirebaseAppDistributionAuthClient
13
+ extend Helper::FirebaseAppDistributionHelper
14
+
15
+ def self.run(params)
16
+ auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
17
+ fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
18
+
19
+ UI.message("⏳ Fetching latest release for app #{params[:app]}...")
20
+
21
+ releases = fad_api_client.list_releases(app_name_from_app_id(params[:app]), 1)[:releases] || []
22
+ if releases.empty?
23
+ latest_release = nil
24
+ UI.important("No releases for app #{params[:app]} found in App Distribution. Returning nil and setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE].")
25
+ else
26
+ latest_release = releases[0]
27
+ UI.success("✅ Latest release fetched successfully. Returning release and setting Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE].")
28
+ end
29
+ Actions.lane_context[SharedValues::FIREBASE_APP_DISTRO_LATEST_RELEASE] = latest_release
30
+ return latest_release
31
+ end
32
+
33
+ #####################################################
34
+ # @!group Documentation
35
+ #####################################################
36
+
37
+ def self.description
38
+ "Fetches the latest release in Firebase App Distribution"
39
+ end
40
+
41
+ def self.details
42
+ [
43
+ "Fetches information about the most recently created release in App Distribution, including the version and release notes. Returns nil if no releases are found."
44
+ ].join("\n")
45
+ end
46
+
47
+ def self.available_options
48
+ [
49
+ FastlaneCore::ConfigItem.new(key: :app,
50
+ env_name: "FIREBASEAPPDISTRO_APP",
51
+ description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
52
+ optional: false,
53
+ type: String),
54
+ FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
55
+ description: "Auth token generated using Firebase CLI's login:ci command",
56
+ optional: true,
57
+ type: String),
58
+ FastlaneCore::ConfigItem.new(key: :service_credentials_file,
59
+ description: "Path to Google service account json",
60
+ optional: true,
61
+ type: String),
62
+ FastlaneCore::ConfigItem.new(key: :debug,
63
+ description: "Print verbose debug output",
64
+ optional: true,
65
+ default_value: false,
66
+ is_string: false)
67
+ ]
68
+ end
69
+
70
+ def self.output
71
+ [
72
+ ['FIREBASE_APP_DISTRO_LATEST_RELEASE', 'A hash representing the lastest release created in Firebase App Distribution']
73
+ ]
74
+ end
75
+
76
+ def self.return_value
77
+ "Hash representation of the lastest release created in Firebase App Distribution (see https://firebase.google.com/docs/reference/app-distribution/rest/v1/projects.apps.releases#resource:-release)"
78
+ end
79
+
80
+ def self.return_type
81
+ :hash
82
+ end
83
+
84
+ def self.authors
85
+ ["lkellogg@google.com"]
86
+ end
87
+
88
+ def self.is_supported?(platform)
89
+ true
90
+ end
91
+
92
+ def self.example_code
93
+ [
94
+ 'release = firebase_app_distribution_get_latest_release(app: "<your Firebase app ID>")',
95
+ 'increment_build_number({
96
+ build_number: firebase_app_distribution_get_latest_release(app: "<your Firebase app ID>")[:buildVersion].to_i + 1
97
+ })'
98
+ ]
99
+ end
100
+
101
+ def self.sample_return_value
102
+ {
103
+ name: "projects/123456789/apps/1:1234567890:ios:0a1b2c3d4e5f67890/releases/0a1b2c3d4",
104
+ releaseNotes: {
105
+ text: "Here are some release notes!"
106
+ },
107
+ displayVersion: "1.2.3",
108
+ buildVersion: "10",
109
+ createTime: "2021-10-06T15:01:23Z"
110
+ }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,97 @@
1
+ require 'fastlane/action'
2
+ require 'open3'
3
+ require 'shellwords'
4
+ require 'googleauth'
5
+ require_relative '../helper/firebase_app_distribution_helper'
6
+ require_relative '../helper/firebase_app_distribution_error_message'
7
+ require_relative '../client/firebase_app_distribution_api_client'
8
+ require_relative '../helper/firebase_app_distribution_auth_client'
9
+
10
+ module Fastlane
11
+ module Actions
12
+ class FirebaseAppDistributionGetUdidsAction < Action
13
+ extend Auth::FirebaseAppDistributionAuthClient
14
+ extend Helper::FirebaseAppDistributionHelper
15
+
16
+ def self.run(params)
17
+ auth_token = fetch_auth_token(params[:service_credentials_file], params[:firebase_cli_token])
18
+ fad_api_client = Client::FirebaseAppDistributionApiClient.new(auth_token, params[:debug])
19
+
20
+ app_id = params[:app]
21
+ udids = fad_api_client.get_udids(app_id)
22
+
23
+ if udids.empty?
24
+ UI.important("App Distribution fetched 0 tester UDIDs. Nothing written to output file.")
25
+ else
26
+ write_udids_to_file(udids, params[:output_file])
27
+ UI.success("🎉 App Distribution tester UDIDs written to: #{params[:output_file]}")
28
+ end
29
+ end
30
+
31
+ def self.write_udids_to_file(udids, output_file)
32
+ File.open(output_file, 'w') do |f|
33
+ f.write("Device ID\tDevice Name\tDevice Platform\n")
34
+ udids.each do |tester_udid|
35
+ f.write("#{tester_udid[:udid]}\t#{tester_udid[:name]}\t#{tester_udid[:platform]}\n")
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.description
41
+ "Download the UDIDs of your Firebase App Distribution testers"
42
+ end
43
+
44
+ def self.authors
45
+ ["Lee Kellogg"]
46
+ end
47
+
48
+ # supports markdown.
49
+ def self.details
50
+ "Export your testers' device identifiers in a CSV file, so you can add them your provisioning profile. This file can be imported into your Apple developer account using the Register Multiple Devices option. See the [App Distribution docs](https://firebase.google.com/docs/app-distribution/ios/distribute-console#register-tester-devices) for more info."
51
+ end
52
+
53
+ def self.available_options
54
+ [
55
+ FastlaneCore::ConfigItem.new(key: :app,
56
+ env_name: "FIREBASEAPPDISTRO_APP",
57
+ description: "Your app's Firebase App ID. You can find the App ID in the Firebase console, on the General Settings page",
58
+ optional: false,
59
+ type: String),
60
+ FastlaneCore::ConfigItem.new(key: :output_file,
61
+ env_name: "FIREBASEAPPDISTRO_OUTPUT_FILE",
62
+ description: "The path to the file where the tester UDIDs will be written",
63
+ optional: false,
64
+ type: String),
65
+ FastlaneCore::ConfigItem.new(key: :firebase_cli_token,
66
+ description: "Auth token generated using the Firebase CLI's login:ci command",
67
+ optional: true,
68
+ type: String),
69
+ FastlaneCore::ConfigItem.new(key: :service_credentials_file,
70
+ description: "Path to Google service account json",
71
+ optional: true,
72
+ type: String),
73
+ FastlaneCore::ConfigItem.new(key: :debug,
74
+ description: "Print verbose debug output",
75
+ optional: true,
76
+ default_value: false,
77
+ is_string: false)
78
+ ]
79
+ end
80
+
81
+ def self.is_supported?(platform)
82
+ [:ios].include?(platform)
83
+ end
84
+
85
+ def self.example_code
86
+ [
87
+ <<-CODE
88
+ firebase_app_distribution_get_udids(
89
+ app: "<your Firebase app ID>",
90
+ output_file: "tester_udids.txt",
91
+ )
92
+ CODE
93
+ ]
94
+ end
95
+ end
96
+ end
97
+ end