fastlane 2.75.0.beta.20180109010003 → 2.75.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/credentials_manager/lib/credentials_manager/account_manager.rb +11 -9
  3. data/deliver/lib/assets/DeliverfileDefault +2 -9
  4. data/deliver/lib/assets/DeliverfileDefault.swift +2 -12
  5. data/deliver/lib/deliver/setup.rb +1 -4
  6. data/fastlane/lib/.DS_Store +0 -0
  7. data/fastlane/lib/assets/.DS_Store +0 -0
  8. data/fastlane/lib/assets/AppfileTemplate +5 -6
  9. data/fastlane/lib/assets/AppfileTemplate.swift +3 -4
  10. data/fastlane/lib/assets/DefaultFastfileTemplate +10 -67
  11. data/fastlane/lib/assets/DefaultFastfileTemplate.swift +8 -66
  12. data/fastlane/lib/fastlane/.DS_Store +0 -0
  13. data/fastlane/lib/fastlane/actions/.DS_Store +0 -0
  14. data/fastlane/lib/fastlane/actions/docs/.DS_Store +0 -0
  15. data/fastlane/lib/fastlane/actions/get_build_number.rb +7 -1
  16. data/fastlane/lib/fastlane/commands_generator.rb +11 -19
  17. data/fastlane/lib/fastlane/setup/.DS_Store +0 -0
  18. data/fastlane/lib/fastlane/setup/setup.rb +247 -34
  19. data/fastlane/lib/fastlane/setup/setup_android.rb +65 -39
  20. data/fastlane/lib/fastlane/setup/setup_ios.rb +419 -280
  21. data/fastlane/lib/fastlane/version.rb +1 -1
  22. data/fastlane/swift/Deliverfile.swift +1 -1
  23. data/fastlane/swift/Gymfile.swift +1 -1
  24. data/fastlane/swift/Matchfile.swift +1 -1
  25. data/fastlane/swift/Precheckfile.swift +1 -1
  26. data/fastlane/swift/Scanfile.swift +1 -1
  27. data/fastlane/swift/Screengrabfile.swift +1 -1
  28. data/fastlane/swift/Snapshotfile.swift +1 -1
  29. data/fastlane_core/lib/.DS_Store +0 -0
  30. data/fastlane_core/lib/fastlane_core/.DS_Store +0 -0
  31. data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +3 -1
  32. data/sigh/lib/assets/resign.sh +0 -1
  33. data/snapshot/lib/assets/SnapfileTemplate +9 -12
  34. data/snapshot/lib/snapshot/commands_generator.rb +2 -0
  35. data/snapshot/lib/snapshot/setup.rb +20 -9
  36. data/spaceship/lib/spaceship.rb +4 -0
  37. data/spaceship/lib/spaceship/portal/ui/select_team.rb +3 -3
  38. data/spaceship/lib/spaceship/tunes/tunes_client.rb +15 -11
  39. metadata +24 -17
  40. data/fastlane/lib/assets/FastfileTemplateAndroid +0 -65
@@ -1,71 +1,97 @@
1
1
  module Fastlane
2
2
  class SetupAndroid < Setup
3
- def run
4
- response = UI.confirm('Do you have everything committed in version control? If not please do so now!')
5
- return unless response
3
+ attr_accessor :json_key_file
4
+ attr_accessor :package_name
6
5
 
7
- FastlaneCore::FastlaneFolder.create_folder! unless Helper.is_test?
8
- FileUtils.mkdir(File.join(folder, 'actions')) unless File.directory?(File.join(folder, 'actions'))
9
- generate_appfile
10
- generate_fastfile
11
- show_analytics
6
+ def setup_android
7
+ self.platform = :android
8
+ self.is_swift_fastfile = false
9
+
10
+ welcome_to_fastlane
11
+
12
+ self.fastfile_content = fastfile_template_content
13
+ self.appfile_content = appfile_template_content
14
+
15
+ fetch_information_for_appfile
16
+
17
+ FastlaneCore::FastlaneFolder.create_folder!
12
18
 
13
19
  init_supply
14
20
 
15
- UI.success('Successfully finished setting up fastlane')
21
+ self.append_lane([
22
+ "desc \"Runs all the tests\"",
23
+ "lane :test do",
24
+ " gradle(task: \"test\")",
25
+ "end"
26
+ ])
27
+
28
+ self.append_lane([
29
+ "desc \"Submit a new Beta Build to Crashlytics Beta\"",
30
+ "lane :beta do",
31
+ " gradle(task: \"assembleRelease\")",
32
+ " crashlytics",
33
+ "",
34
+ " # sh \"your_script.sh\"",
35
+ " # You can also use other beta testing services here",
36
+ "end"
37
+ ])
38
+
39
+ self.append_lane([
40
+ "desc \"Deploy a new version to the Google Play\"",
41
+ "lane :deploy do",
42
+ " gradle(task: \"assembleRelease\")",
43
+ " upload_to_play_store",
44
+ "end"
45
+ ])
46
+
47
+ self.lane_to_mention = "test"
48
+
49
+ finish_up
16
50
  end
17
51
 
18
- def generate_appfile
19
- UI.message('------------------------------')
20
- UI.success('To not re-enter your packagename and issuer every time you run one of the fastlane tools or fastlane, these will be stored in a so-called Appfile.')
52
+ def fetch_information_for_appfile
53
+ UI.message('')
54
+ UI.message("To avoid re-entering your package name and issuer every time you run fastlane, we'll store those in a so-called Appfile.")
21
55
 
22
- package_name = UI.input("Package Name (com.krausefx.app): ")
56
+ self.package_name = UI.input("Package Name (com.krausefx.app): ")
23
57
  puts("")
24
58
  puts("To automatically upload builds and metadata to Google Play, fastlane needs a service action json secret file".yellow)
