shopify-cli 2.12.0 → 2.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +5 -0
- data/.github/CONTRIBUTING.md +1 -1
- data/.github/PULL_REQUEST_TEMPLATE.md +1 -1
- data/.github/workflows/shopify.yml +2 -1
- data/.github/workflows/stale.yml +41 -0
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +36 -0
- data/Gemfile.lock +18 -18
- data/Rakefile +16 -0
- data/bin/shopify +4 -4
- data/dev.yml +1 -1
- data/ext/javy/hashes/javy-arm-macos-v0.2.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-arm-macos-v0.2.1.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-linux-v0.2.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-linux-v0.2.1.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-macos-v0.2.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-macos-v0.2.1.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-windows-v0.2.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-windows-v0.2.1.gz.sha256 +1 -0
- data/ext/javy/version +1 -1
- data/lib/project_types/extension/features/argo_setup_steps.rb +4 -6
- data/lib/project_types/extension/models/npm_package.rb +19 -1
- data/lib/project_types/extension/models/server_config/development_renderer.rb +4 -3
- data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +13 -0
- data/lib/project_types/extension/tasks/configure_features.rb +15 -2
- data/lib/project_types/extension/tasks/convert_server_config.rb +2 -1
- data/lib/project_types/script/cli.rb +0 -4
- data/lib/project_types/script/commands/create.rb +4 -4
- data/lib/project_types/script/config/extension_points.yml +0 -6
- data/lib/project_types/script/errors.rb +1 -1
- data/lib/project_types/script/forms/create.rb +7 -7
- data/lib/project_types/script/layers/application/build_script.rb +9 -26
- data/lib/project_types/script/layers/application/create_script.rb +9 -10
- data/lib/project_types/script/layers/application/project_dependencies.rb +12 -14
- data/lib/project_types/script/layers/application/push_script.rb +14 -10
- data/lib/project_types/script/layers/domain/errors.rb +3 -3
- data/lib/project_types/script/layers/domain/push_package.rb +6 -0
- data/lib/project_types/script/layers/domain/script_config.rb +2 -4
- data/lib/project_types/script/layers/domain/script_project.rb +3 -2
- data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +0 -16
- data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +0 -1
- data/lib/project_types/script/layers/infrastructure/languages/typescript_project_creator.rb +19 -4
- data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +2 -10
- data/lib/project_types/script/layers/infrastructure/languages/wasm_project_creator.rb +0 -3
- data/lib/project_types/script/layers/infrastructure/languages/wasm_task_runner.rb +1 -1
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +3 -21
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +14 -26
- data/lib/project_types/script/layers/infrastructure/script_service.rb +4 -2
- data/lib/project_types/script/messages/messages.rb +9 -9
- data/lib/project_types/script/ui/error_handler.rb +4 -4
- data/lib/project_types/script/ui/strict_spinner.rb +4 -6
- data/lib/project_types/theme/cli.rb +2 -0
- data/lib/project_types/theme/commands/common/root_helper.rb +11 -5
- data/lib/project_types/theme/commands/list.rb +34 -0
- data/lib/project_types/theme/commands/open.rb +65 -0
- data/lib/project_types/theme/commands/package.rb +1 -0
- data/lib/project_types/theme/commands/pull.rb +4 -4
- data/lib/project_types/theme/commands/push.rb +4 -4
- data/lib/project_types/theme/conversions/base_glob.rb +20 -5
- data/lib/project_types/theme/forms/select.rb +11 -39
- data/lib/project_types/theme/messages/messages.rb +33 -2
- data/lib/project_types/theme/presenters/theme_presenter.rb +48 -0
- data/lib/project_types/theme/presenters/themes_presenter.rb +32 -0
- data/lib/shopify_cli/api.rb +1 -1
- data/lib/shopify_cli/command.rb +1 -7
- data/lib/shopify_cli/commands/app/deploy.rb +0 -1
- data/lib/shopify_cli/constants.rb +2 -2
- data/lib/shopify_cli/context.rb +13 -15
- data/lib/shopify_cli/core/entry_point.rb +1 -1
- data/lib/shopify_cli/core/monorail.rb +14 -6
- data/lib/shopify_cli/environment.rb +19 -11
- data/lib/shopify_cli/exception_reporter.rb +2 -0
- data/lib/shopify_cli/messages/messages.rb +5 -5
- data/lib/shopify_cli/packager.rb +1 -1
- data/lib/shopify_cli/result.rb +14 -0
- data/lib/shopify_cli/services/app/create/node_service.rb +2 -14
- data/lib/shopify_cli/services/app/create/php_service.rb +1 -6
- data/lib/shopify_cli/services/app/create/rails_service.rb +5 -13
- data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +5 -5
- data/lib/shopify_cli/theme/dev_server/watcher.rb +10 -2
- data/lib/shopify_cli/theme/development_theme.rb +2 -5
- data/lib/shopify_cli/theme/syncer.rb +27 -32
- data/lib/shopify_cli/theme/theme.rb +16 -27
- data/lib/shopify_cli/theme/theme_admin_api.rb +72 -0
- data/lib/shopify_cli/transform_data_structure.rb +3 -2
- data/lib/shopify_cli/version.rb +1 -1
- data/shipit.yml +3 -0
- data/shopify-cli.gemspec +9 -2
- data/shopify-dev +9 -11
- metadata +21 -9
- data/lib/project_types/rails/ruby.rb +0 -17
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +0 -21
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -109
@@ -15,7 +15,9 @@ module ShopifyCLI
|
|
15
15
|
},
|
16
16
|
core: {
|
17
17
|
errors: {
|
18
|
-
missing_node: "Node is required to continue. Install
|
18
|
+
missing_node: "Node.js is required to continue. Install Node.js here: https://nodejs.org/en/download.",
|
19
|
+
missing_npm: "npm is required to continue. Install npm here: https://www.npmjs.com/get-npm.",
|
20
|
+
missing_ruby: "Ruby is required to continue. Install Ruby here: https://www.ruby-lang.org/en/downloads.",
|
19
21
|
option_parser: {
|
20
22
|
invalid_option: "The option {{command:%s}} is not supported.",
|
21
23
|
missing_argument: "The required argument {{command:%s}} is missing.",
|
@@ -95,8 +97,6 @@ module ShopifyCLI
|
|
95
97
|
HELP
|
96
98
|
error: {
|
97
99
|
node_required: "node is required to create an app project. Download at https://nodejs.org/en/download.",
|
98
|
-
node_version_failure: "Failed to get the current node version. Please make sure it is installed as " \
|
99
|
-
"per the instructions at https://nodejs.org/en.",
|
100
100
|
npm_required: "npm is required to create an app project. Download at https://www.npmjs.com/get-npm.",
|
101
101
|
npm_version_failure: "Failed to get the current npm version. Please make sure it is installed as per " \
|
102
102
|
"the instructions at https://www.npmjs.com/get-npm.",
|
@@ -131,8 +131,6 @@ module ShopifyCLI
|
|
131
131
|
{{underline:https://getcomposer.org/download/}}
|
132
132
|
COMPOSER
|
133
133
|
npm_required: "npm is required to create an app project. Download at https://www.npmjs.com/get-npm.",
|
134
|
-
npm_version_failure: "Failed to get the current npm version. Please make sure it is installed as per " \
|
135
|
-
"the instructions at https://www.npmjs.com/get-npm.",
|
136
134
|
app_setup: "Failed to set up the app",
|
137
135
|
},
|
138
136
|
|
@@ -283,6 +281,8 @@ module ShopifyCLI
|
|
283
281
|
localization: {
|
284
282
|
error: {
|
285
283
|
bundle_too_large: "Total size of all locale files must be less than %s.",
|
284
|
+
duplicate_locale_code: "Duplicate locale found: `%s`; locale codes"\
|
285
|
+
" should be unique and are case insensitive.",
|
286
286
|
file_empty: "Locale file `%s` is empty.",
|
287
287
|
file_too_large: "Locale file `%s` too large; size must be less than %s.",
|
288
288
|
invalid_file_extension: "Invalid locale filename: `%s`; only .json files are allowed.",
|
data/lib/shopify_cli/packager.rb
CHANGED
@@ -81,7 +81,7 @@ module ShopifyCLI
|
|
81
81
|
puts "Grabbing sha256 checksum from Rubygems.org"
|
82
82
|
require "digest/sha2"
|
83
83
|
require "open-uri"
|
84
|
-
gem_checksum = open("https://rubygems.org/downloads/shopify-cli-#{ShopifyCLI::VERSION}.gem") do |io|
|
84
|
+
gem_checksum = URI.open("https://rubygems.org/downloads/shopify-cli-#{ShopifyCLI::VERSION}.gem") do |io|
|
85
85
|
Digest::SHA256.new.hexdigest(io.read)
|
86
86
|
end
|
87
87
|
|
data/lib/shopify_cli/result.rb
CHANGED
@@ -181,6 +181,13 @@ module ShopifyCLI
|
|
181
181
|
self
|
182
182
|
end
|
183
183
|
|
184
|
+
##
|
185
|
+
# returns the value this success represents
|
186
|
+
#
|
187
|
+
def unwrap!
|
188
|
+
value
|
189
|
+
end
|
190
|
+
|
184
191
|
##
|
185
192
|
# returns the success value and ignores the fallback value that was either
|
186
193
|
# provided as a method argument or by passing a block. However, the caller
|
@@ -339,6 +346,13 @@ module ShopifyCLI
|
|
339
346
|
raise ArgumentError, "expected either a fallback value or a block" unless (args.length == 1) ^ block
|
340
347
|
block ? block.call(@error) : args.pop
|
341
348
|
end
|
349
|
+
|
350
|
+
##
|
351
|
+
# raises the error this failure represents
|
352
|
+
#
|
353
|
+
def unwrap!
|
354
|
+
raise error
|
355
|
+
end
|
342
356
|
end
|
343
357
|
|
344
358
|
##
|
@@ -88,24 +88,12 @@ module ShopifyCLI
|
|
88
88
|
end
|
89
89
|
|
90
90
|
def check_node
|
91
|
-
|
92
|
-
context.abort(context.message("core.app.create.node.error.node_required")) if cmd_path.nil?
|
93
|
-
|
94
|
-
version, stat = context.capture2e("node", "-v")
|
95
|
-
unless stat.success?
|
96
|
-
context.abort(context.message("core.app.create.node.error.node_version_failure"))
|
97
|
-
end
|
98
|
-
|
91
|
+
version = ShopifyCLI::Environment.node_version(context: context)
|
99
92
|
context.done(context.message("core.app.create.node.node_version", version))
|
100
93
|
end
|
101
94
|
|
102
95
|
def check_npm
|
103
|
-
|
104
|
-
context.abort(context.message("core.app.create.node.error.npm_required")) if cmd_path.nil?
|
105
|
-
|
106
|
-
version, stat = context.capture2e("npm", "-v")
|
107
|
-
context.abort(context.message("core.app.create.node.error.npm_version_failure")) unless stat.success?
|
108
|
-
|
96
|
+
version = ShopifyCLI::Environment.npm_version(context: context)
|
109
97
|
context.done(context.message("core.app.create.node.npm_version", version))
|
110
98
|
end
|
111
99
|
|
@@ -69,12 +69,7 @@ module ShopifyCLI
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def check_npm
|
72
|
-
|
73
|
-
context.abort(context.message("core.app.create.php.error.npm_required")) if cmd_path.nil?
|
74
|
-
|
75
|
-
version, stat = context.capture2e("npm", "-v")
|
76
|
-
context.abort(context.message("core.app.create.php.error.npm_version_failure")) unless stat.success?
|
77
|
-
|
72
|
+
version = ShopifyCLI::Environment.npm_version(context: context)
|
78
73
|
context.done(context.message("core.app.create.php.npm_version", version))
|
79
74
|
end
|
80
75
|
|
@@ -108,8 +108,8 @@ module ShopifyCLI
|
|
108
108
|
end
|
109
109
|
|
110
110
|
def check_ruby
|
111
|
-
ruby_version =
|
112
|
-
return if ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.
|
111
|
+
ruby_version = Environment.ruby_version(context)
|
112
|
+
return if ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.1.0")
|
113
113
|
context.abort(context.message("core.app.create.rails.error.invalid_ruby_version"))
|
114
114
|
end
|
115
115
|
|
@@ -219,18 +219,10 @@ module ShopifyCLI
|
|
219
219
|
end
|
220
220
|
|
221
221
|
def install_webpacker?
|
222
|
-
rails_version
|
223
|
-
|
224
|
-
end
|
225
|
-
|
226
|
-
def rails_version
|
227
|
-
output, status = context.capture2e("rails", "--version")
|
228
|
-
unless status.success?
|
229
|
-
context.abort(context.message("core.app.create.rails.error.install_failure", "rails"))
|
230
|
-
end
|
222
|
+
rails_version = Environment.rails_version(context: context)
|
223
|
+
webpacker_config = File.exist?(File.join(context.root, "config/webpacker.yml"))
|
231
224
|
|
232
|
-
|
233
|
-
::Semantic::Version.new(version)
|
225
|
+
rails_version < ::Semantic::Version.new("7.0.0") && !webpacker_config
|
234
226
|
end
|
235
227
|
end
|
236
228
|
end
|
@@ -28,6 +28,10 @@ module ShopifyCLI
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
+
def api_client
|
32
|
+
@api_client ||= ThemeAdminAPI.new(@ctx, @theme.shop)
|
33
|
+
end
|
34
|
+
|
31
35
|
def updated_file?(body, file)
|
32
36
|
remote_checksum = body.dig("asset", "checksum")
|
33
37
|
local_checksum = file.checksum
|
@@ -45,12 +49,8 @@ module ShopifyCLI
|
|
45
49
|
end
|
46
50
|
|
47
51
|
def fetch_asset(file)
|
48
|
-
|
49
|
-
@ctx,
|
50
|
-
shop: @theme.shop,
|
52
|
+
api_client.get(
|
51
53
|
path: "themes/#{@theme.id}/assets.json",
|
52
|
-
method: "GET",
|
53
|
-
api_version: "unstable",
|
54
54
|
query: URI.encode_www_form("asset[key]" => file.relative_path.to_s),
|
55
55
|
)
|
56
56
|
rescue ShopifyCLI::API::APIRequestNotFoundError
|
@@ -45,13 +45,21 @@ module ShopifyCLI
|
|
45
45
|
def filter_theme_files(files)
|
46
46
|
files
|
47
47
|
.select { |file| @theme.theme_file?(file) }
|
48
|
-
.
|
48
|
+
.map { |file| @theme[file] }
|
49
|
+
.reject { |file| ignore_file?(file) }
|
49
50
|
end
|
50
51
|
|
51
52
|
def filter_remote_files(files)
|
52
53
|
files
|
53
54
|
.select { |file| @syncer.remote_file?(file) }
|
54
|
-
.
|
55
|
+
.map { |file| @theme[file] }
|
56
|
+
.reject { |file| ignore_file?(file) }
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def ignore_file?(file)
|
62
|
+
@ignore_filter&.ignore?(file.relative_path.to_s)
|
55
63
|
end
|
56
64
|
end
|
57
65
|
end
|
@@ -45,11 +45,8 @@ module ShopifyCLI
|
|
45
45
|
def exists?
|
46
46
|
return false unless id
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
shop: shop,
|
51
|
-
path: "themes/#{id}.json",
|
52
|
-
api_version: "unstable",
|
48
|
+
api_client.get(
|
49
|
+
path: "themes/#{id}.json"
|
53
50
|
)
|
54
51
|
rescue ShopifyCLI::API::APIRequestNotFoundError
|
55
52
|
false
|
@@ -7,15 +7,15 @@ require "forwardable"
|
|
7
7
|
require_relative "syncer/error_reporter"
|
8
8
|
require_relative "syncer/standard_reporter"
|
9
9
|
require_relative "syncer/operation"
|
10
|
+
require_relative "theme_admin_api"
|
10
11
|
|
11
12
|
module ShopifyCLI
|
12
13
|
module Theme
|
13
14
|
class Syncer
|
14
15
|
extend Forwardable
|
15
16
|
|
16
|
-
API_VERSION = "unstable"
|
17
|
-
|
18
17
|
attr_reader :checksums
|
18
|
+
attr_reader :checksums_mutex
|
19
19
|
attr_accessor :include_filter
|
20
20
|
attr_accessor :ignore_filter
|
21
21
|
|
@@ -39,6 +39,9 @@ module ShopifyCLI
|
|
39
39
|
# Mutex used to pause all threads when backing-off when hitting API rate limits
|
40
40
|
@backoff_mutex = Mutex.new
|
41
41
|
|
42
|
+
# Mutex used to coordinate changes in the checksums (shared accross all threads)
|
43
|
+
@checksums_mutex = Mutex.new
|
44
|
+
|
42
45
|
# Latest theme assets checksums. Updated on each upload.
|
43
46
|
@checksums = {}
|
44
47
|
|
@@ -46,12 +49,16 @@ module ShopifyCLI
|
|
46
49
|
@error_checksums = []
|
47
50
|
end
|
48
51
|
|
52
|
+
def api_client
|
53
|
+
@api_client ||= ThemeAdminAPI.new(@ctx, @theme.shop)
|
54
|
+
end
|
55
|
+
|
49
56
|
def lock_io!
|
50
|
-
@reporters.each
|
57
|
+
@reporters.each(&:disable!)
|
51
58
|
end
|
52
59
|
|
53
60
|
def unlock_io!
|
54
|
-
@reporters.each
|
61
|
+
@reporters.each(&:enable!)
|
55
62
|
end
|
56
63
|
|
57
64
|
def enqueue_updates(files)
|
@@ -96,11 +103,8 @@ module ShopifyCLI
|
|
96
103
|
end
|
97
104
|
|
98
105
|
def fetch_checksums!
|
99
|
-
_status, response =
|
100
|
-
@
|
101
|
-
shop: @theme.shop,
|
102
|
-
path: "themes/#{@theme.id}/assets.json",
|
103
|
-
api_version: API_VERSION,
|
106
|
+
_status, response = api_client.get(
|
107
|
+
path: "themes/#{@theme.id}/assets.json"
|
104
108
|
)
|
105
109
|
update_checksums(response)
|
106
110
|
end
|
@@ -118,7 +122,7 @@ module ShopifyCLI
|
|
118
122
|
operation = @queue.pop
|
119
123
|
break if operation.nil? # shutdown was called
|
120
124
|
perform(operation)
|
121
|
-
rescue Exception => e
|
125
|
+
rescue Exception => e # rubocop:disable Lint/RescueException
|
122
126
|
error_suffix = ": #{e}"
|
123
127
|
error_suffix += + "\n\t#{e.backtrace.join("\n\t")}" if @ctx.debug?
|
124
128
|
report_error(operation, error_suffix)
|
@@ -239,12 +243,8 @@ module ShopifyCLI
|
|
239
243
|
asset[:attachment] = Base64.encode64(file.read)
|
240
244
|
end
|
241
245
|
|
242
|
-
_status, body, response =
|
243
|
-
@ctx,
|
244
|
-
shop: @theme.shop,
|
246
|
+
_status, body, response = api_client.put(
|
245
247
|
path: "themes/#{@theme.id}/assets.json",
|
246
|
-
method: "PUT",
|
247
|
-
api_version: API_VERSION,
|
248
248
|
body: JSON.generate(asset: asset)
|
249
249
|
)
|
250
250
|
|
@@ -272,23 +272,19 @@ module ShopifyCLI
|
|
272
272
|
end
|
273
273
|
|
274
274
|
def ignored_by_include_filter?(path)
|
275
|
-
include_filter && !include_filter.match?(path)
|
275
|
+
!!include_filter && !include_filter.match?(path)
|
276
276
|
end
|
277
277
|
|
278
278
|
def get(file)
|
279
|
-
_status, body, response =
|
280
|
-
@ctx,
|
281
|
-
shop: @theme.shop,
|
279
|
+
_status, body, response = api_client.get(
|
282
280
|
path: "themes/#{@theme.id}/assets.json",
|
283
|
-
method: "GET",
|
284
|
-
api_version: API_VERSION,
|
285
281
|
query: URI.encode_www_form("asset[key]" => file.relative_path.to_s),
|
286
282
|
)
|
287
283
|
|
288
284
|
update_checksums(body)
|
289
285
|
|
290
286
|
attachment = body.dig("asset", "attachment")
|
291
|
-
|
287
|
+
if attachment
|
292
288
|
file.write(Base64.decode64(attachment))
|
293
289
|
else
|
294
290
|
file.write(body.dig("asset", "value"))
|
@@ -298,14 +294,10 @@ module ShopifyCLI
|
|
298
294
|
end
|
299
295
|
|
300
296
|
def delete(file)
|
301
|
-
_status, _body, response =
|
302
|
-
@ctx,
|
303
|
-
shop: @theme.shop,
|
297
|
+
_status, _body, response = api_client.delete(
|
304
298
|
path: "themes/#{@theme.id}/assets.json",
|
305
|
-
method: "DELETE",
|
306
|
-
api_version: API_VERSION,
|
307
299
|
body: JSON.generate(asset: {
|
308
|
-
key: file.relative_path.to_s
|
300
|
+
key: file.relative_path.to_s,
|
309
301
|
})
|
310
302
|
)
|
311
303
|
|
@@ -314,14 +306,17 @@ module ShopifyCLI
|
|
314
306
|
|
315
307
|
def update_checksums(api_response)
|
316
308
|
api_response.values.flatten.each do |asset|
|
317
|
-
|
309
|
+
next unless asset["key"]
|
310
|
+
checksums_mutex.synchronize do
|
318
311
|
@checksums[asset["key"]] = asset["checksum"]
|
319
312
|
end
|
320
313
|
end
|
321
314
|
# Generate .liquid asset files are reported twice in checksum:
|
322
315
|
# once of generated, once for .liquid. We only keep the .liquid, that's the one we have
|
323
316
|
# on disk.
|
324
|
-
|
317
|
+
checksums_mutex.synchronize do
|
318
|
+
@checksums.reject! { |key, _| @checksums.key?("#{key}.liquid") }
|
319
|
+
end
|
325
320
|
end
|
326
321
|
|
327
322
|
def file_has_changed?(file)
|
@@ -332,7 +327,7 @@ module ShopifyCLI
|
|
332
327
|
parsed_body = JSON.parse(exception&.response&.body)
|
333
328
|
message = parsed_body.dig("errors", "asset") || parsed_body["message"] || exception.message
|
334
329
|
# Truncate to first lines
|
335
|
-
[message].flatten.map { |
|
330
|
+
[message].flatten.map { |mess| mess.split("\n", 2).first }
|
336
331
|
rescue JSON::ParserError
|
337
332
|
[exception.message]
|
338
333
|
end
|
@@ -340,7 +335,7 @@ module ShopifyCLI
|
|
340
335
|
def backoff_if_near_limit!(used, limit)
|
341
336
|
if used > limit - @threads.size
|
342
337
|
@ctx.debug("Near API call limit, waiting 2 sec…")
|
343
|
-
@backoff_mutex.synchronize { sleep
|
338
|
+
@backoff_mutex.synchronize { sleep(2) }
|
344
339
|
end
|
345
340
|
end
|
346
341
|
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative "file"
|
3
|
+
require_relative "theme_admin_api"
|
3
4
|
|
4
5
|
require "pathname"
|
5
6
|
require "time"
|
@@ -59,7 +60,7 @@ module ShopifyCLI
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def shop
|
62
|
-
|
63
|
+
api_client.get_shop_or_abort
|
63
64
|
end
|
64
65
|
|
65
66
|
def editor_url
|
@@ -101,9 +102,7 @@ module ShopifyCLI
|
|
101
102
|
def create
|
102
103
|
raise InvalidThemeRole, "Can't create live theme. Use publish." if live?
|
103
104
|
|
104
|
-
_status, body =
|
105
|
-
@ctx,
|
106
|
-
shop: shop,
|
105
|
+
_status, body = api_client.post(
|
107
106
|
path: "themes.json",
|
108
107
|
body: JSON.generate({
|
109
108
|
theme: {
|
@@ -111,31 +110,21 @@ module ShopifyCLI
|
|
111
110
|
role: role,
|
112
111
|
},
|
113
112
|
}),
|
114
|
-
method: "POST",
|
115
|
-
api_version: "unstable",
|
116
113
|
)
|
117
114
|
|
118
115
|
@id = body["theme"]["id"]
|
119
116
|
end
|
120
117
|
|
121
118
|
def delete
|
122
|
-
|
123
|
-
|
124
|
-
shop: shop,
|
125
|
-
method: "DELETE",
|
126
|
-
path: "themes/#{id}.json",
|
127
|
-
api_version: "unstable",
|
119
|
+
api_client.delete(
|
120
|
+
path: "themes/#{id}.json"
|
128
121
|
)
|
129
122
|
end
|
130
123
|
|
131
124
|
def publish
|
132
125
|
return if live?
|
133
|
-
|
134
|
-
@ctx,
|
135
|
-
shop: shop,
|
136
|
-
method: "PUT",
|
126
|
+
api_client.put(
|
137
127
|
path: "themes/#{id}.json",
|
138
|
-
api_version: "unstable",
|
139
128
|
body: JSON.generate(theme: {
|
140
129
|
role: "main",
|
141
130
|
})
|
@@ -205,23 +194,23 @@ module ShopifyCLI
|
|
205
194
|
end
|
206
195
|
|
207
196
|
def fetch_themes(ctx)
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
path: "themes.json"
|
212
|
-
api_version: "unstable",
|
197
|
+
api_client = ThemeAdminAPI.new(ctx)
|
198
|
+
|
199
|
+
api_client.get(
|
200
|
+
path: "themes.json"
|
213
201
|
)
|
214
202
|
end
|
215
203
|
end
|
216
204
|
|
217
205
|
private
|
218
206
|
|
207
|
+
def api_client
|
208
|
+
@api_client ||= ThemeAdminAPI.new(@ctx)
|
209
|
+
end
|
210
|
+
|
219
211
|
def load_info_from_api
|
220
|
-
_status, body =
|
221
|
-
|
222
|
-
shop: shop,
|
223
|
-
path: "themes/#{id}.json",
|
224
|
-
api_version: "unstable",
|
212
|
+
_status, body = api_client.get(
|
213
|
+
path: "themes/#{id}.json"
|
225
214
|
)
|
226
215
|
|
227
216
|
@name = body.dig("theme", "name")
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ShopifyCLI
|
2
|
+
module Theme
|
3
|
+
class ThemeAdminAPI
|
4
|
+
API_VERSION = "unstable"
|
5
|
+
|
6
|
+
attr_reader :shop
|
7
|
+
|
8
|
+
def initialize(ctx, shop = nil)
|
9
|
+
@ctx = ctx
|
10
|
+
@shop = shop || get_shop_or_abort
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(path:, **args)
|
14
|
+
rest_request(method: "GET", path: path, **args)
|
15
|
+
end
|
16
|
+
|
17
|
+
def put(path:, **args)
|
18
|
+
rest_request(method: "PUT", path: path, **args)
|
19
|
+
end
|
20
|
+
|
21
|
+
def post(path:, **args)
|
22
|
+
rest_request(method: "POST", path: path, **args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def delete(path:, **args)
|
26
|
+
rest_request(method: "DELETE", path: path, **args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_shop_or_abort # rubocop:disable Naming/AccessorMethodName
|
30
|
+
ShopifyCLI::AdminAPI.get_shop_or_abort(@ctx)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def rest_request(**args)
|
36
|
+
ShopifyCLI::AdminAPI.rest_request(
|
37
|
+
@ctx,
|
38
|
+
shop: @shop,
|
39
|
+
api_version: API_VERSION,
|
40
|
+
**args.compact
|
41
|
+
)
|
42
|
+
rescue ShopifyCLI::API::APIRequestForbiddenError,
|
43
|
+
ShopifyCLI::API::APIRequestUnauthorizedError => error
|
44
|
+
# The Admin API returns 403 Forbidden responses on different
|
45
|
+
# scenarios:
|
46
|
+
#
|
47
|
+
# * when a user doesn't have permissions for a request:
|
48
|
+
# <APIRequestForbiddenError: 403 {}>
|
49
|
+
#
|
50
|
+
# * when an asset operation cannot be performed:
|
51
|
+
# <APIRequestForbiddenError: 403 {"message":"templates/gift_card.liquid could not be deleted"}>
|
52
|
+
if empty_response_error?(error)
|
53
|
+
return handle_permissions_error
|
54
|
+
end
|
55
|
+
|
56
|
+
raise error
|
57
|
+
end
|
58
|
+
|
59
|
+
def handle_permissions_error
|
60
|
+
ensure_user_error = @ctx.message("theme.ensure_user_error", shop)
|
61
|
+
ensure_user_try_this = @ctx.message("theme.ensure_user_try_this")
|
62
|
+
|
63
|
+
@ctx.abort(ensure_user_error, ensure_user_try_this)
|
64
|
+
end
|
65
|
+
|
66
|
+
def empty_response_error?(error)
|
67
|
+
error_message = error&.response&.body.to_s
|
68
|
+
error_message.empty?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -40,6 +40,7 @@ module ShopifyCLI
|
|
40
40
|
|
41
41
|
property! :underscore_keys, accepts: [true, false], default: false, reader: :underscore_keys?
|
42
42
|
property! :symbolize_keys, accepts: [true, false], default: false, reader: :symbolize_keys?
|
43
|
+
property! :shallow, accepts: [true, false], default: false, reader: :shallow?
|
43
44
|
property! :associative_array_container,
|
44
45
|
accepts: ->(c) { c.respond_to?(:new) && c.method_defined?(:[]=) },
|
45
46
|
default: -> { Hash }
|
@@ -47,10 +48,10 @@ module ShopifyCLI
|
|
47
48
|
def call(object)
|
48
49
|
case object
|
49
50
|
when Array
|
50
|
-
object.map(&self).map(&:value)
|
51
|
+
shallow? ? object.dup : object.map(&self).map(&:value)
|
51
52
|
when Hash
|
52
53
|
object.each.with_object(associative_array_container.new) do |(key, value), result|
|
53
|
-
result[transform_key(key)] = call(value).value
|
54
|
+
result[transform_key(key)] = shallow? ? value : call(value).value
|
54
55
|
end
|
55
56
|
else
|
56
57
|
ShopifyCLI::Result.success(object)
|
data/lib/shopify_cli/version.rb
CHANGED
data/shipit.yml
ADDED
data/shopify-cli.gemspec
CHANGED
@@ -35,11 +35,18 @@ Gem::Specification.new do |spec|
|
|
35
35
|
spec.require_paths = ["lib", "vendor"]
|
36
36
|
spec.executables << "shopify"
|
37
37
|
|
38
|
-
spec.add_development_dependency("bundler", "~> 2.
|
38
|
+
spec.add_development_dependency("bundler", "~> 2.3.8")
|
39
39
|
spec.add_development_dependency("rake", "~> 12.3", ">= 12.3.3")
|
40
40
|
spec.add_development_dependency("minitest", "~> 5.0")
|
41
41
|
|
42
42
|
spec.add_dependency("bugsnag", "~> 6.22")
|
43
43
|
spec.add_dependency("listen", "~> 3.7.0")
|
44
|
-
|
44
|
+
|
45
|
+
# We prefer being more strict here with the version range to have a more deterministic build.
|
46
|
+
# The added benefit is that, if the user upgrades the CLI, and we have "~> 1.10.1" version range,
|
47
|
+
# they will get a theme-check update.
|
48
|
+
# Whereas if we were to have "~> 1.9", that version would still be satisfied and thus not upgraded.
|
49
|
+
# Both shopify-cli and theme-check gems are owned and developed by Shopify.
|
50
|
+
# These gems are currently being actively developed and it's easiest to update them together.
|
51
|
+
spec.add_dependency("theme-check", "~> 1.10.1")
|
45
52
|
end
|
data/shopify-dev
CHANGED
@@ -3,16 +3,14 @@
|
|
3
3
|
require_relative "./bin/load_shopify"
|
4
4
|
|
5
5
|
exit(proc do
|
6
|
-
|
7
|
-
ShopifyCLI::
|
8
|
-
ShopifyCLI::Core::EntryPoint.call(ARGV.dup)
|
9
|
-
end
|
10
|
-
rescue StandardError => error
|
11
|
-
ShopifyCLI::ErrorHandler.exception = error
|
12
|
-
if ShopifyCLI::Environment.print_stacktrace?
|
13
|
-
raise error
|
14
|
-
else
|
15
|
-
1
|
16
|
-
end
|
6
|
+
ShopifyCLI::ErrorHandler.call do
|
7
|
+
ShopifyCLI::Core::EntryPoint.call(ARGV.dup)
|
17
8
|
end
|
9
|
+
rescue StandardError => error
|
10
|
+
ShopifyCLI::ErrorHandler.exception = error
|
11
|
+
if ShopifyCLI::Environment.print_stacktrace?
|
12
|
+
raise error
|
13
|
+
else
|
14
|
+
1
|
15
|
+
end
|
18
16
|
end.call)
|