spaceship 0.23.0 → 0.24.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/spaceship/client.rb +35 -7
- data/lib/spaceship/du/du_client.rb +1 -1
- data/lib/spaceship/du/upload_file.rb +1 -1
- data/lib/spaceship/portal/certificate.rb +2 -2
- data/lib/spaceship/tunes/app_submission.rb +4 -4
- data/lib/spaceship/tunes/app_trailer.rb +2 -4
- data/lib/spaceship/tunes/app_version.rb +3 -3
- data/lib/spaceship/tunes/application.rb +24 -5
- data/lib/spaceship/tunes/build.rb +24 -6
- data/lib/spaceship/tunes/build_details.rb +48 -0
- data/lib/spaceship/tunes/build_train.rb +3 -3
- data/lib/spaceship/tunes/language_item.rb +1 -1
- data/lib/spaceship/tunes/tunes.rb +1 -0
- data/lib/spaceship/tunes/tunes_client.rb +18 -2
- data/lib/spaceship/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a0aa9495ff354e1c5b95d9e37c3d4bbe3fdd78a
|
4
|
+
data.tar.gz: 4cb03238dd2e3c63d807b8b15e989b818845f6b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 178f2df3b4200629861bccb40f34ad30c6abb5510caa70eb7d55b4a5a670b2b60fa3d7edc1d1a104b884f30fc735e7ec22c09d564887be0c5d77a033ad5ed0e4
|
7
|
+
data.tar.gz: e3a7a94c04cdb8c41a1545b6e477306befe94d8007c1f567a9a9391ad9422af66f649b87719afa46244ee8a3adfe4e739ead4b8c20f99f8c4307609404083b0d
|
data/lib/spaceship/client.rb
CHANGED
@@ -28,19 +28,47 @@ module Spaceship
|
|
28
28
|
# /tmp/spaceship[time]_[pid].log by default
|
29
29
|
attr_accessor :logger
|
30
30
|
|
31
|
+
# Base class for errors that want to present their message as
|
32
|
+
# preferred error info for fastlane error handling. See:
|
33
|
+
# fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb
|
34
|
+
class BasicPreferredInfoError < StandardError
|
35
|
+
TITLE = 'The request could not be completed because:'.freeze
|
36
|
+
|
37
|
+
def preferred_error_info
|
38
|
+
message ? [TITLE, message] : nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
31
42
|
# Invalid user credentials were provided
|
32
|
-
class InvalidUserCredentialsError <
|
43
|
+
class InvalidUserCredentialsError < BasicPreferredInfoError; end
|
33
44
|
|
34
45
|
# Raised when no user credentials were passed at all
|
35
|
-
class NoUserCredentialsError <
|
46
|
+
class NoUserCredentialsError < BasicPreferredInfoError; end
|
36
47
|
|
37
|
-
class UnexpectedResponse < StandardError
|
48
|
+
class UnexpectedResponse < StandardError
|
49
|
+
attr_reader :error_info
|
50
|
+
|
51
|
+
def initialize(error_info = nil)
|
52
|
+
super(error_info)
|
53
|
+
@error_info = error_info
|
54
|
+
end
|
55
|
+
|
56
|
+
def preferred_error_info
|
57
|
+
return nil unless @error_info.kind_of?(Hash) && @error_info['resultString']
|
58
|
+
|
59
|
+
[
|
60
|
+
"Apple provided the following error info:",
|
61
|
+
@error_info['resultString'],
|
62
|
+
@error_info['userString']
|
63
|
+
].compact.uniq # sometimes 'resultString' and 'userString' are the same value
|
64
|
+
end
|
65
|
+
end
|
38
66
|
|
39
67
|
# Raised when 302 is received from portal request
|
40
|
-
class AppleTimeoutError <
|
68
|
+
class AppleTimeoutError < BasicPreferredInfoError; end
|
41
69
|
|
42
70
|
# Raised when 401 is received from portal request
|
43
|
-
class UnauthorizedAccessError <
|
71
|
+
class UnauthorizedAccessError < BasicPreferredInfoError; end
|
44
72
|
|
45
73
|
# Authenticates with Apple's web services. This method has to be called once
|
46
74
|
# to generate a valid session. The session will automatically be used from then
|
@@ -238,7 +266,7 @@ module Spaceship
|
|
238
266
|
|
239
267
|
def request(method, url_or_path = nil, params = nil, headers = {}, &block)
|
240
268
|
headers.merge!(csrf_tokens)
|
241
|
-
headers
|
269
|
+
headers['User-Agent'] = USER_AGENT
|
242
270
|
|
243
271
|
# Before encoding the parameters, log them
|
244
272
|
log_request(method, url_or_path, params)
|
@@ -297,7 +325,7 @@ module Spaceship
|
|
297
325
|
end
|
298
326
|
|
299
327
|
if content.nil?
|
300
|
-
raise UnexpectedResponse.new
|
328
|
+
raise UnexpectedResponse.new(response.body)
|
301
329
|
else
|
302
330
|
store_csrf_tokens(response)
|
303
331
|
content
|
@@ -62,7 +62,7 @@ module Spaceship
|
|
62
62
|
req.headers['X-Apple-Upload-ContentProviderId'] = content_provider_id
|
63
63
|
req.headers['X-Original-Filename'] = upload_file.file_name
|
64
64
|
req.headers['X-Apple-Upload-Validation-RuleSets'] = du_validation_rule_set if du_validation_rule_set
|
65
|
-
req.headers['Content-Length'] =
|
65
|
+
req.headers['Content-Length'] = upload_file.file_size.to_s
|
66
66
|
req.headers['Connection'] = "keep-alive"
|
67
67
|
end
|
68
68
|
|
@@ -12,7 +12,7 @@ module Spaceship
|
|
12
12
|
class << self
|
13
13
|
def from_path(path)
|
14
14
|
raise "Image must exists at path: #{path}" unless File.exist?(path)
|
15
|
-
path = remove_alpha_channel(path) if File.extname(path).
|
15
|
+
path = remove_alpha_channel(path) if File.extname(path).casecmp('.png').zero?
|
16
16
|
|
17
17
|
content_type = Utilities.content_type(path)
|
18
18
|
self.new(
|
@@ -193,8 +193,8 @@ module Spaceship
|
|
193
193
|
csr = OpenSSL::X509::Request.new
|
194
194
|
csr.version = 0
|
195
195
|
csr.subject = OpenSSL::X509::Name.new([
|
196
|
-
|
197
|
-
|
196
|
+
['CN', 'PEM', OpenSSL::ASN1::UTF8STRING]
|
197
|
+
])
|
198
198
|
csr.public_key = key.public_key
|
199
199
|
csr.sign(key, OpenSSL::Digest::SHA1.new)
|
200
200
|
return [csr, key]
|
@@ -98,14 +98,14 @@ module Spaceship
|
|
98
98
|
def factory(attrs)
|
99
99
|
# fill content rights section if iTC returns nil
|
100
100
|
if attrs["contentRights"].nil?
|
101
|
-
attrs
|
101
|
+
attrs["contentRights"] = {
|
102
102
|
"containsThirdPartyContent" => {
|
103
103
|
"value" => nil
|
104
104
|
},
|
105
105
|
"hasRights" => {
|
106
106
|
"value" => nil
|
107
107
|
}
|
108
|
-
}
|
108
|
+
}
|
109
109
|
end
|
110
110
|
|
111
111
|
obj = self.new(attrs)
|
@@ -115,8 +115,8 @@ module Spaceship
|
|
115
115
|
# @param application (Spaceship::Tunes::Application) The app this submission is for
|
116
116
|
def create(application, version)
|
117
117
|
attrs = client.prepare_app_submissions(application.apple_id, application.edit_version.version_id)
|
118
|
-
attrs
|
119
|
-
attrs
|
118
|
+
attrs[:application] = application
|
119
|
+
attrs[:version] = version
|
120
120
|
|
121
121
|
return self.factory(attrs)
|
122
122
|
end
|
@@ -40,8 +40,7 @@ module Spaceship
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def reset!(attrs = {})
|
43
|
-
update_raw_data!
|
44
|
-
({
|
43
|
+
update_raw_data!({
|
45
44
|
video_asset_token: nil,
|
46
45
|
picture_asset_token: nil,
|
47
46
|
descriptionXML: nil,
|
@@ -54,8 +53,7 @@ module Spaceship
|
|
54
53
|
video_status: nil,
|
55
54
|
device_type: nil,
|
56
55
|
language: nil
|
57
|
-
}.merge(attrs)
|
58
|
-
)
|
56
|
+
}.merge(attrs))
|
59
57
|
end
|
60
58
|
|
61
59
|
private
|
@@ -168,8 +168,8 @@ module Spaceship
|
|
168
168
|
attrs = client.app_version(app_id, is_live)
|
169
169
|
return nil unless attrs
|
170
170
|
|
171
|
-
attrs
|
172
|
-
attrs
|
171
|
+
attrs[:application] = application
|
172
|
+
attrs[:is_live] = is_live
|
173
173
|
|
174
174
|
return self.factory(attrs)
|
175
175
|
end
|
@@ -429,7 +429,7 @@ module Spaceship
|
|
429
429
|
|
430
430
|
trailer.merge!({
|
431
431
|
"pictureAssetToken" => video_preview_data["token"],
|
432
|
-
"previewFrameTimeCode" =>
|
432
|
+
"previewFrameTimeCode" => ts.to_s,
|
433
433
|
"isPortrait" => Utilities.portrait?(video_preview_path)
|
434
434
|
})
|
435
435
|
else # removing trailer
|
@@ -130,7 +130,7 @@ module Spaceship
|
|
130
130
|
|
131
131
|
def details
|
132
132
|
attrs = client.app_details(apple_id)
|
133
|
-
attrs
|
133
|
+
attrs[:application] = self
|
134
134
|
Tunes::AppDetails.factory(attrs)
|
135
135
|
end
|
136
136
|
|
@@ -138,7 +138,7 @@ module Spaceship
|
|
138
138
|
ensure_not_a_bundle
|
139
139
|
versions = client.versions_history(apple_id, platform)
|
140
140
|
versions.map do |attrs|
|
141
|
-
attrs
|
141
|
+
attrs[:application] = self
|
142
142
|
Tunes::AppVersionHistory.factory(attrs)
|
143
143
|
end
|
144
144
|
end
|
@@ -193,12 +193,31 @@ module Spaceship
|
|
193
193
|
# @!group Builds
|
194
194
|
#####################################################
|
195
195
|
|
196
|
-
# A reference to all the build trains
|
196
|
+
# TestFlight: A reference to all the build trains
|
197
197
|
# @return [Hash] a hash, the version number being the key
|
198
198
|
def build_trains
|
199
199
|
Tunes::BuildTrain.all(self, self.apple_id)
|
200
200
|
end
|
201
201
|
|
202
|
+
# The numbers of all build trains that were uploaded
|
203
|
+
# @return [Array] An array of train version numbers
|
204
|
+
def all_build_train_numbers
|
205
|
+
client.all_build_trains(app_id: self.apple_id).fetch("trains").collect do |current|
|
206
|
+
current["versionString"]
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Receive the build details for a specific build
|
211
|
+
# useful if the app is not listed in the TestFlight build list
|
212
|
+
# which might happen if you don't use TestFlight
|
213
|
+
# This is used to receive dSYM files from Apple
|
214
|
+
def all_builds_for_train(train: nil)
|
215
|
+
client.all_builds_for_train(app_id: self.apple_id, train: train).fetch("items", []).collect do |attrs|
|
216
|
+
attrs[:apple_id] = self.apple_id
|
217
|
+
Tunes::Build.factory(attrs)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
202
221
|
# @return [Array]A list of binaries which are not even yet processing based on the version
|
203
222
|
# These are all build that have no information except the upload date
|
204
223
|
# Those builds can also be the builds that are stuck on iTC.
|
@@ -206,7 +225,7 @@ module Spaceship
|
|
206
225
|
data = client.build_trains(apple_id, 'internal') # we need to fetch all trains here to get the builds
|
207
226
|
|
208
227
|
builds = data.fetch('processingBuilds', []).collect do |attrs|
|
209
|
-
attrs
|
228
|
+
attrs[:build_train] = self
|
210
229
|
Tunes::ProcessingBuild.factory(attrs)
|
211
230
|
end
|
212
231
|
|
@@ -220,7 +239,7 @@ module Spaceship
|
|
220
239
|
data = client.build_trains(apple_id, 'internal') # we need to fetch all trains here to get the builds
|
221
240
|
|
222
241
|
builds = data.fetch('processingBuilds', []).collect do |attrs|
|
223
|
-
attrs
|
242
|
+
attrs[:build_train] = self
|
224
243
|
Tunes::ProcessingBuild.factory(attrs)
|
225
244
|
end
|
226
245
|
|
@@ -6,6 +6,11 @@ module Spaceship
|
|
6
6
|
# @!group General metadata
|
7
7
|
#####################################################
|
8
8
|
|
9
|
+
# @return (String) The App identifier of this app, provided by iTunes Connect
|
10
|
+
# @example
|
11
|
+
# "1013943394"
|
12
|
+
attr_accessor :apple_id
|
13
|
+
|
9
14
|
# @return (Spaceship::Tunes::BuildTrain) A reference to the build train this build is contained in
|
10
15
|
attr_accessor :build_train
|
11
16
|
|
@@ -112,12 +117,25 @@ module Spaceship
|
|
112
117
|
self.internal_expiry_date ||= 0
|
113
118
|
end
|
114
119
|
|
120
|
+
def details
|
121
|
+
response = client.build_details(app_id: self.apple_id,
|
122
|
+
train: self.train_version,
|
123
|
+
build_number: self.build_version)
|
124
|
+
response['apple_id'] = self.apple_id
|
125
|
+
BuildDetails.factory(response)
|
126
|
+
end
|
127
|
+
|
128
|
+
def apple_id
|
129
|
+
return @apple_id if @apple_id
|
130
|
+
return self.build_train.application.apple_id
|
131
|
+
end
|
132
|
+
|
115
133
|
def update_build_information!(whats_new: nil,
|
116
134
|
description: nil,
|
117
135
|
feedback_email: nil)
|
118
136
|
parameters = {
|
119
|
-
app_id: self.
|
120
|
-
train: self.
|
137
|
+
app_id: self.apple_id,
|
138
|
+
train: self.train_version,
|
121
139
|
build_number: self.build_version,
|
122
140
|
platform: self.platform
|
123
141
|
}.merge({
|
@@ -150,8 +168,8 @@ module Spaceship
|
|
150
168
|
# }
|
151
169
|
def submit_for_beta_review!(metadata)
|
152
170
|
parameters = {
|
153
|
-
app_id: self.
|
154
|
-
train: self.
|
171
|
+
app_id: self.apple_id,
|
172
|
+
train: self.train_version,
|
155
173
|
build_number: self.build_version,
|
156
174
|
platform: self.platform,
|
157
175
|
|
@@ -199,8 +217,8 @@ module Spaceship
|
|
199
217
|
|
200
218
|
# This will cancel the review process for this TestFlight build
|
201
219
|
def cancel_beta_review!
|
202
|
-
client.remove_testflight_build_from_review!(app_id: self.
|
203
|
-
train: self.
|
220
|
+
client.remove_testflight_build_from_review!(app_id: self.apple_id,
|
221
|
+
train: self.train_version,
|
204
222
|
build_number: self.build_version,
|
205
223
|
platform: self.platform)
|
206
224
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Spaceship
|
2
|
+
module Tunes
|
3
|
+
# Represents the details of a build
|
4
|
+
class BuildDetails < TunesBase
|
5
|
+
# @return (String) The App identifier of this app, provided by iTunes Connect
|
6
|
+
# @example
|
7
|
+
# "1013943394"
|
8
|
+
attr_accessor :apple_id
|
9
|
+
|
10
|
+
# @return (String) Link to the dSYM file (not always available)
|
11
|
+
# lol, it's unencrypted http
|
12
|
+
attr_accessor :dsym_url
|
13
|
+
|
14
|
+
# @return [Bool]
|
15
|
+
attr_accessor :include_symbols
|
16
|
+
|
17
|
+
# @return [Integer]
|
18
|
+
attr_accessor :number_of_asset_packs
|
19
|
+
|
20
|
+
# @return [Bool]
|
21
|
+
attr_accessor :contains_odr
|
22
|
+
|
23
|
+
# e.g. "13A340"
|
24
|
+
attr_accessor :build_sdk
|
25
|
+
|
26
|
+
# @return [String] e.g. "MyApp.ipa"
|
27
|
+
attr_accessor :file_name
|
28
|
+
|
29
|
+
attr_mapping(
|
30
|
+
'apple_id' => :apple_id,
|
31
|
+
'dsymurl' => :dsym_url,
|
32
|
+
'includesSymbols' => :include_symbols,
|
33
|
+
'numberOfAssetPacks' => :number_of_asset_packs,
|
34
|
+
'containsODR' => :contains_odr,
|
35
|
+
'buildSdk' => :build_sdk,
|
36
|
+
'fileName' => :file_name
|
37
|
+
)
|
38
|
+
|
39
|
+
class << self
|
40
|
+
# Create a new object based on a hash.
|
41
|
+
# This is used to create a new object based on the server response.
|
42
|
+
def factory(attrs)
|
43
|
+
self.new(attrs)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -49,7 +49,7 @@ module Spaceship
|
|
49
49
|
|
50
50
|
result = {}
|
51
51
|
trains.each do |attrs|
|
52
|
-
attrs
|
52
|
+
attrs[:application] = application
|
53
53
|
current = self.factory(attrs)
|
54
54
|
result[current.version_string] = current
|
55
55
|
end
|
@@ -62,12 +62,12 @@ module Spaceship
|
|
62
62
|
super
|
63
63
|
|
64
64
|
@builds = (self.raw_data['builds'] || []).collect do |attrs|
|
65
|
-
attrs
|
65
|
+
attrs[:build_train] = self
|
66
66
|
Tunes::Build.factory(attrs)
|
67
67
|
end
|
68
68
|
|
69
69
|
@processing_builds = (self.raw_data['buildsInProcessing'] || []).collect do |attrs|
|
70
|
-
attrs
|
70
|
+
attrs[:build_train] = self
|
71
71
|
Tunes::Build.factory(attrs)
|
72
72
|
end
|
73
73
|
|
@@ -26,7 +26,7 @@ module Spaceship
|
|
26
26
|
end
|
27
27
|
return result if result
|
28
28
|
|
29
|
-
raise "Language '#{lang}' is not activated for this app version."
|
29
|
+
raise "Language '#{lang}' is not activated / available for this app version."
|
30
30
|
end
|
31
31
|
|
32
32
|
# @return (Array) An array containing all languages that are already available
|
@@ -15,6 +15,7 @@ require 'spaceship/tunes/user_detail'
|
|
15
15
|
require 'spaceship/tunes/app_screenshot'
|
16
16
|
require 'spaceship/tunes/language_converter'
|
17
17
|
require 'spaceship/tunes/build'
|
18
|
+
require 'spaceship/tunes/build_details'
|
18
19
|
require 'spaceship/tunes/processing_build'
|
19
20
|
require 'spaceship/tunes/build_train'
|
20
21
|
require 'spaceship/tunes/device_type'
|
@@ -2,7 +2,7 @@ module Spaceship
|
|
2
2
|
# rubocop:disable Metrics/ClassLength
|
3
3
|
class TunesClient < Spaceship::Client
|
4
4
|
# ITunesConnectError is only thrown when iTunes Connect raises an exception
|
5
|
-
class ITunesConnectError <
|
5
|
+
class ITunesConnectError < BasicPreferredInfoError
|
6
6
|
end
|
7
7
|
|
8
8
|
# raised if the server failed to save temporarily
|
@@ -80,7 +80,7 @@ module Spaceship
|
|
80
80
|
|
81
81
|
if t_name.length > 0
|
82
82
|
teams.each do |t|
|
83
|
-
t_id = t['contentProvider']['contentProviderId'].to_s if t['contentProvider']['name'].
|
83
|
+
t_id = t['contentProvider']['contentProviderId'].to_s if t['contentProvider']['name'].casecmp(t_name.downcase).zero?
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
@@ -574,6 +574,22 @@ module Spaceship
|
|
574
574
|
handle_itc_response(r.body)
|
575
575
|
end
|
576
576
|
|
577
|
+
# All build trains, even if there is no TestFlight
|
578
|
+
def all_build_trains(app_id: nil)
|
579
|
+
r = request(:get, "ra/apps/#{app_id}/buildHistory?platform=ios")
|
580
|
+
handle_itc_response(r.body)
|
581
|
+
end
|
582
|
+
|
583
|
+
def all_builds_for_train(app_id: nil, train: nil)
|
584
|
+
r = request(:get, "ra/apps/#{app_id}/trains/#{train}/buildHistory?platform=ios")
|
585
|
+
handle_itc_response(r.body)
|
586
|
+
end
|
587
|
+
|
588
|
+
def build_details(app_id: nil, train: nil, build_number: nil)
|
589
|
+
r = request(:get, "ra/apps/#{app_id}/platforms/ios/trains/#{train}/builds/#{build_number}/details")
|
590
|
+
handle_itc_response(r.body)
|
591
|
+
end
|
592
|
+
|
577
593
|
def update_build_information!(app_id: nil,
|
578
594
|
train: nil,
|
579
595
|
build_number: nil,
|
data/lib/spaceship/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: spaceship
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.24.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Krause
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-03-
|
12
|
+
date: 2016-03-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: credentials_manager
|
@@ -241,14 +241,14 @@ dependencies:
|
|
241
241
|
requirements:
|
242
242
|
- - "~>"
|
243
243
|
- !ruby/object:Gem::Version
|
244
|
-
version: 0.
|
244
|
+
version: 0.38.0
|
245
245
|
type: :development
|
246
246
|
prerelease: false
|
247
247
|
version_requirements: !ruby/object:Gem::Requirement
|
248
248
|
requirements:
|
249
249
|
- - "~>"
|
250
250
|
- !ruby/object:Gem::Version
|
251
|
-
version: 0.
|
251
|
+
version: 0.38.0
|
252
252
|
description: Because you would rather spend your time building stuff than fighting
|
253
253
|
provisioning
|
254
254
|
email:
|
@@ -297,6 +297,7 @@ files:
|
|
297
297
|
- lib/spaceship/tunes/app_version_states_history.rb
|
298
298
|
- lib/spaceship/tunes/application.rb
|
299
299
|
- lib/spaceship/tunes/build.rb
|
300
|
+
- lib/spaceship/tunes/build_details.rb
|
300
301
|
- lib/spaceship/tunes/build_train.rb
|
301
302
|
- lib/spaceship/tunes/device_type.rb
|
302
303
|
- lib/spaceship/tunes/language_converter.rb
|
@@ -332,7 +333,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
332
333
|
version: '0'
|
333
334
|
requirements: []
|
334
335
|
rubyforge_project:
|
335
|
-
rubygems_version: 2.5.1
|
336
|
+
rubygems_version: 2.4.5.1
|
336
337
|
signing_key:
|
337
338
|
specification_version: 4
|
338
339
|
summary: Because you would rather spend your time building stuff than fighting provisioning
|