fastlane 2.105.2 → 2.106.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +73 -72
  3. data/bin/fastlane +24 -1
  4. data/deliver/lib/deliver/app_screenshot.rb +7 -0
  5. data/deliver/lib/deliver/submit_for_review.rb +14 -1
  6. data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
  7. data/fastlane/lib/fastlane/actions/download_from_play_store.rb +61 -0
  8. data/fastlane/lib/fastlane/actions/modify_services.rb +7 -5
  9. data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +2 -2
  10. data/fastlane/lib/fastlane/actions/register_device.rb +6 -4
  11. data/fastlane/lib/fastlane/actions/register_devices.rb +9 -8
  12. data/fastlane/lib/fastlane/actions/upload_to_play_store.rb +12 -0
  13. data/fastlane/lib/fastlane/helper/crashlytics_helper.rb +1 -1
  14. data/fastlane/lib/fastlane/version.rb +1 -1
  15. data/fastlane/swift/Deliverfile.swift +1 -1
  16. data/fastlane/swift/Fastlane.swift +24 -6
  17. data/fastlane/swift/Gymfile.swift +1 -1
  18. data/fastlane/swift/Matchfile.swift +1 -1
  19. data/fastlane/swift/MatchfileProtocol.swift +3 -4
  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/device_manager.rb +1 -1
  26. data/fastlane_core/lib/fastlane_core/helper.rb +1 -1
  27. data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +13 -10
  28. data/frameit/lib/frameit/editor.rb +3 -2
  29. data/frameit/lib/frameit/frame_downloader.rb +1 -1
  30. data/match/lib/match.rb +2 -2
  31. data/match/lib/match/change_password.rb +31 -18
  32. data/match/lib/match/commands_generator.rb +20 -7
  33. data/match/lib/match/encryption.rb +18 -0
  34. data/match/lib/match/encryption/interface.rb +17 -0
  35. data/match/lib/match/encryption/openssl.rb +155 -0
  36. data/match/lib/match/generator.rb +5 -5
  37. data/match/lib/match/module.rb +5 -1
  38. data/match/lib/match/nuke.rb +33 -15
  39. data/match/lib/match/options.rb +11 -12
  40. data/match/lib/match/runner.rb +59 -38
  41. data/match/lib/match/storage.rb +16 -0
  42. data/match/lib/match/storage/git_storage.rb +228 -0
  43. data/match/lib/match/storage/interface.rb +50 -0
  44. data/snapshot/lib/snapshot/screenshot_rotate.rb +3 -8
  45. data/spaceship/README.md +10 -1
  46. data/spaceship/lib/spaceship/api/.DS_Store +0 -0
  47. data/spaceship/lib/spaceship/api/.base.rb.swp +0 -0
  48. data/spaceship/lib/spaceship/du/du_client.rb +2 -0
  49. data/spaceship/lib/spaceship/portal/device.rb +0 -1
  50. data/spaceship/lib/spaceship/portal/portal_client.rb +7 -1
  51. data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
  52. data/spaceship/lib/spaceship/tunes/app_details.rb +1 -1
  53. data/spaceship/lib/spaceship/tunes/device_type.rb +1 -1
  54. metadata +46 -20
  55. data/fastlane_core/lib/fastlane_core/itunes_search_api.rb +0 -50
  56. data/match/lib/match/encrypt.rb +0 -133
  57. data/match/lib/match/git_helper.rb +0 -209
