shopify-cli 2.11.2 → 2.14.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 +4 -4
- data/.github/CODEOWNERS +5 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +1 -1
- data/.github/workflows/shopify.yml +2 -1
- data/.rubocop.yml +1 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +44 -1
- data/Gemfile.lock +18 -18
- data/Rakefile +16 -0
- data/bin/shopify +12 -8
- data/dev.yml +1 -1
- data/docs/users/installation.md +1 -44
- 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 +114 -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 +2 -4
- data/lib/project_types/script/commands/create.rb +5 -5
- data/lib/project_types/script/commands/push.rb +4 -6
- data/lib/project_types/script/config/extension_points.yml +0 -10
- data/lib/project_types/script/errors.rb +1 -1
- data/lib/project_types/script/forms/create.rb +7 -20
- data/lib/project_types/script/layers/application/build_script.rb +9 -26
- data/lib/project_types/script/layers/application/connect_app.rb +3 -2
- 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/errors.rb +11 -0
- 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/tool_version_checker.rb +26 -0
- data/lib/project_types/script/layers/infrastructure/languages/typescript_project_creator.rb +22 -10
- data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +32 -29
- 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/loaders/project.rb +8 -7
- data/lib/project_types/script/messages/messages.rb +22 -21
- data/lib/project_types/script/ui/error_handler.rb +17 -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 +71 -0
- data/lib/project_types/theme/commands/init.rb +2 -0
- 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 +18 -10
- data/lib/project_types/theme/commands/push.rb +17 -9
- data/lib/project_types/theme/commands/serve.rb +6 -2
- data/lib/project_types/theme/conversions/base_glob.rb +50 -0
- data/lib/project_types/theme/conversions/ignore_glob.rb +15 -0
- data/lib/project_types/theme/conversions/include_glob.rb +15 -0
- data/lib/project_types/theme/forms/select.rb +11 -39
- data/lib/project_types/theme/messages/messages.rb +38 -7
- 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/commands/app/create/node.rb +1 -0
- data/lib/shopify_cli/commands/app/create/php.rb +1 -0
- data/lib/shopify_cli/commands/app/create/rails.rb +1 -0
- data/lib/shopify_cli/commands/app/deploy.rb +1 -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 +6 -0
- data/lib/shopify_cli/exception_reporter.rb +2 -0
- data/lib/shopify_cli/git.rb +9 -1
- data/lib/shopify_cli/messages/messages.rb +21 -1
- data/lib/shopify_cli/packager.rb +1 -1
- data/lib/shopify_cli/result.rb +14 -0
- data/lib/shopify_cli/services/app/create/rails_service.rb +1 -1
- data/lib/shopify_cli/tasks/ensure_git_dependency.rb +14 -0
- data/lib/shopify_cli/tasks.rb +1 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +5 -5
- data/lib/shopify_cli/theme/dev_server/proxy.rb +14 -2
- 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/include_filter.rb +4 -2
- data/lib/shopify_cli/theme/syncer.rb +40 -36
- data/lib/shopify_cli/theme/theme.rb +16 -27
- data/lib/shopify_cli/theme/theme_admin_api.rb +71 -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 +26 -8
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +0 -25
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -98
|
@@ -5,7 +5,7 @@ module ShopifyCLI
|
|
|
5
5
|
module EntryPoint
|
|
6
6
|
class << self
|
|
7
7
|
def call(args, ctx = Context.new)
|
|
8
|
-
if ctx.development?
|
|
8
|
+
if ctx.development? && !ctx.testing?
|
|
9
9
|
ctx.warn(
|
|
10
10
|
ctx.message("core.warning.development_version", File.join(ShopifyCLI::ROOT, "bin", ShopifyCLI::TOOL_NAME))
|
|
11
11
|
)
|
|
@@ -17,11 +17,7 @@ module ShopifyCLI
|
|
|
17
17
|
|
|
18
18
|
def log(name, args, &block) # rubocop:disable Lint/UnusedMethodArgument
|
|
19
19
|
command, command_name = Commands::Registry.lookup_command(name)
|
|
20
|
-
|
|
21
|
-
if command
|
|
22
|
-
subcommand, subcommand_name = command.subcommand_registry.lookup_command(args.first)
|
|
23
|
-
final_command << subcommand_name if subcommand
|
|
24
|
-
end
|
|
20
|
+
full_command = self.full_command(command, args, resolved_command: [command_name])
|
|
25
21
|
|
|
26
22
|
start_time = now_in_milliseconds
|
|
27
23
|
err = nil
|
|
@@ -35,9 +31,21 @@ module ShopifyCLI
|
|
|
35
31
|
# If there's an error, we don't prompt from here and we let the exception
|
|
36
32
|
# reporter do that.
|
|
37
33
|
if report?(prompt: err.nil?)
|
|
38
|
-
send_event(start_time,
|
|
34
|
+
send_event(start_time, full_command, args - full_command, err&.message)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def full_command(command, args, resolved_command:)
|
|
40
|
+
resolved_command = resolved_command.dup
|
|
41
|
+
if command
|
|
42
|
+
subcommand, subcommand_name = command.subcommand_registry.lookup_command(args.first)
|
|
43
|
+
resolved_command << subcommand_name if subcommand
|
|
44
|
+
if subcommand&.subcommand_registry
|
|
45
|
+
resolved_command = full_command(subcommand, args.drop(1), resolved_command: resolved_command)
|
|
39
46
|
end
|
|
40
47
|
end
|
|
48
|
+
resolved_command
|
|
41
49
|
end
|
|
42
50
|
|
|
43
51
|
private
|
|
@@ -25,6 +25,12 @@ module ShopifyCLI
|
|
|
25
25
|
::Semantic::Version.new(out.chomp)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
def self.npm_version(context: Context.new)
|
|
29
|
+
out, err, stat = context.capture3("npm", "--version")
|
|
30
|
+
raise ShopifyCLI::Abort, err unless stat.success?
|
|
31
|
+
::Semantic::Version.new(out.chomp)
|
|
32
|
+
end
|
|
33
|
+
|
|
28
34
|
def self.interactive=(interactive)
|
|
29
35
|
@interactive = interactive
|
|
30
36
|
end
|
data/lib/shopify_cli/git.rb
CHANGED
|
@@ -4,7 +4,15 @@ module ShopifyCLI
|
|
|
4
4
|
# git.
|
|
5
5
|
class Git
|
|
6
6
|
class << self
|
|
7
|
-
# Check if Git
|
|
7
|
+
# Check if Git exists in the environment
|
|
8
|
+
def exists?(ctx)
|
|
9
|
+
_output, status = ctx.capture2e("git", "version")
|
|
10
|
+
status.success?
|
|
11
|
+
rescue Errno::ENOENT # git is not installed
|
|
12
|
+
false
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Check if the current working directory is a Git repository
|
|
8
16
|
def available?(ctx)
|
|
9
17
|
_output, status = ctx.capture2e("git", "status")
|
|
10
18
|
status.success?
|
|
@@ -277,6 +277,24 @@ module ShopifyCLI
|
|
|
277
277
|
HELP
|
|
278
278
|
},
|
|
279
279
|
},
|
|
280
|
+
extension: {
|
|
281
|
+
push: {
|
|
282
|
+
checkout_ui_extension: {
|
|
283
|
+
localization: {
|
|
284
|
+
error: {
|
|
285
|
+
bundle_too_large: "Total size of all locale files must be less than %s.",
|
|
286
|
+
file_empty: "Locale file `%s` is empty.",
|
|
287
|
+
file_too_large: "Locale file `%s` too large; size must be less than %s.",
|
|
288
|
+
invalid_file_extension: "Invalid locale filename: `%s`; only .json files are allowed.",
|
|
289
|
+
invalid_locale_code: "Invalid locale filename: `%s`; locale code should be 2 or 3 letters,"\
|
|
290
|
+
" optionally followed by a two-letter region code, e.g. `fr-CA`.",
|
|
291
|
+
single_default_locale: "There must be one and only one locale identified as the default locale,"\
|
|
292
|
+
" e.g. `en.default.json`",
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
},
|
|
280
298
|
error_reporting: {
|
|
281
299
|
unhandled_error: {
|
|
282
300
|
message: "{{x}} {{red:An unexpected error occured.}}",
|
|
@@ -355,6 +373,7 @@ module ShopifyCLI
|
|
|
355
373
|
error: {
|
|
356
374
|
directory_exists: "Project directory already exists. Please create a project with a new name.",
|
|
357
375
|
no_branches_found: "Could not find any git branches",
|
|
376
|
+
nonexistent: "Git needs to be installed: https://git-scm.com/download",
|
|
358
377
|
repo_not_initiated:
|
|
359
378
|
"Git repo is not initiated. Please run {{command:git init}} and make at least one commit.",
|
|
360
379
|
no_commits_made: "No git commits have been made. Please make at least one commit.",
|
|
@@ -672,7 +691,8 @@ module ShopifyCLI
|
|
|
672
691
|
},
|
|
673
692
|
},
|
|
674
693
|
ensure_dev_store: {
|
|
675
|
-
could_not_verify_store: "Couldn't verify your store
|
|
694
|
+
could_not_verify_store: "Couldn't verify your store. If you don't have a development store set up, "\
|
|
695
|
+
"please create one in your Partners dashboard and run `shopify app connect`.",
|
|
676
696
|
convert_to_dev_store: <<~MESSAGE,
|
|
677
697
|
Do you want to convert %s to a development store?
|
|
678
698
|
Doing this will allow you to install your app, but the store will become {{bold:transfer-disabled}}.
|
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
|
##
|
|
@@ -109,7 +109,7 @@ module ShopifyCLI
|
|
|
109
109
|
|
|
110
110
|
def check_ruby
|
|
111
111
|
ruby_version = Rails::Ruby.version(context)
|
|
112
|
-
return if ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.
|
|
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
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require "shopify_cli"
|
|
2
|
+
|
|
3
|
+
module ShopifyCLI
|
|
4
|
+
module Tasks
|
|
5
|
+
class EnsureGitDependency < ShopifyCLI::Task
|
|
6
|
+
def call(ctx)
|
|
7
|
+
return if ShopifyCLI::Environment.acceptance_test?
|
|
8
|
+
unless ShopifyCLI::Git.exists?(ctx)
|
|
9
|
+
raise ShopifyCLI::Abort, ctx.message("core.git.error.nonexistent")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/shopify_cli/tasks.rb
CHANGED
|
@@ -34,6 +34,7 @@ module ShopifyCLI
|
|
|
34
34
|
register :CreateApiClient, :create_api_client, "shopify_cli/tasks/create_api_client"
|
|
35
35
|
register :EnsureAuthenticated, :ensure_authenticated, "shopify_cli/tasks/ensure_authenticated"
|
|
36
36
|
register :EnsureEnv, :ensure_env, "shopify_cli/tasks/ensure_env"
|
|
37
|
+
register :EnsureGitDependency, :ensure_git_dependency, "shopify_cli/tasks/ensure_git_dependency"
|
|
37
38
|
register :EnsureLoopbackURL, :ensure_loopback_url, "shopify_cli/tasks/ensure_loopback_url"
|
|
38
39
|
register :EnsureProjectType, :ensure_project_type, "shopify_cli/tasks/ensure_project_type"
|
|
39
40
|
register :EnsureDevStore, :ensure_dev_store, "shopify_cli/tasks/ensure_dev_store"
|
|
@@ -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
|
|
@@ -3,7 +3,6 @@ require "net/http"
|
|
|
3
3
|
require "stringio"
|
|
4
4
|
require "time"
|
|
5
5
|
require "cgi"
|
|
6
|
-
|
|
7
6
|
require_relative "proxy/template_param_builder"
|
|
8
7
|
|
|
9
8
|
module ShopifyCLI
|
|
@@ -70,13 +69,26 @@ module ShopifyCLI
|
|
|
70
69
|
@core_endpoints << env["PATH_INFO"]
|
|
71
70
|
end
|
|
72
71
|
|
|
73
|
-
body = response.body
|
|
72
|
+
body = patch_body(env, response.body)
|
|
74
73
|
body = [body] unless body.respond_to?(:each)
|
|
75
74
|
[response.code, headers, body]
|
|
76
75
|
end
|
|
77
76
|
|
|
78
77
|
private
|
|
79
78
|
|
|
79
|
+
def patch_body(env, body)
|
|
80
|
+
return [""] unless body
|
|
81
|
+
|
|
82
|
+
body.gsub(%r{(data-.+=(["']))(http:|https:)?//#{@theme.shop}(.*)(\2)}) do |_|
|
|
83
|
+
match = Regexp.last_match
|
|
84
|
+
"#{match[1]}http://#{host(env)}#{match[4]}#{match[5]}"
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def host(env)
|
|
89
|
+
env["HTTP_HOST"]
|
|
90
|
+
end
|
|
91
|
+
|
|
80
92
|
def has_body?(headers)
|
|
81
93
|
headers["Content-Length"] || headers["Transfer-Encoding"]
|
|
82
94
|
end
|
|
@@ -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
|
|
@@ -9,7 +9,8 @@ module ShopifyCLI
|
|
|
9
9
|
|
|
10
10
|
attr_reader :globs, :regexes
|
|
11
11
|
|
|
12
|
-
def initialize(patterns = [])
|
|
12
|
+
def initialize(root, patterns = [])
|
|
13
|
+
@root = Pathname.new(root)
|
|
13
14
|
@patterns = patterns.nil? ? [] : patterns.compact.reject(&:empty?)
|
|
14
15
|
|
|
15
16
|
regexes, globs = patterns_to_regexes_and_globs(@patterns)
|
|
@@ -22,9 +23,10 @@ module ShopifyCLI
|
|
|
22
23
|
return true unless present?(@patterns)
|
|
23
24
|
|
|
24
25
|
path = path.to_s
|
|
25
|
-
|
|
26
26
|
return true if path.empty?
|
|
27
27
|
|
|
28
|
+
path = @root.join(path).to_s
|
|
29
|
+
|
|
28
30
|
regexes.each do |regex|
|
|
29
31
|
return true if regex_match?(regex, path)
|
|
30
32
|
end
|
|
@@ -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,19 +39,26 @@ 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
|
|
|
45
|
-
# Checksums of assets with errors.
|
|
48
|
+
# Checksums of assets with errors.
|
|
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)
|
|
@@ -168,7 +172,7 @@ module ShopifyCLI
|
|
|
168
172
|
# Delete local files not present remotely
|
|
169
173
|
missing_files = @theme.theme_files
|
|
170
174
|
.reject { |file| checksums.key?(file.relative_path.to_s) }.uniq
|
|
171
|
-
.reject { |file|
|
|
175
|
+
.reject { |file| ignore_file?(file) }
|
|
172
176
|
missing_files.each do |file|
|
|
173
177
|
@ctx.debug("rm #{file.relative_path}")
|
|
174
178
|
file.delete
|
|
@@ -195,7 +199,7 @@ module ShopifyCLI
|
|
|
195
199
|
# Already enqueued
|
|
196
200
|
return if @pending.include?(operation)
|
|
197
201
|
|
|
198
|
-
if
|
|
202
|
+
if ignore_operation?(operation)
|
|
199
203
|
@ctx.debug("ignore #{operation.file_path}")
|
|
200
204
|
return
|
|
201
205
|
end
|
|
@@ -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
|
|
|
@@ -253,8 +253,17 @@ module ShopifyCLI
|
|
|
253
253
|
response
|
|
254
254
|
end
|
|
255
255
|
|
|
256
|
-
def
|
|
256
|
+
def ignore_operation?(operation)
|
|
257
257
|
path = operation.file_path
|
|
258
|
+
ignore_path?(path)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def ignore_file?(file)
|
|
262
|
+
path = file.path
|
|
263
|
+
ignore_path?(path)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def ignore_path?(path)
|
|
258
267
|
ignored_by_ignore_filter?(path) || ignored_by_include_filter?(path)
|
|
259
268
|
end
|
|
260
269
|
|
|
@@ -263,23 +272,19 @@ module ShopifyCLI
|
|
|
263
272
|
end
|
|
264
273
|
|
|
265
274
|
def ignored_by_include_filter?(path)
|
|
266
|
-
include_filter && !include_filter.match?(path)
|
|
275
|
+
!!include_filter && !include_filter.match?(path)
|
|
267
276
|
end
|
|
268
277
|
|
|
269
278
|
def get(file)
|
|
270
|
-
_status, body, response =
|
|
271
|
-
@ctx,
|
|
272
|
-
shop: @theme.shop,
|
|
279
|
+
_status, body, response = api_client.get(
|
|
273
280
|
path: "themes/#{@theme.id}/assets.json",
|
|
274
|
-
method: "GET",
|
|
275
|
-
api_version: API_VERSION,
|
|
276
281
|
query: URI.encode_www_form("asset[key]" => file.relative_path.to_s),
|
|
277
282
|
)
|
|
278
283
|
|
|
279
284
|
update_checksums(body)
|
|
280
285
|
|
|
281
286
|
attachment = body.dig("asset", "attachment")
|
|
282
|
-
|
|
287
|
+
if attachment
|
|
283
288
|
file.write(Base64.decode64(attachment))
|
|
284
289
|
else
|
|
285
290
|
file.write(body.dig("asset", "value"))
|
|
@@ -289,14 +294,10 @@ module ShopifyCLI
|
|
|
289
294
|
end
|
|
290
295
|
|
|
291
296
|
def delete(file)
|
|
292
|
-
_status, _body, response =
|
|
293
|
-
@ctx,
|
|
294
|
-
shop: @theme.shop,
|
|
297
|
+
_status, _body, response = api_client.delete(
|
|
295
298
|
path: "themes/#{@theme.id}/assets.json",
|
|
296
|
-
method: "DELETE",
|
|
297
|
-
api_version: API_VERSION,
|
|
298
299
|
body: JSON.generate(asset: {
|
|
299
|
-
key: file.relative_path.to_s
|
|
300
|
+
key: file.relative_path.to_s,
|
|
300
301
|
})
|
|
301
302
|
)
|
|
302
303
|
|
|
@@ -305,14 +306,17 @@ module ShopifyCLI
|
|
|
305
306
|
|
|
306
307
|
def update_checksums(api_response)
|
|
307
308
|
api_response.values.flatten.each do |asset|
|
|
308
|
-
|
|
309
|
+
next unless asset["key"]
|
|
310
|
+
checksums_mutex.synchronize do
|
|
309
311
|
@checksums[asset["key"]] = asset["checksum"]
|
|
310
312
|
end
|
|
311
313
|
end
|
|
312
314
|
# Generate .liquid asset files are reported twice in checksum:
|
|
313
315
|
# once of generated, once for .liquid. We only keep the .liquid, that's the one we have
|
|
314
316
|
# on disk.
|
|
315
|
-
|
|
317
|
+
checksums_mutex.synchronize do
|
|
318
|
+
@checksums.reject! { |key, _| @checksums.key?("#{key}.liquid") }
|
|
319
|
+
end
|
|
316
320
|
end
|
|
317
321
|
|
|
318
322
|
def file_has_changed?(file)
|
|
@@ -323,7 +327,7 @@ module ShopifyCLI
|
|
|
323
327
|
parsed_body = JSON.parse(exception&.response&.body)
|
|
324
328
|
message = parsed_body.dig("errors", "asset") || parsed_body["message"] || exception.message
|
|
325
329
|
# Truncate to first lines
|
|
326
|
-
[message].flatten.map { |
|
|
330
|
+
[message].flatten.map { |mess| mess.split("\n", 2).first }
|
|
327
331
|
rescue JSON::ParserError
|
|
328
332
|
[exception.message]
|
|
329
333
|
end
|
|
@@ -331,7 +335,7 @@ module ShopifyCLI
|
|
|
331
335
|
def backoff_if_near_limit!(used, limit)
|
|
332
336
|
if used > limit - @threads.size
|
|
333
337
|
@ctx.debug("Near API call limit, waiting 2 sec…")
|
|
334
|
-
@backoff_mutex.synchronize { sleep
|
|
338
|
+
@backoff_mutex.synchronize { sleep(2) }
|
|
335
339
|
end
|
|
336
340
|
end
|
|
337
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")
|