shopify_transporter 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|