fastlane 2.108.0 → 2.109.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +76 -76
  3. data/deliver/lib/deliver/app_screenshot.rb +22 -1
  4. data/fastlane/lib/fastlane/action_collector.rb +1 -22
  5. data/fastlane/lib/fastlane/actions/appetize.rb +20 -3
  6. data/fastlane/lib/fastlane/actions/docs/build_ios_app.md +12 -9
  7. data/fastlane/lib/fastlane/actions/pod_lib_lint.rb +25 -16
  8. data/fastlane/lib/fastlane/actions/pod_push.rb +1 -1
  9. data/fastlane/lib/fastlane/actions/puts.rb +1 -1
  10. data/fastlane/lib/fastlane/actions/set_github_release.rb +1 -0
  11. data/fastlane/lib/fastlane/actions/testfairy.rb +11 -5
  12. data/fastlane/lib/fastlane/fast_file.rb +9 -6
  13. data/fastlane/lib/fastlane/helper/adb_helper.rb +3 -3
  14. data/fastlane/lib/fastlane/helper/crashlytics_helper.rb +5 -5
  15. data/fastlane/lib/fastlane/version.rb +1 -1
  16. data/fastlane/swift/Deliverfile.swift +1 -1
  17. data/fastlane/swift/Fastlane.swift +16 -6
  18. data/fastlane/swift/Gymfile.swift +1 -1
  19. data/fastlane/swift/Matchfile.swift +1 -1
  20. data/fastlane/swift/Precheckfile.swift +1 -1
  21. data/fastlane/swift/Scanfile.swift +1 -1
  22. data/fastlane/swift/Screengrabfile.swift +1 -1
  23. data/fastlane/swift/Snapshotfile.swift +1 -1
  24. data/fastlane_core/lib/fastlane_core.rb +0 -1
  25. data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +2 -2
  26. data/fastlane_core/lib/fastlane_core/project.rb +9 -11
  27. data/match/lib/match/encryption.rb +33 -9
  28. data/match/lib/match/runner.rb +15 -3
  29. data/match/lib/match/spaceship_ensure.rb +2 -5
  30. data/match/lib/match/storage.rb +29 -7
  31. data/match/lib/match/storage/git_storage.rb +30 -48
  32. data/match/lib/match/storage/interface.rb +39 -1
  33. data/precheck/lib/precheck/rules/other_platforms_rule.rb +4 -1
  34. data/scan/lib/scan/error_handler.rb +8 -1
  35. data/sigh/lib/assets/resign.sh +39 -63
  36. data/snapshot/lib/snapshot/reports_generator.rb +1 -0
  37. data/spaceship/lib/spaceship/client.rb +174 -58
  38. data/spaceship/lib/spaceship/commands_generator.rb +1 -0
  39. data/spaceship/lib/spaceship/du/du_client.rb +18 -12
  40. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  41. data/spaceship/lib/spaceship/tunes/app_submission.rb +2 -1
  42. data/spaceship/lib/spaceship/tunes/device_type.rb +1 -1
  43. data/spaceship/lib/spaceship/tunes/tunes_client.rb +5 -2
  44. data/spaceship/lib/spaceship/{two_step_client.rb → two_step_or_factor_client.rb} +65 -96
  45. data/supply/lib/supply/options.rb +17 -3
  46. data/supply/lib/supply/uploader.rb +12 -5
  47. metadata +17 -19
  48. data/deliver/lib/deliver/.app_screenshot.rb.swp +0 -0
  49. data/fastlane_core/lib/fastlane_core/tool_collector.rb +0 -304
@@ -23,7 +23,7 @@ module Spaceship
23
23
  puts("Please check your credentials and try again.".yellow)
24
24
  puts("This could be an issue with App Store Connect,".yellow)
25
25
  puts("Please try unsetting the FASTLANE_SESSION environment variable".yellow)
26
- puts("and re-run `fastlane spaceauth`".yellow)
26
+ puts("(if it is set) and re-run `fastlane spaceauth`".yellow)
27
27
  raise "Problem connecting to App Store Connect"
28
28
  end
29
29
 
