fastlane 2.105.2 → 2.106.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +73 -72
- data/bin/fastlane +24 -1
- data/deliver/lib/deliver/app_screenshot.rb +7 -0
- data/deliver/lib/deliver/submit_for_review.rb +14 -1
- data/fastlane/lib/fastlane/actions/docs/sync_code_signing.md +1 -1
- data/fastlane/lib/fastlane/actions/download_from_play_store.rb +61 -0
- data/fastlane/lib/fastlane/actions/modify_services.rb +7 -5
- data/fastlane/lib/fastlane/actions/push_to_git_remote.rb +2 -2
- data/fastlane/lib/fastlane/actions/register_device.rb +6 -4
- data/fastlane/lib/fastlane/actions/register_devices.rb +9 -8
- data/fastlane/lib/fastlane/actions/upload_to_play_store.rb +12 -0
- data/fastlane/lib/fastlane/helper/crashlytics_helper.rb +1 -1
- data/fastlane/lib/fastlane/version.rb +1 -1
- data/fastlane/swift/Deliverfile.swift +1 -1
- data/fastlane/swift/Fastlane.swift +24 -6
- data/fastlane/swift/Gymfile.swift +1 -1
- data/fastlane/swift/Matchfile.swift +1 -1
- data/fastlane/swift/MatchfileProtocol.swift +3 -4
- data/fastlane/swift/Precheckfile.swift +1 -1
- data/fastlane/swift/Scanfile.swift +1 -1
- data/fastlane/swift/Screengrabfile.swift +1 -1
- data/fastlane/swift/Snapshotfile.swift +1 -1
- data/fastlane_core/lib/fastlane_core.rb +0 -1
- data/fastlane_core/lib/fastlane_core/device_manager.rb +1 -1
- data/fastlane_core/lib/fastlane_core/helper.rb +1 -1
- data/fastlane_core/lib/fastlane_core/itunes_transporter.rb +13 -10
- data/frameit/lib/frameit/editor.rb +3 -2
- data/frameit/lib/frameit/frame_downloader.rb +1 -1
- data/match/lib/match.rb +2 -2
- data/match/lib/match/change_password.rb +31 -18
- data/match/lib/match/commands_generator.rb +20 -7
- data/match/lib/match/encryption.rb +18 -0
- data/match/lib/match/encryption/interface.rb +17 -0
- data/match/lib/match/encryption/openssl.rb +155 -0
- data/match/lib/match/generator.rb +5 -5
- data/match/lib/match/module.rb +5 -1
- data/match/lib/match/nuke.rb +33 -15
- data/match/lib/match/options.rb +11 -12
- data/match/lib/match/runner.rb +59 -38
- data/match/lib/match/storage.rb +16 -0
- data/match/lib/match/storage/git_storage.rb +228 -0
- data/match/lib/match/storage/interface.rb +50 -0
- data/snapshot/lib/snapshot/screenshot_rotate.rb +3 -8
- data/spaceship/README.md +10 -1
- data/spaceship/lib/spaceship/api/.DS_Store +0 -0
- data/spaceship/lib/spaceship/api/.base.rb.swp +0 -0
- data/spaceship/lib/spaceship/du/du_client.rb +2 -0
- data/spaceship/lib/spaceship/portal/device.rb +0 -1
- data/spaceship/lib/spaceship/portal/portal_client.rb +7 -1
- data/spaceship/lib/spaceship/spaceauth_runner.rb +1 -1
- data/spaceship/lib/spaceship/tunes/app_details.rb +1 -1
- data/spaceship/lib/spaceship/tunes/device_type.rb +1 -1
- metadata +46 -20
- data/fastlane_core/lib/fastlane_core/itunes_search_api.rb +0 -50
- data/match/lib/match/encrypt.rb +0 -133
- 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
|
data/match/lib/match/encrypt.rb
DELETED
@@ -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
|