bullet_train-api 1.1.14 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/account/platform/applications_controller.rb +17 -0
- data/app/controllers/api/open_api_controller.rb +17 -9
- data/app/controllers/concerns/api/controllers/base.rb +10 -14
- data/app/controllers/concerns/api/v1/users/controller_base.rb +3 -0
- data/app/models/platform/application.rb +1 -1
- data/app/views/api/v1/open_api/shared/_paths.yaml.erb +35 -12
- data/app/views/api/v1/open_api/teams/_paths.yaml.erb +22 -9
- data/app/views/api/v1/open_api/users/_paths.yaml.erb +22 -9
- data/config/locales/en/platform/applications.en.yml +2 -0
- data/config/routes.rb +4 -0
- data/lib/bullet_train/api/version.rb +1 -1
- data/lib/tasks/bullet_train/api_tasks.rake +76 -4
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fe4705d3656c5f8b457093920b2cf07b22d9c060a0767d9a0b88b361a5c1c460
|
4
|
+
data.tar.gz: 4ab0dfc1c339c8782d107a09b9399d315d9715c38b3733f5cdf319774d28ad46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7a952f03eab5f2ebfe560f9ab211c912515406820afa7f2098cd7fb409c6044681e9fc5b384283cc974deba4a677343984e79a2d4a7ea17b119eb817b208254b
|
7
|
+
data.tar.gz: 496356f9bd2de1b8a732c01e2af55d6aa8c7dc179161781de492284b4ba5c4a27891de92565a2d3fad12aa1ac38111eae663dc7e9f2b37b2411c60099b51f11e
|
@@ -21,6 +21,23 @@ class Account::Platform::ApplicationsController < Account::ApplicationController
|
|
21
21
|
def edit
|
22
22
|
end
|
23
23
|
|
24
|
+
def provision
|
25
|
+
if ENV["TESTING_PROVISION_KEY"].present? && params[:key] == ENV["TESTING_PROVISION_KEY"]
|
26
|
+
user = User.create(email: "test@#{SecureRandom.hex}.example.com", password: (password = SecureRandom.hex), password_confirmation: password)
|
27
|
+
provision_team = current_user.teams.create(name: "provision-team-#{SecureRandom.hex}", time_zone: user.time_zone)
|
28
|
+
test_application = Platform::Application.new(name: "test-application-#{SecureRandom.hex}", team: provision_team)
|
29
|
+
|
30
|
+
if test_application.save
|
31
|
+
access_token = test_application.create_access_token
|
32
|
+
render json: {message: I18n.t("platform/applications.notifications.test_application_created"), team_id: provision_team.id, access_token: access_token.token}
|
33
|
+
else
|
34
|
+
render json: {errors: test_application.errors, status: :unprocessable_entity}
|
35
|
+
end
|
36
|
+
else
|
37
|
+
render json: {message: I18n.t("platform/applications.notifications.test_application_failure")}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
24
41
|
# POST /account/teams/:team_id/platform/applications
|
25
42
|
# POST /account/teams/:team_id/platform/applications.json
|
26
43
|
def create
|
@@ -6,6 +6,7 @@ module OpenApiHelper
|
|
6
6
|
lines.unshift(first_line).join.html_safe
|
7
7
|
end
|
8
8
|
|
9
|
+
# TODO: Remove this method? It's not being used anywhere
|
9
10
|
def components_for(model)
|
10
11
|
for_model model do
|
11
12
|
indent(render("api/#{@version}/open_api/#{model.name.underscore.pluralize}/components"), 2)
|
@@ -31,7 +32,9 @@ module OpenApiHelper
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def automatic_components_for(model, locals: {})
|
34
|
-
|
35
|
+
path = "app/views/api/#{@version}"
|
36
|
+
paths = ([path] + `bundle show --paths`.lines.map { |gem_path| "#{gem_path.chomp}/#{path}" })
|
37
|
+
jbuilder = Jbuilder::Schema.renderer(paths, locals: {
|
35
38
|
# If we ever get to the point where we need a real model here, we should implement an example team in seeds that we can source it from.
|
36
39
|
model.name.underscore.split("/").last.to_sym => model.new,
|
37
40
|
# Same here, if we ever need this to be a real object, this should be `test@example.com` with an `SecureRandom.hex` password.
|
@@ -71,15 +74,20 @@ module OpenApiHelper
|
|
71
74
|
|
72
75
|
def attribute(attribute)
|
73
76
|
heading = t("#{current_model.name.underscore.pluralize}.fields.#{attribute}.heading")
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
77
|
+
attribute_data = current_model.columns_hash[attribute.to_s]
|
78
|
+
|
79
|
+
# TODO: File fields don't show up in the columns_hash. How should we handle these?
|
80
|
+
# Default to `string` when the type returns nil.
|
81
|
+
type = attribute_data.nil? ? "string" : attribute_data.type
|
82
|
+
|
83
|
+
attribute_block = <<~YAML
|
84
|
+
#{attribute}:
|
85
|
+
description: "#{heading}"
|
86
|
+
type: #{type}
|
87
|
+
YAML
|
88
|
+
indent(attribute_block.chomp, 2)
|
82
89
|
end
|
90
|
+
alias_method :parameter, :attribute
|
83
91
|
end
|
84
92
|
|
85
93
|
class Api::OpenApiController < ApplicationController
|
@@ -4,10 +4,6 @@ require "pagy_cursor/pagy/extras/uuid_cursor"
|
|
4
4
|
module Api::Controllers::Base
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
-
# TODO Why doesn't `before_action :doorkeeper_authorize!` throw an exception?
|
8
|
-
class NotAuthenticatedError < StandardError
|
9
|
-
end
|
10
|
-
|
11
7
|
included do
|
12
8
|
include ActionController::Helpers
|
13
9
|
helper ApplicationHelper
|
@@ -16,14 +12,13 @@ module Api::Controllers::Base
|
|
16
12
|
include Pagy::Backend
|
17
13
|
|
18
14
|
before_action :set_default_response_format
|
19
|
-
before_action :doorkeeper_authorize!
|
20
15
|
after_action :set_pagination_headers
|
21
16
|
|
22
17
|
def modify_url_params(url, new_params)
|
23
18
|
uri = URI.parse(url)
|
24
19
|
query = Rack::Utils.parse_query(uri.query)
|
25
20
|
new_params.each do |key, value|
|
26
|
-
query[key] = value
|
21
|
+
query[key.to_s] = value
|
27
22
|
end
|
28
23
|
uri.query = Rack::Utils.build_query(query)
|
29
24
|
uri.to_s
|
@@ -35,8 +30,9 @@ module Api::Controllers::Base
|
|
35
30
|
if @pagy.has_more?
|
36
31
|
if (collection = instance_variable_get(collection_variable))
|
37
32
|
next_cursor = collection.last.id
|
38
|
-
|
39
|
-
|
33
|
+
link_header = response.headers["Link"]
|
34
|
+
link_value = "<#{modify_url_params(request.url, after: next_cursor)}>; rel=\"next\""
|
35
|
+
response.headers["Link"] = link_header ? "#{link_header}, #{link_value}" : link_value
|
40
36
|
response.headers["Pagination-Next"] = next_cursor
|
41
37
|
end
|
42
38
|
end
|
@@ -46,10 +42,6 @@ module Api::Controllers::Base
|
|
46
42
|
render json: {error: "Not found"}, status: :not_found
|
47
43
|
end
|
48
44
|
|
49
|
-
rescue_from NotAuthenticatedError do |exception|
|
50
|
-
render json: {error: "Invalid token"}, status: :unauthorized
|
51
|
-
end
|
52
|
-
|
53
45
|
before_action :apply_pagination, only: [:index]
|
54
46
|
end
|
55
47
|
|
@@ -65,7 +57,7 @@ module Api::Controllers::Base
|
|
65
57
|
end
|
66
58
|
|
67
59
|
def current_user
|
68
|
-
raise
|
60
|
+
raise Doorkeeper::Errors::InvalidToken unless doorkeeper_token.present?
|
69
61
|
# TODO Remove this rescue once workspace clusters can write to this column on the identity server.
|
70
62
|
# TODO Make this logic configurable so that downstream developers can write different methods for this column getting updated.
|
71
63
|
begin
|
@@ -95,8 +87,12 @@ module Api::Controllers::Base
|
|
95
87
|
end
|
96
88
|
|
97
89
|
class_methods do
|
90
|
+
def controller_namespace
|
91
|
+
name.split("::").first(2).join("::")
|
92
|
+
end
|
93
|
+
|
98
94
|
def regex_to_remove_controller_namespace
|
99
|
-
/^#{
|
95
|
+
/^#{controller_namespace + "::"}/
|
100
96
|
end
|
101
97
|
end
|
102
98
|
end
|
@@ -11,6 +11,9 @@ module Api::V1::Users::ControllerBase
|
|
11
11
|
:last_name,
|
12
12
|
:time_zone,
|
13
13
|
:locale,
|
14
|
+
:current_password,
|
15
|
+
:password,
|
16
|
+
:password_confirmation,
|
14
17
|
# 🚅 super scaffolding will insert new fields above this line.
|
15
18
|
*permitted_arrays,
|
16
19
|
# 🚅 super scaffolding will insert new arrays above this line.
|
@@ -34,7 +34,7 @@ class Platform::Application < ApplicationRecord
|
|
34
34
|
def create_user_and_membership
|
35
35
|
faux_password = SecureRandom.hex
|
36
36
|
create_user(email: "noreply+#{SecureRandom.hex}@bullettrain.co", password: faux_password, password_confirmation: faux_password, first_name: label_string)
|
37
|
-
create_membership(team: team, user: user)
|
37
|
+
create_membership(team: team, user: user, platform_agent: true)
|
38
38
|
membership.roles << Role.admin
|
39
39
|
end
|
40
40
|
|
@@ -13,22 +13,23 @@
|
|
13
13
|
required: true
|
14
14
|
schema:
|
15
15
|
type: string
|
16
|
+
- $ref: "#/components/parameters/after"
|
16
17
|
responses:
|
17
18
|
"404":
|
18
19
|
description: "Not Found"
|
19
20
|
"200":
|
20
21
|
description: "OK"
|
22
|
+
headers:
|
23
|
+
Pagination-Next:
|
24
|
+
$ref: "#/components/headers/PaginationNext"
|
25
|
+
Link:
|
26
|
+
$ref: "#/components/headers/Link"
|
21
27
|
content:
|
22
28
|
application/json:
|
23
29
|
schema:
|
24
|
-
type:
|
25
|
-
|
26
|
-
|
27
|
-
type: array
|
28
|
-
items:
|
29
|
-
$ref: "#/components/schemas/ScaffoldingCompletelyConcreteTangibleThingAttributes"
|
30
|
-
has_more:
|
31
|
-
type: boolean
|
30
|
+
type: array
|
31
|
+
items:
|
32
|
+
$ref: "#/components/schemas/ScaffoldingCompletelyConcreteTangibleThingAttributes"
|
32
33
|
<% end %>
|
33
34
|
<% unless except.include?(:create) %>
|
34
35
|
post:
|
@@ -42,6 +43,17 @@
|
|
42
43
|
required: true
|
43
44
|
schema:
|
44
45
|
type: string
|
46
|
+
requestBody:
|
47
|
+
description: "Information about a new Tangible Thing"
|
48
|
+
required: true
|
49
|
+
content:
|
50
|
+
application/json:
|
51
|
+
schema:
|
52
|
+
type: object
|
53
|
+
properties:
|
54
|
+
scaffolding_completely_concrete_tangible_thing:
|
55
|
+
type: object
|
56
|
+
$ref: "#/components/schemas/ScaffoldingCompletelyConcreteTangibleThingParameters"
|
45
57
|
responses:
|
46
58
|
"404":
|
47
59
|
description: "Not Found"
|
@@ -50,7 +62,7 @@
|
|
50
62
|
content:
|
51
63
|
application/json:
|
52
64
|
schema:
|
53
|
-
$ref: "#/components/schemas/
|
65
|
+
$ref: "#/components/schemas/ScaffoldingCompletelyConcreteTangibleThingAttributes"
|
54
66
|
<% end %>
|
55
67
|
<% end %>
|
56
68
|
<% unless except.include?(:show) && except.include?(:update) && except.include?(:destroy) %>
|
@@ -81,6 +93,17 @@
|
|
81
93
|
operationId: updateScaffoldingCompletelyConcreteTangibleThings
|
82
94
|
parameters:
|
83
95
|
- $ref: "#/components/parameters/id"
|
96
|
+
requestBody:
|
97
|
+
description: "Information about updated fields in Tangible Thing"
|
98
|
+
required: true
|
99
|
+
content:
|
100
|
+
application/json:
|
101
|
+
schema:
|
102
|
+
type: object
|
103
|
+
properties:
|
104
|
+
scaffolding_completely_concrete_tangible_thing:
|
105
|
+
type: object
|
106
|
+
$ref: "#/components/schemas/ScaffoldingCompletelyConcreteTangibleThingParameters"
|
84
107
|
responses:
|
85
108
|
"404":
|
86
109
|
description: "Not Found"
|
@@ -89,7 +112,7 @@
|
|
89
112
|
content:
|
90
113
|
application/json:
|
91
114
|
schema:
|
92
|
-
$ref: "#/components/schemas/
|
115
|
+
$ref: "#/components/schemas/ScaffoldingCompletelyConcreteTangibleThingAttributes"
|
93
116
|
<% end %>
|
94
117
|
<% unless except.include?(:destroy) %>
|
95
118
|
delete:
|
@@ -102,7 +125,7 @@
|
|
102
125
|
responses:
|
103
126
|
"404":
|
104
127
|
description: "Not Found"
|
105
|
-
"
|
106
|
-
description: "
|
128
|
+
"204":
|
129
|
+
description: "No Content"
|
107
130
|
<% end %>
|
108
131
|
<% end %>
|
@@ -4,22 +4,24 @@
|
|
4
4
|
- Teams
|
5
5
|
summary: "List Teams"
|
6
6
|
operationId: listTeams
|
7
|
+
parameters:
|
8
|
+
- $ref: "#/components/parameters/after"
|
7
9
|
responses:
|
8
10
|
"404":
|
9
11
|
description: "Not Found"
|
10
12
|
"200":
|
11
13
|
description: "OK"
|
14
|
+
headers:
|
15
|
+
Pagination-Next:
|
16
|
+
$ref: "#/components/headers/PaginationNext"
|
17
|
+
Link:
|
18
|
+
$ref: "#/components/headers/Link"
|
12
19
|
content:
|
13
20
|
application/json:
|
14
21
|
schema:
|
15
|
-
type:
|
16
|
-
|
17
|
-
|
18
|
-
type: array
|
19
|
-
items:
|
20
|
-
$ref: "#/components/schemas/TeamAttributes"
|
21
|
-
has_more:
|
22
|
-
type: boolean
|
22
|
+
type: array
|
23
|
+
items:
|
24
|
+
$ref: "#/components/schemas/TeamAttributes"
|
23
25
|
/teams/{id}:
|
24
26
|
get:
|
25
27
|
tags:
|
@@ -44,6 +46,17 @@
|
|
44
46
|
operationId: updateTeam
|
45
47
|
parameters:
|
46
48
|
- $ref: "#/components/parameters/id"
|
49
|
+
requestBody:
|
50
|
+
description: "Information about updated fields in Team"
|
51
|
+
required: true
|
52
|
+
content:
|
53
|
+
application/json:
|
54
|
+
schema:
|
55
|
+
type: object
|
56
|
+
properties:
|
57
|
+
team:
|
58
|
+
type: object
|
59
|
+
$ref: "#/components/schemas/TeamParameters"
|
47
60
|
responses:
|
48
61
|
"404":
|
49
62
|
description: "Not Found"
|
@@ -52,4 +65,4 @@
|
|
52
65
|
content:
|
53
66
|
application/json:
|
54
67
|
schema:
|
55
|
-
$ref: "#/components/schemas/
|
68
|
+
$ref: "#/components/schemas/TeamAttributes"
|
@@ -4,22 +4,24 @@
|
|
4
4
|
- Users
|
5
5
|
summary: "List Users"
|
6
6
|
operationId: listUsers
|
7
|
+
parameters:
|
8
|
+
- $ref: "#/components/parameters/after"
|
7
9
|
responses:
|
8
10
|
"404":
|
9
11
|
description: "Not Found"
|
10
12
|
"200":
|
11
13
|
description: "OK"
|
14
|
+
headers:
|
15
|
+
Pagination-Next:
|
16
|
+
$ref: "#/components/headers/PaginationNext"
|
17
|
+
Link:
|
18
|
+
$ref: "#/components/headers/Link"
|
12
19
|
content:
|
13
20
|
application/json:
|
14
21
|
schema:
|
15
|
-
type:
|
16
|
-
|
17
|
-
|
18
|
-
type: array
|
19
|
-
items:
|
20
|
-
$ref: "#/components/schemas/UserAttributes"
|
21
|
-
has_more:
|
22
|
-
type: boolean
|
22
|
+
type: array
|
23
|
+
items:
|
24
|
+
$ref: "#/components/schemas/UserAttributes"
|
23
25
|
/users/{id}:
|
24
26
|
get:
|
25
27
|
tags:
|
@@ -44,6 +46,17 @@
|
|
44
46
|
operationId: updateUser
|
45
47
|
parameters:
|
46
48
|
- $ref: "#/components/parameters/id"
|
49
|
+
requestBody:
|
50
|
+
description: "Information about updated fields in User"
|
51
|
+
required: true
|
52
|
+
content:
|
53
|
+
application/json:
|
54
|
+
schema:
|
55
|
+
type: object
|
56
|
+
properties:
|
57
|
+
user:
|
58
|
+
type: object
|
59
|
+
$ref: "#/components/schemas/UserParameters"
|
47
60
|
responses:
|
48
61
|
"404":
|
49
62
|
description: "Not Found"
|
@@ -52,4 +65,4 @@
|
|
52
65
|
content:
|
53
66
|
application/json:
|
54
67
|
schema:
|
55
|
-
$ref: "#/components/schemas/
|
68
|
+
$ref: "#/components/schemas/UserAttributes"
|
@@ -81,6 +81,8 @@ en:
|
|
81
81
|
created: Platform Application was successfully created.
|
82
82
|
updated: Platform Application was successfully updated.
|
83
83
|
destroyed: Platform Application was successfully destroyed.
|
84
|
+
test_application_created: Test Platform Application was successfully created.
|
85
|
+
test_application_failure: You must provide the proper testing provision key to create a test application.
|
84
86
|
account:
|
85
87
|
platform:
|
86
88
|
applications: *applications
|
data/config/routes.rb
CHANGED
@@ -17,6 +17,10 @@ Rails.application.routes.draw do
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
if ENV["TESTING_PROVISION_KEY"].present?
|
21
|
+
get "/testing/provision", to: "account/platform/applications#provision"
|
22
|
+
end
|
23
|
+
|
20
24
|
namespace :api do
|
21
25
|
match "*version/openapi.yaml" => "open_api#index", :via => :get
|
22
26
|
|
@@ -1,4 +1,76 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require "scaffolding"
|
2
|
+
require "scaffolding/file_manipulator"
|
3
|
+
|
4
|
+
namespace :bullet_train do
|
5
|
+
namespace :api do
|
6
|
+
desc "Bump the current version of application's API"
|
7
|
+
task :bump_version do
|
8
|
+
# Calculate new version.
|
9
|
+
initializer_content = File.new("config/initializers/api.rb").readline
|
10
|
+
previous_version = initializer_content.scan(/v\d+/).pop
|
11
|
+
new_version = "v#{previous_version.scan(/\d+/).pop.to_i + 1}"
|
12
|
+
|
13
|
+
# Update initializer.
|
14
|
+
File.write("config/initializers/api.rb", initializer_content.gsub(previous_version, new_version))
|
15
|
+
|
16
|
+
[
|
17
|
+
"app/controllers/api/#{new_version}",
|
18
|
+
"app/views/api/#{new_version}",
|
19
|
+
"test/controllers/api/#{new_version}"
|
20
|
+
].each do |dir|
|
21
|
+
Dir.mkdir(dir)
|
22
|
+
end
|
23
|
+
|
24
|
+
files_to_update = [
|
25
|
+
"config/routes/api/#{previous_version}.rb",
|
26
|
+
Dir.glob("app/controllers/api/#{previous_version}/**/*.rb") +
|
27
|
+
Dir.glob("app/views/api/#{previous_version}/**/*.json.jbuilder") +
|
28
|
+
Dir.glob("test/controllers/api/#{previous_version}/**/*.rb")
|
29
|
+
].flatten
|
30
|
+
|
31
|
+
files_to_update.each do |file_name|
|
32
|
+
previous_file_contents = File.open(file_name).readlines
|
33
|
+
new_file_name = file_name.gsub(previous_version, new_version)
|
34
|
+
|
35
|
+
updated_file_contents = previous_file_contents.map do |line|
|
36
|
+
if line.match?(previous_version)
|
37
|
+
line.gsub(previous_version, new_version)
|
38
|
+
else
|
39
|
+
line.gsub("Api::#{previous_version.upcase}", "Api::#{new_version.upcase}")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# We can't create new files unless each directory under #{api/previous_version}
|
44
|
+
# has been created under the new api directory. For example, we have to create
|
45
|
+
# the `projects` directory before we can create the file `api/v2/projects/pages.json.jbuilder.`
|
46
|
+
new_version_dir, dir_hierarchy = new_file_name.split(/(?<=#{new_version})\//)
|
47
|
+
if dir_hierarchy.present? && dir_hierarchy.match?("/")
|
48
|
+
dir_hierarchy = dir_hierarchy.split("/")
|
49
|
+
dir_hierarchy.inject(new_version_dir) do |base, child_dir_or_file|
|
50
|
+
# Stop making new directories if the string has an extention like ".rb"
|
51
|
+
break if child_dir_or_file.match?(/\./)
|
52
|
+
|
53
|
+
new_hierarchy = "#{base}/#{child_dir_or_file}"
|
54
|
+
Dir.mkdir(new_hierarchy) unless Dir.exist?(new_hierarchy)
|
55
|
+
new_hierarchy
|
56
|
+
end
|
57
|
+
end
|
58
|
+
Scaffolding::FileManipulator.write(new_file_name, updated_file_contents)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Here we make sure config/api/#{new_version}.rb is called from within the main routes file.
|
62
|
+
previous_file_contents = File.open("config/routes.rb").readlines
|
63
|
+
updated_file_contents = previous_file_contents.map do |line|
|
64
|
+
if line.match?("draw \"api/#{previous_version}\"")
|
65
|
+
new_version_draw_line = line.gsub(previous_version, new_version)
|
66
|
+
line + new_version_draw_line
|
67
|
+
else
|
68
|
+
line
|
69
|
+
end
|
70
|
+
end
|
71
|
+
Scaffolding::FileManipulator.write("config/routes.rb", updated_file_contents)
|
72
|
+
|
73
|
+
puts "Finished bumping to #{new_version}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullet_train-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Culver
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: standard
|