shopify-cli 2.11.2 → 2.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +5 -0
  3. data/.github/PULL_REQUEST_TEMPLATE.md +1 -1
  4. data/.github/workflows/shopify.yml +2 -1
  5. data/.rubocop.yml +1 -1
  6. data/.ruby-version +1 -1
  7. data/CHANGELOG.md +44 -1
  8. data/Gemfile.lock +18 -18
  9. data/Rakefile +16 -0
  10. data/bin/shopify +12 -8
  11. data/dev.yml +1 -1
  12. data/docs/users/installation.md +1 -44
  13. data/ext/javy/hashes/javy-arm-macos-v0.2.0.gz.sha256 +1 -0
  14. data/ext/javy/hashes/javy-arm-macos-v0.2.1.gz.sha256 +1 -0
  15. data/ext/javy/hashes/javy-x86_64-linux-v0.2.0.gz.sha256 +1 -0
  16. data/ext/javy/hashes/javy-x86_64-linux-v0.2.1.gz.sha256 +1 -0
  17. data/ext/javy/hashes/javy-x86_64-macos-v0.2.0.gz.sha256 +1 -0
  18. data/ext/javy/hashes/javy-x86_64-macos-v0.2.1.gz.sha256 +1 -0
  19. data/ext/javy/hashes/javy-x86_64-windows-v0.2.0.gz.sha256 +1 -0
  20. data/ext/javy/hashes/javy-x86_64-windows-v0.2.1.gz.sha256 +1 -0
  21. data/ext/javy/version +1 -1
  22. data/lib/project_types/extension/features/argo_setup_steps.rb +4 -6
  23. data/lib/project_types/extension/models/npm_package.rb +19 -1
  24. data/lib/project_types/extension/models/server_config/development_renderer.rb +4 -3
  25. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +114 -0
  26. data/lib/project_types/extension/tasks/configure_features.rb +15 -2
  27. data/lib/project_types/extension/tasks/convert_server_config.rb +2 -1
  28. data/lib/project_types/script/cli.rb +2 -4
  29. data/lib/project_types/script/commands/create.rb +5 -5
  30. data/lib/project_types/script/commands/push.rb +4 -6
  31. data/lib/project_types/script/config/extension_points.yml +0 -10
  32. data/lib/project_types/script/errors.rb +1 -1
  33. data/lib/project_types/script/forms/create.rb +7 -20
  34. data/lib/project_types/script/layers/application/build_script.rb +9 -26
  35. data/lib/project_types/script/layers/application/connect_app.rb +3 -2
  36. data/lib/project_types/script/layers/application/create_script.rb +9 -10
  37. data/lib/project_types/script/layers/application/project_dependencies.rb +12 -14
  38. data/lib/project_types/script/layers/application/push_script.rb +14 -10
  39. data/lib/project_types/script/layers/domain/errors.rb +3 -3
  40. data/lib/project_types/script/layers/domain/push_package.rb +6 -0
  41. data/lib/project_types/script/layers/domain/script_config.rb +2 -4
  42. data/lib/project_types/script/layers/domain/script_project.rb +3 -2
  43. data/lib/project_types/script/layers/infrastructure/errors.rb +11 -0
  44. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +0 -16
  45. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +0 -1
  46. data/lib/project_types/script/layers/infrastructure/languages/tool_version_checker.rb +26 -0
  47. data/lib/project_types/script/layers/infrastructure/languages/typescript_project_creator.rb +22 -10
  48. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +32 -29
  49. data/lib/project_types/script/layers/infrastructure/languages/wasm_project_creator.rb +0 -3
  50. data/lib/project_types/script/layers/infrastructure/languages/wasm_task_runner.rb +1 -1
  51. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +3 -21
  52. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +14 -26
  53. data/lib/project_types/script/layers/infrastructure/script_service.rb +4 -2
  54. data/lib/project_types/script/loaders/project.rb +8 -7
  55. data/lib/project_types/script/messages/messages.rb +22 -21
  56. data/lib/project_types/script/ui/error_handler.rb +17 -4
  57. data/lib/project_types/script/ui/strict_spinner.rb +4 -6
  58. data/lib/project_types/theme/cli.rb +2 -0
  59. data/lib/project_types/theme/commands/common/root_helper.rb +71 -0
  60. data/lib/project_types/theme/commands/init.rb +2 -0
  61. data/lib/project_types/theme/commands/list.rb +34 -0
  62. data/lib/project_types/theme/commands/open.rb +65 -0
  63. data/lib/project_types/theme/commands/package.rb +1 -0
  64. data/lib/project_types/theme/commands/pull.rb +18 -10
  65. data/lib/project_types/theme/commands/push.rb +17 -9
  66. data/lib/project_types/theme/commands/serve.rb +6 -2
  67. data/lib/project_types/theme/conversions/base_glob.rb +50 -0
  68. data/lib/project_types/theme/conversions/ignore_glob.rb +15 -0
  69. data/lib/project_types/theme/conversions/include_glob.rb +15 -0
  70. data/lib/project_types/theme/forms/select.rb +11 -39
  71. data/lib/project_types/theme/messages/messages.rb +38 -7
  72. data/lib/project_types/theme/presenters/theme_presenter.rb +48 -0
  73. data/lib/project_types/theme/presenters/themes_presenter.rb +32 -0
  74. data/lib/shopify_cli/api.rb +1 -1
  75. data/lib/shopify_cli/commands/app/create/node.rb +1 -0
  76. data/lib/shopify_cli/commands/app/create/php.rb +1 -0
  77. data/lib/shopify_cli/commands/app/create/rails.rb +1 -0
  78. data/lib/shopify_cli/commands/app/deploy.rb +1 -1
  79. data/lib/shopify_cli/constants.rb +2 -2
  80. data/lib/shopify_cli/context.rb +13 -15
  81. data/lib/shopify_cli/core/entry_point.rb +1 -1
  82. data/lib/shopify_cli/core/monorail.rb +14 -6
  83. data/lib/shopify_cli/environment.rb +6 -0
  84. data/lib/shopify_cli/exception_reporter.rb +2 -0
  85. data/lib/shopify_cli/git.rb +9 -1
  86. data/lib/shopify_cli/messages/messages.rb +21 -1
  87. data/lib/shopify_cli/packager.rb +1 -1
  88. data/lib/shopify_cli/result.rb +14 -0
  89. data/lib/shopify_cli/services/app/create/rails_service.rb +1 -1
  90. data/lib/shopify_cli/tasks/ensure_git_dependency.rb +14 -0
  91. data/lib/shopify_cli/tasks.rb +1 -0
  92. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +5 -5
  93. data/lib/shopify_cli/theme/dev_server/proxy.rb +14 -2
  94. data/lib/shopify_cli/theme/dev_server/watcher.rb +10 -2
  95. data/lib/shopify_cli/theme/development_theme.rb +2 -5
  96. data/lib/shopify_cli/theme/include_filter.rb +4 -2
  97. data/lib/shopify_cli/theme/syncer.rb +40 -36
  98. data/lib/shopify_cli/theme/theme.rb +16 -27
  99. data/lib/shopify_cli/theme/theme_admin_api.rb +71 -0
  100. data/lib/shopify_cli/transform_data_structure.rb +3 -2
  101. data/lib/shopify_cli/version.rb +1 -1
  102. data/shipit.yml +3 -0
  103. data/shopify-cli.gemspec +9 -2
  104. data/shopify-dev +9 -11
  105. metadata +26 -8
  106. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +0 -25
  107. 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