25
- puts("Feel free to just click Enter to skip not provide certain things")
26
59
  puts("Follow the Setup Guide on how to get the Json file: https://docs.fastlane.tools/actions/supply/".yellow)
27
- json_key_file = UI.input("Path to the json secret file: ")
28
-
29
- template = File.read("#{Fastlane::ROOT}/lib/assets/AppfileTemplateAndroid")
30
- template.gsub!('[[JSON_KEY_FILE]]', json_key_file)
31
- template.gsub!('[[PACKAGE_NAME]]', package_name)
32
- path = File.join(folder, 'Appfile')
33
- File.write(path, template)
34
- UI.success("Created new file '#{path}'. Edit it to manage your preferred app metadata information.")
35
- end
60
+ puts("Feel free to press Enter at any time in order to skip providing pieces of information when asked")
61
+ self.json_key_file = UI.input("Path to the json secret file: ")
36
62
 
37
- def generate_fastfile
38
- template = File.read("#{Fastlane::ROOT}/lib/assets/FastfileTemplateAndroid")
39
-
40
- template.gsub!('[[FASTLANE_VERSION]]', Fastlane::VERSION)
41
-
42
- path = File.join(folder, 'Fastfile')
43
- File.write(path, template)
44
- UI.success("Created new file '#{path}'. Edit it to manage your own deployment lanes.")
63
+ self.appfile_content.gsub!("[[JSON_KEY_FILE]]", self.json_key_file)
64
+ self.appfile_content.gsub!("[[PACKAGE_NAME]]", self.package_name)
45
65
  end
46
66
 
47
67
  def init_supply
48
68
  UI.message("")
49
- question = "Do you plan on uploading metadata, screenshots and builds to Google Play using fastlane?".yellow
69
+ question = "Do you plan on uploading metadata, screenshots, and builds to Google Play using fastlane?".yellow
50
70
  UI.message(question)
51
- UI.message("This will download your existing metadata and screenshots into the `fastlane` folder")
52
- if UI.confirm(question)
71
+ UI.message("We will now download your existing metadata and screenshots into the `fastlane` folder so fastlane can manage it")
72
+ if UI.confirm("Download existing metadata and setup metadata management?")
53
73
  begin
54
74
  require 'supply'
55
75
  require 'supply/setup'
56
- Supply.config = FastlaneCore::Configuration.create(Supply::Options.available_options, {})
76
+ supply_config = {
77
+ json_key: self.json_key_file,
78
+ package_name: self.package_name
79
+ }
80
+ Supply.config = FastlaneCore::Configuration.create(Supply::Options.available_options, supply_config)
57
81
  Supply::Setup.new.perform_download
58
82
  rescue => ex
59
83
  UI.error(ex.to_s)
60
- UI.error("supply failed, but don't worry, you can launch supply using `fastlane supply init` whenever you want.")
84
+ UI.error("Setting up `supply` (metadata management action) failed, but don't worry, you can try setting it up again using `fastlane supply init` whenever you want.")
61
85
  end
62
86
  else
63
- UI.success("You can run `fastlane supply init` to do so at a later point.")
87
+ UI.success("You can run `fastlane supply init` to set up metadata management at a later point.")
64
88
  end
65
89
  end
66
90
 
67
- def folder
68
- FastlaneCore::FastlaneFolder.path
91
+ def finish_up
92
+ self.fastfile_content.gsub!(":ios", ":android")
93
+
94
+ super
69
95
  end
70
96
  end
71
97
  end
@@ -1,376 +1,515 @@
1
- require 'spaceship'
2
-
3
1
  module Fastlane
2
+ # rubocop:disable Metrics/ClassLength
4
3
  class SetupIos < Setup
5
- # the tools that are already enabled
6
- attr_accessor :tools
4
+ # Reference to the iOS project `project.rb`
7
5
  attr_accessor :project
8
- attr_accessor :apple_id
9
6
 
10
- attr_accessor :portal_ref
11
- attr_accessor :itc_ref
7
+ # App Identifier of the current app
8
+ attr_accessor :app_identifier
9
+
10
+ # Scheme of the Xcode project
11
+ attr_accessor :scheme
12
12
 
13
- attr_accessor :dev_portal_team
14
- attr_accessor :itc_team
13
+ # If the current setup requires a login, this is where we'll store the team ID
14
+ attr_accessor :itc_team_id
15
+ attr_accessor :adp_team_id
15
16
 
16
- attr_accessor :app_identifier
17
- attr_accessor :app_name
17
+ attr_accessor :app_exists_on_itc
18
+
19
+ attr_accessor :automatic_versioning_enabled
20
+
21
+ def setup_ios
22
+ require 'spaceship'
23
+
24
+ self.platform = :ios
18
25
 
19
- attr_accessor :is_swift_fastfile
26
+ welcome_to_fastlane
20
27
 
21
- def run(user: nil, is_swift_fastfile: false)
22
- self.is_swift_fastfile = is_swift_fastfile
23
- self.apple_id = user
24
- show_infos
28
+ if preferred_setup_method
29
+ self.send(preferred_setup_method)
25
30
 
26
- FastlaneCore::FastlaneFolder.create_folder! unless Helper.is_test?
27
- is_manual_setup = false
31
+ # We call `choose_swift` here also, as we skip the selection (see below)
32
+ choose_swift
33
+ return
34
+ end
35
+
36
+ options = {
37
+ "📸 Automate screenshots" => :ios_screenshots,
38
+ "👩‍✈️ Automate beta distribution to TestFlight" => :ios_testflight,
39
+ "🚀 Automate App Store distribution" => :ios_app_store,
40
+ "🛠 Manual setup - manually setup your project to automate your tasks" => :ios_manual
41
+ }
42
+
43
+ selected = UI.select("What would you like to use fastlane for?", options.keys)
44
+ method_to_use = options[selected]
28
45
 