@@ -136,7 +136,8 @@ module Spaceship
136
136
  if self.content_rights_has_rights.nil? || self.content_rights_contains_third_party_content.nil?
137
137
  raw_data_clone.set(["contentRights"], nil)
138
138
  end
139
- raw_data_clone.delete("version")
139
+ raw_data_clone.delete(:version)
140
+ raw_data_clone.delete(:application)
140
141
 
141
142
  # Check whether the application makes use of IDFA or not
142
143
  # and automatically set the mandatory limitsTracking value in the request JSON accordingly.
@@ -1,7 +1,7 @@
1
1
  module Spaceship
2
2
  module Tunes
3
3
  class DeviceType
4
- @types = ['iphone4', 'iphone35', 'iphone6', 'iphone6Plus', 'iphone58', 'iphone65', 'ipad', 'ipadPro', 'ipad105', 'watch', 'watchSeries4', 'appleTV', 'desktop']
4
+ @types = ['iphone35', 'iphone4', 'iphone6', 'iphone6Plus', 'iphone58', 'iphone65', 'ipad', 'ipad105', 'ipadPro', 'ipadPro11', 'ipadPro129', 'watch', 'watchSeries4', 'appleTV', 'desktop']
5
5
  class << self
6
6
  attr_accessor :types
7
7
 
@@ -34,9 +34,12 @@ module Spaceship
34
34
  'iphone6' => [1334, 750],
35
35
  'iphone6Plus' => [2208, 1242],
36
36
  'iphone58' => [2436, 1125],
37
+ 'iphone65' => [2688, 1242],
37
38
  'ipad' => [1024, 768],
38
39
  'ipad105' => [2224, 1668],
39
- 'ipadPro' => [2732, 2048]
40
+ 'ipadPro' => [2732, 2048],
41
+ 'iPadPro11' => [2388, 1668],
42
+ 'iPadPro129' => [2732, 2048]
40
43
  }
41
44
 
42
45
  r = resolutions[device]
@@ -222,7 +225,7 @@ module Spaceship
222
225
  elsif errors.count == 1 && errors.first.include?("try again later")
223
226
  raise ITunesConnectTemporaryError.new, errors.first
224
227
  elsif errors.count == 1 && errors.first.include?("Forbidden")
225
- raise_insuffient_permission_error!
228
+ raise_insufficient_permission_error!
226
229
  elsif flaky_api_call
227
230
  raise ITunesConnectPotentialServerError.new, errors.join(' ')
228
231
  else
@@ -1,125 +1,60 @@
1
- require 'tempfile'
2
-
3
1
  require_relative 'globals'
4
2
  require_relative 'tunes/tunes_client'
5
3
  require_relative 'tunes/recovery_device'
6
4
 
7
5
  module Spaceship
8
6
  class Client
9
- def handle_two_step(response)
7
+ def handle_two_step_or_factor(response)
10
8
  @x_apple_id_session_id = response["x-apple-id-session-id"]
11
9
  @scnt = response["scnt"]
12
10
 
11
+ puts("")
12
+ puts("Two-step Verification (4 digits code) or Two-factor Authentication (6 digits code) is enabled for account '#{self.user}'")
13
+ puts("More information about Two-step Verification (4 digits code): https://support.apple.com/en-us/HT204152")
14
+ puts("More information about Two-factor Authentication (6 digits code): https://support.apple.com/en-us/HT204915")
15
+ puts("")
16
+
13
17
  r = request(:get) do |req|
14
18
  req.url("https://idmsa.apple.com/appleauth/auth")
15
19
  update_request_headers(req)
16
20
  end
17
21
 
18
22
  if r.body.kind_of?(Hash) && r.body["trustedDevices"].kind_of?(Array)
