graphql_migrate_execution 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/lib/graphql_migrate_execution/action.rb +42 -0
- data/lib/graphql_migrate_execution/add_future.rb +9 -0
- data/lib/graphql_migrate_execution/analyze.rb +30 -0
- data/lib/graphql_migrate_execution/dataloader_all.rb +59 -0
- data/lib/graphql_migrate_execution/dataloader_batch.rb +8 -0
- data/lib/graphql_migrate_execution/dataloader_manual.rb +11 -0
- data/lib/graphql_migrate_execution/dataloader_shorthand.rb +25 -0
- data/lib/graphql_migrate_execution/do_nothing.rb +7 -0
- data/lib/graphql_migrate_execution/field_definition.rb +99 -0
- data/lib/graphql_migrate_execution/implicit.rb +13 -0
- data/lib/graphql_migrate_execution/not_implemented.rb +7 -0
- data/lib/graphql_migrate_execution/remove_legacy.rb +9 -0
- data/lib/graphql_migrate_execution/resolve_each.rb +17 -0
- data/lib/graphql_migrate_execution/resolve_static.rb +17 -0
- data/lib/graphql_migrate_execution/resolver_method.rb +105 -0
- data/lib/graphql_migrate_execution/strategy.rb +67 -0
- data/lib/graphql_migrate_execution/type_definition.rb +20 -0
- data/lib/graphql_migrate_execution/version.rb +3 -0
- data/lib/graphql_migrate_execution/visitor.rb +196 -0
- data/lib/graphql_migrate_execution.rb +51 -0
- data/readme.md +39 -0
- metadata +138 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 0f6058451a84d02d1fa53a18b8efcd287b31447eb9dade84f04d5ffc84d10d8e
|
|
4
|
+
data.tar.gz: dcf635144c19e61ac91bb1f0306db57be16bbbb5574e00f7eaa8463692f3ced0
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: edc61bb290ed152c015678797cf66a4f3356476e71c3c3a2a5be507f410dc02bd625f795b6dc91574d3815110e7da49214e062503d0f9e522c64aa3caf43827f
|
|
7
|
+
data.tar.gz: aa765c1ddb59287f57a71d2a06b4e73e819088d95f5849aa52d68c53212cc806e41bd3e097ac3e3bcebe058730380e2e737f2a469e7d7b82a3e2fb0fcd925292
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright 2026 Robert Mosolgo
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class Action
|
|
4
|
+
def initialize(migration, path, source)
|
|
5
|
+
@migration = migration
|
|
6
|
+
@path = path
|
|
7
|
+
@source = source
|
|
8
|
+
@type_definitions = Hash.new { |h, k| h[k] = TypeDefinition.new(k) }
|
|
9
|
+
@field_definitions_by_strategy = Hash.new { |h, k| h[k] = [] }
|
|
10
|
+
@total_field_definitions = 0
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
attr_reader :type_definitions
|
|
14
|
+
|
|
15
|
+
def run
|
|
16
|
+
parse_result = Prism.parse(@source, filepath: @path)
|
|
17
|
+
visitor = Visitor.new(@source, @type_definitions)
|
|
18
|
+
visitor.visit(parse_result.value)
|
|
19
|
+
@type_definitions.each do |name, type_defn|
|
|
20
|
+
type_defn.field_definitions.each do |f_name, f_defn|
|
|
21
|
+
@total_field_definitions += 1
|
|
22
|
+
f_defn.check_for_resolver_method
|
|
23
|
+
@field_definitions_by_strategy[f_defn.migration_strategy] << f_defn
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def call_method_on_strategy(method_name)
|
|
32
|
+
new_source = @source.dup
|
|
33
|
+
@field_definitions_by_strategy.each do |strategy_class, field_definitions|
|
|
34
|
+
strategy = strategy_class.new
|
|
35
|
+
field_definitions.each do |field_defn|
|
|
36
|
+
strategy.public_send(method_name, field_defn, new_source)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
new_source
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "irb"
|
|
3
|
+
module GraphqlMigrateExecution
|
|
4
|
+
class Analyze < Action
|
|
5
|
+
def run
|
|
6
|
+
super
|
|
7
|
+
message = "Found #{@total_field_definitions} field definitions:".dup
|
|
8
|
+
|
|
9
|
+
@field_definitions_by_strategy.each do |strategy_class, definitions|
|
|
10
|
+
message << "\n\n#{color("#{color(strategy_class.name.split("::").last, strategy_class.color)} (#{definitions.size})", :BOLD)}:\n"
|
|
11
|
+
if !@migration.skip_description
|
|
12
|
+
message << "\n#{strategy_class::DESCRIPTION.split("\n").map { |l| l.length > 0 ? " #{l}" : l }.join("\n")}\n"
|
|
13
|
+
end
|
|
14
|
+
max_path = definitions.map { |f| f.path.size }.max + 2
|
|
15
|
+
definitions.each do |field_defn|
|
|
16
|
+
name = field_defn.path.ljust(max_path)
|
|
17
|
+
message << "\n - #{name} (#{field_defn.resolve_mode.inspect} -> #{field_defn.resolve_mode_key.inspect}) @ #{@path}:#{field_defn.source_line}"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
message
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def color(str, color_or_colors)
|
|
27
|
+
IRB::Color.colorize(str, Array(color_or_colors), colorable: @migration.colorable)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class DataloaderAll < Strategy
|
|
4
|
+
DESCRIPTION = <<~DESC
|
|
5
|
+
These fields can be migrated to a `.load_all` call.
|
|
6
|
+
DESC
|
|
7
|
+
self.color = :GREEN
|
|
8
|
+
|
|
9
|
+
def add_future(field_definition, new_source)
|
|
10
|
+
inject_resolve_keyword(new_source, field_definition, :resolve_batch)
|
|
11
|
+
def_node = field_definition.resolver_method.node
|
|
12
|
+
call_node = def_node.body.body.first
|
|
13
|
+
case call_node.name
|
|
14
|
+
when :request, :load
|
|
15
|
+
load_arg_node = call_node.arguments.arguments.first
|
|
16
|
+
with_node = call_node.receiver
|
|
17
|
+
source_class_node, *source_args_nodes = with_node.arguments
|
|
18
|
+
when :dataload
|
|
19
|
+
source_class_node, *source_args_nodes, load_arg_node = call_node.arguments.arguments
|
|
20
|
+
else
|
|
21
|
+
raise ArgumentError, "Unexpected DataloadAll method name: #{def_node.name.inspect}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
old_load_arg_s = load_arg_node.slice
|
|
25
|
+
new_load_arg_s = case old_load_arg_s
|
|
26
|
+
when "object"
|
|
27
|
+
"objects"
|
|
28
|
+
when /object((\.|\[)[:a-zA-Z0-9_\.\"\'\[\]]+)/
|
|
29
|
+
call_chain = $1
|
|
30
|
+
if /^\.[a-z0-9_A-Z]+$/.match?(call_chain)
|
|
31
|
+
"objects.map(&:#{call_chain[1..-1]})"
|
|
32
|
+
else
|
|
33
|
+
"objects.map { |obj| obj#{call_chain} }"
|
|
34
|
+
end
|
|
35
|
+
else
|
|
36
|
+
raise ArgumentError, "Failed to transform Dataloader argument: #{old_load_arg_s.inspect}"
|
|
37
|
+
end
|
|
38
|
+
new_args = [
|
|
39
|
+
source_class_node.slice,
|
|
40
|
+
*source_args_nodes.map(&:slice),
|
|
41
|
+
new_load_arg_s
|
|
42
|
+
].join(", ")
|
|
43
|
+
|
|
44
|
+
old_method_source = def_node.slice_lines
|
|
45
|
+
new_method_source = old_method_source.sub(/def ([a-z_A-Z0-9]+)(\(|$| )/) do
|
|
46
|
+
is_adding_args = $2.size == 0
|
|
47
|
+
"def self.#{$1}#{is_adding_args ? "(" : $2}objects, context#{is_adding_args ? ")" : ", "}"
|
|
48
|
+
end
|
|
49
|
+
new_method_source.sub!(call_node.slice, "context.dataload_all(#{new_args})")
|
|
50
|
+
|
|
51
|
+
combined_new_source = new_method_source + "\n" + old_method_source
|
|
52
|
+
new_source.sub!(old_method_source, combined_new_source)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def remove_legacy(field_definition, new_source)
|
|
56
|
+
remove_resolver_method(new_source, field_definition)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class DataloaderManual < Strategy
|
|
4
|
+
DESCRIPTION = <<~DESC
|
|
5
|
+
These fields use Dataloader in a way that can't be automatically migrated. You'll have to migrate them manually.
|
|
6
|
+
If you have a lot of these, consider opening up an issue on GraphQL-Ruby -- maybe we can find a way to programmatically support them.
|
|
7
|
+
DESC
|
|
8
|
+
|
|
9
|
+
self.color = :RED
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class DataloaderShorthand < Strategy
|
|
4
|
+
DESCRIPTION = <<~DESC
|
|
5
|
+
These fields can use a `dataload: ...` configuration.
|
|
6
|
+
DESC
|
|
7
|
+
self.color = :GREEN
|
|
8
|
+
|
|
9
|
+
def add_future(field_definition, new_source)
|
|
10
|
+
rm = field_definition.resolver_method
|
|
11
|
+
if (da = rm.dataload_association)
|
|
12
|
+
dataload_config = "{ association: #{da.inspect} }"
|
|
13
|
+
elsif rm.source_arg_nodes.empty?
|
|
14
|
+
dataload_config = rm.source_class_node.full_name
|
|
15
|
+
else
|
|
16
|
+
dataload_config = "{ with: #{rm.source_class_node.full_name}, by: [#{rm.source_arg_nodes.map { |n| Visitor.source_for_constant_node(n) }.join(", ")}] }"
|
|
17
|
+
end
|
|
18
|
+
inject_field_keyword(new_source, field_definition, :dataload, dataload_config)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def remove_legacy(field_definition, new_source)
|
|
22
|
+
remove_resolver_method(new_source, field_definition)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class FieldDefinition
|
|
4
|
+
def initialize(type_definition, name, node)
|
|
5
|
+
@type_definition = type_definition
|
|
6
|
+
@name = name.to_sym
|
|
7
|
+
@node = node
|
|
8
|
+
|
|
9
|
+
@resolve_mode = nil
|
|
10
|
+
@hash_key = nil
|
|
11
|
+
@resolver = nil
|
|
12
|
+
@type_instance_method = nil
|
|
13
|
+
@object_direct_method = nil
|
|
14
|
+
@dig = nil
|
|
15
|
+
@already_migrated = nil
|
|
16
|
+
|
|
17
|
+
@resolver_method = nil
|
|
18
|
+
@unknown_options = []
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def migration_strategy
|
|
22
|
+
case resolve_mode
|
|
23
|
+
when nil, :implicit_resolve
|
|
24
|
+
Implicit
|
|
25
|
+
when :hash_key, :object_direct_method, :dig
|
|
26
|
+
DoNothing
|
|
27
|
+
when :already_migrated
|
|
28
|
+
case @already_migrated.keys.first
|
|
29
|
+
when :resolve_each
|
|
30
|
+
ResolveEach
|
|
31
|
+
when :resolve_static
|
|
32
|
+
ResolveStatic
|
|
33
|
+
when :resolve_batch
|
|
34
|
+
NotImplemented
|
|
35
|
+
else
|
|
36
|
+
raise ArgumentError, "Unexpected already_migrated: #{@already_migrated.inspect}"
|
|
37
|
+
end
|
|
38
|
+
when :type_instance_method
|
|
39
|
+
resolver_method.migration_strategy
|
|
40
|
+
when :resolver
|
|
41
|
+
DoNothing
|
|
42
|
+
else
|
|
43
|
+
raise "No migration strategy for resolve_mode #{@resolve_mode.inspect}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :name, :node, :unknown_options, :type_definition, :resolve_mode
|
|
48
|
+
|
|
49
|
+
def source
|
|
50
|
+
node.location.slice
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def future_resolve_shorthand
|
|
54
|
+
method_name = resolver_method.name
|
|
55
|
+
name == method_name ? true : method_name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
attr_writer :resolve_mode
|
|
59
|
+
|
|
60
|
+
attr_accessor :hash_key, :object_direct_method, :type_instance_method, :resolver, :dig, :already_migrated
|
|
61
|
+
|
|
62
|
+
def path
|
|
63
|
+
@path ||= "#{type_definition.name}.#{@name}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def source_line
|
|
67
|
+
@node.location.start_line
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolver_method
|
|
71
|
+
case @resolver_method
|
|
72
|
+
when nil
|
|
73
|
+
method_name = @type_instance_method || @name
|
|
74
|
+
@resolver_method = @type_definition.resolver_methods[method_name] || :NOT_FOUND
|
|
75
|
+
resolver_method
|
|
76
|
+
when :NOT_FOUND
|
|
77
|
+
nil
|
|
78
|
+
else
|
|
79
|
+
@resolver_method
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def implicit_resolve
|
|
84
|
+
@name
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def resolve_mode_key
|
|
88
|
+
resolve_mode && public_send(resolve_mode)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def check_for_resolver_method
|
|
92
|
+
if resolve_mode.nil? && (resolver_method)
|
|
93
|
+
@resolve_mode = :type_instance_method
|
|
94
|
+
@type_instance_method = @name
|
|
95
|
+
end
|
|
96
|
+
nil
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class Implicit < Strategy
|
|
4
|
+
DESCRIPTION = <<~DESC
|
|
5
|
+
These fields use GraphQL-Ruby's default, implicit resolution behavior. It's changing in the future, please audit these fields and choose a migration strategy:
|
|
6
|
+
|
|
7
|
+
- `--preserve-implicit`: Don't add any new configuration; use GraphQL-Ruby's future direct method send behavior (ie `object.public_send(field_name, **arguments)`)
|
|
8
|
+
- `--shim-implicit`: Add a method to preserve GraphQL-Ruby's previous dynamic implicit behavior (ie, checking for `respond_to?` and `key?`)
|
|
9
|
+
DESC
|
|
10
|
+
|
|
11
|
+
self.color = :YELLOW
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class NotImplemented < Strategy
|
|
4
|
+
DESCRIPTION = "GraphQL-Ruby doesn't have a migration strategy for these fields. Automated migration may be possible -- please open an issue on GitHub with the source for these fields to investigate."
|
|
5
|
+
self.color = :RED
|
|
6
|
+
end
|
|
7
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class ResolveEach < Strategy
|
|
4
|
+
DESCRIPTION = "These can be converted with `resolve_each:`. Dataloader was not detected in these resolver methods."
|
|
5
|
+
self.color = :GREEN
|
|
6
|
+
|
|
7
|
+
def add_future(field_definition, new_source)
|
|
8
|
+
inject_resolve_keyword(new_source, field_definition, :resolve_each)
|
|
9
|
+
replace_resolver_method(new_source, field_definition, "object, context")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def remove_legacy(field_definition, new_source)
|
|
13
|
+
remove_field_keyword(new_source, field_definition, :resolver_method)
|
|
14
|
+
remove_resolver_method(new_source, field_definition)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class ResolveStatic < Strategy
|
|
4
|
+
DESCRIPTION = "These can be converted with `resolve_static:`. Dataloader was not detected in these resolver methods."
|
|
5
|
+
self.color = :GREEN
|
|
6
|
+
|
|
7
|
+
def add_future(field_definition, new_source)
|
|
8
|
+
inject_resolve_keyword(new_source, field_definition, :resolve_static)
|
|
9
|
+
replace_resolver_method(new_source, field_definition, "context")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def remove_legacy(field_definition, new_source)
|
|
13
|
+
remove_field_keyword(new_source, field_definition, :resolver_method)
|
|
14
|
+
remove_resolver_method(new_source, field_definition)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class ResolverMethod
|
|
4
|
+
def initialize(name, node)
|
|
5
|
+
@name = name
|
|
6
|
+
@node = node
|
|
7
|
+
@parameter_names = if node.parameters
|
|
8
|
+
node.parameters.keywords.map(&:name)
|
|
9
|
+
else
|
|
10
|
+
[]
|
|
11
|
+
end
|
|
12
|
+
@self_sends = Set.new
|
|
13
|
+
@calls_object = false
|
|
14
|
+
@calls_context = false
|
|
15
|
+
@calls_class = false
|
|
16
|
+
@calls_dataloader = false
|
|
17
|
+
@dataloader_call = false
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_reader :name, :node, :parameter_names, :self_sends
|
|
21
|
+
|
|
22
|
+
attr_reader :source_class_node, :source_arg_nodes, :load_arg_node, :dataload_association
|
|
23
|
+
|
|
24
|
+
attr_accessor :calls_object, :calls_context, :calls_class, :calls_dataloader
|
|
25
|
+
|
|
26
|
+
attr_accessor :dataloader_call
|
|
27
|
+
|
|
28
|
+
def source
|
|
29
|
+
node.location.slice_lines
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def migration_strategy
|
|
33
|
+
calls_to_self = self_sends.to_a
|
|
34
|
+
if @calls_context
|
|
35
|
+
calls_to_self.delete(:context)
|
|
36
|
+
end
|
|
37
|
+
if @calls_object
|
|
38
|
+
calls_to_self.delete(:object)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
calls_to_self.delete(:dataloader)
|
|
42
|
+
calls_to_self.delete(:dataload_association)
|
|
43
|
+
calls_to_self.delete(:dataload_record)
|
|
44
|
+
calls_to_self.delete(:dataload)
|
|
45
|
+
|
|
46
|
+
# Global-ish methods:
|
|
47
|
+
calls_to_self.delete(:raise)
|
|
48
|
+
|
|
49
|
+
# Locals:
|
|
50
|
+
calls_to_self -= @parameter_names
|
|
51
|
+
|
|
52
|
+
if calls_to_self.empty?
|
|
53
|
+
if calls_dataloader
|
|
54
|
+
if !dataloader_call
|
|
55
|
+
return DataloaderManual
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
call_node = node.body.body.first
|
|
59
|
+
case call_node.name
|
|
60
|
+
when :dataload
|
|
61
|
+
@source_class_node = call_node.arguments.arguments.first
|
|
62
|
+
@source_arg_nodes = call_node.arguments.arguments[1...-1]
|
|
63
|
+
@load_arg_node = call_node.arguments.arguments.last
|
|
64
|
+
when :dataload_association
|
|
65
|
+
if (assoc_args = call_node.arguments.arguments).size == 1 &&
|
|
66
|
+
((assoc_arg = assoc_args.first).is_a?(Prism::SymbolNode))
|
|
67
|
+
assoc_sym = assoc_arg.unescaped.to_sym
|
|
68
|
+
@dataload_association = assoc_sym == name ? true : assoc_sym
|
|
69
|
+
end
|
|
70
|
+
else
|
|
71
|
+
if (source_call = call_node.receiver) # eg dataloader.with(...).load(...)
|
|
72
|
+
@source_class_node = source_call.arguments.arguments.first
|
|
73
|
+
@source_arg_nodes = source_call.arguments.arguments[1..-1]
|
|
74
|
+
@load_arg_node = call_node.arguments.arguments.last
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
input_is_object = @load_arg_node.is_a?(Prism::CallNode) && @load_arg_node.name == :object
|
|
79
|
+
# Guess whether these args are free of runtime context:
|
|
80
|
+
shortcutable_source_args = @source_arg_nodes && (@source_arg_nodes.empty? || (@source_arg_nodes.all? { |a| Visitor.constant_node?(a) }))
|
|
81
|
+
if @source_class_node.is_a?(Prism::ConstantPathNode) && shortcutable_source_args && input_is_object
|
|
82
|
+
DataloaderShorthand
|
|
83
|
+
else
|
|
84
|
+
case call_node.name
|
|
85
|
+
when :load, :request, :dataload
|
|
86
|
+
DataloaderAll
|
|
87
|
+
when :load_all, :request_all, :dataload_record
|
|
88
|
+
DataloaderBatch
|
|
89
|
+
when :dataload_association
|
|
90
|
+
DataloaderShorthand
|
|
91
|
+
else
|
|
92
|
+
DataloaderManual
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
elsif calls_object
|
|
96
|
+
ResolveEach
|
|
97
|
+
else
|
|
98
|
+
ResolveStatic
|
|
99
|
+
end
|
|
100
|
+
else
|
|
101
|
+
NotImplemented
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class Strategy
|
|
4
|
+
def add_future(field_definition, new_source)
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def remove_legacy(field_definition, new_source)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
attr_accessor :color
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def inject_resolve_keyword(new_source, field_definition, keyword)
|
|
17
|
+
value = field_definition.future_resolve_shorthand.inspect
|
|
18
|
+
inject_field_keyword(new_source, field_definition, keyword, value)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def inject_field_keyword(new_source, field_definition, keyword, value)
|
|
22
|
+
field_definition_source = field_definition.source
|
|
23
|
+
new_definition_source = if field_definition_source[/ [a-z_]+:/] # Does it already have keywords?
|
|
24
|
+
field_definition_source.sub(/(field.+?)((?: do)|(?: {)|$)/, "\\1, #{keyword}: #{value}\\2")
|
|
25
|
+
else
|
|
26
|
+
field_definition_source + ", #{keyword}: #{value}"
|
|
27
|
+
end
|
|
28
|
+
new_source.sub!(field_definition_source, new_definition_source)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def remove_field_keyword(new_source, field_definition, keyword)
|
|
33
|
+
field_definition_source = field_definition.source
|
|
34
|
+
new_definition_source = field_definition_source.sub(/, #{keyword}: \S+(,|$)/, "\\1")
|
|
35
|
+
new_source.sub!(field_definition_source, new_definition_source)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def replace_resolver_method(new_source, field_definition, new_params)
|
|
39
|
+
resolver_method = field_definition.resolver_method
|
|
40
|
+
method_name = resolver_method.name
|
|
41
|
+
old_method = resolver_method.source
|
|
42
|
+
new_class_method = old_method
|
|
43
|
+
.sub("def ", 'def self.')
|
|
44
|
+
|
|
45
|
+
if resolver_method.parameter_names.empty?
|
|
46
|
+
new_class_method.sub!(method_name.to_s, "#{method_name}(#{new_params})")
|
|
47
|
+
else
|
|
48
|
+
new_class_method.sub!("def self.#{method_name}(", "def self.#{method_name}(#{new_params}, ")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
old_lines = old_method.split("\n")
|
|
52
|
+
new_body = old_lines.first[/^ +/] + " self.class.#{method_name}(#{new_params}#{resolver_method.parameter_names.map { |n| ", #{n}: #{n}"}.join})"
|
|
53
|
+
new_inst_method = [old_lines.first, new_body, old_lines.last].join("\n")
|
|
54
|
+
|
|
55
|
+
new_double_definition = new_class_method + "\n" + new_inst_method + "\n"
|
|
56
|
+
new_source.sub!(old_method, new_double_definition)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def remove_resolver_method(new_source, field_definition)
|
|
60
|
+
src_pattern = /(\n*)(#{Regexp.quote(field_definition.resolver_method.source)})(\n*)/
|
|
61
|
+
new_source.sub!(src_pattern) do
|
|
62
|
+
# $2 includes a newline, too
|
|
63
|
+
"#{$1.length > 1 ? "\n" : ""}#{$3.length > 0 ? "\n" : ""}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class TypeDefinition
|
|
4
|
+
def initialize(name)
|
|
5
|
+
@name = name
|
|
6
|
+
@field_definitions = {}
|
|
7
|
+
@resolver_methods = {}
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
attr_reader :resolver_methods, :name, :field_definitions
|
|
11
|
+
|
|
12
|
+
def field_definition(name, node)
|
|
13
|
+
@field_definitions[name] = FieldDefinition.new(self, name, node)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def resolver_method(name, node)
|
|
17
|
+
@resolver_methods[name] = ResolverMethod.new(name, node)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphqlMigrateExecution
|
|
3
|
+
class Visitor < Prism::Visitor
|
|
4
|
+
def initialize(source, type_definitions)
|
|
5
|
+
@source = source
|
|
6
|
+
@type_definitions = type_definitions
|
|
7
|
+
@type_definition_stack = []
|
|
8
|
+
@current_field_definition = nil
|
|
9
|
+
@current_resolver_method = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def visit_class_node(node)
|
|
13
|
+
if node.superclass
|
|
14
|
+
td = @type_definitions[node.name]
|
|
15
|
+
@type_definition_stack << td
|
|
16
|
+
end
|
|
17
|
+
super
|
|
18
|
+
ensure
|
|
19
|
+
if td
|
|
20
|
+
@type_definition_stack.pop
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def visit_module_node(node)
|
|
25
|
+
td = @type_definitions[node.name]
|
|
26
|
+
@type_definition_stack << td
|
|
27
|
+
super
|
|
28
|
+
ensure
|
|
29
|
+
@type_definition_stack.pop
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def visit_keyword_hash_node(node)
|
|
33
|
+
if @current_field_definition
|
|
34
|
+
node.elements.each do |assoc|
|
|
35
|
+
if assoc.key.is_a?(Prism::SymbolNode)
|
|
36
|
+
case assoc.key.unescaped
|
|
37
|
+
when "hash_key"
|
|
38
|
+
@current_field_definition.resolve_mode ||= :hash_key
|
|
39
|
+
@current_field_definition.hash_key = get_keyword_value(assoc.value)
|
|
40
|
+
when "resolver"
|
|
41
|
+
@current_field_definition.resolve_mode ||= :resolver
|
|
42
|
+
@current_field_definition.resolver = get_keyword_value(assoc.value)
|
|
43
|
+
when "method"
|
|
44
|
+
@current_field_definition.resolve_mode ||= :object_direct_method
|
|
45
|
+
@current_field_definition.object_direct_method = get_keyword_value(assoc.value)
|
|
46
|
+
when "resolver_method"
|
|
47
|
+
@current_field_definition.resolve_mode ||= :type_instance_method
|
|
48
|
+
@current_field_definition.type_instance_method = get_keyword_value(assoc.value)
|
|
49
|
+
when "dig"
|
|
50
|
+
@current_field_definition.resolve_mode ||= :dig
|
|
51
|
+
@current_field_definition.dig = get_keyword_value(assoc.value)
|
|
52
|
+
when "resolve_each", "resolve_static", "resolve_batch"
|
|
53
|
+
# These should override any other keywords that are discovered
|
|
54
|
+
@current_field_definition.resolve_mode = :already_migrated
|
|
55
|
+
@current_field_definition.already_migrated = { assoc.key.unescaped.to_sym => get_keyword_value(assoc.value) }
|
|
56
|
+
else
|
|
57
|
+
# fallback_value, connection, extensions, extras, resolver, mutation, subscription
|
|
58
|
+
@current_field_definition.unknown_options << assoc.key.unescaped
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
super
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def visit_call_node(node)
|
|
67
|
+
if node.receiver.nil? && node.name == :field
|
|
68
|
+
first_arg = node.arguments.arguments.first # rubocop:disable Development/ContextIsPassedCop
|
|
69
|
+
if first_arg.is_a?(Prism::SymbolNode)
|
|
70
|
+
field_name = first_arg.unescaped
|
|
71
|
+
td = @type_definition_stack.last
|
|
72
|
+
@current_field_definition = td.field_definition(field_name, node)
|
|
73
|
+
else
|
|
74
|
+
warn "GraphQL-Ruby warning: Skipping unrecognized field definition: #{node.inspect}"
|
|
75
|
+
end
|
|
76
|
+
elsif @current_resolver_method
|
|
77
|
+
if node.receiver.nil? || node.receiver.is_a?(Prism::SelfNode)
|
|
78
|
+
@current_resolver_method.self_sends.add(node.name)
|
|
79
|
+
case node.name
|
|
80
|
+
when :object
|
|
81
|
+
@current_resolver_method.calls_object = true
|
|
82
|
+
when :context
|
|
83
|
+
@current_resolver_method.calls_context = true
|
|
84
|
+
when :class
|
|
85
|
+
@current_resolver_method.calls_class = true
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
case node.name
|
|
90
|
+
when :dataloader, :dataload, :dataload_association, :dataload_record, :dataload_all
|
|
91
|
+
@current_resolver_method.calls_dataloader = true
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
super
|
|
95
|
+
ensure
|
|
96
|
+
if td
|
|
97
|
+
@current_field_definition = nil
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def visit_def_node(node)
|
|
102
|
+
if node.receiver.nil?
|
|
103
|
+
td = @type_definition_stack.last
|
|
104
|
+
@current_resolver_method = td.resolver_method(node.name, node)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
body = node.body.body
|
|
108
|
+
if body.length == 1 && (call_node = body.first).is_a?(Prism::CallNode)
|
|
109
|
+
case call_node.name
|
|
110
|
+
when :load, :request, :load_all, :request_all
|
|
111
|
+
if (call_node2 = call_node.receiver).is_a?(Prism::CallNode) && call_node2.name == :with
|
|
112
|
+
@current_resolver_method.dataloader_call = true
|
|
113
|
+
end
|
|
114
|
+
when :dataload_record, :dataload_association, :dataload
|
|
115
|
+
@current_resolver_method.dataloader_call = true
|
|
116
|
+
else
|
|
117
|
+
# not a single dataloader call
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
super
|
|
121
|
+
ensure
|
|
122
|
+
@current_resolver_method = nil
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def get_keyword_value(value_node)
|
|
128
|
+
case value_node
|
|
129
|
+
when Prism::SymbolNode
|
|
130
|
+
value_node.unescaped.to_sym
|
|
131
|
+
when Prism::StringNode
|
|
132
|
+
value_node.unescaped
|
|
133
|
+
when Prism::IntegerNode, Prism::FloatNode
|
|
134
|
+
value_node.value
|
|
135
|
+
when Prism::TrueNode
|
|
136
|
+
true
|
|
137
|
+
when Prism::FalseNode
|
|
138
|
+
false
|
|
139
|
+
when Prism::ConstantPathNode, Prism::ConstantReadNode
|
|
140
|
+
value_node.full_name
|
|
141
|
+
when Prism::CallNode
|
|
142
|
+
:DYNAMIC_CALL_NODE
|
|
143
|
+
when Prism::ArrayNode
|
|
144
|
+
value_node.elements.map { |n| get_keyword_value(n) }
|
|
145
|
+
when Prism::NilNode
|
|
146
|
+
"nil"
|
|
147
|
+
else
|
|
148
|
+
# nil, constants, `self` ...?
|
|
149
|
+
raise ArgumentError, "GraphQL-MigrateExecution can't parse this keyword argument yet, but it could. Please open an issue on GraphQL-Ruby with this error message (node class: #{value_node.class})\n\n#{value_node.inspect}"
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def self.constant_node?(node)
|
|
154
|
+
case node
|
|
155
|
+
when Prism::ConstantPathNode,
|
|
156
|
+
Prism::ConstantReadNode,
|
|
157
|
+
Prism::StringNode,
|
|
158
|
+
Prism::SymbolNode,
|
|
159
|
+
Prism::IntegerNode,
|
|
160
|
+
Prism::FloatNode,
|
|
161
|
+
Prism::TrueNode,
|
|
162
|
+
Prism::FalseNode,
|
|
163
|
+
Prism::NilNode
|
|
164
|
+
true
|
|
165
|
+
when Prism::ArrayNode
|
|
166
|
+
node.elements.all? { |n| constant_node?(n) }
|
|
167
|
+
else
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def self.source_for_constant_node(value_node)
|
|
173
|
+
case value_node
|
|
174
|
+
when Prism::SymbolNode
|
|
175
|
+
":#{value_node.unescaped}"
|
|
176
|
+
when Prism::StringNode
|
|
177
|
+
value_node.unescaped.inspect
|
|
178
|
+
when Prism::IntegerNode, Prism::FloatNode
|
|
179
|
+
value_node.value.inspect
|
|
180
|
+
when Prism::TrueNode
|
|
181
|
+
"true"
|
|
182
|
+
when Prism::FalseNode
|
|
183
|
+
"false"
|
|
184
|
+
when Prism::ConstantPathNode, Prism::ConstantReadNode
|
|
185
|
+
value_node.full_name
|
|
186
|
+
when Prism::ArrayNode
|
|
187
|
+
value_node.elements.map { |n| get_keyword_value(n) }
|
|
188
|
+
when Prism::NilNode
|
|
189
|
+
"nil"
|
|
190
|
+
else
|
|
191
|
+
# nil, constants, `self` ...?
|
|
192
|
+
raise ArgumentError, "GraphQL-MigrateExecution can't parse this keyword argument yet, but it could. Please open an issue on GraphQL-Ruby with this error message (node class: #{value_node.class})\n\n#{value_node.inspect}"
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "graphql_migrate_execution/action"
|
|
3
|
+
require "graphql_migrate_execution/add_future"
|
|
4
|
+
require "graphql_migrate_execution/remove_legacy"
|
|
5
|
+
require "graphql_migrate_execution/analyze"
|
|
6
|
+
require "graphql_migrate_execution/field_definition"
|
|
7
|
+
require "graphql_migrate_execution/resolver_method"
|
|
8
|
+
require "graphql_migrate_execution/type_definition"
|
|
9
|
+
require "graphql_migrate_execution/visitor"
|
|
10
|
+
require "graphql_migrate_execution/strategy"
|
|
11
|
+
require "graphql_migrate_execution/implicit"
|
|
12
|
+
require "graphql_migrate_execution/do_nothing"
|
|
13
|
+
require "graphql_migrate_execution/resolve_each"
|
|
14
|
+
require "graphql_migrate_execution/resolve_static"
|
|
15
|
+
require "graphql_migrate_execution/not_implemented"
|
|
16
|
+
require "graphql_migrate_execution/dataloader_all"
|
|
17
|
+
require "graphql_migrate_execution/dataloader_batch"
|
|
18
|
+
require "graphql_migrate_execution/dataloader_manual"
|
|
19
|
+
require "graphql_migrate_execution/dataloader_shorthand"
|
|
20
|
+
require "graphql_migrate_execution/not_implemented"
|
|
21
|
+
require "irb"
|
|
22
|
+
|
|
23
|
+
module GraphqlMigrateExecution
|
|
24
|
+
class Migration
|
|
25
|
+
def initialize(glob, concise: false, migrate: false, cleanup: false, only: nil, implicit: nil, colorable: IRB::Color.colorable?)
|
|
26
|
+
@glob = glob
|
|
27
|
+
@skip_description = concise
|
|
28
|
+
@colorable = colorable
|
|
29
|
+
@only = only
|
|
30
|
+
@implicit = implicit
|
|
31
|
+
@action_class = if migrate
|
|
32
|
+
AddFuture
|
|
33
|
+
elsif cleanup
|
|
34
|
+
RemoveLegacy
|
|
35
|
+
else
|
|
36
|
+
Analyze
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
attr_reader :skip_description, :colorable
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run
|
|
44
|
+
Dir.glob(@glob).each do |filepath|
|
|
45
|
+
source = File.read(filepath)
|
|
46
|
+
file_migrate = @action_class.new(self, filepath, source)
|
|
47
|
+
puts file_migrate.run
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/readme.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# GraphqlMigrateExecution
|
|
2
|
+
|
|
3
|
+
[](https://github.com/rmosolgo/graphql_migrate_execution/actions/workflows/ci.yaml) [](https://badge.fury.io/rb/graphql_migrate_execution)
|
|
4
|
+
|
|
5
|
+
A command-line development tool to update your Ruby source code to support [GraphQL::Execution::Next](https://graphql-ruby.org/execution/next), then clean up unused legacy configs after you don't need them anymore.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
bundle add graphql_migrate_execution
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Use
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Usage: graphql_migrate_execution glob [options]
|
|
17
|
+
|
|
18
|
+
A development tool for adopting GraphQL-Ruby's new runtime module, GraphQL::Execution::Next
|
|
19
|
+
|
|
20
|
+
Inspect the files matched by `glob` and ...
|
|
21
|
+
|
|
22
|
+
- (default) print an analysis result for what can be updated
|
|
23
|
+
- `--migrate`: update files with new configuration
|
|
24
|
+
- `--cleanup`: remove legacy configuration and instance methods
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
|
|
28
|
+
--migrate Update the files with future-compatibile configuration
|
|
29
|
+
--cleanup Remove resolver instance methods for GraphQL-Ruby's old runtime
|
|
30
|
+
--concise Don't print migration strategy descriptions
|
|
31
|
+
--implicit MODE Handle implicit field resolution using MODE
|
|
32
|
+
--only PATTERN Only analyze or update fields whose path (`Type.field`) matches /PATTERN/
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Develop
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
bundle exec rake test # TEST=test/...
|
|
39
|
+
```
|
metadata
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: graphql_migrate_execution
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Robert Mosolgo
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-03-13 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: irb
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: ostruct
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: minitest
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: minitest-focus
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - ">="
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
82
|
+
description: A development script for migrating to GraphQL-Ruby's new runtime engine
|
|
83
|
+
email:
|
|
84
|
+
- rdmosolgo@gmail.com
|
|
85
|
+
executables: []
|
|
86
|
+
extensions: []
|
|
87
|
+
extra_rdoc_files: []
|
|
88
|
+
files:
|
|
89
|
+
- MIT-LICENSE
|
|
90
|
+
- lib/graphql_migrate_execution.rb
|
|
91
|
+
- lib/graphql_migrate_execution/action.rb
|
|
92
|
+
- lib/graphql_migrate_execution/add_future.rb
|
|
93
|
+
- lib/graphql_migrate_execution/analyze.rb
|
|
94
|
+
- lib/graphql_migrate_execution/dataloader_all.rb
|
|
95
|
+
- lib/graphql_migrate_execution/dataloader_batch.rb
|
|
96
|
+
- lib/graphql_migrate_execution/dataloader_manual.rb
|
|
97
|
+
- lib/graphql_migrate_execution/dataloader_shorthand.rb
|
|
98
|
+
- lib/graphql_migrate_execution/do_nothing.rb
|
|
99
|
+
- lib/graphql_migrate_execution/field_definition.rb
|
|
100
|
+
- lib/graphql_migrate_execution/implicit.rb
|
|
101
|
+
- lib/graphql_migrate_execution/not_implemented.rb
|
|
102
|
+
- lib/graphql_migrate_execution/remove_legacy.rb
|
|
103
|
+
- lib/graphql_migrate_execution/resolve_each.rb
|
|
104
|
+
- lib/graphql_migrate_execution/resolve_static.rb
|
|
105
|
+
- lib/graphql_migrate_execution/resolver_method.rb
|
|
106
|
+
- lib/graphql_migrate_execution/strategy.rb
|
|
107
|
+
- lib/graphql_migrate_execution/type_definition.rb
|
|
108
|
+
- lib/graphql_migrate_execution/version.rb
|
|
109
|
+
- lib/graphql_migrate_execution/visitor.rb
|
|
110
|
+
- readme.md
|
|
111
|
+
homepage: https://github.com/rmosolgo/graphql_migrate_execution
|
|
112
|
+
licenses:
|
|
113
|
+
- MIT
|
|
114
|
+
metadata:
|
|
115
|
+
homepage_uri: https://graphql-ruby.org
|
|
116
|
+
changelog_uri: https://github.com/rmosolgo/graphql-ruby/blob/master/CHANGELOG.md
|
|
117
|
+
source_code_uri: https://github.com/rmosolgo/graphql-ruby
|
|
118
|
+
bug_tracker_uri: https://github.com/rmosolgo/graphql-ruby/issues
|
|
119
|
+
mailing_list_uri: https://buttondown.email/graphql-ruby
|
|
120
|
+
rubygems_mfa_required: 'true'
|
|
121
|
+
rdoc_options: []
|
|
122
|
+
require_paths:
|
|
123
|
+
- lib
|
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: 2.7.0
|
|
129
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
130
|
+
requirements:
|
|
131
|
+
- - ">="
|
|
132
|
+
- !ruby/object:Gem::Version
|
|
133
|
+
version: '0'
|
|
134
|
+
requirements: []
|
|
135
|
+
rubygems_version: 4.0.3
|
|
136
|
+
specification_version: 4
|
|
137
|
+
summary: A development script for migrating to GraphQL-Ruby's new runtime engine
|
|
138
|
+
test_files: []
|