shopify_transporter 1.0.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 +7 -0
- data/Gemfile +23 -0
- data/LICENSE +20 -0
- data/README.md +290 -0
- data/RELEASING +16 -0
- data/Rakefile +11 -0
- data/exe/shopify_transporter +47 -0
- data/lib/shopify_transporter/generators/base_group.rb +25 -0
- data/lib/shopify_transporter/generators/generate.rb +64 -0
- data/lib/shopify_transporter/generators/new.rb +23 -0
- data/lib/shopify_transporter/generators.rb +3 -0
- data/lib/shopify_transporter/pipeline/all_platforms/metafields.rb +61 -0
- data/lib/shopify_transporter/pipeline/magento/customer/addresses_attribute.rb +60 -0
- data/lib/shopify_transporter/pipeline/magento/customer/top_level_attributes.rb +47 -0
- data/lib/shopify_transporter/pipeline/magento/order/addresses_attribute.rb +46 -0
- data/lib/shopify_transporter/pipeline/magento/order/line_items.rb +55 -0
- data/lib/shopify_transporter/pipeline/magento/order/top_level_attributes.rb +39 -0
- data/lib/shopify_transporter/pipeline/magento/product/top_level_attributes.rb +36 -0
- data/lib/shopify_transporter/pipeline/stage.rb +16 -0
- data/lib/shopify_transporter/pipeline.rb +4 -0
- data/lib/shopify_transporter/record_builder.rb +51 -0
- data/lib/shopify_transporter/shopify/attributes_accumulator.rb +43 -0
- data/lib/shopify_transporter/shopify/attributes_helpers.rb +53 -0
- data/lib/shopify_transporter/shopify/customer.rb +79 -0
- data/lib/shopify_transporter/shopify/order.rb +107 -0
- data/lib/shopify_transporter/shopify/product.rb +118 -0
- data/lib/shopify_transporter/shopify/record.rb +60 -0
- data/lib/shopify_transporter/shopify.rb +7 -0
- data/lib/shopify_transporter/version.rb +4 -0
- data/lib/shopify_transporter.rb +247 -0
- data/lib/tasks/factory_bot.rake +9 -0
- data/lib/templates/custom_stage.tt +31 -0
- data/lib/templates/gemfile.tt +5 -0
- data/lib/templates/magento/config.tt +23 -0
- data/shopify_transporter.gemspec +36 -0
- metadata +152 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_support/inflector'
|
3
|
+
require 'csv'
|
4
|
+
require_relative 'record'
|
5
|
+
require_relative 'attributes_helpers'
|
6
|
+
|
7
|
+
module ShopifyTransporter
|
8
|
+
module Shopify
|
9
|
+
class Product < Record
|
10
|
+
TOP_LEVEL_ATTRIBUTES = %w(
|
11
|
+
handle title body_html vendor product_type tags template_suffix published_scope published published_at
|
12
|
+
option1_name option1_value option2_name option2_value option3_name option3_value variant_sku
|
13
|
+
metafields_global_title_tag metafields_global_description_tag
|
14
|
+
).freeze
|
15
|
+
|
16
|
+
VARIANT_PREFIX = 'variant_'
|
17
|
+
|
18
|
+
VARIANT_ATTRIBUTES = %w(
|
19
|
+
sku grams inventory_tracker inventory_qty inventory_policy
|
20
|
+
fulfillment_service price compare_at_price requires_shipping
|
21
|
+
taxable barcode weight_unit tax_code
|
22
|
+
).freeze
|
23
|
+
|
24
|
+
class << self
|
25
|
+
include AttributesHelpers
|
26
|
+
|
27
|
+
def header
|
28
|
+
[
|
29
|
+
'Handle', 'Title', 'Body (HTML)', 'Vendor', 'Type', 'Tags', 'Template Suffix', 'Published Scope',
|
30
|
+
'Published', 'Published At', 'Option1 Name', 'Option1 Value', 'Option2 Name', 'Option2 Value',
|
31
|
+
'Option3 Name', 'Option3 Value', 'Variant SKU', 'Metafields Global Title Tag',
|
32
|
+
'Metafields Global Description Tag', 'Metafield Namespace', 'Metafield Key', 'Metafield Value',
|
33
|
+
'Metafield Value Type', 'Variant Grams', 'Variant Inventory Tracker', 'Variant Inventory Qty',
|
34
|
+
'Variant Inventory Policy', 'Variant Fulfillment Service', 'Variant Price', 'Variant Compare At Price',
|
35
|
+
'Variant Requires Shipping', 'Variant Taxable', 'Variant Barcode', 'Image Attachment', 'Image Src',
|
36
|
+
'Image Position', 'Image Alt Text', 'Variant Image', 'Variant Weight Unit', 'Variant Tax Code'
|
37
|
+
].to_csv
|
38
|
+
end
|
39
|
+
|
40
|
+
def columns
|
41
|
+
@columns ||= header.parse_csv.map do |header_column|
|
42
|
+
header_column = 'product_type' if header_column == 'Type'
|
43
|
+
normalize_string(header_column)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def keys
|
48
|
+
%w(handle).freeze
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_csv
|
53
|
+
CSV.generate do |csv|
|
54
|
+
csv << top_level_row_values
|
55
|
+
metafield_row_values.each { |row| csv << row }
|
56
|
+
variant_row_values.each { |row| csv << row }
|
57
|
+
variant_metafield_row_values.each { |row| csv << row }
|
58
|
+
image_row_values.each { |row| csv << row }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def top_level_row_values
|
63
|
+
base_hash.merge(record_hash.slice(*TOP_LEVEL_ATTRIBUTES)).tap do |product_hash|
|
64
|
+
next if record_hash['options'].blank?
|
65
|
+
|
66
|
+
product_hash['option1_name'] = record_hash['options'][0]['name']
|
67
|
+
product_hash['option2_name'] = record_hash['options'][1]['name']
|
68
|
+
product_hash['option3_name'] = record_hash['options'][2]['name']
|
69
|
+
end.values
|
70
|
+
end
|
71
|
+
|
72
|
+
def variant_row_values
|
73
|
+
return [] if record_hash['variants'].blank?
|
74
|
+
record_hash['variants'].map do |variant_hash|
|
75
|
+
variant = variant_hash.slice(*VARIANT_ATTRIBUTES)
|
76
|
+
variant.transform_keys! { |k| "#{VARIANT_PREFIX}#{k}" }
|
77
|
+
variant.merge!(variant_option_hash(variant_hash))
|
78
|
+
variant['variant_image'] = variant_hash['variant_image'] && variant_hash['variant_image']['src']
|
79
|
+
row_values_from(variant)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def variant_metafield_row_values
|
84
|
+
return [] if record_hash['variants'].blank?
|
85
|
+
record_hash['variants'].flat_map do |variant_hash|
|
86
|
+
next if variant_hash['metafields'].blank?
|
87
|
+
variant_hash['metafields'].map do |metafield_hash|
|
88
|
+
metafield = metafield_hash.slice(*METAFIELD_ATTRIBUTES)
|
89
|
+
metafield.transform_keys! { |k| "#{METAFIELD_PREFIX}#{k}" }
|
90
|
+
metafield.merge!(variant_option_hash(variant_hash))
|
91
|
+
row_values_from(metafield) if self.class.has_values?(metafield)
|
92
|
+
end.compact
|
93
|
+
end.compact
|
94
|
+
end
|
95
|
+
|
96
|
+
def image_row_values
|
97
|
+
return [] if record_hash['images'].blank?
|
98
|
+
record_hash['images'].map do |image_hash|
|
99
|
+
image = {
|
100
|
+
'image_attachment' => image_hash['attachment'],
|
101
|
+
'image_src' => image_hash['src'],
|
102
|
+
'image_position' => image_hash['position'],
|
103
|
+
'image_alt_text' => image_hash['alt'],
|
104
|
+
}
|
105
|
+
row_values_from(image) if self.class.has_values?(image)
|
106
|
+
end.compact
|
107
|
+
end
|
108
|
+
|
109
|
+
def variant_option_hash(variant_hash)
|
110
|
+
{
|
111
|
+
'option1_value' => variant_hash['option1'],
|
112
|
+
'option2_value' => variant_hash['option2'],
|
113
|
+
'option3_value' => variant_hash['option3'],
|
114
|
+
}
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module ShopifyTransporter
|
3
|
+
module Shopify
|
4
|
+
class Record
|
5
|
+
METAFIELD_PREFIX = "metafield_"
|
6
|
+
|
7
|
+
METAFIELD_ATTRIBUTES = %w(namespace key value value_type).freeze
|
8
|
+
|
9
|
+
def initialize(hash = {})
|
10
|
+
@record_hash = hash
|
11
|
+
end
|
12
|
+
|
13
|
+
def metafield_row_values
|
14
|
+
return [] if @record_hash['metafields'].blank?
|
15
|
+
@record_hash['metafields'].map do |metafield_hash|
|
16
|
+
metafield = metafield_hash.slice(*METAFIELD_ATTRIBUTES)
|
17
|
+
metafield.transform_keys! { |k| "#{METAFIELD_PREFIX}#{k}" }
|
18
|
+
row_values_from(metafield) if self.class.has_values?(metafield)
|
19
|
+
end.compact
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def header
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_csv
|
29
|
+
raise NotImplementedError
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_accessor :record_hash
|
35
|
+
|
36
|
+
class << self
|
37
|
+
def columns
|
38
|
+
raise NotImplementedError
|
39
|
+
end
|
40
|
+
|
41
|
+
def keys
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
def has_values?(hash)
|
46
|
+
hash.except(*keys).any? { |_, v| v }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def base_hash
|
51
|
+
row = self.class.columns.each_with_object({}) { |column_name, hash| hash[column_name] = nil }
|
52
|
+
row.merge(record_hash.slice(*self.class.keys))
|
53
|
+
end
|
54
|
+
|
55
|
+
def row_values_from(row_hash)
|
56
|
+
base_hash.merge(row_hash).values_at(*self.class.columns)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'thor'
|
3
|
+
require_relative 'shopify_transporter/generators.rb'
|
4
|
+
require_relative 'shopify_transporter/pipeline.rb'
|
5
|
+
require_relative 'shopify_transporter/shopify.rb'
|
6
|
+
require_relative 'shopify_transporter/record_builder.rb'
|
7
|
+
Dir["#{Dir.pwd}/lib/custom_pipeline_stages/**/*.rb"].each { |f| require f }
|
8
|
+
|
9
|
+
module ShopifyTransporter
|
10
|
+
DEFAULT_METAFIELD_NAMESPACE = 'migrated_data'
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'csv'
|
14
|
+
require 'yaml'
|
15
|
+
require 'yajl'
|
16
|
+
|
17
|
+
class TransporterTool
|
18
|
+
class ConversionError < StandardError; end
|
19
|
+
|
20
|
+
class StageNotFoundError < ConversionError
|
21
|
+
def initialize(stage_name)
|
22
|
+
super("Unable to find stage named '#{stage_name}'")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class InvalidObjectType < ConversionError
|
27
|
+
def initialize(object_type)
|
28
|
+
super(
|
29
|
+
"Unable to find object type named: '#{object_type}' in config.yml." \
|
30
|
+
"Are you sure '#{object_type}' is listed in the config.yml?"
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class UnsupportedStageTypeError < ConversionError
|
36
|
+
def initialize(name, type)
|
37
|
+
super(
|
38
|
+
"Stage: '#{name}' has an unsupported type: '#{type}'. It must be one of 'default', 'custom' or 'all_platforms'"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def initialize(*files, config, object_type)
|
44
|
+
config_file(config)
|
45
|
+
input_files(*files)
|
46
|
+
@object_type = object_type
|
47
|
+
return if @config.nil? || @input_files.nil?
|
48
|
+
|
49
|
+
raise InvalidObjectType, object_type unless supported_object_type?(object_type)
|
50
|
+
|
51
|
+
build_classes_based_on_config
|
52
|
+
initialize_stages
|
53
|
+
end
|
54
|
+
|
55
|
+
SUPPORTED_FILE_TYPES = %w(.csv .json)
|
56
|
+
|
57
|
+
def supported_object_type?(object_type)
|
58
|
+
@config['object_types'].key?(object_type)
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize_stages
|
62
|
+
@pipeline_stages ||= {}
|
63
|
+
@config['object_types'][@object_type]['pipeline_stages'].each do |pipeline_stage|
|
64
|
+
initialize_pipeline_stage(pipeline_stage)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def run
|
69
|
+
return if @input_files.nil?
|
70
|
+
|
71
|
+
unless file_extension_supported?
|
72
|
+
$stderr.puts "File type must be one of: #{SUPPORTED_FILE_TYPES.join(', ')}."
|
73
|
+
return
|
74
|
+
end
|
75
|
+
|
76
|
+
@input_files.each do |file_name|
|
77
|
+
run_based_on_file_ext(file_name)
|
78
|
+
end
|
79
|
+
|
80
|
+
complete
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def file_extension_supported?
|
86
|
+
@input_files.all? do |file_name|
|
87
|
+
ext = get_ext(file_name)
|
88
|
+
SUPPORTED_FILE_TYPES.include?(ext)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get_ext(file_name)
|
93
|
+
File.extname(file_name).downcase
|
94
|
+
end
|
95
|
+
|
96
|
+
def run_based_on_file_ext(file_name)
|
97
|
+
ext = get_ext(file_name)
|
98
|
+
ext == '.csv' ? run_csv(file_name) : run_json(file_name)
|
99
|
+
end
|
100
|
+
|
101
|
+
def run_csv(file_name)
|
102
|
+
row_number = 1
|
103
|
+
CSV.foreach(file_name, headers: true).each do |row|
|
104
|
+
row_number += 1
|
105
|
+
process(row.to_hash, file_name, row_number)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def run_json(file_name)
|
110
|
+
file_data = File.read(file_name)
|
111
|
+
parsed_file_data = Yajl::Parser.parse(file_data)
|
112
|
+
return if parsed_file_data.nil? || parsed_file_data.empty?
|
113
|
+
|
114
|
+
record = 1
|
115
|
+
parsed_file_data.each do |json_row|
|
116
|
+
process(json_row, file_name, record)
|
117
|
+
record += 1
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def initialize_pipeline_stage(pipeline_stage)
|
122
|
+
name = stage_name(pipeline_stage)
|
123
|
+
params = stage_params(pipeline_stage)
|
124
|
+
type = stage_type(pipeline_stage)
|
125
|
+
@pipeline_stages[name] = stage_class_from(name, type).new(params)
|
126
|
+
end
|
127
|
+
|
128
|
+
def stage_name(pipeline_stage)
|
129
|
+
pipeline_stage.class == String ? pipeline_stage : pipeline_stage.keys.first
|
130
|
+
end
|
131
|
+
|
132
|
+
def class_exists?(pipeline_stage_class)
|
133
|
+
pipeline_stage_class && pipeline_stage_class < ShopifyTransporter::Pipeline::Stage
|
134
|
+
end
|
135
|
+
|
136
|
+
def stage_params(pipeline_stage)
|
137
|
+
pipeline_stage['params'] if pipeline_stage.class == Hash
|
138
|
+
end
|
139
|
+
|
140
|
+
def stage_type(pipeline_stage)
|
141
|
+
return 'default' if pipeline_stage.class == String || pipeline_stage['type'].nil?
|
142
|
+
pipeline_stage['type']
|
143
|
+
end
|
144
|
+
|
145
|
+
def custom_stage_class_from(stage_name)
|
146
|
+
"CustomPipeline::#{@object_type.capitalize}::#{stage_name}"
|
147
|
+
end
|
148
|
+
|
149
|
+
def stage_class_from(name, type)
|
150
|
+
class_name = stage_classname(name, type)
|
151
|
+
klass = begin
|
152
|
+
k = Object.const_get(class_name)
|
153
|
+
k.is_a?(Class) && k
|
154
|
+
rescue NameError
|
155
|
+
nil
|
156
|
+
end
|
157
|
+
raise StageNotFoundError, name unless class_exists?(klass)
|
158
|
+
klass
|
159
|
+
end
|
160
|
+
|
161
|
+
def stage_classname(name, type)
|
162
|
+
case type
|
163
|
+
when 'default'
|
164
|
+
"ShopifyTransporter::Pipeline::#{@config['platform_type']}::#{@object_type.capitalize}::#{name}"
|
165
|
+
when 'all_platforms'
|
166
|
+
"ShopifyTransporter::Pipeline::AllPlatforms::#{name}"
|
167
|
+
when 'custom'
|
168
|
+
"CustomPipeline::#{@object_type.capitalize}::#{name}"
|
169
|
+
else
|
170
|
+
raise UnsupportedStageTypeError.new(name, type)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def process(input, file_name, row_number)
|
175
|
+
@record_builder.build(input) do |record|
|
176
|
+
run_pipeline(input, record)
|
177
|
+
end
|
178
|
+
rescue ShopifyTransporter::RequiredKeyMissing, ShopifyTransporter::MissingParentObject => e
|
179
|
+
$stderr.puts error_message_from(e, file_name, row_number)
|
180
|
+
end
|
181
|
+
|
182
|
+
def run_pipeline(row, record)
|
183
|
+
@pipeline_stages.each do |_stage_name, stage|
|
184
|
+
stage.convert(row, record)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def error_message_from(error, file_name, row_number)
|
189
|
+
ext = get_ext(file_name)
|
190
|
+
if ext == '.csv'
|
191
|
+
"error: #{file_name}:#{row_number}, message: #{error.message}"
|
192
|
+
else
|
193
|
+
"error in file: #{file_name} at record number #{row_number}, message: #{error.message}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def complete
|
198
|
+
puts @record_class.header
|
199
|
+
@record_builder.instances.each do |_, record_hash|
|
200
|
+
puts @record_class.new(record_hash).to_csv
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def config_file(config)
|
205
|
+
@config = YAML.load_file(config) if valid_config_file?(config)
|
206
|
+
end
|
207
|
+
|
208
|
+
def input_files(*files)
|
209
|
+
@input_files = *files if valid_files?(*files)
|
210
|
+
end
|
211
|
+
|
212
|
+
def valid_config_file?(config)
|
213
|
+
valid_file?(config)
|
214
|
+
end
|
215
|
+
|
216
|
+
def valid_files?(*files)
|
217
|
+
return false if files.any? do |f|
|
218
|
+
!valid_file?(f)
|
219
|
+
end
|
220
|
+
|
221
|
+
true
|
222
|
+
end
|
223
|
+
|
224
|
+
def valid_file?(path)
|
225
|
+
unless File.exist?(path)
|
226
|
+
puts "File #{path} can't be found"
|
227
|
+
return false
|
228
|
+
end
|
229
|
+
|
230
|
+
true
|
231
|
+
end
|
232
|
+
|
233
|
+
def build_classes_based_on_config
|
234
|
+
@record_class = Object.const_get("ShopifyTransporter::Shopify::#{@object_type.capitalize}")
|
235
|
+
@record_builder = ShopifyTransporter::RecordBuilder.new(
|
236
|
+
record_key_from_config, key_required_from_config
|
237
|
+
)
|
238
|
+
end
|
239
|
+
|
240
|
+
def record_key_from_config
|
241
|
+
@config['object_types'][@object_type]['record_key']
|
242
|
+
end
|
243
|
+
|
244
|
+
def key_required_from_config
|
245
|
+
@config['object_types'][@object_type]['key_required']
|
246
|
+
end
|
247
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'shopify_transporter'
|
3
|
+
|
4
|
+
module CustomPipeline
|
5
|
+
module <%= @object_type %>
|
6
|
+
class <%= @pipeline_class %> < ShopifyTransporter::Pipeline::Stage
|
7
|
+
def convert(input, record)
|
8
|
+
# The convert command reads the input files one-by-one, line-by-line.
|
9
|
+
#
|
10
|
+
# For each row, the value of the record_key column is used to lookup the Shopify object being built.
|
11
|
+
#
|
12
|
+
# If the Shopify object doesn't exist, it's created as a default empty hash.
|
13
|
+
#
|
14
|
+
# It's the role of a pipeline stage to examine the input rows and populate attributes on the Shopify object.
|
15
|
+
#
|
16
|
+
# For example, the TopLevelAttributes stage of a Magento customer migration would look for a column called firstname on the input,
|
17
|
+
# and then populate the Shopify object accordingly:
|
18
|
+
#
|
19
|
+
# record['first_name'] = input['firstname']
|
20
|
+
#
|
21
|
+
# Any modifications to the record within a pipeline stage are permanent to the Shopify record associated with the record_key.
|
22
|
+
#
|
23
|
+
# The next pipline stage to receive the record will receive the same input and the existing record which would consist of:
|
24
|
+
#
|
25
|
+
# {
|
26
|
+
# 'first_name' => 'John',
|
27
|
+
# }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
platform_type: Magento
|
2
|
+
object_types:
|
3
|
+
customer:
|
4
|
+
record_key: email
|
5
|
+
pipeline_stages:
|
6
|
+
- TopLevelAttributes
|
7
|
+
- AddressesAttribute
|
8
|
+
- Metafields:
|
9
|
+
type: all_platforms
|
10
|
+
params:
|
11
|
+
# Specify a custom namespace for your metafields with metafield_namespace.
|
12
|
+
# Uses migrated_data by default.
|
13
|
+
# metafield_namespace: migrated_data
|
14
|
+
metafields:
|
15
|
+
- website
|
16
|
+
- group
|
17
|
+
- free_trial_start_at
|
18
|
+
order:
|
19
|
+
record_key: increment_id
|
20
|
+
pipeline_stages:
|
21
|
+
- TopLevelAttributes
|
22
|
+
- LineItems
|
23
|
+
- AddressesAttribute
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'shopify_transporter/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.required_ruby_version = '>= 2.4.0'
|
7
|
+
|
8
|
+
spec.name = %q{shopify_transporter}
|
9
|
+
spec.version = ShopifyTransporter::VERSION
|
10
|
+
spec.author = 'Shopify'
|
11
|
+
spec.email = %q{developers@shopify.com}
|
12
|
+
|
13
|
+
spec.summary = 'Tools for migrating to Shopify'
|
14
|
+
spec.description = 'The Transporter tool allows you to convert data from a third-party platform into a format that can be imported into Shopify.'
|
15
|
+
spec.homepage = %q{https://help.shopify.com/manual/migrating-to-shopify}
|
16
|
+
spec.license = 'Shopify'
|
17
|
+
spec.extra_rdoc_files = [
|
18
|
+
'LICENSE',
|
19
|
+
'README.md',
|
20
|
+
'RELEASING'
|
21
|
+
]
|
22
|
+
|
23
|
+
whitelisted_files = "exe lib Gemfile LICENSE Rakefile README.md shopify_transporter.gemspec"
|
24
|
+
|
25
|
+
spec.files = `git ls-files -z #{whitelisted_files}`.split("\x0")
|
26
|
+
spec.bindir = 'exe'
|
27
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler', '~> 1'
|
31
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
32
|
+
|
33
|
+
spec.add_dependency 'activesupport', '~> 5.1'
|
34
|
+
spec.add_dependency 'thor', '~> 0.20'
|
35
|
+
spec.add_dependency 'yajl-ruby', '~> 1.3'
|
36
|
+
end
|