graphql 2.6.6 → 2.6.8
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/generators/graphql/templates/schema.erb +2 -1
- data/lib/graphql/dashboard/application_controller.rb +5 -1
- data/lib/graphql/dashboard.rb +3 -0
- data/lib/graphql/dataloader/async_dataloader.rb +65 -32
- data/lib/graphql/execution/field_resolve_step.rb +2 -2
- data/lib/graphql/execution/interpreter.rb +6 -0
- data/lib/graphql/execution/runner.rb +1 -2
- data/lib/graphql/execution/selections_step.rb +4 -4
- data/lib/graphql/language/block_string.rb +6 -11
- data/lib/graphql/language/lexer.rb +5 -2
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language.rb +1 -1
- data/lib/graphql/schema/build_from_definition.rb +11 -3
- data/lib/graphql/schema/input_object.rb +2 -2
- data/lib/graphql/subscriptions/serialize.rb +10 -4
- 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: 12ae3a5cc9e399639228a8ccfc1654c7bcaf73a6c81339ad72f7ce90b159eb9d
|
|
4
|
+
data.tar.gz: a998bd1576174777857e365cbe67ffc7b68273bbaa17ad0cd1e609193254417f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2c95b6f2c0aec486d42170e970f59272ece87e9433c5ba8e3fff4838da802993f1de9fcaf3b2c2807fb3365674a7a915265c248470987bf0a9f47d13b90699d1
|
|
7
|
+
data.tar.gz: 03eb4c0fb7cddfd2cebce9a50ce3a9264f6473d4ca037e8a60ddb5d14b3f880a54c6b249958b2b58100dca9ea9b3ad9de04ad25cd333f0d00e620fd724ff7ff6
|
|
@@ -26,7 +26,8 @@ class <%= schema_name %> < GraphQL::Schema
|
|
|
26
26
|
raise(GraphQL::RequiredImplementationMissingError)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# Limit the size of incoming queries:
|
|
29
|
+
# Limit the depth and size of incoming queries:
|
|
30
|
+
max_depth(15)
|
|
30
31
|
max_query_string_tokens(5000)
|
|
31
32
|
|
|
32
33
|
# Stop validating when it encounters this many errors:
|
|
@@ -22,7 +22,11 @@ module Graphql
|
|
|
22
22
|
|
|
23
23
|
def schema_class
|
|
24
24
|
@schema_class ||= begin
|
|
25
|
-
|
|
25
|
+
configured_schemas = Array(request.path_parameters[:schema] || request.path_parameters["schema"])
|
|
26
|
+
schema_param = request.query_parameters["schema"]
|
|
27
|
+
schema_param = configured_schemas.find { |schema| schema.to_s == schema_param } if schema_param
|
|
28
|
+
schema_param ||= configured_schemas.first
|
|
29
|
+
|
|
26
30
|
case schema_param
|
|
27
31
|
when Class
|
|
28
32
|
schema_param
|
data/lib/graphql/dashboard.rb
CHANGED
|
@@ -10,6 +10,9 @@ module Graphql
|
|
|
10
10
|
# @example Mounting the Dashboard in your app
|
|
11
11
|
# mount GraphQL::Dashboard, at: "graphql_dashboard", schema: "MySchema"
|
|
12
12
|
#
|
|
13
|
+
# Pass an array to allow selecting from multiple schemas with the `schema` query parameter.
|
|
14
|
+
# mount GraphQL::Dashboard, at: "graphql_dashboard", schema: ["MySchema", "OtherSchema"]
|
|
15
|
+
#
|
|
13
16
|
# @example Authenticating the Dashboard with HTTP Basic Auth
|
|
14
17
|
# # config/initializers/graphql_dashboard.rb
|
|
15
18
|
# GraphQL::Dashboard.middleware.use(Rack::Auth::Basic) do |username, password|
|
|
@@ -4,6 +4,11 @@ module GraphQL
|
|
|
4
4
|
class Dataloader
|
|
5
5
|
class AsyncDataloader < Dataloader
|
|
6
6
|
def self.use(...)
|
|
7
|
+
install_graphql_methods
|
|
8
|
+
super
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.install_graphql_methods
|
|
7
12
|
if !Async::Task.method_defined?(:cancel)
|
|
8
13
|
Async::Task.alias_method(:cancel, :stop)
|
|
9
14
|
end
|
|
@@ -11,7 +16,6 @@ module GraphQL
|
|
|
11
16
|
Async::Task.attr_accessor(:graphql_async_dataloader_run)
|
|
12
17
|
Async::Task.attr_accessor(:graphql_async_dataloader_condition)
|
|
13
18
|
end
|
|
14
|
-
super
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def initialize(...)
|
|
@@ -54,7 +58,10 @@ module GraphQL
|
|
|
54
58
|
@running_tasks = nil
|
|
55
59
|
@tasks_channel = nil
|
|
56
60
|
@tasks_channel_task = nil
|
|
57
|
-
@
|
|
61
|
+
@activity = nil
|
|
62
|
+
@task_error = nil
|
|
63
|
+
@expected_resumes = 0
|
|
64
|
+
@mode = nil
|
|
58
65
|
|
|
59
66
|
@snoozed_jobs_condition = Async::Condition.new
|
|
60
67
|
@snoozed_sources_condition = Async::Condition.new
|
|
@@ -77,20 +84,41 @@ module GraphQL
|
|
|
77
84
|
@tasks_channel_task.cancel
|
|
78
85
|
end
|
|
79
86
|
|
|
80
|
-
def
|
|
81
|
-
@
|
|
82
|
-
|
|
87
|
+
def wait_for_activity
|
|
88
|
+
@activity.wait
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def quiesced?
|
|
92
|
+
@running_tasks.empty? && @tasks_channel.empty? && @expected_resumes == 0
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def has_pending_work?
|
|
96
|
+
@mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def has_bandwidth?
|
|
100
|
+
@mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Signalled tasks don't appear in any accounting until their first slice
|
|
104
|
+
# pushes `:resumed_task`, so they have to be counted at signal time:
|
|
105
|
+
def expect_resumes(count)
|
|
106
|
+
@expected_resumes = count
|
|
83
107
|
end
|
|
84
108
|
|
|
85
|
-
def
|
|
86
|
-
@
|
|
87
|
-
|
|
109
|
+
def check_error!
|
|
110
|
+
if (err = @task_error)
|
|
111
|
+
@task_error = nil
|
|
112
|
+
raise err
|
|
113
|
+
end
|
|
88
114
|
end
|
|
89
115
|
|
|
90
116
|
def new_queues(mode)
|
|
117
|
+
@mode = mode
|
|
91
118
|
@tasks_channel = Async::Queue.new(parent: @root_task)
|
|
92
|
-
@
|
|
93
|
-
@
|
|
119
|
+
@activity = Async::Condition.new
|
|
120
|
+
@task_error = nil
|
|
121
|
+
@expected_resumes = 0
|
|
94
122
|
@running_tasks = []
|
|
95
123
|
@tasks_channel_task = @root_task.async do |_t|
|
|
96
124
|
while ((msg, data) = @tasks_channel.wait)
|
|
@@ -99,23 +127,18 @@ module GraphQL
|
|
|
99
127
|
@running_tasks.push(data)
|
|
100
128
|
data.run
|
|
101
129
|
when :resumed_task
|
|
130
|
+
if @expected_resumes > 0
|
|
131
|
+
@expected_resumes -= 1
|
|
132
|
+
end
|
|
102
133
|
@running_tasks.push(data)
|
|
103
134
|
when :finished_task, :paused_task
|
|
104
135
|
@running_tasks.delete(data)
|
|
105
|
-
has_pending_work = mode == :jobs ? @jobs.any? : @dataloader.pending_sources.any?(&:pending?) # rubocop:disable Development/NoneWithoutBlockCop
|
|
106
|
-
if @running_tasks.empty?
|
|
107
|
-
@no_running_tasks.resolve(true)
|
|
108
|
-
has_bandwidth = mode == :jobs ? jobs_bandwidth? : sources_bandwidth?
|
|
109
|
-
if (!has_pending_work) || (!has_bandwidth)
|
|
110
|
-
@finished_all_tasks.resolve(true)
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
136
|
when :task_error
|
|
114
|
-
@
|
|
115
|
-
@finished_all_tasks.reject(data)
|
|
137
|
+
@task_error ||= data
|
|
116
138
|
else
|
|
117
139
|
raise ArgumentError, "Unknown tasks_channel action: #{msg.inspect}"
|
|
118
140
|
end
|
|
141
|
+
@activity.signal
|
|
119
142
|
end
|
|
120
143
|
end
|
|
121
144
|
end
|
|
@@ -240,19 +263,20 @@ module GraphQL
|
|
|
240
263
|
private
|
|
241
264
|
|
|
242
265
|
def run_queue(run, condition, mode)
|
|
243
|
-
|
|
266
|
+
opened_queues = false
|
|
244
267
|
|
|
245
|
-
if
|
|
246
|
-
|
|
268
|
+
if condition.waiting?
|
|
269
|
+
opened_queues = true
|
|
247
270
|
run.new_queues(mode)
|
|
271
|
+
run.expect_resumes(condition.instance_variable_get(:@ready).num_waiting)
|
|
248
272
|
condition.signal
|
|
249
273
|
end
|
|
250
274
|
|
|
251
|
-
|
|
252
|
-
|
|
275
|
+
loop do
|
|
276
|
+
pending_work = (mode == :jobs) ? (!run.jobs.empty? && run.jobs_bandwidth? ? run.jobs : nil) : (drain_pending_sources)
|
|
253
277
|
if pending_work
|
|
254
|
-
if
|
|
255
|
-
|
|
278
|
+
if opened_queues == false
|
|
279
|
+
opened_queues = true
|
|
256
280
|
run.new_queues(mode)
|
|
257
281
|
end
|
|
258
282
|
num_tasks = mode == :sources ? run.current_sources_fiber_limit : 1
|
|
@@ -262,14 +286,23 @@ module GraphQL
|
|
|
262
286
|
spawn_tasks(run, mode, condition, pending_work, num_tasks)
|
|
263
287
|
end
|
|
264
288
|
|
|
265
|
-
|
|
266
|
-
|
|
289
|
+
if !opened_queues
|
|
290
|
+
break
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
run.check_error!
|
|
267
294
|
|
|
268
|
-
|
|
269
|
-
|
|
295
|
+
if run.quiesced?
|
|
296
|
+
if !run.has_pending_work? || !run.has_bandwidth?
|
|
297
|
+
break
|
|
298
|
+
end
|
|
299
|
+
# Quiesced, but more work appeared - loop around to drain it.
|
|
300
|
+
else
|
|
301
|
+
run.wait_for_activity
|
|
302
|
+
end
|
|
270
303
|
end
|
|
271
304
|
ensure
|
|
272
|
-
if
|
|
305
|
+
if opened_queues
|
|
273
306
|
run.close_queues
|
|
274
307
|
end
|
|
275
308
|
end
|
|
@@ -130,7 +130,7 @@ module GraphQL
|
|
|
130
130
|
highest_nulled_depth = path.size
|
|
131
131
|
highest_list_depth = nil
|
|
132
132
|
current_field_step = self
|
|
133
|
-
while current_field_step
|
|
133
|
+
while current_field_step && current_field_step.field_definition
|
|
134
134
|
return_type = current_field_step.field_definition.type
|
|
135
135
|
if propagating_null && return_type.non_null?
|
|
136
136
|
highest_nulled_depth = current_field_step.path.size
|
|
@@ -147,7 +147,7 @@ module GraphQL
|
|
|
147
147
|
|
|
148
148
|
if highest_list_depth.nil? || highest_nulled_depth <= highest_list_depth
|
|
149
149
|
kill_field_step = self
|
|
150
|
-
while kill_field_step && highest_nulled_depth <= kill_field_step.path.size
|
|
150
|
+
while kill_field_step && kill_field_step.field_definition && highest_nulled_depth <= kill_field_step.path.size
|
|
151
151
|
kill_field_step.selections_step.killed = true
|
|
152
152
|
kill_field_step = kill_field_step.selections_step.field_resolve_step
|
|
153
153
|
end
|
|
@@ -119,6 +119,12 @@ module GraphQL
|
|
|
119
119
|
end
|
|
120
120
|
|
|
121
121
|
results
|
|
122
|
+
rescue SystemStackError => err
|
|
123
|
+
queries.map do |query|
|
|
124
|
+
schema.query_stack_error(query, err)
|
|
125
|
+
query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
|
|
126
|
+
query.result
|
|
127
|
+
end
|
|
122
128
|
rescue Exception
|
|
123
129
|
# TODO rescue at a higher level so it will catch errors in analysis, too
|
|
124
130
|
# Assign values here so that the query's `@executed` becomes true
|
|
@@ -5,7 +5,6 @@ module GraphQL
|
|
|
5
5
|
def initialize(multiplex)
|
|
6
6
|
@multiplex = multiplex
|
|
7
7
|
@schema = multiplex.schema
|
|
8
|
-
@steps_queue = []
|
|
9
8
|
@runtime_type_at = {}.compare_by_identity
|
|
10
9
|
@static_type_at = {}.compare_by_identity
|
|
11
10
|
@finalizers = nil
|
|
@@ -53,7 +52,7 @@ module GraphQL
|
|
|
53
52
|
@dataloader.append_job(step)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
|
-
attr_reader :
|
|
55
|
+
attr_reader :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
|
|
57
56
|
|
|
58
57
|
# @return [void]
|
|
59
58
|
def add_finalizer(query, result_value, key, finalizer)
|
|
@@ -21,17 +21,12 @@ module GraphQL
|
|
|
21
21
|
next
|
|
22
22
|
end
|
|
23
23
|
line_length = line.size
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
4
|
|
28
|
-
elsif line.match?(/\A[^ ]/)
|
|
29
|
-
0
|
|
30
|
-
else
|
|
31
|
-
line[/\A */].size
|
|
24
|
+
leading = 0
|
|
25
|
+
while leading < line_length && line.getbyte(leading) == 32
|
|
26
|
+
leading += 1
|
|
32
27
|
end
|
|
33
|
-
if
|
|
34
|
-
common_indent =
|
|
28
|
+
if leading < line_length && (common_indent.nil? || leading < common_indent)
|
|
29
|
+
common_indent = leading
|
|
35
30
|
end
|
|
36
31
|
end
|
|
37
32
|
|
|
@@ -41,7 +36,7 @@ module GraphQL
|
|
|
41
36
|
if idx == 0
|
|
42
37
|
next
|
|
43
38
|
else
|
|
44
|
-
line
|
|
39
|
+
line[0, common_indent] = ""
|
|
45
40
|
end
|
|
46
41
|
end
|
|
47
42
|
end
|
|
@@ -175,11 +175,14 @@ module GraphQL
|
|
|
175
175
|
end
|
|
176
176
|
|
|
177
177
|
def line_number
|
|
178
|
-
@scanner.string
|
|
178
|
+
@scanner.string.byteslice(0, @pos).b.count("\n".b) + 1
|
|
179
179
|
end
|
|
180
180
|
|
|
181
181
|
def column_number
|
|
182
|
-
@scanner.string
|
|
182
|
+
line_prefix = @scanner.string.byteslice(0, @pos)
|
|
183
|
+
line_prefix = line_prefix.b unless line_prefix.valid_encoding?
|
|
184
|
+
newline_index = line_prefix.rindex("\n")
|
|
185
|
+
newline_index ? line_prefix.length - newline_index : line_prefix.length + 1
|
|
183
186
|
end
|
|
184
187
|
|
|
185
188
|
def raise_parse_error(message, line = line_number, col = column_number)
|
|
@@ -304,7 +304,8 @@ module GraphQL
|
|
|
304
304
|
children_method_names.map { |m| "#{m}: NO_CHILDREN" } +
|
|
305
305
|
DEFAULT_INITIALIZE_OPTIONS
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
# Intern descriptions so identical strings across SDL documents share one frozen object.
|
|
308
|
+
assignments = scalar_method_names.map { |m| m == :description ? "@#{m} = #{m} && -#{m}" : "@#{m} = #{m}" } +
|
|
308
309
|
children_method_names.map { |m| "@#{m} = #{m}.freeze" }
|
|
309
310
|
|
|
310
311
|
if name.end_with?("Definition") && name != "FragmentDefinition"
|
data/lib/graphql/language.rb
CHANGED
|
@@ -82,6 +82,7 @@ module GraphQL
|
|
|
82
82
|
replace_late_bound_types_with_built_in(types)
|
|
83
83
|
|
|
84
84
|
schema_extensions = nil
|
|
85
|
+
definitions_by_name = nil
|
|
85
86
|
document.definitions.each do |definition|
|
|
86
87
|
case definition
|
|
87
88
|
when GraphQL::Language::Nodes::SchemaDefinition, GraphQL::Language::Nodes::DirectiveDefinition
|
|
@@ -101,9 +102,16 @@ module GraphQL
|
|
|
101
102
|
if prev_type.nil? || prev_type.is_a?(Schema::LateBoundType)
|
|
102
103
|
if definition.is_a?(GraphQL::Language::Nodes::ObjectTypeDefinition) || definition.is_a?(Language::Nodes::InterfaceTypeDefinition)
|
|
103
104
|
interface_names = definition.interfaces.map(&:name)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
if !interface_names.empty?
|
|
106
|
+
definitions_by_name ||= document.definitions.each_with_object({}) do |d, by_name|
|
|
107
|
+
by_name[d.name] ||= d if d.respond_to?(:name)
|
|
108
|
+
end
|
|
109
|
+
transitive_names = interface_names.map { |n| definitions_by_name[n]&.interfaces&.map(&:name) }
|
|
110
|
+
transitive_names.flatten!
|
|
111
|
+
transitive_names.compact!
|
|
112
|
+
else
|
|
113
|
+
transitive_names = interface_names
|
|
114
|
+
end
|
|
107
115
|
if !(missing_transitive_interfaces = transitive_names - interface_names).empty?
|
|
108
116
|
raise GraphQL::Schema::InvalidDocumentError, "type #{definition.name} is missing one or more transitive interface names: #{missing_transitive_interfaces.join(", ")}. Add them to the type's `implements` list and try again."
|
|
109
117
|
end
|
|
@@ -172,12 +172,12 @@ module GraphQL
|
|
|
172
172
|
types = ctx.types
|
|
173
173
|
|
|
174
174
|
if input.is_a?(Array)
|
|
175
|
-
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input
|
|
175
|
+
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input) })
|
|
176
176
|
end
|
|
177
177
|
|
|
178
178
|
if !(input.respond_to?(:to_h) || input.respond_to?(:to_unsafe_h))
|
|
179
179
|
# We're not sure it'll act like a hash, so reject it:
|
|
180
|
-
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input
|
|
180
|
+
return GraphQL::Query::InputValidationResult.from_problem(INVALID_OBJECT_MESSAGE % { object: JSON.generate(input) })
|
|
181
181
|
end
|
|
182
182
|
|
|
183
183
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
|
+
require "date"
|
|
2
3
|
require "set"
|
|
3
4
|
module GraphQL
|
|
4
5
|
class Subscriptions
|
|
@@ -10,6 +11,7 @@ module GraphQL
|
|
|
10
11
|
SYMBOL_KEYS_KEY = "__sym_keys__"
|
|
11
12
|
TIMESTAMP_KEY = "__timestamp__"
|
|
12
13
|
TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S.%N%z" # eg '2020-01-01 23:59:59.123456789+05:00'
|
|
14
|
+
TIMESTAMP_CLASS_NAMES = ["Date", "DateTime", "Time"].freeze
|
|
13
15
|
OPEN_STRUCT_KEY = "__ostruct__"
|
|
14
16
|
|
|
15
17
|
module_function
|
|
@@ -24,7 +26,7 @@ module GraphQL
|
|
|
24
26
|
# @param obj [Object] Some subscription-related data to dump
|
|
25
27
|
# @return [String] The stringified object
|
|
26
28
|
def dump(obj)
|
|
27
|
-
JSON.generate(dump_value(obj)
|
|
29
|
+
JSON.generate(dump_value(obj))
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
# This is for turning objects into subscription scopes.
|
|
@@ -72,15 +74,19 @@ module GraphQL
|
|
|
72
74
|
value[SYMBOL_KEY].to_sym
|
|
73
75
|
when TIMESTAMP_KEY
|
|
74
76
|
timestamp_class_name, *timestamp_args = value[TIMESTAMP_KEY]
|
|
75
|
-
timestamp_class =
|
|
76
|
-
|
|
77
|
+
timestamp_class = if TIMESTAMP_CLASS_NAMES.include?(timestamp_class_name)
|
|
78
|
+
Object.const_get(timestamp_class_name, false)
|
|
79
|
+
end
|
|
80
|
+
if defined?(ActiveSupport::TimeWithZone) && timestamp_class_name == ActiveSupport::TimeWithZone.name
|
|
77
81
|
zone_name, timestamp_s = timestamp_args
|
|
78
82
|
zone = ActiveSupport::TimeZone[zone_name]
|
|
79
83
|
raise "Zone #{zone_name} not found, unable to deserialize" unless zone
|
|
80
84
|
zone.strptime(timestamp_s, TIMESTAMP_FORMAT)
|
|
81
|
-
|
|
85
|
+
elsif timestamp_class
|
|
82
86
|
timestamp_s = timestamp_args.first
|
|
83
87
|
timestamp_class.strptime(timestamp_s, TIMESTAMP_FORMAT)
|
|
88
|
+
else
|
|
89
|
+
raise ArgumentError, "Unsupported timestamp class: #{timestamp_class_name.inspect}"
|
|
84
90
|
end
|
|
85
91
|
when OPEN_STRUCT_KEY
|
|
86
92
|
ostruct_values = load_value(value[OPEN_STRUCT_KEY])
|
data/lib/graphql/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: graphql
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.6.
|
|
4
|
+
version: 2.6.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Mosolgo
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-08-13 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|