19
- if r.body.fetch("securityCode", {})["tooManyCodesLock"].to_s.length > 0
20
- raise Tunes::Error.new, "Too many verification codes have been sent. Enter the last code you received, use one of your devices, or try again later."
21
- end
22
-
23
- old_client = (begin
24
- Tunes::RecoveryDevice.client
25
- rescue
26
- nil # since client might be nil, which raises an exception
27
- end)
28
- Tunes::RecoveryDevice.client = self # temporary set it as it's required by the factory method
29
- devices = r.body["trustedDevices"].collect do |current|
30
- Tunes::RecoveryDevice.factory(current)
31
- end
32
- Tunes::RecoveryDevice.client = old_client
33
-
34
- puts("Two Step Verification for account '#{self.user}' is enabled")
35
- puts("Please select a device to verify your identity")
36
- available = devices.collect do |c|
37
- "#{c.name}\t#{c.model_name || 'SMS'}\t(#{c.device_id})"
38
- end
39
- result = choose(*available)
40
- device_id = result.match(/.*\t.*\t\((.*)\)/)[1]
41
- select_device(r, device_id)
23
+ handle_two_step(r)
42
24
  elsif r.body.kind_of?(Hash) && r.body["trustedPhoneNumbers"].kind_of?(Array) && r.body["trustedPhoneNumbers"].first.kind_of?(Hash)
43
25
  handle_two_factor(r)
44
26
  else
45
- raise "Invalid 2 step response #{r.body}"
27
+ raise "Although response from Apple indicated activated Two-step Verification or Two-factor Authentication, spaceship didn't know how to handle this response: #{r.body}"
46
28
  end
47
29
  end
48
30
 
49
- def handle_two_factor(response)
50
- two_factor_url = "https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification"
51
- puts("Two Factor Authentication for account '#{self.user}' is enabled")
52
-
53
- if !File.exist?(persistent_cookie_path) && self.class.spaceship_session_env.to_s.length.zero?
54
- puts("If you're running this in a non-interactive session (e.g. server or CI)")
55
- puts("check out #{two_factor_url}")
56
- else
57
- # If the cookie is set but still required, the cookie is expired
58
- puts("Your session cookie has been expired.")
59
- end
60
-
61
- security_code = response.body["securityCode"]
62
- # {"length"=>6,
63
- # "tooManyCodesSent"=>false,
64
- # "tooManyCodesValidated"=>false,
65
- # "securityCodeLocked"=>false}
66
- code_length = security_code["length"]
67
- code = ask("Please enter the #{code_length} digit code: ")
68
- puts("Requesting session...")
69
-
70
- # Send securityCode back to server to get a valid session
71
- r = request(:post) do |req|
72
- req.url("https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode")
73
- req.headers['Content-Type'] = 'application/json'
74
- req.body = { "securityCode" => { "code" => code.to_s } }.to_json
75
-
76
- update_request_headers(req)
31
+ def handle_two_step(r)
32
+ if r.body.fetch("securityCode", {})["tooManyCodesLock"].to_s.length > 0
33
+ raise Tunes::Error.new, "Too many verification codes have been sent. Enter the last code you received, use one of your devices, or try again later."
77
34
  end
78
35
 
79
- # we use `Spaceship::TunesClient.new.handle_itc_response`
80
- # since this might be from the Dev Portal, but for 2 step
81
- Spaceship::TunesClient.new.handle_itc_response(r.body)
82
-
83
- store_session
84
-
85
- return true
86
- end
87
-
88
- # Only needed for 2 step
89
- def load_session_from_file
90
- if File.exist?(persistent_cookie_path)
91
- puts("Loading session from '#{persistent_cookie_path}'") if Spaceship::Globals.verbose?
92
- @cookie.load(persistent_cookie_path)
93
- return true
36
+ old_client = (begin
37
+ Tunes::RecoveryDevice.client
38
+ rescue
39
+ nil # since client might be nil, which raises an exception
40
+ end)
41
+ Tunes::RecoveryDevice.client = self # temporary set it as it's required by the factory method
42
+ devices = r.body["trustedDevices"].collect do |current|
43
+ Tunes::RecoveryDevice.factory(current)
94
44
  end
95
- return false
96
- end
97
-
98
- def load_session_from_env
99
- return if self.class.spaceship_session_env.to_s.length == 0
100
- puts("Loading session from environment variable") if Spaceship::Globals.verbose?
45
+ Tunes::RecoveryDevice.client = old_client
101
46
 