- final_command = [command_name]
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, final_command, args - final_command, err&.message)
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
@@ -46,6 +46,8 @@ module ShopifyCLI
46
46
  Bugsnag.notify(error) do |event|
47
47
  event.add_metadata(:device, metadata)
48
48
  end
49
+ rescue
50
+ nil
49
51
  end
50
52
 
51
53
  def self.report?(context:)
@@ -4,7 +4,15 @@ module ShopifyCLI
4
4
  # git.
5
5
  class Git
6
6
  class << self
7
- # Check if Git is available in the environment
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 %s",
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}}.
@@ -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
 
@@ -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.0.0")
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
@@ -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
- ShopifyCLI::AdminAPI.rest_request(
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
- .reject { |file| @ignore_filter&.ignore?(file) }
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
- .reject { |file| @ignore_filter&.ignore?(file) }
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
- ShopifyCLI::AdminAPI.rest_request(
49
- @ctx,
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 { |reporter| reporter.disable! }
57
+ @reporters.each(&:disable!)
51
58
  end
52
59
 
53
60
  def unlock_io!
54
- @reporters.each { |reporter| reporter.enable! }
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 = ShopifyCLI::AdminAPI.rest_request(
100
- @ctx,
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| @ignore_filter&.ignore?(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 ignore?(operation)
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 = ShopifyCLI::AdminAPI.rest_request(
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 ignore?(operation)
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 = ShopifyCLI::AdminAPI.rest_request(
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
- value = if attachment
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 = ShopifyCLI::AdminAPI.rest_request(
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
- if asset["key"]
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
- @checksums.reject! { |key, _| @checksums.key?("#{key}.liquid") }
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 { |message| message.split("\n", 2).first }
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 2 }
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
- AdminAPI.get_shop_or_abort(@ctx)
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 = ShopifyCLI::AdminAPI.rest_request(
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
- AdminAPI.rest_request(
123
- @ctx,
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
- AdminAPI.rest_request(
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
- AdminAPI.rest_request(
209
- ctx,
210
- shop: AdminAPI.get_shop_or_abort(ctx),
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 = AdminAPI.rest_request(
221
- @ctx,
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")