shopify_toolkit 0.5.1 → 0.5.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 101513a4e3028e85086e96c5fc36b992df1fd77804dcf6f55207a3f3d3761582
4
- data.tar.gz: 10eff581de2567092a710ad7fd0533c51d74c6ead9c1003a0467941806624dda
3
+ metadata.gz: 767c783d8c115219c9b25c83e6defa01772cc321a411c072e149ed82e10bc2ba
4
+ data.tar.gz: 576dfd3753579fac15388f5b22186a5b35b9298d974875ace256532b0af2063d
5
5
  SHA512:
6
- metadata.gz: '08b4bd6ee54f7764ce9999dd01b438f0ac89c440c3c5dba2e4cf684de40350fc6e3a4f018942f2b9fee484fc4dfb89b1ca585548812de39c4cf04aa31e51cf57'
7
- data.tar.gz: 0d52e249d1abecea1643a2109a984290216c7babe61bd036f241168246ee1a796380747e7f767c23b9593ff044862fcdf7fe0b0507778fdfcde714377712a9e6
6
+ metadata.gz: e441dfcdffb2552f0773d132db88a8204a0f60c2624f40f8cff2b0d01304de480e92a95d8d6db5f396bec0c1bce7179d1315fb49b3bb0347f59c14070d4b1685
7
+ data.tar.gz: 14786f1aa5d5d4e27274f591996f90960c6fdd326f1613088fb54f52f26ebf447b06eeaea4ebf3455c0f868bd9d5f1e71e176c58b75d15c499dd0dd9186caf2f
data/README.md CHANGED
@@ -24,6 +24,10 @@ A toolkit for working with Custom Shopify Apps built on Rails.
24
24
  - [ ] GraphQL Admin API client with built-in caching
25
25
  - [ ] GraphQL Admin API client with built-in error handling
26
26
  - [ ] GraphQL Admin API client with built-in logging
27
+ - [ ] Bulk Operations
28
+ - [ ] Interface for uploading and getting results for query / mutation
29
+ - [ ] Error handling and Logging
30
+ - [ ] Callbacks
27
31
 
28
32
  ## Installation
29
33
 
@@ -40,10 +44,11 @@ bundle add shopify_toolkit
40
44
  Within a Rails application created with ShopifyApp, generate a new migration file:
41
45
 
42
46
  ```bash
43
- bundle exec shopify-toolkit generate migration AddProductPressReleases
47
+ bundle exec shopify-toolkit generate_migration AddProductPressReleases
44
48
  ```
45
49
 
46
50
  Then, add the following code to the migration file:
51
+
47
52
  ```ruby
48
53
  # config/shopify/migrate/20250528130134_add_product_press_releases.rb
49
54
  class AddProductPressReleases < ShopifyToolkit::Migration
@@ -96,6 +101,40 @@ ShopifyToolkit::MetafieldSchema.define do
96
101
  end
97
102
  ```
98
103
 
104
+ ### Monkey Patching for Specific Use Cases
105
+
106
+ In some scenarios, you may need to customize the behavior of ShopifyToolkit to handle specific limitations or requirements. For example, you might want to skip certain Shopify-managed metaobject definitions that cannot be created due to reserved namespaces or limited access permissions.
107
+
108
+ ```rb
109
+ # config/initializers/shopify_toolkit_patches.rb
110
+
111
+ require "shopify_toolkit/metaobject_statements"
112
+
113
+ module ShopifyToolkit
114
+ module MetaobjectStatements
115
+ # Override create_metaobject_definition to skip Shopify-managed definitions
116
+ alias_method :original_create_metaobject_definition, :create_metaobject_definition
117
+
118
+ def create_metaobject_definition(type, **options)
119
+ # Skip Shopify-managed metaobject definitions during schema load
120
+ if type.to_s.start_with?("shopify--")
121
+ say "Skipping Shopify-managed metaobject definition: #{type}"
122
+ return
123
+ end
124
+
125
+ original_create_metaobject_definition(type, **options)
126
+ end
127
+ end
128
+ end
129
+ ```
130
+
131
+ This approach allows you to:
132
+
133
+ - Skip creation of reserved metaobject types (e.g., those prefixed with `shopify--`)
134
+ - Handle cases where certain metaobjects cannot be created due to API limitations
135
+ - Customize validation logic for specific metaobject types
136
+ - Filter out problematic metafield references during schema loading
137
+
99
138
  ### Analyzing a Matrixify CSV Result files