29
- setup_project
30
- react_native_pre_checks
31
- ask_for_apple_id
46
+ # we want to first ask the user of what they want to do
47
+ # to make them already excited about all the things they can do with fastlane
48
+ # and only then we ask them about the language they want to use
49
+ choose_swift
32
50
 
33
51
  begin
34
- if self.project.mac?
35
- UI.important("Generating apps on the Apple Developer Portal and iTunes Connect is not currently available for Mac apps")
36
- else
37
- detect_if_app_is_available
38
- end
39
- print_config_table
40
- if self.project.schemes.count > 1
41
- UI.important("Note: If the values above are incorrect, it is possible the wrong scheme was selected")
42
- end
43
- if UI.confirm("Please confirm the above values")
44
- default_setup
52
+ self.send(method_to_use)
53
+ rescue => ex
54
+ # If it's already manual, and it has failed
55
+ # we need to re-raise the exception, as something definitely is wrong
56
+ raise ex if method_to_use == :ios_manual
57
+
58
+ # If we're here, that means something else failed. We now show the
59
+ # error message and fallback to `:ios_manual`
60
+ UI.error("--------------------")
61
+ UI.error("fastlane init failed")
62
+ UI.error("--------------------")
63
+
64
+ UI.verbose(ex.backtrace.join("\n"))
65
+ if ex.kind_of?(Spaceship::Client::BasicPreferredInfoError) || ex.kind_of?(Spaceship::Client::UnexpectedResponse)
66
+ UI.error(ex.preferred_error_info)
45
67
  else
46
- is_manual_setup = true
47
- UI.message("Falling back to manual onboarding")
48
- manual_setup
68
+ UI.error(ex.to_s)
49
69
  end
50
- UI.success('Successfully finished setting up fastlane')
51
- rescue Spaceship::Client::InsufficientPermissions, Spaceship::Client::ProgramLicenseAgreementUpdated => ex
52
- # We don't want to fallback to manual onboarding for this
53
- # as the user needs to first accept the agreement / get more permissions
54
- # Let's re-raise the exception to properly show the error message
55
- raise ex
56
- rescue => ex # this will also be caused by Ctrl + C
57
- UI.message("Ran into error while trying to connect to iTunes Connect / Dev Portal: #{ex}")
58
- UI.message("Falling back to manual onboarding")
59
-
60
- if is_manual_setup
61
- handle_exception(exception: ex)
70
+
71
+ UI.important("Something failed while running `fastlane init`")
72
+ UI.important("Tried using Apple ID with email '#{self.user}'")
73
+ UI.important("You can either retry, or fallback to manual setup which will create a basic Fastfile")
74
+ if UI.confirm("Would you like to fallback to a manual Fastfile?")
75
+ self.ios_manual
62
76
  else
63
- UI.error(ex.to_s)
64
- UI.error('An error occurred during the setup process. Falling back to manual setup!')
65
- try_manual_setup
77
+ self.send(method_to_use)
66
78
  end
79
+ # the second time, we're just failing, and don't use a `begin` `rescue` block any more
67
80
  end
68
81
  end
69
82
 
70
- def handle_exception(exception: nil)
71
- # Something went wrong with the setup, clear the folder again
72
- # and restore previous files
73
- UI.error('Error occurred with the setup program! Reverting changes now!')
74
- restore_previous_state
75
- raise exception
76
- end
83
+ # Different iOS flows
84
+ def ios_testflight
85
+ UI.header("Setting up fastlane for iOS TestFlight distribution")
86
+ find_and_setup_xcode_project
87
+ apple_xcode_project_versioning_enabled
88
+ ask_for_credentials(adp: true, itc: true)
89
+ verify_app_exists_adp!
90
+ verify_app_exists_itc!
77
91
 
78
- def try_manual_setup
79
- manual_setup
80
- rescue => ex
81
- handle_exception(exception: ex)
82
- end
83
-
84
- # React Native specific code
85
- # Make it easy for people to onboard
86
- def react_native_pre_checks
87
- return unless self.class.project_uses_react_native?
88
- if app_identifier.to_s.length == 0
89
- error_message = []
90
- error_message << "Could not detect bundle identifier of your react-native app."
91
- error_message << "Make sure to open the Xcode project and update the bundle identifier"
92
- error_message << "in the `General` section of your project settings."
93
- error_message << "Restart `fastlane init` once you're done!"
94
- UI.user_error!(error_message.join(" "))
92
+ if self.is_swift_fastfile
93
+ lane = ["func betaLane() {",
94
+ "desc(\"Push a new beta build to TestFlight\")",
95
+ increment_build_number_if_applicable,
96
+ "\tbuildApp(#{project_prefix}scheme: \"#{self.scheme}\")",
97
+ "\tuploadToTestflight(username: \"#{self.user}\")",
98
+ "}"]
99
+ else
100
+ lane = ["desc \"Push a new beta build to TestFlight\"",
101
+ "lane :beta do",
102
+ increment_build_number_if_applicable,
103
+ " build_app(#{project_prefix}scheme: \"#{self.scheme}\")",
104
+ " upload_to_testflight",
105
+ "end"]
95
106
  end
96
- end
107
+ self.append_lane(lane)
97
108
 
98
- def self.project_uses_react_native?(path: Dir.pwd)
99
- package_json = File.join(path, "..", "package.json")
100
- return false unless File.basename(path) == "ios"
101
- return false unless File.exist?(package_json)
102
- package_content = File.read(package_json)
103
- return true if package_content.include?("react-native")
104
- false
109
+ self.lane_to_mention = "beta"
110
+ finish_up
105
111
  end
106
112
 