102
- file = Tempfile.new('cookie.yml')
103
- file.write(self.class.spaceship_session_env.gsub("\\n", "\n"))
104
- file.close
105
-
106
- begin
107
- @cookie.load(file.path)
108
- rescue => ex
109
- puts("Error loading session from environment")
110
- puts("Make sure to pass the session in a valid format")
111
- raise ex
112
- ensure
113
- file.unlink
47
+ puts("Two-step Verification (4 digits code) is enabled for account '#{self.user}'")
48
+ puts("Please select a device to verify your identity")
49
+ available = devices.collect do |c|
50
+ "#{c.name}\t#{c.model_name || 'SMS'}\t(#{c.device_id})"
114
51
  end
52
+ result = choose(*available)
53
+ device_id = result.match(/.*\t.*\t\((.*)\)/)[1]
54
+ select_device(r, device_id)
115
55
  end
116
56
 
117
- # Fetch the session cookie from the environment
118
- # (if exists)
119
- def self.spaceship_session_env
120
- ENV["FASTLANE_SESSION"] || ENV["SPACESHIP_SESSION"]
121
- end
122
-
57
+ # this is extracted into its own method so it can be called multiple times (see end)
123
58
  def select_device(r, device_id)
124
59
  # Request Token
125
60
  r = request(:put) do |req|
@@ -138,9 +73,8 @@ module Spaceship
138
73
  # Send token back to server to get a valid session
139
74
  r = request(:post) do |req|
140
75
  req.url("https://idmsa.apple.com/appleauth/auth/verify/device/#{device_id}/securitycode")
141
- req.body = { "code" => code.to_s }.to_json
142
76
  req.headers['Content-Type'] = 'application/json'
143
-
77
+ req.body = { "code" => code.to_s }.to_json
144
78
  update_request_headers(req)
145
79
  end
146
80
 
@@ -178,6 +112,41 @@ module Spaceship
178
112
  return true
179
113
  end
180
114
 
115
+ def handle_two_factor(response)
116
+ two_factor_url = "https://github.com/fastlane/fastlane/tree/master/spaceship#2-step-verification"
117
+ puts("Two-factor Authentication (6 digits code) is enabled for account '#{self.user}'")
118
+
119
+ puts("If you're running this in a non-interactive session (e.g. server or CI)")
120
+ puts("check out #{two_factor_url}")
121
+
122
+ security_code = response.body["securityCode"]
123
+ # securityCode =
124
+ # {"length"=>6,
125
+ # "tooManyCodesSent"=>false,
126
+ # "tooManyCodesValidated"=>false,
127
+ # "securityCodeLocked"=>false}
128
+ code_length = security_code["length"]
129
+ code = ask("Please enter the #{code_length} digit code: ")
130
+ puts("Requesting session...")
131
+
132
+ # Send securityCode back to server to get a valid session
133
+ r = request(:post) do |req|
134
+ req.url("https://idmsa.apple.com/appleauth/auth/verify/trusteddevice/securitycode")
135
+ req.headers['Content-Type'] = 'application/json'
136
+ req.body = { "securityCode" => { "code" => code.to_s } }.to_json
137
+
138
+ update_request_headers(req)
139
+ end
140
+
141
+ # we use `Spaceship::TunesClient.new.handle_itc_response`
142
+ # since this might be from the Dev Portal, but for 2 step
143
+ Spaceship::TunesClient.new.handle_itc_response(r.body)
144
+
145
+ store_session
146
+
147
+ return true
148
+ end
149
+
181
150
  def store_session
182
151
  # If the request was successful, r.body is actually nil
183
152
  # The previous request will fail if the user isn't on a team
@@ -92,7 +92,7 @@ module Supply
92
92
  env_name: "SUPPLY_APK",
93
93
  description: "Path to the APK file to upload",
94
94
  short_option: "-b",
95
- conflicting_options: [:apk_paths, :aab],
95
+ conflicting_options: [:apk_paths, :aab, :aab_paths],
96
96
  code_gen_sensitive: true,
97
97
  default_value: Dir["*.apk"].last || Dir[File.join("app", "build", "outputs", "apk", "app-Release.apk")].last,
98
98
  default_value_dynamic: true,
@@ -103,7 +103,7 @@ module Supply
103
103
  end),