100
139
 
101
140
  Matrixify is a popular Shopify app that allows you to import/export data from Shopify using CSV files. The CSV files that Matrixify generates are very verbose and can be difficult to work with. This tool allows you to analyze the CSV files and extract the data you need.
@@ -104,8 +143,10 @@ The tool will import the file into a local SQLite database
104
143
  and open a console for you to run queries against the data
105
144
  using ActiveRecord.
106
145
 
146
+ > Note: To use it with `bundle exec`, sqlite3 must be included in the project’s bundle
147
+
107
148
  ```shell
108
- shopify-csv analyze products-result.csv --force-import
149
+ shopify-toolkit analyze products-result.csv --force-import
109
150
  ==> Importing products-result.csv into /var/folders/hn/z7b7s1kj3js4k7_qk3lj27kr0000gn/T/shopify-toolkit-analyze-products_result_csv.sqlite3
110
151
  -- create_table(:results, {:force=>true})
111
152
  -> 0.0181s
@@ -119,7 +160,7 @@ shopify-csv analyze products-result.csv --force-import
119
160
  "UPDATE: Found by Handle | Assuming MERGE for Variant Command | Assuming MERGE for Tags Command | Variants updated by SKU: 1 | Warning: The following media were not uploaded to Shopify: [https://mymedia.com/image.jpg: Error downloading from Web: 302 Moved Temporarily]",
120
161
  ...]
121
162
  >> first
122
- #<ShopifyCSV::Result:0x0000000300bf1668
163
+ #<ShopifyToolkit::Result:0x0000000300bf1668
123
164
  id: 1,
124
165
  data: nil,
125
166
  handle: "my-product",
@@ -6,6 +6,7 @@ module ShopifyToolkit::MetafieldStatements
6
6
  extend ActiveSupport::Concern
7
7
  include ShopifyToolkit::Migration::Logging
8
8
  include ShopifyToolkit::AdminClient
9
+ include ShopifyToolkit::MetaobjectUtilities
9
10
 
10
11
  def self.log_time(method_name)
11
12
  current_method = instance_method(method_name)
@@ -26,6 +27,21 @@ module ShopifyToolkit::MetafieldStatements
26
27
  return
27
28
  end
28
29
 
30
+ # Process validations to convert metaobject types to GIDs (only for metaobject reference fields)
31
+ if options[:validations] && is_metaobject_reference_type?(type)
32
+ begin
33
+ options[:validations] = convert_validations_types_to_gids(options[:validations])
34
+ rescue RuntimeError => e
35
+ if e.message.include?("not found")
36
+ say "ERROR: Cannot create metafield #{namespace}:#{key} - references non-existent metaobject. This suggests the metaobject was filtered out or failed to create."
37
+ say " Original error: #{e.message}"
38
+ return
39
+ else
40
+ raise e
41
+ end
42
+ end
43
+ end
44
+
29
45
  # https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metafieldDefinitionCreate
30
46
  query =
31
47
  "# GraphQL
@@ -117,6 +133,11 @@ module ShopifyToolkit::MetafieldStatements
117
133
  return
118
134
  end
119
135
 
136
+ # Process validations to convert metaobject types to GIDs (only for metaobject reference fields)
137
+ if options[:validations] && options[:type] && is_metaobject_reference_type?(options[:type])
138
+ options[:validations] = convert_validations_types_to_gids(options[:validations])
139
+ end
140
+
120
141
  shopify_admin_client
121
142
  .query(
122
143
  # Documentation: https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metafieldDefinitionUpdate
@@ -6,6 +6,9 @@ module ShopifyToolkit::MetaobjectStatements
6
6
  extend ActiveSupport::Concern
7
7
  include ShopifyToolkit::Migration::Logging
8
8
  include ShopifyToolkit::AdminClient
9
+ include ShopifyToolkit::MetaobjectUtilities
10
+
11
+ @@pending_field_validations = []
9
12
 
10
13
  def self.log_time(method_name)
11
14
  current_method = instance_method(method_name)
@@ -14,18 +17,152 @@ module ShopifyToolkit::MetaobjectStatements
14
17
  end
15
18
  end
16
19
 
17
- # create_metafield :products, :my_metafield, :single_line_text_field, name: "Prova"
18
- # @param namespace: if nil the metafield will be app-specific (default: :custom)
20
+ def apply_pending_field_validations
21
+ return if @@pending_field_validations.empty?
22
+
23
+ say "Applying #{@@pending_field_validations.size} pending field validations"
24
+
25
+ @@pending_field_validations.reject! do |item|
26
+ metaobject_type = item[:metaobject_type]
27
+ field_key = item[:field_key]
28
+ validations = item[:validations]
29
+
30
+ begin
31
+ success = add_field_validations_to_metaobject(metaobject_type, field_key, validations, item)
32
+ unless success
33
+ say "-- Deferring field '#{field_key}' in '#{metaobject_type}' (missing dependencies)"
34
+ end
35
+ success
36
+ rescue StandardError => e
37
+ say "-- Failed to process field '#{field_key}' in '#{metaobject_type}': #{e.message}"
38
+ true # Remove from array (don't retry errors)
39
+ end
40
+ end
41
+ end
42
+
19
43
  log_time \
20
44
  def create_metaobject_definition(type, **options)
21
- # Skip creation if metafield already exists
45
+ # Skip creation if metaobject already exists
22
46
  existing_gid = get_metaobject_definition_gid(type)
23
47
  if existing_gid
24
48
  say "Metaobject #{type} already exists, skipping creation"
25
49
  return existing_gid
26
50
  end
27
51
 
28
- # https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metafieldDefinitionCreate
52
+ # Transform options for GraphQL API
53
+ definition = options.merge(type: type.to_s)
54
+
55
+ begin
56
+ # Convert field_definitions to fieldDefinitions and transform field structure
57
+ if options[:field_definitions]
58
+ definition[:fieldDefinitions] = options[:field_definitions].filter_map do |field|
59
+ field_def = build_field_definition(field)
60
+ field_needs_validations = is_metaobject_reference_type?(field[:type])
61
+
62
+ if field[:validations]
63
+ begin
64
+ converted_validations = convert_validations_types_to_gids(field[:validations])
65
+ field_def[:validations] = converted_validations if converted_validations&.any?
66
+ rescue RuntimeError => e
67
+ if e.message.include?("not found")
68
+ @@pending_field_validations << {
69
+ metaobject_type: type,
70
+ field_key: field[:key],
71
+ field_definition: field,
72
+ validations: field[:validations]
73
+ }
74
+ say "Deferring field '#{field[:key]}' in '#{type}' (missing dependency)"
75
+
76
+ if field_needs_validations
77
+ next # Skip this field entirely
78
+ end
79
+ end
80
+ end
81
+ elsif field_needs_validations
82
+ next # Skip fields that need validations but don't have any
83
+ end
84
+
85
+ field_def
86
+ end
87
+
88
+ definition.delete(:field_definitions)
89
+ end
90
+
91
+ # Remove admin access to avoid API restrictions
92
+ if definition[:access]&.is_a?(Hash)
93
+ definition[:access] = definition[:access].dup
94
+ definition[:access].delete("admin") if definition[:access]["admin"]
95
+ definition.delete(:access) if definition[:access].empty?
96
+ end
97
+
98
+ # Clean up empty validations arrays that cause API errors
99
+ if definition[:fieldDefinitions]
100
+ definition[:fieldDefinitions].each do |field_def|
101
+ field_def.delete(:validations) if field_def[:validations]&.empty?
102
+ end
103
+ end
104
+
105
+ result = create_metaobject_definition_immediate(definition)
106
+ result
107
+ end
108
+ end
109
+
110
+ def add_field_validations_to_metaobject(metaobject_type, field_key, validations, item = nil)
111
+ # Get the existing metaobject definition
112
+ existing_gid = get_metaobject_definition_gid(metaobject_type)
113
+ unless existing_gid
114
+ say "Error: Cannot add validations to '#{metaobject_type}' - metaobject not found"
115
+ return false
116
+ end
117
+
118
+ begin
119
+ converted_validations = convert_validations_types_to_gids(validations)
120
+
121
+ if converted_validations&.any?
122
+ # Use the passed item, or try to find it (for backward compatibility)
123
+ if item.nil?
124
+ item = @@pending_field_validations.find { |pending_item|
125
+ pending_item[:metaobject_type] == metaobject_type && pending_item[:field_key] == field_key
126
+ }
127
+ end
128
+
129
+ if item && item[:field_definition]
130
+ field_def = item[:field_definition]
131
+ new_field = build_field_definition(field_def, converted_validations)
132
+
133
+ field_operation = { create: new_field }
134
+ update_metaobject_definition(metaobject_type, fieldDefinitions: [field_operation])
135
+
136
+ say "Added field '#{field_key}' to '#{metaobject_type}'"
137
+ return true
138
+ else
139
+ return false
140
+ end
141
+ else
142
+ return false
143
+ end
144
+
145
+ rescue RuntimeError
146
+ return false # Keep trying later or don't retry errors
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ def build_field_definition(field, validations = nil)
153
+ field_def = {
154
+ key: field[:key].to_s,
155
+ name: field[:name],
156
+ type: field[:type].to_s
157
+ }
158
+ field_def[:description] = field[:description] if field[:description]
159
+ field_def[:required] = field[:required] if field[:required]
160
+ field_def[:validations] = validations if validations&.any?
161
+ field_def
162
+ end
163
+
164
+ def create_metaobject_definition_immediate(definition)
165
+ # https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metaobjectDefinitionCreate
29
166
  query =
30
167
  "# GraphQL
31
168
  mutation CreateMetaobjectDefinition($definition: MetaobjectDefinitionCreateInput!) {
@@ -47,32 +184,13 @@ module ShopifyToolkit::MetaobjectStatements
47
184
  }
48
185
  }
49
186
  "
50
- variables = { definition: { type:, **options } }
187
+ variables = { definition: definition }
51
188
 
52
189
  shopify_admin_client
53
190
  .query(query:, variables:)
54
191
  .tap { handle_shopify_admin_client_errors(_1, "data.metaobjectDefinitionCreate.userErrors") }
55
192
  end
56
193
 
57
- def get_metaobject_definition_gid(type)
58
- result =
59
- shopify_admin_client
60
- .query(
61
- query:
62
- "# GraphQL
63
- query GetMetaobjectDefinitionID($type: String!) {
64
- metaobjectDefinitionByType(type: $type) {
65
- id
66
- }
67
- }",
68
- variables: { type: type.to_s },
69
- )
70
- .tap { handle_shopify_admin_client_errors(_1) }
71
- .body
72
-
73
- result.dig("data", "metaobjectDefinitionByType", "id")
74
- end
75
-
76
194
  def update_metaobject_definition(type, **options)
77
195
  existing_gid = get_metaobject_definition_gid(type)
78
196
 
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/concern"
4
+
5
+ module ShopifyToolkit::MetaobjectUtilities
6
+ extend ActiveSupport::Concern
7
+ include ShopifyToolkit::AdminClient
8
+
9
+ def is_metaobject_reference_type?(type)
10
+ type.to_sym.in?([:metaobject_reference, :"list.metaobject_reference", :mixed_reference, :"list.mixed_reference"])
11
+ end
12
+
13
+ def convert_validations_types_to_gids(validations)
14
+ return validations unless validations&.any?
15
+
16
+ validations.filter_map do |validation|
17
+ convert_metaobject_validation(validation)
18
+ end
19
+ end
20
+
21
+ def get_metaobject_definition_gid(type)
22
+ result =
23
+ shopify_admin_client
24
+ .query(
25
+ query:
26
+ "# GraphQL
27
+ query GetMetaobjectDefinitionID($type: String!) {
28
+ metaobjectDefinitionByType(type: $type) {
29
+ id
30
+ }
31
+ }",
32
+ variables: { type: type.to_s },
33
+ )
34
+ .tap { handle_shopify_admin_client_errors(_1) }
35
+ .body
36
+
37
+ gid = result.dig("data", "metaobjectDefinitionByType", "id")
38
+
39
+ return gid
40
+ end
41
+
42
+ def get_metaobject_definition_type_by_gid(gid)
43
+ result =
44
+ shopify_admin_client
45
+ .query(
46
+ query:
47
+ "# GraphQL
48
+ query GetMetaobjectDefinitionType($id: ID!) {
49
+ metaobjectDefinition(id: $id) {
50
+ type
51
+ }
52
+ }",
53
+ variables: { id: gid },
54
+ )
55
+ .tap { handle_shopify_admin_client_errors(_1) }
56
+ .body
57
+
58
+ result.dig("data", "metaobjectDefinition", "type")
59
+ end
60
+
61
+ private
62
+
63
+ def convert_metaobject_validation(validation)
64
+ name = validation[:name] || validation["name"]
65
+ value = validation[:value] || validation["value"]
66
+
67
+ return validation unless metaobject_type_validation?(name) && value
68
+
69
+ parsed_value = parse_json_if_needed(value)
70
+ convert_types_to_gids(parsed_value)
71
+ end
72
+
73
+ def metaobject_type_validation?(name)
74
+ name.in?(["metaobject_definition_type", "metaobject_definition_types"])
75
+ end
76
+
77
+ def parse_json_if_needed(value)
78
+ return value unless value.is_a?(String) && value.start_with?("[") && value.end_with?("]")
79
+
80
+ JSON.parse(value)
81
+ rescue JSON::ParserError
82
+ value
83
+ end
84
+
85
+ def convert_types_to_gids(value)
86
+ if value.is_a?(Array)
87
+ convert_array_types_to_gids(value)
88
+ else
89
+ convert_single_type_to_gid(value)
90
+ end
91
+ end
92
+
93
+ def convert_array_types_to_gids(types)
94
+ gids = types.map do |type|
95
+ gid = get_metaobject_definition_gid(type)
96
+ raise "Metaobject type '#{type}' not found" unless gid
97
+ gid
98
+ end
99
+ { name: "metaobject_definition_ids", value: gids.to_json }
100
+ end
101
+
102
+ def convert_single_type_to_gid(type)
103
+ gid = get_metaobject_definition_gid(type)
104
+ raise "Metaobject type '#{type}' not found" unless gid
105
+ { name: "metaobject_definition_id", value: gid }
106
+ end
107
+ end
@@ -6,6 +6,7 @@ require "active_support/core_ext/module/delegation"
6
6
  class ShopifyToolkit::Migrator # :nodoc:
7
7
  include ShopifyToolkit::AdminClient
8
8
  include ShopifyToolkit::MetafieldStatements
9
+ include ShopifyToolkit::MetaobjectStatements
9
10
 
10
11
  singleton_class.attr_accessor :migrations_paths
11
12
  self.migrations_paths = ["config/shopify/migrate"]
@@ -8,6 +8,8 @@ require "active_support/core_ext/module/delegation"
8
8
  module ShopifyToolkit::Schema
9
9
  extend self
10
10
  include ShopifyToolkit::MetafieldStatements
11
+ include ShopifyToolkit::MetaobjectStatements
12
+ include ShopifyToolkit::MetaobjectUtilities
11
13
  include ShopifyToolkit::Migration::Logging
12
14
 
13
15
  delegate :logger, to: Rails
@@ -50,8 +52,19 @@ module ShopifyToolkit::Schema
50
52
  end
51
53
 
52
54
  announce "Loading metafield schema from #{path}"
53
- say_with_time "Executing schema statements" do
54
- load path
55
+
56
+ # Parse the schema file to separate metaobject and metafield definitions
57
+ schema_content = File.read(path)
58
+
59
+ say_with_time "Executing metaobject definitions" do
60
+ # Execute only metaobject definitions first
61
+ execute_metaobject_definitions(schema_content)
62
+ apply_pending_field_validations
63
+ end
64
+
65
+ say_with_time "Executing metafield definitions" do
66
+ # Execute only metafield definitions after all metaobjects exist
67
+ execute_metafield_definitions(schema_content)
55
68
  end
56
69
  end
57
70
 
@@ -69,6 +82,66 @@ module ShopifyToolkit::Schema
69
82
  instance_eval(&block)
70
83
  end
71
84
 
85
+ def convert_validations_gids_to_types(validations, metafield_type)
86
+ return validations unless validations&.any? && is_metaobject_reference_type?(metafield_type)
87
+
88
+ validations.filter_map do |validation|
89
+ convert_metaobject_validation_to_type(validation)
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def convert_metaobject_validation_to_type(validation)
96
+ name = validation["name"]
97
+ value = validation["value"]
98
+
99
+ return validation unless metaobject_gid_validation?(name)
100
+
101
+ parsed_value = parse_json_if_needed(value)
102
+ convert_gids_to_types(validation, parsed_value)
103
+ end
104
+
105
+ def metaobject_gid_validation?(name)
106
+ name.in?(["metaobject_definition_id", "metaobject_definition_ids"])
107
+ end
108
+
109
+ def convert_gids_to_types(validation, value)
110
+ if value.is_a?(Array)
111
+ convert_array_gids_to_types(validation, value)
112
+ else
113
+ convert_single_gid_to_type(validation, value)
114
+ end
115
+ end
116
+
117
+ def convert_array_gids_to_types(validation, gids)
118
+ types = gids.filter_map { |gid| convert_gid_to_type_if_valid(gid) }
119
+
120
+ return nil unless types.any?
121
+
122
+ validation.merge("name" => "metaobject_definition_types", "value" => types)
123
+ end
124
+
125
+ def convert_single_gid_to_type(validation, gid)
126
+ type = convert_gid_to_type_if_valid(gid)
127
+
128
+ return nil unless type
129
+
130
+ validation.merge("name" => "metaobject_definition_type", "value" => type)
131
+ end
132
+
133
+ def convert_gid_to_type_if_valid(gid)
134
+ return gid unless gid&.start_with?("gid://shopify/MetaobjectDefinition/")
135
+
136
+ type = get_metaobject_definition_type_by_gid(gid)
137
+
138
+ if type.nil?
139
+ say "Warning: Metafield validation references unknown metaobject GID #{gid} - excluding from portable schema"
140
+ end
141
+
142
+ type
143
+ end
144
+
72
145
  def fetch_definitions(owner_type:)
73
146
  owner_type = owner_type.to_s.singularize.upcase
74
147
 
@@ -116,15 +189,64 @@ module ShopifyToolkit::Schema
116
189
  result.dig("data", "metafieldDefinitions", "nodes") || []
117
190
  end
118
191
 
192
+ def fetch_metaobject_definitions
193
+ query = <<~GRAPHQL
194
+ query {
195
+ metaobjectDefinitions(first: 250) {
196
+ nodes {
197
+ id
198
+ type
199
+ name
200
+ description
201
+ fieldDefinitions {
202
+ key
203
+ name
204
+ description
205
+ type {
206
+ name
207
+ }
208
+ required
209
+ validations {
210
+ name
211
+ value
212
+ }
213
+ }
214
+ access {
215
+ admin
216
+ storefront
217
+ }
218
+ capabilities {
219
+ publishable {
220
+ enabled
221
+ }
222
+ translatable {
223
+ enabled
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ GRAPHQL
230
+
231
+ result =
232
+ shopify_admin_client
233
+ .query(query:)
234
+ .tap { handle_shopify_admin_client_errors(_1) }
235
+ .body
236
+
237
+ result.dig("data", "metaobjectDefinitions", "nodes") || []
238
+ end
239
+
119
240
  def generate_schema_content
120
- definitions =
241
+ metaobject_definitions = fetch_metaobject_definitions
242
+ metafield_definitions =
121
243
  OWNER_TYPES.flat_map { |owner_type| fetch_definitions(owner_type:) }
122
244
 
123
245
  content = StringIO.new
124
246
  content << <<~RUBY
125
- # This file is auto-generated from the current state of the Shopify metafields.
126
- # Instead of editing this file, please use the metafields migration feature of ShopifyToolkit
127
- # to incrementally modify your metafields, and then regenerate this schema definition.
247
+ # This file is auto-generated from the current state of the Shopify metafields and metaobjects.
248
+ # Instead of editing this file, please use the migration features of ShopifyToolkit
249
+ # to incrementally modify your metafields and metaobjects, and then regenerate this schema definition.
128
250
  #
129
251
  # This file is the source used to define your metafields when running `bin/rails shopify:schema:load`.
130
252
  #
@@ -132,8 +254,59 @@ module ShopifyToolkit::Schema
132
254
  ShopifyToolkit::Schema.define do
133
255
  RUBY
134
256
 
135
- # Sort for consistent output
136
- definitions
257
+ # Add metaobject definitions first
258
+ metaobject_definitions
259
+ .sort_by { _1["type"] }
260
+ .each do |definition|
261
+ type = definition["type"]
262
+ name = definition["name"]
263
+ description = definition["description"]
264
+
265
+ field_definitions = definition["fieldDefinitions"]&.map do |field|
266
+ field_hash = {
267
+ key: field["key"].to_sym,
268
+ type: field["type"]["name"].to_sym,
269
+ name: field["name"]
270
+ }
271
+ field_hash[:description] = field["description"] if field["description"] && !field["description"].empty?
272
+ field_hash[:required] = field["required"] if field["required"] == true
273
+
274
+ # Convert validations for metaobject reference fields within metaobjects
275
+ if field["validations"]&.any? && is_metaobject_reference_type?(field["type"]["name"])
276
+ field_hash[:validations] = convert_validations_gids_to_types(field["validations"], field["type"]["name"])&.map { |v| v.transform_keys(&:to_sym) }
277
+ elsif field["validations"]&.any?
278
+ field_hash[:validations] = field["validations"]&.map { |v| v.transform_keys(&:to_sym) }
279
+ end
280
+
281
+ field_hash
282
+ end
283
+
284
+ access = definition["access"]
285
+ capabilities = definition["capabilities"]
286
+
287
+ args = [type.to_sym]
288
+ kwargs = { name: name }
289
+ kwargs[:description] = description if description && !description.empty?
290
+ kwargs[:field_definitions] = field_definitions if field_definitions&.any?
291
+ kwargs[:access] = access if access&.any?
292
+
293
+ # Add capabilities if non-default
294
+ if capabilities&.any? { |_, v| v["enabled"] == true }
295
+ kwargs[:capabilities] = capabilities.transform_keys(&:to_sym).transform_values { |v| v.transform_keys(&:to_sym) }
296
+ end
297
+
298
+ args_string = args.map(&:inspect).join(", ")
299
+ kwargs_string = kwargs.map { |k, v| "#{k}: #{v.inspect}" }.join(", ")
300
+ content.puts " create_metaobject_definition #{args_string}, #{kwargs_string}"
301
+ end
302
+
303
+ # Add blank line between metaobjects and metafields if both exist
304
+ if metaobject_definitions.any? && metafield_definitions.any?
305
+ content.puts ""
306
+ end
307
+
308
+ # Add metafield definitions
309
+ metafield_definitions
137
310
  .sort_by { [_1["ownerType"], _1["namespace"], _1["key"]] }
138
311
  .each do
139
312
  owner_type = _1["ownerType"].downcase.pluralize.to_sym
@@ -142,7 +315,7 @@ module ShopifyToolkit::Schema
142
315
  name = _1["name"]
143
316
  namespace = _1["namespace"]&.to_sym
144
317
  description = _1["description"]
145
- validations = _1["validations"]&.map { |v| v.transform_keys(&:to_sym) }
318
+ validations = convert_validations_gids_to_types(_1["validations"], type)&.map { |v| v.transform_keys(&:to_sym) }
146
319
  capabilities =
147
320
  _1["capabilities"]
148
321
  &.transform_keys(&:to_sym)
@@ -152,10 +325,10 @@ module ShopifyToolkit::Schema
152
325
  kwargs = { name: name }
153
326
  kwargs[:namespace] = namespace if namespace && namespace != :custom
154
327
  kwargs[:description] = description if description
155
- kwargs[:validations] = validations if validations.present?
328
+ kwargs[:validations] = validations if validations&.any?
156
329
 
157
330
  # Only include capabilities if they have non-default values
158
- if capabilities.present?
331
+ if capabilities&.any?
159
332
  has_non_default_capabilities =
160
333
  capabilities.any? do |cap, value|
161
334
  case cap
@@ -176,4 +349,43 @@ module ShopifyToolkit::Schema
176
349
  content.puts "end"
177
350
  content.string
178
351
  end
352
+
353
+ def execute_metaobject_definitions(schema_content)
354
+ # Create a filtered version that only includes metaobject definitions
355
+ metaobject_content = filter_schema_content(schema_content, :metaobject)
356
+ eval_schema_content(metaobject_content)
357
+ end
358
+
359
+ def execute_metafield_definitions(schema_content)
360
+ # Create a filtered version that only includes metafield definitions
361
+ metafield_content = filter_schema_content(schema_content, :metafield)
362
+ eval_schema_content(metafield_content)
363
+ end
364
+
365
+ def filter_schema_content(schema_content, type)
366
+ lines = schema_content.lines
367
+ filtered_lines = []
368
+
369
+ # Always include the header and footer
370
+ filtered_lines << lines.first(8) # Header lines up to "ShopifyToolkit::Schema.define do"
371
+ filtered_lines.flatten!
372
+
373
+ lines.each do |line|
374
+ case type
375
+ when :metaobject
376
+ if line.strip.start_with?("create_metaobject_definition")
377
+ filtered_lines << line
378
+ end
379
+ when :metafield
380
+ filtered_lines << line if line.strip.start_with?("create_metafield")
381
+ end
382
+ end
383
+
384
+ filtered_lines << "end\n" # Closing line
385
+ filtered_lines.join
386
+ end
387
+
388
+ def eval_schema_content(content)
389
+ instance_eval(content)
390
+ end
179
391
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ShopifyToolkit
4
- VERSION = "0.5.1"
4
+ VERSION = "0.5.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify_toolkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.5.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elia Schito
8
8
  - Nebulab Team
9
+ autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2025-06-04 00:00:00.000000000 Z
12
+ date: 2025-10-31 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: railties
@@ -94,6 +95,7 @@ dependencies:
94
95
  - - ">="
95
96
  - !ruby/object:Gem::Version
96
97
  version: '14.8'
98
+ description:
97
99
  email:
98
100
  - elia@schito.me
99
101
  executables:
@@ -112,6 +114,7 @@ files:
112
114
  - lib/shopify_toolkit/command_line.rb
113
115
  - lib/shopify_toolkit/metafield_statements.rb
114
116
  - lib/shopify_toolkit/metaobject_statements.rb
117
+ - lib/shopify_toolkit/metaobject_utilities.rb
115
118
  - lib/shopify_toolkit/migration.rb
116
119
  - lib/shopify_toolkit/migration/logging.rb
117
120
  - lib/shopify_toolkit/migrator.rb
@@ -127,6 +130,7 @@ metadata:
127
130
  homepage_uri: https://github.com/nebulab/shopify_toolkit?tab=readme-ov-file#readme
128
131
  source_code_uri: https://github.com/nebulab/shopify_toolkit
129
132
  changelog_uri: https://github.com/nebulab/shopify_toolkit/releases
133
+ post_install_message:
130
134
  rdoc_options: []
131
135
  require_paths:
132
136
  - lib
@@ -141,7 +145,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
145
  - !ruby/object:Gem::Version
142
146
  version: '0'
143
147
  requirements: []
144
- rubygems_version: 3.6.2
148
+ rubygems_version: 3.5.16
149
+ signing_key:
145
150
  specification_version: 4
146
151
  summary: A collection of tools for dealing with Shopify apps.
147
152
  test_files: []