107
- def default_setup
108
- copy_existing_files
109
- generate_appfile(manually: false)
110
- detect_installed_tools # after copying the existing files
111
- if !self.project.mac? && self.itc_ref.nil? && self.portal_ref.nil?
112
- create_app_if_necessary
113
+ def ios_app_store
114
+ UI.header("Setting up fastlane for iOS App Store distribution")
115
+ find_and_setup_xcode_project
116
+ apple_xcode_project_versioning_enabled
117
+ ask_for_credentials(adp: true, itc: true)
118
+ verify_app_exists_adp!
119
+ verify_app_exists_itc!
120
+
121
+ if self.app_exists_on_itc
122
+ UI.header("Manage app metadata?")
123
+ UI.message("Would you like to have fastlane manage your app's metadata?")
124
+ UI.message("If you enable this feature, fastlane will download your existing metadata and screenshots.")
125
+ UI.message("This way, you'll be able to edit your app's metadata in local `.txt` files.")
126
+ UI.message("After editing the local `.txt` files, just run fastlane and all changes will be pushed up.")
127
+ UI.message("If you don't want to use this feature, you can still use fastlane to upload and distribute new builds to the App Store")
128
+ include_metadata = UI.confirm("Would you like fastlane to manage your app's metadata?")
129
+ if include_metadata
130
+ require 'deliver'
131
+ require 'deliver/setup'
132
+
133
+ deliver_options = FastlaneCore::Configuration.create(
134
+ Deliver::Options.available_options,
135
+ {
136
+ run_precheck_before_submit: false, # precheck doesn't need to run during init
137
+ username: self.user,
138
+ app_identifier: self.app_identifier,
139
+ team_id: self.itc_team_id
140
+ }
141
+ )
142
+
143
+ Deliver::DetectValues.new.run!(deliver_options, {}) # needed to fetch the app details
144
+ Deliver::Setup.new.run(deliver_options, is_swift: self.is_swift_fastfile)
145
+ end
113
146
  end
114
- enable_deliver
115
- generate_fastfile(manually: false)
116
147
 
117
148
  if self.is_swift_fastfile
118
- update_swift_runner
149
+ lane = ["func releaseLane() {",
150
+ "desc(\"Push a new release build to the App Store\")",
151
+ increment_build_number_if_applicable,
152
+ "\tbuildApp(#{project_prefix}scheme: \"#{self.scheme}\")"]
153
+ if include_metadata
154
+ lane << "\tuploadToAppStore(username: \"#{self.user}\", app: \"#{self.app_identifier}\")"
155
+ else
156
+ lane << "\tuploadToAppStore(username: \"#{self.user}\", app: \"#{self.app_identifier}\", skipScreenshots: true, skipMetadata: true)"
157
+ end
158
+ lane << "}"
159
+ else
160
+ lane = ["desc \"Push a new release build to the App Store\"",
161
+ "lane :release do",
162
+ increment_build_number_if_applicable,
163
+ " build_app(#{project_prefix}scheme: \"#{self.scheme}\")"]
164
+ if include_metadata
165
+ lane << " upload_to_app_store"
166
+ else
167
+ lane << " upload_to_app_store(skip_metadata: true, skip_screenshots: true)"
168
+ end
169
+ lane << "end"
119
170
  end
120
171
 
121
- show_analytics
172
+ append_lane(lane)
173
+ self.lane_to_mention = "release"
174
+ finish_up
122
175
  end
123
176
 
124
- def manual_setup
125
- copy_existing_files
126
- generate_appfile(manually: true)
127
- detect_installed_tools # after copying the existing files
128
- ask_to_enable_other_tools
129
- generate_fastfile(manually: true)
177
+ def ios_screenshots
178
+ UI.header("Setting up fastlane to automate iOS screenshots")
130
179
 
131
- if self.is_swift_fastfile
132
- update_swift_runner
133
- end
180
+ UI.message("fastlane uses UI Tests to automate generating localized screenshots of your iOS app")
181
+ UI.message("fastlane will now create 2 helper files that are needed to get the setup running")
182
+ UI.message("For more information on how this works and best practices, check out")
183
+ UI.message("\thttps://docs.fastlane.tools/getting-started/ios/screenshots/".cyan)
184
+ continue_with_enter
134
185
 
135
- show_analytics
136
- end
137
-
138
- def ask_to_enable_other_tools
139
- if self.itc_ref.nil? && self.portal_ref.nil?
140
- wants_to_create_app = UI.confirm('Would you like to create your app on iTunes Connect and the Developer Portal?')
141
- if wants_to_create_app
142
- create_app_if_necessary
143
- detect_if_app_is_available # check if the app was, in fact, created.
144
- end
186
+ begin
187
+ find_and_setup_xcode_project(ask_for_scheme: false) # to get the bundle identifier
188
+ rescue => ex
189
+ # If this fails, it's no big deal, since we really just want the bundle identifier
190
+ # so instead, we'll just ask the user
191
+ UI.verbose(ex.to_s)
145
192
  end
146
- if self.itc_ref && self.portal_ref
147
- wants_to_setup_deliver = UI.confirm("Do you want to setup 'deliver', which is used to upload app screenshots, app metadata and app updates to the App Store? This requires the app to be in the App Store already")
148
- enable_deliver if wants_to_setup_deliver
193
+
194
+ require 'snapshot'
195
+ require 'snapshot/setup'
196
+
197
+ Snapshot::Setup.create(
198
+ FastlaneCore::FastlaneFolder.path,
199
+ is_swift_fastfile: self.is_swift_fastfile,
200
+ print_instructions_on_failure: true
201
+ )
202
+
203
+ UI.message("If you want more details on how to setup automatic screenshots, check out")
204
+ UI.message("\thttps://docs.fastlane.tools/getting-started/ios/screenshots/#setting-up-snapshot".cyan)
205
+ continue_with_enter
206
+
207
+ available_schemes = self.project.schemes
208
+ ui_testing_scheme = UI.select("Which is your UI Testing scheme? If you can't find it in this list, make sure it's marked as `Shared` in the Xcode scheme list", available_schemes)
209
+
210
+ UI.header("Automatically upload to iTC?")
211
+ UI.message("Would you like fastlane to automatically upload all generated screenshots to iTunes Connect")
212
+ UI.message("after generating them?")
213
+ UI.message("If you enable this feature you'll need to provide your iTunes Connect credentials so fastlane can upload the screenshots to iTunes Connect")
214
+ automatic_upload = UI.confirm("Enable automatic upload of localized screenshots to iTunes Connect?")
215
+ if automatic_upload
216
+ ask_for_credentials(adp: true, itc: true)
217
+ verify_app_exists_itc!
149
218
  end
