fastlane-plugin-match_keystore 0.1.8 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5c402baf5a7ce59cb81cc842718697d1854539f5a9d9ea37f5005110a0b7a82
|
4
|
+
data.tar.gz: 4885c27fb682f3fb03b6e9e8c30406f1888365df72b6a9d5172b0650753f5cea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ccb884061e5c1a9151539fc4eaa4466a334e5ada0174b5fef38030bb386f82714a843e4ec0b06baea1245744c91dfffafd5aa5d808d0cdeb2c54d756743e6899
|
7
|
+
data.tar.gz: 763e7decf059eb0343e9dedf79f561b904ed24b7daa8a3be0bdbf6cc7ddbf51b86a02e991325ebe875f4fb29f7232859b78d7015f3c26c70c6149aa088d6b292
|
data/README.md
CHANGED
@@ -24,14 +24,18 @@ The keystore properties are encrypted with AES in order to secure sensitive data
|
|
24
24
|
|
25
25
|
```ruby
|
26
26
|
lane :release_and_sign do |options|
|
27
|
-
|
28
|
-
gradle(task: "clean")
|
27
|
+
gradle(task: "clean")
|
29
28
|
gradle(task: 'assemble', build_type: 'Release')
|
30
29
|
|
31
30
|
signed_apk_path = match_keystore(
|
32
31
|
git_url: "https://github.com/<GITHUB_USERNAME>/keystores.git", # Please use a private Git repository !
|
33
32
|
package_name: "com.your.package.name",
|
34
33
|
apk_path: "/app/build/outputs/apk/app-release.apk" # Or path without APK: /app/build/outputs/apk/
|
34
|
+
# Optional:
|
35
|
+
match_secret: "A-very-str0ng-password!", # The secret use to encrypt/decrypt Keystore passwords on Git repo (for CI)
|
36
|
+
existing_keystore: "assets/existing-keystore.jks", # Optional, if needed to import an existing keystore
|
37
|
+
override_keystore: true, # Optional, override an existing Keystore on Git repo
|
38
|
+
keystore_data: "assets/keystore.json" # Optional, all data required to create a new Keystore (use to bypass prompt)
|
35
39
|
)
|
36
40
|
|
37
41
|
# Return the path of signed APK (useful for other lanes such as `publish_to_firebase`, `upload_to_play_store`)
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'fastlane/action'
|
2
2
|
require 'fileutils'
|
3
3
|
require 'os'
|
4
|
+
require 'json'
|
5
|
+
require 'digest'
|
4
6
|
require_relative '../helper/match_keystore_helper'
|
5
7
|
|
6
8
|
module Fastlane
|
@@ -13,6 +15,17 @@ module Fastlane
|
|
13
15
|
|
14
16
|
class MatchKeystoreAction < Action
|
15
17
|
|
18
|
+
def self.to_md5(value)
|
19
|
+
hash_value = Digest::MD5.hexdigest value
|
20
|
+
hash_value
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.load_json(json_path)
|
24
|
+
file = File.read(json_path)
|
25
|
+
data_hash = JSON.parse(file)
|
26
|
+
data_hash
|
27
|
+
end
|
28
|
+
|
16
29
|
def self.load_properties(properties_filename)
|
17
30
|
properties = {}
|
18
31
|
File.open(properties_filename, 'r') do |properties_file|
|
@@ -42,7 +55,7 @@ module Fastlane
|
|
42
55
|
|
43
56
|
def self.get_build_tools
|
44
57
|
android_home = self.get_android_home()
|
45
|
-
build_tools_root = android_home
|
58
|
+
build_tools_root = File.join(android_home, '/build-tools')
|
46
59
|
|
47
60
|
sub_dirs = Dir.glob(File.join(build_tools_root, '*', ''))
|
48
61
|
build_tools_last_version = ''
|
@@ -52,46 +65,55 @@ module Fastlane
|
|
52
65
|
|
53
66
|
build_tools_last_version
|
54
67
|
end
|
68
|
+
|
69
|
+
def self.check_openssl_version
|
70
|
+
output = `openssl version`
|
71
|
+
if !output.start_with?("OpenSSL")
|
72
|
+
raise "Please install OpenSSL 1.1.1 at least https://www.openssl.org/"
|
73
|
+
end
|
74
|
+
UI.message("OpenSSL version: " + output.strip)
|
75
|
+
end
|
55
76
|
|
56
77
|
def self.gen_key(key_path, password)
|
57
|
-
`rm -f #{key_path}`
|
58
|
-
|
59
|
-
`echo "#{password}" | openssl dgst -sha512 | cut -c1-128 > #{key_path}`
|
60
|
-
else
|
61
|
-
`echo "#{password}" | openssl dgst -sha512 | awk '{print $2}' | cut -c1-128 > #{key_path}`
|
62
|
-
end
|
78
|
+
`rm -f '#{key_path}'`
|
79
|
+
`echo "#{password}" | openssl dgst -sha512 | awk '{print $2}' | cut -c1-128 > '#{key_path}'`
|
63
80
|
end
|
64
81
|
|
65
82
|
def self.encrypt_file(clear_file, encrypt_file, key_path)
|
66
|
-
`rm -f #{encrypt_file}`
|
67
|
-
`openssl enc -aes-256-cbc -salt -in #{clear_file} -out #{encrypt_file} -pass file
|
83
|
+
`rm -f '#{encrypt_file}'`
|
84
|
+
`openssl enc -aes-256-cbc -salt -pbkdf2 -in '#{clear_file}' -out '#{encrypt_file}' -pass file:'#{key_path}'`
|
68
85
|
end
|
69
86
|
|
70
87
|
def self.decrypt_file(encrypt_file, clear_file, key_path)
|
71
|
-
`rm -f #{clear_file}`
|
72
|
-
`openssl enc -d -aes-256-cbc -in #{encrypt_file} -out #{clear_file} -pass file
|
88
|
+
`rm -f '#{clear_file}'`
|
89
|
+
`openssl enc -d -aes-256-cbc -pbkdf2 -in '#{encrypt_file}' -out '#{clear_file}' -pass file:'#{key_path}'`
|
73
90
|
end
|
74
91
|
|
75
92
|
def self.sign_apk(apk_path, keystore_path, key_password, alias_name, alias_password, zip_align)
|
76
93
|
|
77
94
|
build_tools_path = self.get_build_tools()
|
95
|
+
UI.message("BUild tools path: #{build_tools_path}")
|
78
96
|
|
79
97
|
# https://developer.android.com/studio/command-line/zipalign
|
80
98
|
if zip_align == true
|
81
99
|
apk_path_aligned = apk_path.gsub(".apk", "-aligned.apk")
|
82
|
-
`rm -f #{apk_path_aligned}`
|
83
|
-
|
100
|
+
`rm -f '#{apk_path_aligned}'`
|
101
|
+
UI.message("Aligning APK (zipalign): #{apk_path_aligned}")
|
102
|
+
`#{build_tools_path}zipalign -f -c -v 4 '#{apk_path}' '#{apk_path_aligned}'`
|
84
103
|
else
|
104
|
+
UI.message("No zip align!")
|
85
105
|
apk_path_aligned = apk_path
|
86
106
|
end
|
107
|
+
apk_path_signed = apk_path.gsub(".apk", "-signed.apk")
|
108
|
+
apk_path_signed = apk_path_signed.gsub("unsigned", "")
|
109
|
+
apk_path_signed = apk_path_signed.gsub("--", "-")
|
87
110
|
|
88
111
|
# https://developer.android.com/studio/command-line/apksigner
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
`rm -f #{apk_path_aligned}`
|
112
|
+
`rm -f '#{apk_path_signed}'`
|
113
|
+
`#{build_tools_path}apksigner sign --ks '#{keystore_path}' --ks-key-alias '#{alias_name}' --ks-pass pass:'#{key_password}' --key-pass pass:'#{alias_password}' --v1-signing-enabled true --v2-signing-enabled true --out '#{apk_path_signed}' '#{apk_path_aligned}'`
|
114
|
+
|
115
|
+
`#{build_tools_path}apksigner verify '#{apk_path_signed}'`
|
116
|
+
`rm -f '#{apk_path_aligned}'`
|
95
117
|
|
96
118
|
apk_path_signed
|
97
119
|
end
|
@@ -103,6 +125,11 @@ module Fastlane
|
|
103
125
|
|
104
126
|
def self.resolve_apk_path(apk_path)
|
105
127
|
|
128
|
+
# Set default APK path if not set:
|
129
|
+
if apk_path.to_s.strip.empty?
|
130
|
+
apk_path = '/app/build/outputs/apk/'
|
131
|
+
end
|
132
|
+
|
106
133
|
if !apk_path.to_s.end_with?(".apk")
|
107
134
|
|
108
135
|
if !File.directory?(apk_path)
|
@@ -130,15 +157,28 @@ module Fastlane
|
|
130
157
|
apk_path
|
131
158
|
end
|
132
159
|
|
160
|
+
def self.prompt2(params)
|
161
|
+
# UI.message("prompt2: #{params[:value]}")
|
162
|
+
if params[:value].to_s.empty?
|
163
|
+
return_value = other_action.prompt(text: params[:text], secure_text: params[:secure_text], ci_input: params[:ci_input])
|
164
|
+
else
|
165
|
+
return_value = params[:value]
|
166
|
+
end
|
167
|
+
return_value
|
168
|
+
end
|
169
|
+
|
133
170
|
def self.run(params)
|
134
171
|
|
172
|
+
# Get input parameters:
|
135
173
|
git_url = params[:git_url]
|
136
174
|
package_name = params[:package_name]
|
137
175
|
apk_path = params[:apk_path]
|
138
176
|
existing_keystore = params[:existing_keystore]
|
139
|
-
|
177
|
+
match_secret = params[:match_secret]
|
140
178
|
override_keystore = params[:override_keystore]
|
179
|
+
keystore_data = params[:keystore_data]
|
141
180
|
|
181
|
+
# Init constants:
|
142
182
|
keystore_name = 'keystore.jks'
|
143
183
|
properties_name = 'keystore.properties'
|
144
184
|
keystore_info_name = 'keystore.txt'
|
@@ -151,56 +191,86 @@ module Fastlane
|
|
151
191
|
raise "The environment variable ANDROID_HOME is not defined, or Android SDK is not installed!"
|
152
192
|
end
|
153
193
|
|
194
|
+
# Check OpenSSL:
|
195
|
+
self.check_openssl_version
|
196
|
+
|
197
|
+
# Init workign local directory:
|
154
198
|
dir_name = ENV['HOME'] + '/.match_keystore'
|
155
199
|
unless File.directory?(dir_name)
|
156
200
|
UI.message("Creating '.match_keystore' working directory...")
|
157
201
|
FileUtils.mkdir_p(dir_name)
|
158
202
|
end
|
159
203
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
204
|
+
# Init 'security password' for AES encryption:
|
205
|
+
key_name = "#{self.to_md5(git_url)}.hex"
|
206
|
+
key_path = File.join(dir_name, key_name)
|
207
|
+
# UI.message(key_path)
|
164
208
|
if !File.file?(key_path)
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
security_password = ci_password
|
209
|
+
security_password = self.prompt2(text: "Security password: ", secure_text: true, value: match_secret)
|
210
|
+
if security_password.to_s.strip.empty?
|
211
|
+
raise "Security password is not defined! Please use 'match_secret' parameter for CI."
|
169
212
|
end
|
170
|
-
UI.message "Generating security key..."
|
213
|
+
UI.message "Generating security key '#{key_name}'..."
|
171
214
|
self.gen_key(key_path, security_password)
|
172
215
|
end
|
173
216
|
|
217
|
+
# Check is 'security password' is well initialized:
|
174
218
|
tmpkey = self.get_file_content(key_path).strip
|
175
219
|
if tmpkey.length == 128
|
176
|
-
UI.message "Security key initialized"
|
220
|
+
UI.message "Security key '#{key_name}' initialized"
|
177
221
|
else
|
178
|
-
raise "The security key
|
222
|
+
raise "The security key '#{key_name}' is malformed, or not initialized!"
|
179
223
|
end
|
180
224
|
|
181
|
-
|
225
|
+
# Create repo directory to sync remote Keystores repository:
|
226
|
+
repo_dir = File.join(dir_name, self.to_md5(git_url))
|
227
|
+
# UI.message(repo_dir)
|
182
228
|
unless File.directory?(repo_dir)
|
183
229
|
UI.message("Creating 'repo' directory...")
|
184
230
|
FileUtils.mkdir_p(repo_dir)
|
185
231
|
end
|
186
232
|
|
187
|
-
|
188
|
-
|
233
|
+
# Cloning/pulling GIT remote repository:
|
234
|
+
gitDir = File.join(repo_dir, '/.git')
|
235
|
+
if !File.directory?(gitDir)
|
189
236
|
UI.message("Cloning remote Keystores repository...")
|
190
237
|
puts ''
|
191
238
|
`git clone #{git_url} #{repo_dir}`
|
192
239
|
puts ''
|
240
|
+
else
|
241
|
+
UI.message("Pulling remote Keystores repository...")
|
242
|
+
puts ''
|
243
|
+
`cd #{repo_dir} && git pull`
|
244
|
+
puts ''
|
193
245
|
end
|
194
246
|
|
195
|
-
|
247
|
+
# Create sub-directory for Android app:
|
248
|
+
if package_name.to_s.strip.empty?
|
249
|
+
raise "Package name is not defined!"
|
250
|
+
end
|
251
|
+
keystoreAppDir = File.join(repo_dir, package_name)
|
196
252
|
unless File.directory?(keystoreAppDir)
|
197
253
|
UI.message("Creating '#{package_name}' keystore directory...")
|
198
254
|
FileUtils.mkdir_p(keystoreAppDir)
|
199
255
|
end
|
200
256
|
|
201
|
-
keystore_path = keystoreAppDir
|
202
|
-
properties_path = keystoreAppDir
|
203
|
-
properties_encrypt_path = keystoreAppDir
|
257
|
+
keystore_path = File.join(keystoreAppDir, keystore_name)
|
258
|
+
properties_path = File.join(keystoreAppDir, properties_name)
|
259
|
+
properties_encrypt_path = File.join(keystoreAppDir, properties_encrypt_name)
|
260
|
+
|
261
|
+
# Load parameters from JSON for CI or Unit Tests:
|
262
|
+
if keystore_data != nil && File.file?(keystore_data)
|
263
|
+
data_json = self.load_json(keystore_data)
|
264
|
+
data_key_password = data_json['key_password']
|
265
|
+
data_alias_name = data_json['alias_name']
|
266
|
+
data_alias_password = data_json['alias_password']
|
267
|
+
data_full_name = data_json['full_name']
|
268
|
+
data_org_unit = data_json['org_unit']
|
269
|
+
data_org = data_json['org']
|
270
|
+
data_city_locality = data_json['city_locality']
|
271
|
+
data_state_province = data_json['state_province']
|
272
|
+
data_country = data_json['country']
|
273
|
+
end
|
204
274
|
|
205
275
|
# Create keystore with command
|
206
276
|
override_keystore = !existing_keystore.to_s.strip.empty? && File.file?(existing_keystore)
|
@@ -210,28 +280,37 @@ module Fastlane
|
|
210
280
|
FileUtils.remove_dir(keystore_path)
|
211
281
|
end
|
212
282
|
|
213
|
-
key_password =
|
214
|
-
|
215
|
-
|
283
|
+
key_password = self.prompt2(text: "Keystore Password: ", value: data_key_password)
|
284
|
+
if key_password.to_s.strip.empty?
|
285
|
+
raise "Keystore Password is not definined!"
|
286
|
+
end
|
287
|
+
alias_name = self.prompt2(text: "Keystore Alias name: ", value: data_alias_name)
|
288
|
+
if alias_name.to_s.strip.empty?
|
289
|
+
raise "Keystore Alias name is not definined!"
|
290
|
+
end
|
291
|
+
alias_password = self.prompt2(text: "Keystore Alias password: ", value: data_alias_password)
|
292
|
+
if alias_password.to_s.strip.empty?
|
293
|
+
raise "Keystore Alias password is not definined!"
|
294
|
+
end
|
216
295
|
|
217
296
|
# https://developer.android.com/studio/publish/app-signing
|
218
297
|
if !File.file?(existing_keystore)
|
219
298
|
UI.message("Generating Android Keystore...")
|
220
299
|
|
221
|
-
full_name =
|
222
|
-
org_unit =
|
223
|
-
org =
|
224
|
-
city_locality =
|
225
|
-
state_province =
|
226
|
-
country =
|
300
|
+
full_name = self.prompt2(text: "Certificate First and Last Name: ", value: data_full_name)
|
301
|
+
org_unit = self.prompt2(text: "Certificate Organisation Unit: ", value: data_org_unit)
|
302
|
+
org = self.prompt2(text: "Certificate Organisation: ", value: data_org)
|
303
|
+
city_locality = self.prompt2(text: "Certificate City or Locality: ", value: data_city_locality)
|
304
|
+
state_province = self.prompt2(text: "Certificate State or Province: ", value: data_state_province)
|
305
|
+
country = self.prompt2(text: "Certificate Country Code (XX): ", value: data_country)
|
227
306
|
|
228
307
|
keytool_parts = [
|
229
308
|
"keytool -genkey -v",
|
230
|
-
"-keystore #{keystore_path}",
|
231
|
-
"-alias #{alias_name}",
|
309
|
+
"-keystore '#{keystore_path}'",
|
310
|
+
"-alias '#{alias_name}'",
|
232
311
|
"-keyalg RSA -keysize 2048 -validity 10000",
|
233
|
-
"-storepass #{alias_password}
|
234
|
-
"-keypass #{key_password}",
|
312
|
+
"-storepass '#{alias_password}'",
|
313
|
+
"-keypass '#{key_password}'",
|
235
314
|
"-dname \"CN=#{full_name}, OU=#{org_unit}, O=#{org}, L=#{city_locality}, S=#{state_province}, C=#{country}\"",
|
236
315
|
]
|
237
316
|
sh keytool_parts.join(" ")
|
@@ -246,6 +325,7 @@ module Fastlane
|
|
246
325
|
FileUtils.remove_dir(properties_path)
|
247
326
|
end
|
248
327
|
|
328
|
+
# Build URL:
|
249
329
|
store_file = git_url + '/' + package_name + '/' + keystore_name
|
250
330
|
|
251
331
|
out_file = File.new(properties_path, "w")
|
@@ -259,15 +339,17 @@ module Fastlane
|
|
259
339
|
File.delete(properties_path)
|
260
340
|
|
261
341
|
# Print Keystore data in repo:
|
262
|
-
keystore_info_path = keystoreAppDir
|
263
|
-
`yes "" | keytool -list -v -keystore #{keystore_path} > #{keystore_info_path}`
|
342
|
+
keystore_info_path = File.join(keystoreAppDir, keystore_info_name)
|
343
|
+
`yes "" | keytool -list -v -keystore '#{keystore_path}' -storepass '#{key_password}' > '#{keystore_info_path}'`
|
264
344
|
|
265
345
|
UI.message("Upload new Keystore to remote repository...")
|
266
|
-
|
267
|
-
`cd #{repo_dir} && git
|
268
|
-
`cd #{repo_dir} && git
|
346
|
+
puts ''
|
347
|
+
`cd '#{repo_dir}' && git add .`
|
348
|
+
`cd '#{repo_dir}' && git commit -m "[ADD] Keystore for app '#{package_name}'."`
|
349
|
+
`cd '#{repo_dir}' && git push`
|
350
|
+
puts ''
|
269
351
|
|
270
|
-
else
|
352
|
+
else
|
271
353
|
UI.message "Keystore file already exists, continue..."
|
272
354
|
|
273
355
|
self.decrypt_file(properties_encrypt_path, properties_path, key_path)
|
@@ -278,37 +360,40 @@ module Fastlane
|
|
278
360
|
alias_password = properties['aliasPassword']
|
279
361
|
|
280
362
|
File.delete(properties_path)
|
281
|
-
|
282
363
|
end
|
283
364
|
|
365
|
+
# Resolve path to the APK to sign:
|
284
366
|
output_signed_apk = ''
|
285
367
|
apk_path = self.resolve_apk_path(apk_path)
|
286
368
|
|
369
|
+
# Sign APK:
|
287
370
|
if File.file?(apk_path)
|
288
371
|
UI.message("APK to sign: " + apk_path)
|
289
372
|
|
290
373
|
if File.file?(keystore_path)
|
291
374
|
|
292
375
|
UI.message("Signing the APK...")
|
376
|
+
puts ''
|
293
377
|
output_signed_apk = self.sign_apk(
|
294
378
|
apk_path,
|
295
379
|
keystore_path,
|
296
380
|
key_password,
|
297
381
|
alias_name,
|
298
382
|
alias_password,
|
299
|
-
true
|
383
|
+
true # Zip align
|
300
384
|
)
|
385
|
+
puts ''
|
301
386
|
end
|
302
387
|
else
|
303
388
|
UI.message("No APK file found to sign!")
|
304
389
|
end
|
305
390
|
|
391
|
+
# Prepare contect shared values for next lanes:
|
306
392
|
Actions.lane_context[SharedValues::MATCH_KEYSTORE_PATH] = keystore_path
|
307
393
|
Actions.lane_context[SharedValues::MATCH_KEYSTORE_ALIAS_NAME] = alias_name
|
308
394
|
Actions.lane_context[SharedValues::MATCH_KEYSTORE_APK_SIGNED] = output_signed_apk
|
309
395
|
|
310
396
|
output_signed_apk
|
311
|
-
|
312
397
|
end
|
313
398
|
|
314
399
|
def self.description
|
@@ -326,7 +411,8 @@ module Fastlane
|
|
326
411
|
def self.output
|
327
412
|
[
|
328
413
|
['MATCH_KEYSTORE_PATH', 'File path of the Keystore fot the App.'],
|
329
|
-
['MATCH_KEYSTORE_ALIAS_NAME', 'Keystore Alias Name.']
|
414
|
+
['MATCH_KEYSTORE_ALIAS_NAME', 'Keystore Alias Name.'],
|
415
|
+
['MATCH_KEYSTORE_APK_SIGNED', 'Path of the signed APK.']
|
330
416
|
]
|
331
417
|
end
|
332
418
|
|
@@ -350,11 +436,11 @@ module Fastlane
|
|
350
436
|
FastlaneCore::ConfigItem.new(key: :apk_path,
|
351
437
|
env_name: "MATCH_KEYSTORE_APK_PATH",
|
352
438
|
description: "Path of the APK file to sign",
|
353
|
-
optional:
|
439
|
+
optional: true,
|
354
440
|
type: String),
|
355
|
-
FastlaneCore::ConfigItem.new(key: :
|
356
|
-
env_name: "
|
357
|
-
description: "
|
441
|
+
FastlaneCore::ConfigItem.new(key: :match_secret,
|
442
|
+
env_name: "MATCH_KEYSTORE_SECRET",
|
443
|
+
description: "Secret to decrypt keystore.properties file (CI)",
|
358
444
|
optional: true,
|
359
445
|
type: String),
|
360
446
|
FastlaneCore::ConfigItem.new(key: :existing_keystore,
|
@@ -366,7 +452,12 @@ module Fastlane
|
|
366
452
|
env_name: "MATCH_KEYSTORE_OVERRIDE",
|
367
453
|
description: "Override an existing Keystore (false by default)",
|
368
454
|
optional: true,
|
369
|
-
type: Boolean)
|
455
|
+
type: Boolean),
|
456
|
+
FastlaneCore::ConfigItem.new(key: :keystore_data,
|
457
|
+
env_name: "MATCH_KEYSTORE_JSON_PATH",
|
458
|
+
description: "Required data to import an existing keystore, or create a new one",
|
459
|
+
optional: true,
|
460
|
+
type: String)
|
370
461
|
]
|
371
462
|
end
|
372
463
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-match_keystore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher NEY
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: pry
|
@@ -167,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
167
167
|
- !ruby/object:Gem::Version
|
168
168
|
version: '0'
|
169
169
|
requirements: []
|
170
|
-
rubygems_version: 3.0.
|
170
|
+
rubygems_version: 3.0.3
|
171
171
|
signing_key:
|
172
172
|
specification_version: 4
|
173
173
|
summary: Easily sync your Android keystores across your team
|