yolo 1.2.8 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/yolo/config/config.yml +4 -0
- data/lib/yolo/config/settings.rb +10 -0
- data/lib/yolo/formatters/error_formatter.rb +12 -0
- data/lib/yolo/formatters/progress_formatter.rb +35 -0
- data/lib/yolo/tasks/ios/calabash.rb +0 -1
- data/lib/yolo/tasks/ios/release.rb +19 -0
- data/lib/yolo/tools.rb +1 -0
- data/lib/yolo/tools/git.rb +14 -13
- data/lib/yolo/tools/github.rb +145 -0
- data/lib/yolo/tools/ios/release_notes.rb +1 -9
- data/spec/config/settings_spec.rb +7 -0
- data/spec/tasks/ios/release_spec.rb +16 -0
- data/spec/tools/github_spec.rb +67 -0
- data/spec/tools/ios/release_notes_spec.rb +0 -5
- metadata +21 -3
data/lib/yolo/config/config.yml
CHANGED
data/lib/yolo/config/settings.rb
CHANGED
@@ -168,6 +168,16 @@ module Yolo
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
+
#
|
172
|
+
# The github token used to connect to the github api
|
173
|
+
#
|
174
|
+
# @return [String] The token defined in config.yml
|
175
|
+
def github_token
|
176
|
+
if @yaml["github"]["token"] != "token"
|
177
|
+
return @yaml["github"]["token"]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
171
181
|
#
|
172
182
|
# The path to the users home directory, same as ~
|
173
183
|
#
|
@@ -46,6 +46,14 @@ module Yolo
|
|
46
46
|
puts red("There was a problem deploying the ipa: #{error}")
|
47
47
|
end
|
48
48
|
|
49
|
+
#
|
50
|
+
# Outputs a red string stating that there was an issue deploying the bundle to github
|
51
|
+
# @param error [String] The error string
|
52
|
+
#
|
53
|
+
def github_upload_failed(error)
|
54
|
+
puts red("There was a problem deploying the bundle to github: #{error}")
|
55
|
+
end
|
56
|
+
|
49
57
|
#
|
50
58
|
# Outputs a red string stating that no deploy URL was found in the config file
|
51
59
|
#
|
@@ -74,6 +82,10 @@ module Yolo
|
|
74
82
|
puts red("Can't send mail notification, missing details")
|
75
83
|
end
|
76
84
|
|
85
|
+
def no_github_token
|
86
|
+
puts red("No Github token found, please specify one in ~/.yolo/config.yml ")
|
87
|
+
end
|
88
|
+
|
77
89
|
#
|
78
90
|
# Outputs a red string stating that a release notes file could not be found
|
79
91
|
# @param notes [String] The path which should contain a notes file
|
@@ -93,6 +93,41 @@ module Yolo
|
|
93
93
|
puts
|
94
94
|
end
|
95
95
|
|
96
|
+
#
|
97
|
+
# Outputs a string stating that the github release is being generated
|
98
|
+
#
|
99
|
+
def creating_github_release
|
100
|
+
github = "Generating Github release"
|
101
|
+
puts bold(github)
|
102
|
+
puts github.length.times.map {"="}.join
|
103
|
+
puts
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# Outputs a green string stating that the github release was created
|
108
|
+
#
|
109
|
+
def created_release(version)
|
110
|
+
puts green("Release #{version} created")
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Outputs a green string stating that the github release finished
|
115
|
+
#
|
116
|
+
def github_released
|
117
|
+
puts green("Release completed")
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Outputs an underlined bold string stating that the is uploading
|
122
|
+
#
|
123
|
+
def github_uploading
|
124
|
+
puts
|
125
|
+
github = "Uploading package"
|
126
|
+
puts bold(github)
|
127
|
+
puts github.length.times.map {"="}.join
|
128
|
+
puts
|
129
|
+
end
|
130
|
+
|
96
131
|
#
|
97
132
|
# Outputs a green string stating that the release notes have been generated
|
98
133
|
# @param notes [String] The path to the release notes
|
@@ -16,6 +16,8 @@ module Yolo
|
|
16
16
|
attr_accessor :deployment
|
17
17
|
# A Hash of additional options
|
18
18
|
attr_accessor :options
|
19
|
+
# A Github repo to release to
|
20
|
+
attr_accessor :github_repo
|
19
21
|
|
20
22
|
#
|
21
23
|
# Initializes the class with default settings
|
@@ -127,6 +129,22 @@ module Yolo
|
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
132
|
+
#
|
133
|
+
# Release the ipa using the github releases API, the ipa will be zipped and
|
134
|
+
# uploaded as well as the release notes used as the release body and
|
135
|
+
# version for the release title
|
136
|
+
#
|
137
|
+
# @param ipa_path [String] The full path to the IPA file to deploy
|
138
|
+
#
|
139
|
+
def release_to_github(bundle_path)
|
140
|
+
if self.github_repo
|
141
|
+
github = Yolo::Tools::Github.new
|
142
|
+
notes = Yolo::Tools::Ios::ReleaseNotes.html
|
143
|
+
github.repo = self.github_repo
|
144
|
+
github.release(bundle_path, version, notes)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
130
148
|
#
|
131
149
|
# Sends a notificaiton email from a deployment
|
132
150
|
# @param url [String] The URL which the build has been deplyed too
|
@@ -172,6 +190,7 @@ module Yolo
|
|
172
190
|
xcodebuild :build
|
173
191
|
Yolo::Tools::Ios::IPA.generate(app_path,dsym_path,bundle_path) do |ipa|
|
174
192
|
deploy(ipa) if ipa and self.deployment
|
193
|
+
release_to_github(bundle_path) if ipa and self.github_repo
|
175
194
|
end
|
176
195
|
end
|
177
196
|
|
data/lib/yolo/tools.rb
CHANGED
data/lib/yolo/tools/git.rb
CHANGED
@@ -79,6 +79,20 @@ module Yolo
|
|
79
79
|
@commit
|
80
80
|
end
|
81
81
|
|
82
|
+
#
|
83
|
+
# Finds the current branch using some regex and git branch
|
84
|
+
#
|
85
|
+
# @return [String] The current branch name
|
86
|
+
def current_branch
|
87
|
+
branch = `git branch`
|
88
|
+
branchs = branch.split("\n")
|
89
|
+
current_branch = ""
|
90
|
+
branchs.each do |b|
|
91
|
+
current_branch = b if b.count("*") == 1
|
92
|
+
end
|
93
|
+
current_branch.gsub("* ", "")
|
94
|
+
end
|
95
|
+
|
82
96
|
private
|
83
97
|
|
84
98
|
#
|
@@ -198,19 +212,6 @@ module Yolo
|
|
198
212
|
`git log --decorate=short -n 1 --pretty=oneline`
|
199
213
|
end
|
200
214
|
|
201
|
-
#
|
202
|
-
# Finds the current branch using some regex and git branch
|
203
|
-
#
|
204
|
-
# @return [String] The current branch name
|
205
|
-
def current_branch
|
206
|
-
branch = `git branch`
|
207
|
-
branchs = branch.split("\n")
|
208
|
-
current_branch = ""
|
209
|
-
branchs.each do |b|
|
210
|
-
current_branch = b if b.count("*") == 1
|
211
|
-
end
|
212
|
-
current_branch.gsub("* ", "")
|
213
|
-
end
|
214
215
|
end
|
215
216
|
end
|
216
217
|
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "zip"
|
2
|
+
require "net/http"
|
3
|
+
require "uri"
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Yolo
|
7
|
+
module Tools
|
8
|
+
#
|
9
|
+
# Communicates with Github
|
10
|
+
#
|
11
|
+
# @author [Alex Fish]
|
12
|
+
#
|
13
|
+
class Github
|
14
|
+
|
15
|
+
# The github repo that all actions will target
|
16
|
+
attr_accessor :repo
|
17
|
+
|
18
|
+
#
|
19
|
+
# Creates the class with default variables
|
20
|
+
#
|
21
|
+
def initialize
|
22
|
+
@token = Yolo::Config::Settings.instance.github_token
|
23
|
+
if !@token
|
24
|
+
error = Yolo::Formatters::ErrorFormatter.new
|
25
|
+
error.no_github_token
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Release the bundle using the github releases API, the bundle will be zipped and
|
30
|
+
# uploaded as well as the release notes used as the release body and
|
31
|
+
# version for the release title
|
32
|
+
#
|
33
|
+
# @param bundle [String] The full path to the bundle folder to release
|
34
|
+
# @param version [String] The version of the release
|
35
|
+
# @param body [String] The body of the release
|
36
|
+
#
|
37
|
+
def release(bundle, version, body)
|
38
|
+
@progress = Yolo::Formatters::ProgressFormatter.new
|
39
|
+
@progress.creating_github_release
|
40
|
+
|
41
|
+
uri = URI.parse("https://api.github.com/repos/#{self.repo}/releases?access_token=#{@token}")
|
42
|
+
|
43
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
+
http.use_ssl = true
|
45
|
+
|
46
|
+
request = Net::HTTP::Post.new(uri.request_uri)
|
47
|
+
request.body = options(body, version)
|
48
|
+
|
49
|
+
# Tweak headers, removing this will default to application/x-www-form-urlencoded
|
50
|
+
request["Content-Type"] = "application/json"
|
51
|
+
|
52
|
+
response = http.request(request)
|
53
|
+
response = JSON.parse(response.body)
|
54
|
+
|
55
|
+
@progress.created_release(version)
|
56
|
+
|
57
|
+
url = response["upload_url"].gsub("{?name}","")
|
58
|
+
upload_bundle(bundle, url, "#{version}.zip")
|
59
|
+
end
|
60
|
+
|
61
|
+
# Upload the bundle to the github release url
|
62
|
+
#
|
63
|
+
# @param bundle [String] The full path to the bundle folder to release
|
64
|
+
# @param url [String] The github asset url returned from create_release
|
65
|
+
#
|
66
|
+
def upload_bundle(bundle, url, name)
|
67
|
+
@progress = Yolo::Formatters::ProgressFormatter.new
|
68
|
+
@progress.github_uploading
|
69
|
+
zipped_bundle = zip_bundle(bundle)
|
70
|
+
|
71
|
+
response = ""
|
72
|
+
curl = curl_string(name, zipped_bundle, url)
|
73
|
+
puts curl
|
74
|
+
IO.popen(curl) do |io|
|
75
|
+
begin
|
76
|
+
while line = io.readline
|
77
|
+
response << line
|
78
|
+
end
|
79
|
+
if response.length == 0
|
80
|
+
@error_formatter.github_upload_failed("Upload error")
|
81
|
+
end
|
82
|
+
rescue EOFError
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
@progress.github_released
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Generate a curl command string to upload the release to github with
|
92
|
+
#
|
93
|
+
# @param name [String] The name of the file
|
94
|
+
# @param zipped_bundle [String] The full path to the zipped bundle folder
|
95
|
+
# @param url [String] The URL for the request
|
96
|
+
#
|
97
|
+
# @return [String] The curl command string
|
98
|
+
def curl_string(name, zipped_bundle, url)
|
99
|
+
"curl -# -H \"Accept: application/vnd.github.manifold-preview\" \
|
100
|
+
-H \"Content-Type: application/zip\" \
|
101
|
+
--data-binary @#{zipped_bundle} \
|
102
|
+
\"#{url}?name=#{name}&access_token=#{@token}\"
|
103
|
+
"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Zip the bundle ready for upload to github, the zip will have the same
|
107
|
+
# name as the bundle folder with .zip appended
|
108
|
+
#
|
109
|
+
# @param bundle [String] The full path to the bundle folder to zip
|
110
|
+
#
|
111
|
+
# @return [String] The zipped bundle path
|
112
|
+
def zip_bundle(bundle)
|
113
|
+
bundle.sub!(%r[/$],'')
|
114
|
+
archive = File.join(bundle,File.basename(bundle))+'.zip'
|
115
|
+
FileUtils.rm archive, :force=>true
|
116
|
+
|
117
|
+
Zip::File.open(archive, 'w') do |zipfile|
|
118
|
+
Dir["#{bundle}/**/**"].reject{|f|f==archive}.each do |file|
|
119
|
+
zipfile.add(file.sub(bundle+'/',''),file)
|
120
|
+
end
|
121
|
+
return archive
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Generates an options hash for a github release
|
127
|
+
#
|
128
|
+
# @return [String] The current branch name
|
129
|
+
def options(body, version)
|
130
|
+
options = {"body" => body, "tag_name" => version, "name" => version, "target_commitish" => current_branch}
|
131
|
+
options.to_json
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Finds the current branch using the git tool
|
136
|
+
#
|
137
|
+
# @return [String] The current branch name
|
138
|
+
def current_branch
|
139
|
+
git = Yolo::Tools::Git.new
|
140
|
+
git.current_branch
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -22,14 +22,6 @@ module Yolo
|
|
22
22
|
File.open("#{directory}/release_notes.md", 'w') {|f|
|
23
23
|
f.write("### Version\n- - -\n")
|
24
24
|
f.write("#{xcode.version_number} (#{xcode.build_number})\n\n")
|
25
|
-
f.write("### Change log\n- - -\n")
|
26
|
-
f.write("* No Changes\n\n")
|
27
|
-
f.write("### Fixes\n- - -\n")
|
28
|
-
f.write("* No Fixes\n\n")
|
29
|
-
f.write("### Notes\n- - -\n")
|
30
|
-
f.write("No Notes\n\n")
|
31
|
-
f.write("### Known issues\n- - -\n")
|
32
|
-
f.write("No Known issues\n\n")
|
33
25
|
f.write("### Date\n- - -\n")
|
34
26
|
f.write("#{time.day}/#{time.month}/#{time.year} - #{time.hour}:#{time.min}")
|
35
27
|
}
|
@@ -37,7 +29,7 @@ module Yolo
|
|
37
29
|
formatter = Yolo::Formatters::ProgressFormatter.new
|
38
30
|
formatter.notes_generated("#{directory}/release_notes.md")
|
39
31
|
|
40
|
-
|
32
|
+
#`open #{directory}/release_notes.md`
|
41
33
|
end
|
42
34
|
|
43
35
|
#
|
@@ -98,6 +98,9 @@ describe Yolo::Config::Settings do
|
|
98
98
|
"port" => "test_port",
|
99
99
|
"host" => "test_host",
|
100
100
|
"from" => "test_from"
|
101
|
+
},
|
102
|
+
"github" => {
|
103
|
+
"token" => "test_token"
|
101
104
|
}
|
102
105
|
}
|
103
106
|
YAML.stub(:load_file){@yaml}
|
@@ -139,5 +142,9 @@ describe Yolo::Config::Settings do
|
|
139
142
|
it "should hold a mail from" do
|
140
143
|
Yolo::Config::Settings.instance.mail_from.should eq("test_from")
|
141
144
|
end
|
145
|
+
|
146
|
+
it "should hold a github token" do
|
147
|
+
Yolo::Config::Settings.instance.github_token.should eq("test_token")
|
148
|
+
end
|
142
149
|
end
|
143
150
|
end
|
@@ -11,6 +11,10 @@ describe Yolo::Tasks::Ios::Release do
|
|
11
11
|
@email.stub(:send)
|
12
12
|
Yolo::Notify::Ios::OTAEmail.stub(:new){@email}
|
13
13
|
|
14
|
+
@github = mock(Yolo::Tools::Github)
|
15
|
+
@github.stub(:repo=)
|
16
|
+
Yolo::Tools::Github.stub(:new){@github}
|
17
|
+
|
14
18
|
@ota = mock(Yolo::Deployment::OTA)
|
15
19
|
Yolo::Deployment::OTA.stub(:new){@ota}
|
16
20
|
|
@@ -149,6 +153,18 @@ describe Yolo::Tasks::Ios::Release do
|
|
149
153
|
@release.deploy("")
|
150
154
|
end
|
151
155
|
|
156
|
+
it "should release to github if a repo is set" do
|
157
|
+
@github.should_receive(:release)
|
158
|
+
@release.stub(:github_repo){"test_repo"}
|
159
|
+
@release.release_to_github("")
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should not release to github if no repo is set" do
|
163
|
+
@github.should_not_receive(:release)
|
164
|
+
@release.stub(:github_repo){nil}
|
165
|
+
@release.release_to_github("")
|
166
|
+
end
|
167
|
+
|
152
168
|
it "shouldnt build mail options without a url" do
|
153
169
|
@ota.stub(:deploy).and_yield(nil, nil)
|
154
170
|
@email.should_not_receive(:send)
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'yolo'
|
3
|
+
|
4
|
+
describe Yolo::Tools::Github do
|
5
|
+
|
6
|
+
before do
|
7
|
+
Yolo::Formatters::ProgressFormatter.any_instance.stub(:puts)
|
8
|
+
Yolo::Formatters::ErrorFormatter.any_instance.stub(:puts)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "when initlized" do
|
12
|
+
it "should load the token from settings" do
|
13
|
+
Yolo::Config::Settings.instance.stub(:github_token){"token"}
|
14
|
+
github = Yolo::Tools::Github.new
|
15
|
+
github.instance_variable_get(:@token).should_not eq(nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should thrown an error if there is no token" do
|
19
|
+
Yolo::Config::Settings.instance.stub(:github_token){nil}
|
20
|
+
Yolo::Formatters::ErrorFormatter.any_instance.should_receive(:no_github_token)
|
21
|
+
|
22
|
+
github = Yolo::Tools::Github.new
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "when releasing" do
|
27
|
+
before do
|
28
|
+
@git = mock(Yolo::Tools::Git)
|
29
|
+
Yolo::Tools::Git.stub(:new){@git}
|
30
|
+
@git.stub(:current_branch){"branch"}
|
31
|
+
|
32
|
+
@github = Yolo::Tools::Github.new
|
33
|
+
@github.instance_variable_get(:@octokit).stub(:create_release)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should get the current git branch" do
|
37
|
+
@github.instance_eval{current_branch}.should eq("branch")
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should zip the bundle" do
|
41
|
+
@github.instance_variable_get(:@octokit).stub(:upload_asset)
|
42
|
+
@github.should_receive(:zip_bundle).with("path")
|
43
|
+
@github.upload_bundle("path", "version", "name")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "when generating options" do
|
48
|
+
|
49
|
+
before do
|
50
|
+
github = Yolo::Tools::Github.new
|
51
|
+
github.stub(:current_branch){"target_commitish"}
|
52
|
+
@options = github.instance_eval{options("body", "version")}
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should hold a version" do
|
56
|
+
@options["name"].should eq("name")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should hold a body" do
|
60
|
+
@options["body"].should eq("body")
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should hold the target_commitish" do
|
64
|
+
@options["target_commitish"].should eq("target_commitish")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -54,11 +54,6 @@ describe Yolo::Tools::Ios::ReleaseNotes do
|
|
54
54
|
@time.should_receive(:min)
|
55
55
|
@release_notes.generate("path")
|
56
56
|
end
|
57
|
-
|
58
|
-
it "should open the release notes after writting" do
|
59
|
-
@release_notes.should_receive(:`).with(/open current_path\/release_notes.md/)
|
60
|
-
@release_notes.generate("path")
|
61
|
-
end
|
62
57
|
end
|
63
58
|
|
64
59
|
describe "when parsing release notes to plain text" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yolo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: xcodebuild-rb
|
@@ -75,6 +75,22 @@ dependencies:
|
|
75
75
|
- - ! '>='
|
76
76
|
- !ruby/object:Gem::Version
|
77
77
|
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rubyzip
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - '='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 1.1.0
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - '='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.1.0
|
78
94
|
description: yolo is a RubyGem which provides a Ruby interface to Continuous Integration
|
79
95
|
build tools. yolo is currently geared towards the Xcode toolchain and iOS development.
|
80
96
|
email: alex@alexefish.com
|
@@ -111,6 +127,7 @@ files:
|
|
111
127
|
- lib/yolo/tasks/ios.rb
|
112
128
|
- lib/yolo/tasks.rb
|
113
129
|
- lib/yolo/tools/git.rb
|
130
|
+
- lib/yolo/tools/github.rb
|
114
131
|
- lib/yolo/tools/ios/calabash.rb
|
115
132
|
- lib/yolo/tools/ios/coverage.rb
|
116
133
|
- lib/yolo/tools/ios/ipa.rb
|
@@ -134,6 +151,7 @@ files:
|
|
134
151
|
- spec/tasks/ios/ocunit_spec.rb
|
135
152
|
- spec/tasks/ios/release_spec.rb
|
136
153
|
- spec/tools/git_spec.rb
|
154
|
+
- spec/tools/github_spec.rb
|
137
155
|
- spec/tools/ios/calabash_spec.rb
|
138
156
|
- spec/tools/ios/coverage_spec.rb
|
139
157
|
- spec/tools/ios/ipa_spec.rb
|
@@ -160,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
160
178
|
version: '0'
|
161
179
|
requirements: []
|
162
180
|
rubyforge_project:
|
163
|
-
rubygems_version: 1.8.
|
181
|
+
rubygems_version: 1.8.24
|
164
182
|
signing_key:
|
165
183
|
specification_version: 3
|
166
184
|
summary: YOLO!
|