150
- end
151
219
 
152
- def setup_project
153
- config = {}
154
- FastlaneCore::Project.detect_projects(config)
155
- self.project = FastlaneCore::Project.new(config)
156
- self.project.select_scheme(preferred_to_include: self.project.project_name)
157
- self.app_identifier = self.project.default_app_identifier # These two vars need to be accessed in order to be set
158
- self.app_name = self.project.default_app_name # They are set as a side effect, this could/should be changed down the road
159
- end
220
+ if self.is_swift_fastfile
221
+ lane = ["func screenshotsLane() {",
222
+ "desc(\"Generate new localized screenshots\")",
223
+ "\tcaptureScreenshots(#{project_prefix}scheme: \"#{ui_testing_scheme}\")"]
160
224
 
161
- def print_config_table
162
- rows = []
163
- rows << ["Apple ID", self.apple_id]
164
- rows << ["App Name", self.app_name]
165
- rows << ["App Identifier", self.app_identifier]
166
- rows << [(self.project.is_workspace ? "Workspace" : "Project"), self.project.path]
167
- require 'terminal-table'
168
- puts("")
169
- puts(Terminal::Table.new(rows: FastlaneCore::PrintTable.transform_output(rows),
170
- title: "Detected Values"))
171
- puts("")
172
-
173
- unless self.itc_ref || self.project.mac?
174
- UI.important("This app identifier doesn't exist on iTunes Connect yet, it will be created for you")
175
- end
225
+ if automatic_upload
226
+ lane << "\tuploadToAppStore(username: \"#{self.user}\", app: \"#{self.app_identifier}\", skipBinaryUpload: true, skipMetadata: true)"
227
+ end
228
+ lane << "}"
229
+ else
230
+ lane = ["desc \"Generate new localized screenshots\"",
231
+ "lane :screenshots do",
232
+ " capture_screenshots(#{project_prefix}scheme: \"#{ui_testing_scheme}\")"]
176
233
 
177
- unless self.portal_ref || self.project.mac?
178
- UI.important("This app identifier doesn't exist on the Apple Developer Portal yet, it will be created for you")
234
+ if automatic_upload
235
+ lane << " upload_to_app_store(skip_binary_upload: true, skip_metadata: true)"
236
+ end
237
+ lane << "end"
179
238
  end
180
- end
239
+ append_lane(lane)
181
240
 
182
- def show_infos
183
- UI.success('This setup will help you get up and running in no time.')
184
- UI.success("fastlane will check what tools you're already using and set up")
185
- UI.success('the tool automatically for you. Have fun! ')
241
+ self.lane_to_mention = "screenshots"
242
+ finish_up
186
243
  end
187
244
 
188
- def files_to_copy
189
- ['Deliverfile', 'deliver', 'screenshots', 'metadata']
190
- end
245
+ def ios_manual
246
+ UI.header("Setting up fastlane so you can manually configure it")
191
247
 
192
- def copy_existing_files
193
- files_to_copy.each do |current|
194
- current = File.join(File.expand_path('..', FastlaneCore::FastlaneFolder.path), current)
195
- next unless File.exist?(current)
196
- file_name = File.basename(current)
197
- to_path = File.join(folder, file_name)
198
- UI.success("Moving '#{current}' to '#{to_path}'")
199
- FileUtils.mv(current, to_path)
248
+ if self.is_swift_fastfile
249
+ append_lane(["func customLane() {",
250
+ "desc(\"Description of what the lane does\")",
251
+ "\t// add actions here: https://docs.fastlane.tools/actions",
252
+ "}"])
253
+ self.lane_to_mention = "custom" # lane is part of the notation
254
+ else
255
+ append_lane(["desc \"Description of what the lane does\"",
256
+ "lane :custom_lane do",
257
+ " # add actions here: https://docs.fastlane.tools/actions",
258
+ "end"])
259
+ self.lane_to_mention = "custom_lane"
200
260
  end
201
- end
202
261
 
203
- def ask_for_apple_id
204
- self.apple_id ||= UI.input("Your Apple ID (e.g. fastlane@krausefx.com): ")
262
+ finish_up
205
263
  end
206
264
 
207
- def ask_for_app_identifier
208
- self.app_identifier = UI.input("App Identifier (com.krausefx.app): ")
209
- end
265
+ # Helpers
210
266
 
211
- def generate_appfile(manually: false)
212
- template = File.read(appfile_template_path)
213
- if manually
214
- ask_for_app_identifier
215
- ask_for_apple_id
267
+ # Every installation setup that needs an Xcode project should
268
+ # call this method
269
+ def find_and_setup_xcode_project(ask_for_scheme: true)
270
+ UI.message("Parsing your local Xcode project to find the available schemes and the app identifier")
271
+ config = {} # this is needed as the first method call will store information in there
272
+ if self.project_path.end_with?("xcworkspace")
273
+ config[:workspace] = self.project_path
274
+ else
275
+ config[:project] = self.project_path
216
276
  end
217
277
 
218
- template.gsub!('[[DEV_PORTAL_TEAM_ID]]', self.dev_portal_team) if self.dev_portal_team
219
- template.gsub!('[[APP_IDENTIFIER]]', self.app_identifier)
220
- template.gsub!('[[APPLE_ID]]', self.apple_id)
278
+ FastlaneCore::Project.detect_projects(config)
279
+ self.project = FastlaneCore::Project.new(config)
221
280
 