104
104
  FastlaneCore::ConfigItem.new(key: :apk_paths,
105
105
  env_name: "SUPPLY_APK_PATHS",
106
- conflicting_options: [:apk, :aab],
106
+ conflicting_options: [:apk, :aab, :aab_paths],
107
107
  optional: true,
108
108
  type: Array,
109
109
  description: "An array of paths to APK files to upload",
@@ -119,7 +119,7 @@ module Supply
119
119
  env_name: "SUPPLY_AAB",
120
120
  description: "Path to the AAB file to upload",
121
121
  short_option: "-f",
122
- conflicting_options: [:apk_path, :apk_paths],
122
+ conflicting_options: [:apk, :apk_paths, :aab_paths],
123
123
  code_gen_sensitive: true,
124
124
  default_value: Dir["*.aab"].last || Dir[File.join("app", "build", "outputs", "bundle", "release", "bundle.aab")].last,
125
125
  default_value_dynamic: true,
@@ -128,6 +128,20 @@ module Supply
128
128
  UI.user_error!("Could not find aab file at path '#{value}'") unless File.exist?(value)
129
129
  UI.user_error!("aab file is not an aab") unless value.end_with?('.aab')
130
130
  end),
131
+ FastlaneCore::ConfigItem.new(key: :aab_paths,
132
+ env_name: "SUPPLY_AAB_PATHS",
133
+ conflicting_options: [:apk, :apk_paths, :aab],
134
+ optional: true,
135
+ type: Array,
136
+ description: "An array of paths to AAB files to upload",
137
+ short_option: "-z",
138
+ verify_block: proc do |value|
139
+ UI.user_error!("Could not evaluate array from '#{value}'") unless value.kind_of?(Array)
140
+ value.each do |path|
141
+ UI.user_error!("Could not find aab file at path '#{path}'") unless File.exist?(path)
142
+ UI.user_error!("file at path '#{path}' is not an aab") unless path.end_with?('.aab')
143
+ end
144
+ end),
131
145
  FastlaneCore::ConfigItem.new(key: :skip_upload_apk,
132
146
  env_name: "SUPPLY_SKIP_UPLOAD_APK",
133
147
  optional: true,
@@ -46,7 +46,7 @@ module Supply
46
46
  end
47
47
 
48
48
  def verify_config!
49
- unless metadata_path || Supply.config[:apk] || Supply.config[:apk_paths] || Supply.config[:aab] || (Supply.config[:track] && Supply.config[:track_promote_to])
49
+ unless metadata_path || Supply.config[:apk] || Supply.config[:apk_paths] || Supply.config[:aab] || Supply.config[:aab_paths] || (Supply.config[:track] && Supply.config[:track_promote_to])
50
50
  UI.user_error!("No local metadata, apks, aab, or track to promote were found, make sure to run `fastlane supply init` to setup supply")
51
51
  end
52
52
 
@@ -152,11 +152,18 @@ module Supply
152
152
  end
153
153
 
154
154
  def upload_bundles
155
- aab_path = Supply.config[:aab]
156
- return [] unless aab_path
155
+ aab_paths = [Supply.config[:aab]] unless (aab_paths = Supply.config[:aab_paths])
156
+ return [] unless aab_paths
157
+ aab_paths.compact!
157
158
 
158
- UI.message("Preparing aab at path '#{aab_path}' for upload...")
159
- return [client.upload_bundle(aab_path)]
159
+ aab_version_codes = []
160
+
161
+ aab_paths.each do |aab_path|
162
+ UI.message("Preparing aab at path '#{aab_path}' for upload...")
163
+ aab_version_codes.push(client.upload_bundle(aab_path))
164
+ end
165
+
166
+ return aab_version_codes
160
167
  end
161
168
 
162
169
  private
metadata CHANGED
@@ -1,33 +1,33 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.108.0
4
+ version: 2.109.0
5
5
  platform: ruby
6
6
  authors:
7
- - Stefan Natchev
8
- - Luka Mirosevic
7
+ - Matthew Ellis
8
+ - Helmut Januschka
9
+ - Jimmy Dee
9
10
  - Danielle Tomlinson
10
- - Fumiya Nakamura
11
+ - Kohki Miki
12
+ - Manu Wallner
13
+ - Andrew McBurney
14
+ - Josh Holtz
15
+ - Joshua Liebowitz
16
+ - Jorge Revuelta H
11
17
  - Aaron Brager
18
+ - Felix Krause
12
19
  - Jan Piotrowski
13
20
  - Jérôme Lacoste
14
- - Olivier Halligon
15
- - Felix Krause
16
- - Manu Wallner
21
+ - Luka Mirosevic
17
22
  - Iulian Onofrei
18
- - Matthew Ellis
19
- - Jorge Revuelta H
20
- - Kohki Miki
21
- - Jimmy Dee
23
+ - Olivier Halligon
24
+ - Stefan Natchev
22
25
  - Maksym Grebenets
23
- - Andrew McBurney
24
- - Joshua Liebowitz
25
- - Helmut Januschka
26
- - Josh Holtz
26
+ - Fumiya Nakamura
27
27
  autorequire:
28
28
  bindir: bin
29
29
  cert_chain: []
30
- date: 2018-11-05 00:00:00.000000000 Z
30
+ date: 2018-12-04 00:00:00.000000000 Z
31
31
  dependencies:
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: slack-notifier
@@ -903,7 +903,6 @@ files:
903
903
  - deliver/lib/assets/ScreenshotsHelp
904
904
  - deliver/lib/assets/summary.html.erb
905
905
  - deliver/lib/deliver.rb
906
- - deliver/lib/deliver/.app_screenshot.rb.swp
907
906
  - deliver/lib/deliver/app_screenshot.rb
908
907
  - deliver/lib/deliver/commands_generator.rb
909
908
  - deliver/lib/deliver/detect_values.rb
@@ -1329,7 +1328,6 @@ files:
1329
1328
  - fastlane_core/lib/fastlane_core/swag.rb
1330
1329
  - fastlane_core/lib/fastlane_core/tag_version.rb
1331
1330
  - fastlane_core/lib/fastlane_core/test_parser.rb
1332
- - fastlane_core/lib/fastlane_core/tool_collector.rb
1333
1331
  - fastlane_core/lib/fastlane_core/ui/disable_colors.rb
1334
1332
  - fastlane_core/lib/fastlane_core/ui/errors.rb
1335
1333
  - fastlane_core/lib/fastlane_core/ui/errors/fastlane_common_error.rb
@@ -1640,7 +1638,7 @@ files:
1640
1638
  - spaceship/lib/spaceship/tunes/tunes_client.rb
1641
1639
  - spaceship/lib/spaceship/tunes/user_detail.rb
1642
1640
  - spaceship/lib/spaceship/tunes/version_set.rb
1643
- - spaceship/lib/spaceship/two_step_client.rb
1641
+ - spaceship/lib/spaceship/two_step_or_factor_client.rb
1644
1642
  - spaceship/lib/spaceship/ui.rb
1645
1643
  - supply/README.md
1646
1644
  - supply/lib/supply.rb
@@ -1,304 +0,0 @@
1
- require_relative 'helper'
2
-
3
- module FastlaneCore
4
- class ToolCollector
5
- # Learn more at https://docs.fastlane.tools/#metrics
6
-
7
- # This is the original error reporting mechanism, which has always represented
8
- # either controlled (UI.user_error!), or uncontrolled (UI.crash!, anything else)
9
- # exceptions.
10
- #
11
- # Thus, if you call `did_crash`, it will record the failure both here, and in the
12
- # newer, more specific `crash` field.
13
- #
14
- # This value is a String, which is the name of the tool that caused the error
15
- attr_reader :error
16
-
17
- # This is the newer field for tracking only uncontrolled exceptions.
18
- #
19
- # This is written to only when `did_crash` is called, and therefore excludes
20
- # controlled exceptions.
21
- #
22
- # This value is a boolean, which is true if the error was an uncontrolled exception
23
- attr_reader :crash
24
-
25
- def initialize
26
- @crash = false
27
- end
28
-
29
- def did_launch_action(name)
30
- name = name_to_track(name.to_sym)
31
- return unless name
32
-
33
- launches[name] += 1
34
- versions[name] ||= determine_version(name)
35
- end
36
-
37
- # Call when the problem is a caught/controlled exception (e.g. via UI.user_error!)
38
- def did_raise_error(name)
39
- name = name_to_track(name.to_sym)
40
- return unless name
41
-
42
- @error = name
43
- # Don't write to the @crash field so that we can distinguish this exception later
44
- # as being controlled
45
- end
46
-
47
- # Call when the problem is an uncaught/uncontrolled exception (e.g. via UI.crash!)
48
- def did_crash(name)
49
- name = name_to_track(name.to_sym)
50
- return unless name
51
-
52
- # Write to the @error field to maintain the historical behavior of the field, so
53
- # that the server gets the same data in that field from old and new clients
54
- @error = name
55
- # Also specifically note that this exception was uncontrolled in the @crash field
56
- @crash = true
57
- end
58
-
59
- def did_finish
60
- return false if FastlaneCore::Env.truthy?("FASTLANE_OPT_OUT_USAGE")
61
-
62
- if !did_show_message? && !Helper.ci?
63
- show_message
64
- end
65
-
66
- require 'excon'
67
- url = ENV["FASTLANE_METRICS_URL"] || "https://fastlane-metrics.fabric.io/public"
68
-
69
- analytic_event_body = create_analytic_event_body
70
-
71
- # Never generate web requests during tests
72
- unless Helper.test?
73
- Thread.new do
74
- begin
75
- Excon.post(url,
76
- body: analytic_event_body,
77
- headers: { "Content-Type" => 'application/json' })
78
- rescue
79
- # we don't want to show a stack trace if something goes wrong
80
- end
81
- end
82
- end
83
-
84
- return true
85
- rescue
86
- # We don't care about connection errors
87
- end
88
-
89
- def create_analytic_event_body
90
- analytics = []
91
- timestamp_seconds = Time.now.to_i
92
-
93
- # `fastfile_id` helps us track success/failure metrics for Fastfiles we
94
- # generate as part of an automated process.
95
- fastfile_id = ENV["GENERATED_FASTFILE_ID"]
96
-
97
- if fastfile_id && launches.size == 1 && launches['fastlane']
98
- if crash
99
- completion_status = 'crash'
100
- elsif error
101
- completion_status = 'error'
102
- else
103
- completion_status = 'success'
104
- end
105
- analytics << event_for_web_onboarding(fastfile_id, completion_status, timestamp_seconds)
106
- end
107
-
108
- launches.each do |action, count|
109
- action_version = versions[action] || 'unknown'
110
- if crash && error == action
111
- action_completion_status = 'crash'
112
- elsif action == error
113
- action_completion_status = 'error'
114
- else
115
- action_completion_status = 'success'
116
- end
117
- analytics << event_for_completion(action, action_completion_status, action_version, timestamp_seconds)
118
- analytics << event_for_count(action, count, action_version, timestamp_seconds)
119
- end
120
- { analytics: analytics }.to_json
121
- end
122
-
123
- def show_message
124
- UI.message("Sending Crash/Success information")
125
- UI.message("Learn more at https://docs.fastlane.tools/#metrics")
126
- UI.message("No personal/sensitive data is sent. Only sharing the following:")
127
- UI.message(launches)
128
- UI.message(@error) if @error
129
- UI.message("This information is used to fix failing tools and improve those that are most often used.")
130
- UI.message("You can disable this by adding `opt_out_usage` at the top of your Fastfile")
131
- end
132
-
133
- def launches
134
- @launches ||= Hash.new(0)
135
- end
136
-
137
- # Maintains a hash of tool names to their detected versions.
138
- #
139
- # This data is sent in the same manner as launches, as an inline form-encoded JSON value in the POST.
140
- # For example:
141
- #
142
- # {
143
- # match: '0.5.0',
144
- # fastlane: '1.86.1'
145
- # }
146
- def versions
147
- @versions ||= {}
148
- end
149
-
150
- # Override this in subclasses
151
- def is_official?(name)
152
- return true
153
- end
154
-
155
- # Returns nil if we shouldn't track this action
156
- # Returns a (maybe modified) name that should be sent to the analytic ingester
157
- # Modificiation is used to prefix the action name with the name of the plugin
158
- def name_to_track(name)
159
- return nil unless is_official?(name)
160
- name
161
- end
162
-
163
- def did_show_message?
164
- file_name = ".did_show_opt_info"
165
-
166
- legacy_path = File.join(File.expand_path('~'), file_name)
167
- new_path = File.join(FastlaneCore.fastlane_user_dir, file_name)
168
- did_show = File.exist?(new_path) || File.exist?(legacy_path)
169
-
170
- return did_show if did_show
171
-
172
- File.write(new_path, '1')
173
- false
174
- end
175
-
176
- def determine_version(name)
177
- self.class.determine_version(name)
178
- end
179
-
180
- def self.determine_version(name)
181
- unless name.to_s.start_with?("fastlane-plugin")
182
- # In the early days before the mono gem this was more complicated
183
- # Now we have a mono version number, which makes this method easy
184
- # for all built-in actions and tools
185
- require 'fastlane/version'
186
- return Fastlane::VERSION
187
- end
188
-
189
- # For plugins we still need to load the specific version
190
- begin
191
- name = name.to_s.downcase
192
-
193
- # We need to pre-load the version file because tools that are invoked through their actions
194
- # will not yet have run their action, and thus will not yet have loaded the file which defines
195
- # the module and constant we need.
196
- require File.join(name, "version")
197
-
198
- # Go from :foo_bar to 'FooBar'
199
- module_name = name.fastlane_module
200
-
201
- # Look up the VERSION constant defined for the given tool name,
202
- # or return 'unknown' if we can't find it where we'd expect
203
- if Kernel.const_defined?(module_name)
204
- tool_module = Kernel.const_get(module_name)
205
-
206
- if tool_module.const_defined?('VERSION')
207
- return tool_module.const_get('VERSION')
208
- end
209
- end
210
- rescue LoadError
211
- # If there is no version file to load, this is not a tool for which
212
- # we can report a particular version
213
- end
214
-
215
- return nil
216
- end
217
-
218
- def event_for_web_onboarding(fastfile_id, completion_status, timestamp_seconds)
219
- {
220
- event_source: {
221
- oauth_app_name: oauth_app_name,
222
- product: 'fastlane_web_onboarding'
223
- },
224
- actor: {
225
- name: 'customer',
226
- detail: fastfile_id
227
- },
228
- action: {
229
- name: 'fastfile_executed'
230
- },
231
- primary_target: {
232
- name: 'fastlane_completion_status',
233
- detail: completion_status
234
- },
235
- secondary_target: {
236
- name: 'executed',
237
- detail: secondary_target_string('')
238
- },
239
- millis_since_epoch: timestamp_seconds * 1000,
240
- version: 1
241
- }
242
- end
243
-
244
- def event_for_completion(action, completion_status, version, timestamp_seconds)
245
- {
246
- event_source: {
247
- oauth_app_name: oauth_app_name,
248
- product: 'fastlane'
249
- },
250
- actor: {
251
- name: 'action',
252
- detail: action
253
- },
254
- action: {
255
- name: 'execution_completed'
256
- },
257
- primary_target: {
258
- name: 'completion_status',
259
- detail: completion_status
260
- },
261
- secondary_target: {
262
- name: 'version',
263
- detail: secondary_target_string(version)
264
- },
265
- millis_since_epoch: timestamp_seconds * 1000,
266
- version: 1
267
- }
268
- end
269
-
270
- def event_for_count(action, count, version, timestamp_seconds)
271
- {
272
- event_source: {
273
- oauth_app_name: oauth_app_name,
274
- product: 'fastlane'
275
- },
276
- actor: {
277
- name: 'action',
278
- detail: action
279
- },
280
- action: {
281
- name: 'execution_counted'
282
- },
283
- primary_target: {
284
- name: 'count',
285
- detail: count.to_s || "1"
286
- },
287
- secondary_target: {
288
- name: 'version',
289
- detail: secondary_target_string(version)
290
- },
291
- millis_since_epoch: timestamp_seconds * 1000,
292
- version: 1
293
- }
294
- end
295
-
296
- def oauth_app_name
297
- return 'fastlane-enhancer'
298
- end
299
-
300
- def secondary_target_string(string)
301
- return string
302
- end
303
- end
304
- end