@@ -1,50 +0,0 @@
1
- require 'open-uri'
2
-
3
- require_relative 'ui/ui'
4
-
5
- module FastlaneCore
6
- # A wrapper around the Apple iTunes Search API to access app information like
7
- # the app identifier of an app.
8
- class ItunesSearchApi
9
- # Fetch all information you can get from a specific AppleID of an app
10
- # @param id (int) The AppleID of the given app. This usually consists of 9 digits.
11
- # @param country (string) The optional ISO-2A country code
12
- # @return (Hash) the response of the first result from Apple (https://itunes.apple.com/lookup?id=284882215[&country=FR])
13
- # @example Response of Facebook App: https://itunes.apple.com/lookup?id=284882215[&country=FR]
14
- # {
15
- # ...
16
- # artistName: "Facebook, Inc.",
17
- # price: 0,
18
- # version: "14.9",
19
- # ...
20
- # }
21
- def self.fetch(id, country = nil)
22
- # Example: https://itunes.apple.com/lookup?id=284882215[&country=FR]
23
- suffix = country.nil? ? nil : "&country=#{country}"
24
- fetch_url("https://itunes.apple.com/lookup?id=#{id}#{suffix}")
25
- end
26
-
27
- def self.fetch_by_identifier(app_identifier, country = nil)
28
- # Example: http://itunes.apple.com/lookup?bundleId=net.sunapps.1[&country=FR]
29
- suffix = country.nil? ? nil : "&country=#{country}"
30
- fetch_url("https://itunes.apple.com/lookup?bundleId=#{app_identifier}#{suffix}")
31
- end
32
-
33
- # This method only fetches the bundle identifier of a given app
34
- # @param id (int) The AppleID of the given app. This usually consists of 9 digits.
35
- # @return (String) the Bundle identifier of the app
36
- def self.fetch_bundle_identifier(id)
37
- self.fetch(id)['bundleId']
38
- end
39
-
40
- def self.fetch_url(url)
41
- response = JSON.parse(open(url).read)
42
- return nil if response['resultCount'] == 0
43
-
44
- return response['results'].first
45
- rescue
46
- UI.error("Could not find object '#{url}' using the iTunes API")
47
- nil
48
- end
49
- end
50
- end
@@ -1,133 +0,0 @@
1
- require_relative 'module'
2
- require_relative 'change_password'
3
-
4
- module Match
5
- class Encrypt
6
- require 'base64'
7
- require 'openssl'
8
- require 'securerandom'
9
- require 'security'
10
- require 'shellwords'
11
-
12
- def server_name(git_url)
13
- ["match", git_url].join("_")
14
- end
15
-
16
- def password(git_url)
17
- password = ENV["MATCH_PASSWORD"]
18
- unless password
19
- item = Security::InternetPassword.find(server: server_name(git_url))
20
- password = item.password if item
21
- end
22
-
23
- unless password
24
- if !UI.interactive?
25
- UI.error("Neither the MATCH_PASSWORD environment variable nor the local keychain contained a password.")
26
- UI.error("Bailing out instead of asking for a password, since this is non-interactive mode.")
27
- UI.user_error!("Try setting the MATCH_PASSWORD environment variable, or temporarily enable interactive mode to store a password.")
28
- else
29
- UI.important("Enter the passphrase that should be used to encrypt/decrypt your certificates")
30
- UI.important("This passphrase is specific per repository and will be stored in your local keychain")
31
- UI.important("Make sure to remember the password, as you'll need it when you run match on a different machine")
32
- password = ChangePassword.ask_password(confirm: true)
33
- store_password(git_url, password)
34
- end
35
- end
36
-
37
- return password
38
- end
39
-
40
- def store_password(git_url, password)
41
- Security::InternetPassword.add(server_name(git_url), "", password)
42
- end
43
-
44
- # removes the password from the keychain again
45
- def clear_password(git_url)
46
- Security::InternetPassword.delete(server: server_name(git_url))
47
- end
48
-
49
- def encrypt_repo(path: nil, git_url: nil)
50
- iterate(path) do |current|
51
- encrypt(path: current,
52
- password: password(git_url))
53
- UI.success("🔒 Encrypted '#{File.basename(current)}'") if FastlaneCore::Globals.verbose?
54
- end
55
- UI.success("🔒 Successfully encrypted certificates repo")
56
- end
57
-
58
- def decrypt_repo(path: nil, git_url: nil, manual_password: nil)
59
- iterate(path) do |current|
60
- begin
61
- decrypt(path: current,
62
- password: manual_password || password(git_url))
63
- rescue
64
- UI.error("Couldn't decrypt the repo, please make sure you enter the right password!")
65
- UI.user_error!("Invalid password passed via 'MATCH_PASSWORD'") if ENV["MATCH_PASSWORD"]
66
- clear_password(git_url)
67
- decrypt_repo(path: path, git_url: git_url)
68
- return
69
- end
70
- UI.success("🔓 Decrypted '#{File.basename(current)}'") if FastlaneCore::Globals.verbose?
71
- end
72
- UI.success("🔓 Successfully decrypted certificates repo")
73
- end
74
-
75
- private
76
-
77
- def iterate(source_path)
78
- Dir[File.join(source_path, "**", "*.{cer,p12,mobileprovision}")].each do |path|
79
- next if File.directory?(path)
80
- yield(path)
81
- end
82
- end
83
-
84
- # We encrypt with MD5 because that was the most common default value in older fastlane versions which used the local OpenSSL installation
85
- # A more secure key and IV generation is needed in the future
86
- # IV should be randomly generated and provided unencrypted
87
- # salt should be randomly generated and provided unencrypted (like in the current implementation)
88
- # key should be generated with OpenSSL::KDF::pbkdf2_hmac with properly chosen parameters
89
- # Short explanation about salt and IV: https://stackoverflow.com/a/1950674/6324550
90
- def encrypt(path: nil, password: nil)
91
- UI.user_error!("No password supplied") if password.to_s.strip.length == 0
92
-
93
- data_to_encrypt = File.read(path)
94
- salt = SecureRandom.random_bytes(8)
95
-
96
- cipher = OpenSSL::Cipher.new('AES-256-CBC')
97
- cipher.encrypt
98
- cipher.pkcs5_keyivgen(password, salt, 1, "MD5")
99
- encrypted_data = "Salted__" + salt + cipher.update(data_to_encrypt) + cipher.final
100
-
101
- File.write(path, Base64.encode64(encrypted_data))
102
- rescue FastlaneCore::Interface::FastlaneError
103
- raise
104
- rescue => error
105
- UI.error(error.to_s)
106
- UI.crash!("Error encrypting '#{path}'")
107
- end
108
-
109
- # The encryption parameters in this implementations reflect the old behaviour which depended on the users' local OpenSSL version
110
- # 1.0.x OpenSSL and earlier versions use MD5, 1.1.0c and newer uses SHA256, we try both before giving an error
111
- def decrypt(path: nil, password: nil, hash_algorithm: "MD5")
112
- stored_data = Base64.decode64(File.read(path))
113
- salt = stored_data[8..15]
114
- data_to_decrypt = stored_data[16..-1]
115
-
116
- decipher = OpenSSL::Cipher.new('AES-256-CBC')
117
- decipher.decrypt
118
- decipher.pkcs5_keyivgen(password, salt, 1, hash_algorithm)
119
-
120
- decrypted_data = decipher.update(data_to_decrypt) + decipher.final
121
-
122
- File.binwrite(path, decrypted_data)
123
- rescue => error
124
- fallback_hash_algorithm = "SHA256"
125
- if hash_algorithm != fallback_hash_algorithm
126
- decrypt(path, password, fallback_hash_algorithm)
127
- else
128
- UI.error(error.to_s)
129
- UI.crash!("Error decrypting '#{path}'")
130
- end
131
- end
132
- end
133
- end
@@ -1,209 +0,0 @@
1
- require 'fastlane_core/command_executor'
2
- require_relative 'module'
3
- require_relative 'change_password'
4
- require_relative 'encrypt'
5
-
6
- module Match
7
- class GitHelper
8
- MATCH_VERSION_FILE_NAME = "match_version.txt"
9
-
10
- def self.clone(git_url,
11
- shallow_clone,
12
- manual_password: nil,
13
- skip_docs: false,
14
- branch: "master",
15
- git_full_name: nil,
16
- git_user_email: nil,
17
- clone_branch_directly: false)
18
- # Note: if you modify the parameters above, don't forget to also update the method call in
19
- # - runner.rb
20
- # - nuke.rb
21
- # - change_password.rb
22
- # - commands_generator.rb
23
- #
24
- return @dir if @dir
25
-
26
- @dir = Dir.mktmpdir
27
-
28
- command = "git clone '#{git_url}' '#{@dir}'"
29
- if shallow_clone
30
- command << " --depth 1 --no-single-branch"
31
- elsif clone_branch_directly
32
- command += " -b #{branch.shellescape} --single-branch"
33
- end
34
-
35
- UI.message("Cloning remote git repo...")
36
-
37
- if branch && !clone_branch_directly
38
- UI.message("If cloning the repo takes too long, you can use the `clone_branch_directly` option in match.")
39
- end
40
-
41
- begin
42
- # GIT_TERMINAL_PROMPT will fail the `git clone` command if user credentials are missing
43
- FastlaneCore::CommandExecutor.execute(command: "GIT_TERMINAL_PROMPT=0 #{command}",
44
- print_all: FastlaneCore::Globals.verbose?,
45
- print_command: FastlaneCore::Globals.verbose?)
46
- rescue
47
- UI.error("Error cloning certificates repo, please make sure you have read access to the repository you want to use")
48
- if branch && clone_branch_directly
49
- UI.error("You passed '#{branch}' as branch in combination with the `clone_branch_directly` flag. Please remove `clone_branch_directly` flag on the first run for _match_ to create the branch.")
50
- end
51
- UI.error("Run the following command manually to make sure you're properly authenticated:")
52
- UI.command(command)
53
- UI.user_error!("Error cloning certificates git repo, please make sure you have access to the repository - see instructions above")
54
- end
55
-
56
- add_user_config(git_full_name, git_user_email)
57
-
58
- UI.user_error!("Error cloning repo, make sure you have access to it '#{git_url}'") unless File.directory?(@dir)
59
-
60
- checkout_branch(branch) unless branch == "master"
61
-
62
- if !Helper.test? && GitHelper.match_version(@dir).nil? && manual_password.nil? && File.exist?(File.join(@dir, "README.md"))
63
- UI.important("Migrating to new match...")
64
- ChangePassword.update(params: { git_url: git_url,
65
- git_branch: branch,
66
- shallow_clone: shallow_clone },
67
- from: "",
68
- to: Encrypt.new.password(git_url))
69
- return self.clone(git_url, shallow_clone)
70
- end
71
-
72
- Encrypt.new.decrypt_repo(path: @dir, git_url: git_url, manual_password: manual_password)
73
-
74
- return @dir
75
- end
76
-
77
- def self.generate_commit_message(params)
78
- # 'Automatic commit via fastlane'
79
- [
80
- "[fastlane]",
81
- "Updated",
82
- params[:type].to_s,
83
- "and platform",
84
- params[:platform]
85
- ].join(" ")
86
- end
87
-
88
- def self.match_version(workspace)
89
- path = File.join(workspace, MATCH_VERSION_FILE_NAME)
90
- if File.exist?(path)
91
- Gem::Version.new(File.read(path))
92
- end
93
- end
94
-
95
- def self.commit_changes(path, message, git_url, branch = "master", files_to_commmit = nil)
96
- files_to_commmit ||= []
97
- Dir.chdir(path) do
98
- return if `git status`.include?("nothing to commit")
99
-
100
- Encrypt.new.encrypt_repo(path: path, git_url: git_url)
101
- commands = []
102
-
103
- if files_to_commmit.count > 0 # e.g. for nuke this is treated differently
104
- if !File.exist?(MATCH_VERSION_FILE_NAME) || File.read(MATCH_VERSION_FILE_NAME) != Fastlane::VERSION.to_s
105
- files_to_commmit << MATCH_VERSION_FILE_NAME
106
- File.write(MATCH_VERSION_FILE_NAME, Fastlane::VERSION) # stored unencrypted
107
- end
108
-
109
- template = File.read("#{Match::ROOT}/lib/assets/READMETemplate.md")
110
- readme_path = "README.md"
111
- if !File.exist?(readme_path) || File.read(readme_path) != template
112
- files_to_commmit << readme_path
113
- File.write(readme_path, template)
114
- end
115
-
116
- # `git add` each file we want to commit
117
- # - Fixes https://github.com/fastlane/fastlane/issues/8917
118
- # - Fixes https://github.com/fastlane/fastlane/issues/8793
119
- # - Replaces, closes and fixes https://github.com/fastlane/fastlane/pull/8919
120
- commands += files_to_commmit.map do |current_file|
121
- "git add #{current_file.shellescape}"
122
- end
123
- else
124
- # No specific list given, e.g. this happens on `fastlane match nuke`
125
- # We just want to run `git add -A` to commit everything
126
- commands << "git add -A"
127
- end
128
- commands << "git commit -m #{message.shellescape}"
129
- commands << "GIT_TERMINAL_PROMPT=0 git push origin #{branch.shellescape}"
130
-
131
- UI.message("Pushing changes to remote git repo...")
132
-
133
- commands.each do |command|
134
- FastlaneCore::CommandExecutor.execute(command: command,
135
- print_all: FastlaneCore::Globals.verbose?,
136
- print_command: FastlaneCore::Globals.verbose?)
137
- end
138
- end
139
- FileUtils.rm_rf(path)
140
- @dir = nil
141
- rescue => ex
142
- UI.error("Couldn't commit or push changes back to git...")
143
- UI.error(ex)
144
- end
145
-
146
- def self.clear_changes
147
- return unless @dir
148
-
149
- FileUtils.rm_rf(@dir)
150
- @dir = nil
151
- end
152
-
153
- # Create and checkout an specific branch in the git repo
154
- def self.checkout_branch(branch)
155
- return unless @dir
156
-
157
- commands = []
158
- if branch_exists?(branch)
159
- # Checkout the branch if it already exists
160
- commands << "git checkout #{branch.shellescape}"
161
- else
162
- # If a new branch is being created, we create it as an 'orphan' to not inherit changes from the master branch.
163
- commands << "git checkout --orphan #{branch.shellescape}"
164
- # We also need to reset the working directory to not transfer any uncommitted changes to the new branch.
165
- commands << "git reset --hard"
166
- end
167
-
168
- UI.message("Checking out branch #{branch}...")
169
-
170
- Dir.chdir(@dir) do
171
- commands.each do |command|
172
- FastlaneCore::CommandExecutor.execute(command: command,
173
- print_all: FastlaneCore::Globals.verbose?,
174
- print_command: FastlaneCore::Globals.verbose?)
175
- end
176
- end
177
- end
178
-
179
- # Checks if a specific branch exists in the git repo
180
- def self.branch_exists?(branch)
181
- return unless @dir
182
-
183
- result = Dir.chdir(@dir) do
184
- FastlaneCore::CommandExecutor.execute(command: "git --no-pager branch --list origin/#{branch.shellescape} --no-color -r",
185
- print_all: FastlaneCore::Globals.verbose?,
186
- print_command: FastlaneCore::Globals.verbose?)
187
- end
188
- return !result.empty?
189
- end
190
-
191
- def self.add_user_config(user_name, user_email)
192
- # Add git config if needed
193
- commands = []
194
- commands << "git config user.name \"#{user_name}\"" unless user_name.nil?
195
- commands << "git config user.email \"#{user_email}\"" unless user_email.nil?
196
-
197
- return if commands.empty?
198
-
199
- UI.message("Add git user config to local git repo...")
200
- Dir.chdir(@dir) do
201
- commands.each do |command|
202
- FastlaneCore::CommandExecutor.execute(command: command,
203
- print_all: FastlaneCore::Globals.verbose?,
204
- print_command: FastlaneCore::Globals.verbose?)
205
- end
206
- end
207
- end
208
- end
209
- end