222
- if self.is_swift_fastfile
223
- itc_team = self.itc_team ? "\"#{self.itc_team}\"" : "nil"
224
- path = File.join(folder, 'Appfile.swift')
225
- else
226
- itc_team = self.itc_team ? "itc_team_id \"#{self.itc_team}\" # iTunes Connect Team ID\n" : ""
227
- path = File.join(folder, 'Appfile')
281
+ if ask_for_scheme
282
+ self.scheme = self.project.select_scheme(preferred_to_include: self.project.project_name)
228
283
  end
229
- template.gsub!('[[ITC_TEAM]]', itc_team)
230
284
 
231
- File.write(path, template)
232
- UI.success("Created new file '#{path}'. Edit it to manage your preferred app metadata information.")
285
+ self.app_identifier = self.project.default_app_identifier # These two vars need to be accessed in order to be set
286
+ if self.app_identifier.to_s.length == 0
287
+ ask_for_bundle_identifier
288
+ end
233
289
  end
234
290
 
235
- # Detect if the app was created on the Dev Portal / iTC
236
- def detect_if_app_is_available
237
- UI.important("Verifying that app is available on the Apple Developer Portal and iTunes Connect...")
238
- UI.message("Starting login with user '#{self.apple_id}'")
239
- Spaceship.login(self.apple_id, nil)
240
- self.dev_portal_team = Spaceship.select_team
241
- self.portal_ref = Spaceship::App.find(self.app_identifier)
242
-
243
- Spaceship::Tunes.login(@apple_id, nil)
244
- self.itc_team = Spaceship::Tunes.select_team
245
- self.itc_ref = Spaceship::Application.find(self.app_identifier)
291
+ def ask_for_bundle_identifier
292
+ loop do
293
+ return if self.app_identifier.to_s.length > 0
294
+ self.app_identifier = UI.input("Bundle identifier of your app: ")
295
+ end
246
296
  end
247
297
 
248
- def create_app_if_necessary
249
- UI.important("Creating the app on iTunes Connect and the Apple Developer Portal")
250
- require 'produce'
251
- config = {} # this has to be done like this
252
- FastlaneCore::Project.detect_projects(config)
253
- project = FastlaneCore::Project.new(config)
254
- produce_options_hash = {
255
- app_name: project.app_name,
256
- app_identifier: self.app_identifier
257
- }
258
- Produce.config = FastlaneCore::Configuration.create(Produce::Options.available_options, produce_options_hash)
259
- begin
260
- ENV['PRODUCE_APPLE_ID'] = Produce::Manager.start_producing
261
- rescue => exception
262
- if exception.to_s.include?("The App Name you entered has already been used")
263
- UI.important("It looks like that #{project.app_name} has already been taken by someone else, please enter an alternative.")
264
- Produce.config[:app_name] = UI.input("App Name: ")
265
- Produce.config[:skip_devcenter] = true # since we failed on iTC
266
- ENV['PRODUCE_APPLE_ID'] = Produce::Manager.start_producing
298
+ def ask_for_credentials(itc: true, adp: false)
299
+ UI.header("Login with your Apple ID")
300
+ UI.message("To use iTunes Connect and Apple Developer Portal features as part of fastlane,")
301
+ UI.message("we will ask you for your Apple ID username and password")
302
+ UI.message("This is necessary for certain fastlane features, for example:")
303
+ UI.message("")
304
+ UI.message("- Create and manage your provisioning profiles on the Developer Portal")
305
+ UI.message("- Upload and manage TestFlight and App Store builds on iTunes Connect")
306
+ UI.message("- Manage your iTunes Connect app metadata and screenshots")
307
+ UI.message("")
308
+ UI.message("Your Apple ID credentials will only be stored in your Keychain, on your local machine")
309
+ UI.message("For more information, check out")
310
+ UI.message("\thttps://github.com/fastlane/fastlane/tree/master/credentials_manager".cyan)
311
+ UI.message("")
312
+
313
+ if self.user.to_s.length == 0
314
+ UI.important("Please enter your Apple ID developer credentials")
315
+ self.user = UI.input("Apple ID Username:")
316
+ end
317
+ UI.message("Logging in...")
318
+
319
+ # Disable the warning texts and information that's not relevant during onboarding
320
+ ENV["FASTLANE_HIDE_LOGIN_INFORMATION"] = 1.to_s
321
+ ENV["FASTLANE_HIDE_TEAM_INFORMATION"] = 1.to_s
322
+
323
+ if itc
324
+ Spaceship::Tunes.login(self.user)
325
+ Spaceship::Tunes.select_team
326
+ self.itc_team_id = Spaceship::Tunes.client.team_id
327
+ if self.is_swift_fastfile
328
+ self.append_team("var itcTeam: String? { return \"#{self.itc_team_id}\" } // iTunes Connect Team ID")
329
+ else
330
+ self.append_team("itc_team_id \"#{self.itc_team_id}\" # iTunes Connect Team ID")
267
331
  end
268
332
  end
269
- end
270
333
 
271
- def detect_installed_tools
272
- self.tools = {}
273
- self.tools[:snapshot] = File.exist?(File.join(folder, 'Snapfile'))
274
- self.tools[:snapshot] ||= File.exist?(File.join(folder, 'Snapfile.swift'))
275
- self.tools[:cocoapods] = File.exist?(File.join(File.expand_path('..', folder), 'Podfile'))
276
- self.tools[:carthage] = File.exist?(File.join(File.expand_path('..', folder), 'Cartfile'))
334
+ if adp
335
+ Spaceship::Portal.login(self.user)
336
+ Spaceship::Portal.select_team
337
+ self.adp_team_id = Spaceship::Portal.client.team_id
338
+ if self.is_swift_fastfile
339
+ self.append_team("var teamID: String { return \"#{self.adp_team_id}\" } // Apple Developer Portal Team ID")
340
+ else
341
+ self.append_team("team_id \"#{self.adp_team_id}\" # Developer Portal Team ID")
342
+ end
343
+ end
344
+
345
+ UI.success("✅ Logging in with your Apple ID was successful")
277
346
  end
