graphql 2.2.13 → 2.3.0
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 +4 -4
- data/lib/graphql/analysis/ast/visitor.rb +8 -0
- data/lib/graphql/backtrace/inspect_result.rb +0 -12
- data/lib/graphql/language/document_from_schema_definition.rb +1 -1
- data/lib/graphql/language/lexer.rb +29 -28
- data/lib/graphql/language/parser.rb +1 -7
- data/lib/graphql/language.rb +37 -0
- data/lib/graphql/query/context.rb +30 -33
- data/lib/graphql/query.rb +1 -1
- data/lib/graphql/schema/build_from_definition.rb +3 -1
- data/lib/graphql/schema/loader.rb +2 -1
- data/lib/graphql/schema.rb +16 -1
- data/lib/graphql/tracing/platform_tracing.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- 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: 881a55a1017c82563e75cf9898d44be453c1329c849f60b9538fdbd0f0d4b630
|
|
4
|
+
data.tar.gz: e99efcbffe7cab713e9d5fa7156c1f3bb56752b10ae35f9c5b23e705a27f90da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c82107ac040dd40a8bfcf09f0abf23d5a4a40ef8ed68b3e6ff0918e3b4ff02c6ae7f9637fed578bbd78032bde8d61622b65ab8efeb3bd4e2538fc809e626a92a
|
|
7
|
+
data.tar.gz: e0eddc7d0562f9637ecb1707d690b7d4579373c6d8f030e09565a150a60c4272ebb4e5f06cefc5165f6babfde01e5fb8930b4f678d6c1e146df56999634cbc1f
|
|
@@ -118,8 +118,12 @@ module GraphQL
|
|
|
118
118
|
def on_inline_fragment(node, parent)
|
|
119
119
|
on_fragment_with_type(node) do
|
|
120
120
|
@path.push("...#{node.type ? " on #{node.type.name}" : ""}")
|
|
121
|
+
@skipping = @skip_stack.last || skip?(node)
|
|
122
|
+
@skip_stack << @skipping
|
|
123
|
+
|
|
121
124
|
call_on_enter_inline_fragment(node, parent)
|
|
122
125
|
super
|
|
126
|
+
@skipping = @skip_stack.pop
|
|
123
127
|
call_on_leave_inline_fragment(node, parent)
|
|
124
128
|
end
|
|
125
129
|
end
|
|
@@ -187,9 +191,13 @@ module GraphQL
|
|
|
187
191
|
|
|
188
192
|
def on_fragment_spread(node, parent)
|
|
189
193
|
@path.push("... #{node.name}")
|
|
194
|
+
@skipping = @skip_stack.last || skip?(node)
|
|
195
|
+
@skip_stack << @skipping
|
|
196
|
+
|
|
190
197
|
call_on_enter_fragment_spread(node, parent)
|
|
191
198
|
enter_fragment_spread_inline(node)
|
|
192
199
|
super
|
|
200
|
+
@skipping = @skip_stack.pop
|
|
193
201
|
leave_fragment_spread_inline(node)
|
|
194
202
|
call_on_leave_fragment_spread(node, parent)
|
|
195
203
|
@path.pop
|
|
@@ -16,12 +16,6 @@ module GraphQL
|
|
|
16
16
|
"[" +
|
|
17
17
|
obj.map { |v| inspect_truncated(v) }.join(", ") +
|
|
18
18
|
"]"
|
|
19
|
-
when Query::Context::SharedMethods
|
|
20
|
-
if obj.invalid_null?
|
|
21
|
-
"nil"
|
|
22
|
-
else
|
|
23
|
-
inspect_truncated(obj.value)
|
|
24
|
-
end
|
|
25
19
|
else
|
|
26
20
|
inspect_truncated(obj)
|
|
27
21
|
end
|
|
@@ -33,12 +27,6 @@ module GraphQL
|
|
|
33
27
|
"{...}"
|
|
34
28
|
when Array
|
|
35
29
|
"[...]"
|
|
36
|
-
when Query::Context::SharedMethods
|
|
37
|
-
if obj.invalid_null?
|
|
38
|
-
"nil"
|
|
39
|
-
else
|
|
40
|
-
inspect_truncated(obj.value)
|
|
41
|
-
end
|
|
42
30
|
when GraphQL::Execution::Lazy
|
|
43
31
|
"(unresolved)"
|
|
44
32
|
else
|
|
@@ -24,7 +24,7 @@ module GraphQL
|
|
|
24
24
|
@include_built_in_directives = include_built_in_directives
|
|
25
25
|
@include_one_of = false
|
|
26
26
|
|
|
27
|
-
schema_context = schema.context_class.new(query: nil,
|
|
27
|
+
schema_context = schema.context_class.new(query: nil, schema: schema, values: context)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
@warden = @schema.warden_class.new(
|
|
@@ -89,6 +89,8 @@ module GraphQL
|
|
|
89
89
|
"..."
|
|
90
90
|
elsif token_name == :STRING
|
|
91
91
|
string_value
|
|
92
|
+
elsif @scanner.matched_size.nil?
|
|
93
|
+
@scanner.peek(1)
|
|
92
94
|
else
|
|
93
95
|
token_value
|
|
94
96
|
end
|
|
@@ -107,29 +109,27 @@ module GraphQL
|
|
|
107
109
|
}
|
|
108
110
|
UTF_8 = /\\u(?:([\dAa-f]{4})|\{([\da-f]{4,})\})(?:\\u([\dAa-f]{4}))?/i
|
|
109
111
|
VALID_STRING = /\A(?:[^\\]|#{ESCAPES}|#{UTF_8})*\z/o
|
|
112
|
+
ESCAPED = /(?:#{ESCAPES}|#{UTF_8})/o
|
|
110
113
|
|
|
111
114
|
def string_value
|
|
112
115
|
str = token_value
|
|
113
116
|
is_block = str.start_with?('"""')
|
|
114
117
|
if is_block
|
|
115
118
|
str.gsub!(/\A"""|"""\z/, '')
|
|
119
|
+
return Language::BlockString.trim_whitespace(str)
|
|
116
120
|
else
|
|
117
121
|
str.gsub!(/\A"|"\z/, '')
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
if is_block
|
|
121
|
-
str = Language::BlockString.trim_whitespace(str)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
|
125
|
-
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
126
|
-
else
|
|
127
|
-
Lexer.replace_escaped_characters_in_place(str)
|
|
128
122
|
|
|
129
|
-
if !str.valid_encoding?
|
|
123
|
+
if !str.valid_encoding? || !str.match?(VALID_STRING)
|
|
130
124
|
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
131
125
|
else
|
|
132
|
-
str
|
|
126
|
+
Lexer.replace_escaped_characters_in_place(str)
|
|
127
|
+
|
|
128
|
+
if !str.valid_encoding?
|
|
129
|
+
raise_parse_error("Bad unicode escape in #{str.inspect}")
|
|
130
|
+
else
|
|
131
|
+
str
|
|
132
|
+
end
|
|
133
133
|
end
|
|
134
134
|
end
|
|
135
135
|
end
|
|
@@ -254,7 +254,7 @@ module GraphQL
|
|
|
254
254
|
STRING_ESCAPE = %r{[\\][\\/bfnrt]}
|
|
255
255
|
BLOCK_QUOTE = '"""'
|
|
256
256
|
ESCAPED_QUOTE = /\\"/;
|
|
257
|
-
STRING_CHAR = /#{ESCAPED_QUOTE}|[^"
|
|
257
|
+
STRING_CHAR = /#{ESCAPED_QUOTE}|[^"\\\n\r]|#{UNICODE_ESCAPE}|#{STRING_ESCAPE}/
|
|
258
258
|
QUOTED_STRING_REGEXP = %r{#{QUOTE} (?:#{STRING_CHAR})* #{QUOTE}}x
|
|
259
259
|
BLOCK_STRING_REGEXP = %r{
|
|
260
260
|
#{BLOCK_QUOTE}
|
|
@@ -299,24 +299,25 @@ module GraphQL
|
|
|
299
299
|
# Replace any escaped unicode or whitespace with the _actual_ characters
|
|
300
300
|
# To avoid allocating more strings, this modifies the string passed into it
|
|
301
301
|
def self.replace_escaped_characters_in_place(raw_string)
|
|
302
|
-
raw_string.gsub!(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
(
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
302
|
+
raw_string.gsub!(ESCAPED) do |matched_str|
|
|
303
|
+
if (point_str_1 = $1 || $2)
|
|
304
|
+
codepoint_1 = point_str_1.to_i(16)
|
|
305
|
+
if (codepoint_2 = $3)
|
|
306
|
+
codepoint_2 = codepoint_2.to_i(16)
|
|
307
|
+
if (codepoint_1 >= 0xD800 && codepoint_1 <= 0xDBFF) && # leading surrogate
|
|
308
|
+
(codepoint_2 >= 0xDC00 && codepoint_2 <= 0xDFFF) # trailing surrogate
|
|
309
|
+
# A surrogate pair
|
|
310
|
+
combined = ((codepoint_1 - 0xD800) * 0x400) + (codepoint_2 - 0xDC00) + 0x10000
|
|
311
|
+
[combined].pack('U'.freeze)
|
|
312
|
+
else
|
|
313
|
+
# Two separate code points
|
|
314
|
+
[codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
|
|
315
|
+
end
|
|
314
316
|
else
|
|
315
|
-
|
|
316
|
-
[codepoint_1].pack('U'.freeze) + [codepoint_2].pack('U'.freeze)
|
|
317
|
+
[codepoint_1].pack('U'.freeze)
|
|
317
318
|
end
|
|
318
319
|
else
|
|
319
|
-
[
|
|
320
|
+
ESCAPES_REPLACE[matched_str]
|
|
320
321
|
end
|
|
321
322
|
end
|
|
322
323
|
nil
|
|
@@ -739,13 +739,7 @@ module GraphQL
|
|
|
739
739
|
# token_value works for when the scanner matched something
|
|
740
740
|
# which is usually fine and it's good for it to be fast at that.
|
|
741
741
|
def debug_token_value
|
|
742
|
-
|
|
743
|
-
Lexer::Punctuation.const_get(token_name)
|
|
744
|
-
elsif token_name == :ELLIPSIS
|
|
745
|
-
"..."
|
|
746
|
-
else
|
|
747
|
-
@lexer.token_value
|
|
748
|
-
end
|
|
742
|
+
@lexer.debug_token_value(token_name)
|
|
749
743
|
end
|
|
750
744
|
end
|
|
751
745
|
end
|
data/lib/graphql/language.rb
CHANGED
|
@@ -12,6 +12,7 @@ require "graphql/language/static_visitor"
|
|
|
12
12
|
require "graphql/language/token"
|
|
13
13
|
require "graphql/language/visitor"
|
|
14
14
|
require "graphql/language/definition_slice"
|
|
15
|
+
require "strscan"
|
|
15
16
|
|
|
16
17
|
module GraphQL
|
|
17
18
|
module Language
|
|
@@ -33,5 +34,41 @@ module GraphQL
|
|
|
33
34
|
JSON.generate(value, quirks_mode: true)
|
|
34
35
|
end
|
|
35
36
|
end
|
|
37
|
+
|
|
38
|
+
# Returns a new string if any single-quoted newlines were escaped.
|
|
39
|
+
# Otherwise, returns `query_str` unchanged.
|
|
40
|
+
# @return [String]
|
|
41
|
+
def self.escape_single_quoted_newlines(query_str)
|
|
42
|
+
scanner = StringScanner.new(query_str)
|
|
43
|
+
inside_single_quoted_string = false
|
|
44
|
+
new_query_str = nil
|
|
45
|
+
while !scanner.eos?
|
|
46
|
+
if (match = scanner.scan(/(?:\\"|[^"\n\r]|""")+/m)) && new_query_str
|
|
47
|
+
new_query_str << match
|
|
48
|
+
elsif scanner.scan('"')
|
|
49
|
+
new_query_str && (new_query_str << '"')
|
|
50
|
+
inside_single_quoted_string = !inside_single_quoted_string
|
|
51
|
+
elsif scanner.scan("\n")
|
|
52
|
+
if inside_single_quoted_string
|
|
53
|
+
new_query_str ||= query_str[0, scanner.pos - 1]
|
|
54
|
+
new_query_str << '\\n'
|
|
55
|
+
else
|
|
56
|
+
new_query_str && (new_query_str << "\n")
|
|
57
|
+
end
|
|
58
|
+
elsif scanner.scan("\r")
|
|
59
|
+
if inside_single_quoted_string
|
|
60
|
+
new_query_str ||= query_str[0, scanner.pos - 1]
|
|
61
|
+
new_query_str << '\\r'
|
|
62
|
+
else
|
|
63
|
+
new_query_str && (new_query_str << "\r")
|
|
64
|
+
end
|
|
65
|
+
elsif scanner.eos?
|
|
66
|
+
break
|
|
67
|
+
else
|
|
68
|
+
raise ArgumentError, "Unmatchable string scanner segment: #{scanner.rest.inspect}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
new_query_str || query_str
|
|
72
|
+
end
|
|
36
73
|
end
|
|
37
74
|
end
|
|
@@ -6,36 +6,6 @@ module GraphQL
|
|
|
6
6
|
# Expose some query-specific info to field resolve functions.
|
|
7
7
|
# It delegates `[]` to the hash that's passed to `GraphQL::Query#initialize`.
|
|
8
8
|
class Context
|
|
9
|
-
module SharedMethods
|
|
10
|
-
# Return this value to tell the runtime
|
|
11
|
-
# to exclude this field from the response altogether
|
|
12
|
-
def skip
|
|
13
|
-
GraphQL::Execution::SKIP
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
# Add error at query-level.
|
|
17
|
-
# @param error [GraphQL::ExecutionError] an execution error
|
|
18
|
-
# @return [void]
|
|
19
|
-
def add_error(error)
|
|
20
|
-
if !error.is_a?(ExecutionError)
|
|
21
|
-
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
|
22
|
-
end
|
|
23
|
-
errors << error
|
|
24
|
-
nil
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# @example Print the GraphQL backtrace during field resolution
|
|
28
|
-
# puts ctx.backtrace
|
|
29
|
-
#
|
|
30
|
-
# @return [GraphQL::Backtrace] The backtrace for this point in query execution
|
|
31
|
-
def backtrace
|
|
32
|
-
GraphQL::Backtrace.new(self)
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def execution_errors
|
|
36
|
-
@execution_errors ||= ExecutionErrors.new(self)
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
9
|
|
|
40
10
|
class ExecutionErrors
|
|
41
11
|
def initialize(ctx)
|
|
@@ -59,7 +29,6 @@ module GraphQL
|
|
|
59
29
|
alias :push :add
|
|
60
30
|
end
|
|
61
31
|
|
|
62
|
-
include SharedMethods
|
|
63
32
|
extend Forwardable
|
|
64
33
|
|
|
65
34
|
# @return [Array<GraphQL::ExecutionError>] errors returned during execution
|
|
@@ -77,11 +46,10 @@ module GraphQL
|
|
|
77
46
|
# Make a new context which delegates key lookup to `values`
|
|
78
47
|
# @param query [GraphQL::Query] the query who owns this context
|
|
79
48
|
# @param values [Hash] A hash of arbitrary values which will be accessible at query-time
|
|
80
|
-
def initialize(query:, schema: query.schema, values
|
|
49
|
+
def initialize(query:, schema: query.schema, values:)
|
|
81
50
|
@query = query
|
|
82
51
|
@schema = schema
|
|
83
52
|
@provided_values = values || {}
|
|
84
|
-
@object = object
|
|
85
53
|
# Namespaced storage, where user-provided values are in `nil` namespace:
|
|
86
54
|
@storage = Hash.new { |h, k| h[k] = {} }
|
|
87
55
|
@storage[nil] = @provided_values
|
|
@@ -140,6 +108,35 @@ module GraphQL
|
|
|
140
108
|
end
|
|
141
109
|
end
|
|
142
110
|
|
|
111
|
+
# Return this value to tell the runtime
|
|
112
|
+
# to exclude this field from the response altogether
|
|
113
|
+
def skip
|
|
114
|
+
GraphQL::Execution::SKIP
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Add error at query-level.
|
|
118
|
+
# @param error [GraphQL::ExecutionError] an execution error
|
|
119
|
+
# @return [void]
|
|
120
|
+
def add_error(error)
|
|
121
|
+
if !error.is_a?(ExecutionError)
|
|
122
|
+
raise TypeError, "expected error to be a ExecutionError, but was #{error.class}"
|
|
123
|
+
end
|
|
124
|
+
errors << error
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# @example Print the GraphQL backtrace during field resolution
|
|
129
|
+
# puts ctx.backtrace
|
|
130
|
+
#
|
|
131
|
+
# @return [GraphQL::Backtrace] The backtrace for this point in query execution
|
|
132
|
+
def backtrace
|
|
133
|
+
GraphQL::Backtrace.new(self)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def execution_errors
|
|
137
|
+
@execution_errors ||= ExecutionErrors.new(self)
|
|
138
|
+
end
|
|
139
|
+
|
|
143
140
|
def current_path
|
|
144
141
|
current_runtime_state = Thread.current[:__graphql_runtime_info]
|
|
145
142
|
query_runtime_state = current_runtime_state && current_runtime_state[@query]
|
data/lib/graphql/query.rb
CHANGED
|
@@ -99,7 +99,7 @@ module GraphQL
|
|
|
99
99
|
# Even if `variables: nil` is passed, use an empty hash for simpler logic
|
|
100
100
|
variables ||= {}
|
|
101
101
|
@schema = schema
|
|
102
|
-
@context = schema.context_class.new(query: self,
|
|
102
|
+
@context = schema.context_class.new(query: self, values: context)
|
|
103
103
|
@warden = warden
|
|
104
104
|
@subscription_topic = subscription_topic
|
|
105
105
|
@root_value = root_value
|
|
@@ -120,10 +120,12 @@ module GraphQL
|
|
|
120
120
|
|
|
121
121
|
builder = self
|
|
122
122
|
|
|
123
|
+
found_types = types.values
|
|
123
124
|
schema_class = Class.new(schema_superclass) do
|
|
124
125
|
begin
|
|
125
126
|
# Add these first so that there's some chance of resolving late-bound types
|
|
126
|
-
|
|
127
|
+
add_type_and_traverse(found_types, root: false)
|
|
128
|
+
orphan_types(found_types.select { |t| t.respond_to?(:kind) && t.kind.object? })
|
|
127
129
|
query query_root_type
|
|
128
130
|
mutation mutation_root_type
|
|
129
131
|
subscription subscription_root_type
|
|
@@ -32,7 +32,8 @@ module GraphQL
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
Class.new(GraphQL::Schema) do
|
|
35
|
-
|
|
35
|
+
add_type_and_traverse(types.values, root: false)
|
|
36
|
+
orphan_types(types.values.select { |t| t.kind.object? })
|
|
36
37
|
directives(directives)
|
|
37
38
|
description(schema["description"])
|
|
38
39
|
|
data/lib/graphql/schema.rb
CHANGED
|
@@ -861,6 +861,17 @@ module GraphQL
|
|
|
861
861
|
def orphan_types(*new_orphan_types)
|
|
862
862
|
if new_orphan_types.any?
|
|
863
863
|
new_orphan_types = new_orphan_types.flatten
|
|
864
|
+
non_object_types = new_orphan_types.reject { |ot| ot.is_a?(Class) && ot < GraphQL::Schema::Object }
|
|
865
|
+
if non_object_types.any?
|
|
866
|
+
raise ArgumentError, <<~ERR
|
|
867
|
+
Only object type classes should be added as `orphan_types(...)`.
|
|
868
|
+
|
|
869
|
+
- Remove these no-op types from `orphan_types`: #{non_object_types.map { |t| "#{t.inspect} (#{t.kind.name})"}.join(", ")}
|
|
870
|
+
- See https://graphql-ruby.org/type_definitions/interfaces.html#orphan-types
|
|
871
|
+
|
|
872
|
+
To add other types to your schema, you might want `extra_types`: https://graphql-ruby.org/schema/definition.html#extra-types
|
|
873
|
+
ERR
|
|
874
|
+
end
|
|
864
875
|
add_type_and_traverse(new_orphan_types, root: false)
|
|
865
876
|
own_orphan_types.concat(new_orphan_types.flatten)
|
|
866
877
|
end
|
|
@@ -1129,7 +1140,11 @@ module GraphQL
|
|
|
1129
1140
|
}.freeze
|
|
1130
1141
|
end
|
|
1131
1142
|
|
|
1132
|
-
def tracer(new_tracer)
|
|
1143
|
+
def tracer(new_tracer, silence_deprecation_warning: false)
|
|
1144
|
+
if !silence_deprecation_warning
|
|
1145
|
+
warn("`Schema.tracer(#{new_tracer.inspect})` is deprecated; use module-based `trace_with` instead. See: https://graphql-ruby.org/queries/tracing.html")
|
|
1146
|
+
warn " #{caller(1, 1).first}"
|
|
1147
|
+
end
|
|
1133
1148
|
default_trace = trace_class_for(:default, build: true)
|
|
1134
1149
|
if default_trace.nil? || !(default_trace < GraphQL::Tracing::CallLegacyTracers)
|
|
1135
1150
|
trace_with(GraphQL::Tracing::CallLegacyTracers)
|
|
@@ -86,7 +86,7 @@ module GraphQL
|
|
|
86
86
|
else
|
|
87
87
|
warn("`use(#{self.name})` and `Tracing::PlatformTracing` are deprecated. Use a `trace_with(...)` module instead. More info: https://graphql-ruby.org/queries/tracing.html. Please open an issue on the GraphQL-Ruby repo if you want to discuss further!")
|
|
88
88
|
tracer = self.new(**options)
|
|
89
|
-
|
|
89
|
+
schema_defn.tracer(tracer, silence_deprecation_warning: true)
|
|
90
90
|
end
|
|
91
91
|
end
|
|
92
92
|
end
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Mosolgo
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-03-
|
|
11
|
+
date: 2024-03-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|