278
347
 
279
- def enable_deliver
280
- UI.message("Loading up 'deliver', this might take a few seconds")
281
- require 'deliver'
282
- require 'deliver/setup'
348
+ def apple_xcode_project_versioning_enabled
349
+ self.automatic_versioning_enabled = false
350
+
351
+ paths = self.project.project_paths
352
+ return false if paths.count == 0
283
353
 
284
- options = FastlaneCore::Configuration.create(Deliver::Options.available_options, {})
285
- options[:run_precheck_before_submit] = false # precheck doesn't need to run during init
286
- options[:username] = self.apple_id if self.apple_id
287
- options[:app_identifier] = self.app_identifier if self.app_identifier
354
+ result = Fastlane::Actions::GetBuildNumberAction.run({
355
+ project: paths.first, # most of the times, there will only be one project in there
356
+ hide_error_when_versioning_disabled: true
357
+ })
288
358
 
289
- Deliver::Runner.new(options) # to login...
290
- Deliver::Setup.new.run(options, is_swift: self.is_swift_fastfile)
359
+ if result.kind_of?(String) && result.to_f > 0
360
+ self.automatic_versioning_enabled = true
361
+ end
362
+ return self.automatic_versioning_enabled
291
363
  end
292
364
 
293
- def generate_fastfile(manually: false)
294
- scheme = self.project.schemes.first unless manually
365
+ def show_information_about_version_bumps
366
+ UI.important("It looks like your project isn't set up to do automatic version incrementing")
367
+ UI.important("To enable fastlane to handle automatic version incrementing for you, please follow this guide:")
368
+ UI.message("\thttps://developer.apple.com/library/content/qa/qa1827/_index.html".cyan)
369
+ UI.important("Afterwards check out the fastlane docs on how to set up automatic build increments")
370
+ UI.message("\thttps://docs.fastlane.tools/getting-started/ios/beta-deployment/#best-practices".cyan)
371
+ end
295
372
 
296
- template = File.read(fastfile_template_path)
373
+ def verify_app_exists_adp!
374
+ UI.user_error!("No app identifier provided") if self.app_identifier.to_s.length == 0
375
+ UI.message("Checking if the app '#{self.app_identifier}' exists in your Apple Developer Portal...")
376
+ app = Spaceship::Portal::App.find(self.app_identifier)
377
+ if app.nil?
378
+ UI.error("It looks like the app '#{self.app_identifier}' isn't available on the #{'Apple Developer Portal'.bold.underline}")
379
+ UI.error("for the team ID '#{self.adp_team_id}' on Apple ID '#{self.user}'")
297
380
 
298
- scheme = UI.input("Optional: The scheme name of your app (If you don't need one, just hit Enter): ") unless scheme
299
- if scheme.length > 0
300
- if self.is_swift_fastfile
301
- template.gsub!('[[SCHEME]]', "scheme: \"#{scheme}\"")
381
+ if UI.confirm("Do you want fastlane to create the App ID for you on the Apple Developer Portal?")
382
+ create_app_online!(mode: :adp)
302
383
  else
303
- template.gsub!('[[SCHEME]]', "(scheme: \"#{scheme}\")")
384
+ UI.important("Alright, we won't create the app for you. Be aware, the build is probably going to fail when you try it")
304
385
  end
305
386
  else
306
- template.gsub!('[[SCHEME]]', "")
387
+ UI.success("✅ Your app '#{self.app_identifier}' is available on iTunes Connect")
307
388
  end
389
+ end
308
390
 
309
- template.gsub!('[[FASTLANE_VERSION]]', Fastlane::VERSION)
310
-
311
- if self.is_swift_fastfile
312
- template.gsub!('snapshot()', '// snapshot') unless self.tools[:snapshot]
313
- template.gsub!('cocoapods()', '// cocoapods()') unless self.tools[:cocoapods]
314
- template.gsub!('carthage()', '// carthage()') unless self.tools[:carthage]
315
- path = File.join(folder, 'Fastfile.swift')
391
+ def verify_app_exists_itc!
392
+ UI.user_error!("No app identifier provided") if self.app_identifier.to_s.length == 0
393
+ UI.message("Checking if the app '#{self.app_identifier}' exists on iTunes Connect...")
394
+ app = Spaceship::Tunes::Application.find(self.app_identifier)
395
+ if app.nil?
396
+ UI.error("Looks like the app '#{self.app_identifier}' isn't available on #{'iTunes Connect'.bold.underline}")
397
+ UI.error("for the team ID '#{self.itc_team_id}' on Apple ID '#{self.user}'")
398
+ if UI.confirm("Would you like fastlane to create the App on iTunes Connect for you?")
399
+ create_app_online!(mode: :itc)
400
+ self.app_exists_on_itc = true
401
+ else
402
+ UI.important("Alright, we won't create the app for you. Be aware, the build is probably going to fail when you try it")
403
+ end
316
404
  else
317
- template.gsub!('snapshot', '# snapshot') unless self.tools[:snapshot]
318
- template.gsub!('cocoapods', '# cocoapods') unless self.tools[:cocoapods]
319
- template.gsub!('carthage', '# cocoapods') unless self.tools[:carthage]
320
- path = File.join(folder, 'Fastfile')
405
+ UI.success("✅ Your app '#{self.app_identifier}' is available on iTunes Connect")
406
+ self.app_exists_on_itc = true
321
407
  end
408
+ end
322
409
 
323
- self.tools.each do |key, value|
324
- UI.message("'#{key}' enabled.".magenta) if value
325
- UI.important("'#{key}' not enabled.") unless value
410
+ def finish_up
411
+ # iOS specific things first
412
+ if self.app_identifier
413
+ self.appfile_content.gsub!("# app_identifier", "app_identifier")
414
+ self.appfile_content.gsub!("[[APP_IDENTIFIER]]", self.app_identifier)
326
415
  end
327
416
 
328
- File.write(path, template)
329
- UI.success("Created new file '#{path}'. Edit it to manage your own deployment lanes.")
330
- end
417
+ if self.user
418
+ self.appfile_content.gsub!("# apple_id", "apple_id")
419
+ self.appfile_content.gsub!("[[APPLE_ID]]", self.user)
420
+ end
421
+
422
+ self.show_information_about_version_bumps unless self.automatic_versioning_enabled
331
423
 
332
- def folder
333
- FastlaneCore::FastlaneFolder.path
424
+ super
334
425
  end
335
426
 
336
- def appfile_template_path
337
- if self.is_swift_fastfile
338
- return "#{Fastlane::ROOT}/lib/assets/AppfileTemplate.swift"
427
+ # Returns the `workspace` or `project` key/value pair for
428
+ # gym and snapshot, but only if necessary
429
+ # (when there are multiple projects in the current directory)
430
+ # it's a prefix, and not a suffix, as Swift cares about the order of parameters
431
+ def project_prefix
432
+ return "" unless self.had_multiple_projects_to_choose_from
433
+
434
+ if self.project_path.end_with?(".xcworkspace")
435
+ return "workspace: \"#{self.project_path}\", "
339
436
  else
340
- return "#{Fastlane::ROOT}/lib/assets/AppfileTemplate"
437
+ return "project: \"#{self.project_path}\", "
341
438
  end
342
439
  end
343
440
 
344
- def fastfile_template_path
345
- if self.is_swift_fastfile
346
- return "#{Fastlane::ROOT}/lib/assets/DefaultFastfileTemplate.swift"
347
- else
348
- return "#{Fastlane::ROOT}/lib/assets/DefaultFastfileTemplate"
349
- end
441
+ # Choose the language the config files should be based in
442
+ def choose_swift
443
+ # The Swift question below is removed, as we don't want everyone
444
+ # to use the Beta setup yet
445
+ # using `||=` since if the user already used `fastlane init swift` we want to just use swift
446
+ # self.is_swift_fastfile ||= UI.confirm("[Beta] Do you want to try our new experimental Swift based configuration files?")
447
+
448
+ self.fastfile_content = fastfile_template_content
449
+ self.appfile_content = appfile_template_content
350
450
  end
351
451
 
352
- def update_swift_runner
353
- runner_source_resources = "#{Fastlane::ROOT}/swift/."
452
+ def increment_build_number_if_applicable
453
+ return nil unless self.automatic_versioning_enabled
454
+ return nil if self.project.project_paths.first.to_s.length == 0
354
455
 
355
- destination_path = File.expand_path('swift', FastlaneCore::FastlaneFolder.path)
356
- FileUtils.cp_r(runner_source_resources, destination_path)
456
+ project_path = self.project.project_paths.first
457
+ # Convert the absolute path to a relative path
458
+ project_path_name = Pathname.new(project_path)
459
+ current_path_name = Pathname.new(File.expand_path("."))
357
460
 
358
- UI.success("Copied Swift fastlane runner project to '#{destination_path}'.")
461
+ relative_project_path = project_path_name.relative_path_from(current_path_name)
462
+
463
+ if self.is_swift_fastfile
464
+ return "\tincrementBuildNumber(xcodeproj: \"#{relative_project_path}\")"
465
+ else
466
+ return " increment_build_number(xcodeproj: \"#{relative_project_path}\")"
467
+ end
359
468
  end
360
469
 
361
- def restore_previous_state
362
- # Move all moved files back
363
- files_to_copy.each do |current|
364
- from_path = File.join(folder, current)
365
- to_path = File.basename(current)
366
- if File.exist?(from_path)
367
- UI.important("Moving '#{from_path}' to '#{to_path}'")
368
- FileUtils.mv(from_path, to_path)
369
- end
470
+ def create_app_online!(mode: nil)
471
+ # mode is either :adp or :itc
472
+ require 'produce'
473
+ produce_options = {
474
+ username: self.user,
475
+ team_id: self.adp_team_id,
476
+ itc_team_id: self.itc_team_id,
477
+ platform: "ios",
478
+ app_identifier: self.app_identifier
479
+ }
480
+ if mode == :adp
481
+ produce_options[:skip_itc] = true
482
+ else
483
+ produce_options[:skip_devcenter] = true
370
484
  end
371
485
 
372
- UI.important("Deleting the 'fastlane' folder")
373
- FileUtils.rm_rf(folder)
486
+ Produce.config = FastlaneCore::Configuration.create(
487
+ Produce::Options.available_options,
488
+ produce_options
489
+ )
490
+
491
+ # The retrying system allows people to correct invalid inputs
492
+ # e.g. the app's name is already taken
493
+ loop do
494
+ begin
495
+ Produce::Manager.start_producing
496
+ UI.success("✅ Successfully created app")
497
+ return # success
498
+ rescue => ex
499
+ # show the user facing error, and inform them of what went wrong
500
+ if ex.kind_of?(Spaceship::Client::BasicPreferredInfoError) || ex.kind_of?(Spaceship::Client::UnexpectedResponse)
501
+ UI.error(ex.preferred_error_info)
502
+ else
503
+ UI.error(ex.to_s)
504
+ end
505
+ UI.error(ex.backtrace.join("\n")) if FastlaneCore::Globals.verbose?
506
+ UI.important("It looks like something went wrong when we tried to create your app on the Apple Developer Portal")
507
+ unless UI.confirm("Would you like to try again (y)? If you enter (n), fastlane will fall back to the manual setup")
508
+ raise ex
509
+ end
510
+ end
511
+ end
374
512
  end
375
513
  end
514
+ # rubocop:enable Metrics/ClassLength
376
515
  end