shopify-cli 2.15.0 → 2.15.3

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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/stale.yml +7 -2
  3. data/.vscode/settings.json +1 -2
  4. data/CHANGELOG.md +70 -19
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +39 -7
  7. data/Rakefile +48 -0
  8. data/ext/javy/hashes/javy-arm-macos-v0.3.0.gz.sha256 +1 -0
  9. data/ext/javy/hashes/javy-x86_64-linux-v0.3.0.gz.sha256 +1 -0
  10. data/ext/javy/hashes/javy-x86_64-macos-v0.3.0.gz.sha256 +1 -0
  11. data/ext/javy/hashes/javy-x86_64-windows-v0.3.0.gz.sha256 +1 -0
  12. data/ext/javy/version +1 -1
  13. data/ext/shopify-extensions/version +1 -1
  14. data/lib/project_types/extension/commands/check.rb +6 -1
  15. data/lib/project_types/extension/forms/questions/ask_template.rb +5 -8
  16. data/lib/project_types/extension/messages/messages.rb +11 -3
  17. data/lib/project_types/extension/models/development_server_requirements.rb +14 -7
  18. data/lib/project_types/extension/models/server_config/root.rb +2 -0
  19. data/lib/project_types/extension/models/specification_handlers/beacon_extension.rb +57 -0
  20. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config.rb +33 -0
  21. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config_repository.rb +75 -0
  22. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +16 -1
  23. data/lib/project_types/extension/tasks/configure_options.rb +2 -1
  24. data/lib/project_types/extension/tasks/convert_server_config.rb +13 -2
  25. data/lib/project_types/extension/tasks/merge_server_config.rb +5 -2
  26. data/lib/project_types/script/cli.rb +1 -0
  27. data/lib/project_types/script/config/extension_points.yml +18 -0
  28. data/lib/project_types/script/layers/application/create_script.rb +14 -6
  29. data/lib/project_types/script/layers/infrastructure/errors.rb +17 -0
  30. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +6 -21
  31. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  32. data/lib/project_types/script/layers/infrastructure/sparse_checkout_details.rb +35 -0
  33. data/lib/project_types/script/messages/messages.rb +3 -0
  34. data/lib/project_types/script/ui/error_handler.rb +11 -0
  35. data/lib/project_types/theme/cli.rb +1 -0
  36. data/lib/project_types/theme/commands/check.rb +4 -1
  37. data/lib/project_types/theme/commands/open.rb +2 -2
  38. data/lib/project_types/theme/commands/push.rb +1 -3
  39. data/lib/project_types/theme/commands/serve.rb +1 -0
  40. data/lib/project_types/theme/commands/share.rb +56 -0
  41. data/lib/project_types/theme/messages/messages.rb +71 -11
  42. data/lib/shopify_cli/changelog.rb +148 -0
  43. data/lib/shopify_cli/command.rb +7 -0
  44. data/lib/shopify_cli/command_options/command_serve_options.rb +10 -0
  45. data/lib/shopify_cli/commands/app/serve.rb +7 -7
  46. data/lib/shopify_cli/commands/login.rb +5 -2
  47. data/lib/shopify_cli/context.rb +13 -0
  48. data/lib/shopify_cli/git.rb +36 -0
  49. data/lib/shopify_cli/identity_auth.rb +24 -4
  50. data/lib/shopify_cli/messages/messages.rb +26 -5
  51. data/lib/shopify_cli/release.rb +194 -0
  52. data/lib/shopify_cli/sed.rb +19 -0
  53. data/lib/shopify_cli/services/app/create/rails_service.rb +10 -2
  54. data/lib/shopify_cli/services/app/serve/node_service.rb +2 -25
  55. data/lib/shopify_cli/services/app/serve/php_service.rb +2 -25
  56. data/lib/shopify_cli/services/app/serve/rails_service.rb +8 -28
  57. data/lib/shopify_cli/services/app/serve/serve_service.rb +57 -0
  58. data/lib/shopify_cli/services.rb +1 -0
  59. data/lib/shopify_cli/tasks/update_dashboard_urls.rb +7 -9
  60. data/lib/shopify_cli/theme/dev_server/hot-reload.js +40 -13
  61. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
  62. data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +51 -0
  63. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +6 -1
  64. data/lib/shopify_cli/theme/dev_server/local_assets.rb +1 -1
  65. data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +35 -0
  66. data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +44 -0
  67. data/lib/shopify_cli/theme/dev_server/watcher.rb +2 -8
  68. data/lib/shopify_cli/theme/dev_server.rb +18 -5
  69. data/lib/shopify_cli/theme/file.rb +15 -4
  70. data/lib/shopify_cli/theme/syncer/checksums.rb +60 -0
  71. data/lib/shopify_cli/theme/syncer/forms/apply_to_all.rb +39 -0
  72. data/lib/shopify_cli/theme/syncer/forms/apply_to_all_form.rb +35 -0
  73. data/lib/shopify_cli/theme/syncer/forms/base_strategy_form.rb +62 -0
  74. data/lib/shopify_cli/theme/syncer/forms/select_delete_strategy.rb +27 -0
  75. data/lib/shopify_cli/theme/syncer/forms/select_update_strategy.rb +28 -0
  76. data/lib/shopify_cli/theme/syncer/ignore_helper.rb +33 -0
  77. data/lib/shopify_cli/theme/syncer/json_delete_handler.rb +51 -0
  78. data/lib/shopify_cli/theme/syncer/json_update_handler.rb +82 -0
  79. data/lib/shopify_cli/theme/syncer/merger.rb +53 -0
  80. data/lib/shopify_cli/theme/syncer/operation.rb +1 -1
  81. data/lib/shopify_cli/theme/syncer.rb +79 -63
  82. data/lib/shopify_cli/theme/theme.rb +26 -4
  83. data/lib/shopify_cli/theme/theme_admin_api.rb +23 -8
  84. data/lib/shopify_cli/thread_pool/job.rb +10 -2
  85. data/lib/shopify_cli/thread_pool.rb +15 -3
  86. data/lib/shopify_cli/tunnel.rb +9 -0
  87. data/lib/shopify_cli/version.rb +1 -1
  88. data/shopify-cli.gemspec +3 -1
  89. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +8 -0
  90. metadata +30 -3
@@ -0,0 +1,194 @@
1
+ require "net/http"
2
+ require "fileutils"
3
+ require "shopify_cli/sed"
4
+ require "shopify_cli/changelog"
5
+ require "octokit"
6
+
7
+ module ShopifyCLI
8
+ class Release
9
+ def initialize(new_version, github_access_token)
10
+ @new_version = new_version
11
+ @changelog = ShopifyCLI::Changelog.new
12
+ @github = Octokit::Client.new(access_token: github_access_token)
13
+ end
14
+
15
+ def prepare!
16
+ ensure_updated_main
17
+ create_release_branch
18
+ update_changelog
19
+ update_versions_in_files
20
+ commit_packaging
21
+ pr = create_pr
22
+ system("open #{pr["html_url"]}")
23
+ end
24
+
25
+ def package!
26
+ ensure_updated_main
27
+ ensure_correct_gem_version
28
+ Rake::Task["package"].invoke
29
+ update_homebrew
30
+ create_github_release
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :new_version, :changelog, :github
36
+
37
+ def ensure_updated_main
38
+ # We can't be sure what is the correct action to take if changes have been
39
+ # made but not committed. Ensure the user handles the situation before
40
+ # moving on.
41
+ unless %x(git status --porcelain).empty?
42
+ raise <<~MESSAGE
43
+ Uncommitted changes have been made to the repository.
44
+ Please make sure `git status` does not show any changes before continuing.
45
+ MESSAGE
46
+ end
47
+ system_or_fail("git checkout main", "check out main branch")
48
+ unless system("git pull")
49
+ raise "git pull failed, cannot be sure there aren't new commits!"
50
+ end
51
+ end
52
+
53
+ def create_release_branch
54
+ puts "Checking out release branch"
55
+ system_or_fail("git checkout -b #{release_branch_name}", "check out release branch")
56
+ end
57
+
58
+ def update_changelog
59
+ if release_notes("Unreleased").empty?
60
+ puts "No unreleased CHANGELOG updates found!"
61
+ else
62
+ puts "Updating CHANGELOG"
63
+ changelog.update_version!(new_version)
64
+ end
65
+ end
66
+
67
+ def update_versions_in_files
68
+ version_file = File.join(ShopifyCLI::ROOT, "lib/shopify_cli/version.rb")
69
+ puts "Updating version.rb"
70
+ ShopifyCLI::Sed.new.replace_inline(version_file, ShopifyCLI::VERSION, new_version)
71
+ gemfile_lock = File.join(ShopifyCLI::ROOT, "Gemfile.lock")
72
+ puts "Updating Gemfile.lock"
73
+ ShopifyCLI::Sed.new.replace_inline(
74
+ gemfile_lock,
75
+ "shopify-cli (#{ShopifyCLI::VERSION})",
76
+ "shopify-cli (#{new_version})",
77
+ )
78
+ end
79
+
80
+ def commit_packaging
81
+ puts "Committing"
82
+ system_or_fail("git commit -am 'Packaging for release v#{new_version}'", "commit")
83
+ system_or_fail("git push -u origin #{release_branch_name}", "push branch")
84
+ end
85
+
86
+ def create_pr
87
+ repo = "Shopify/shopify-cli"
88
+ github.create_pull_request(
89
+ repo,
90
+ "main",
91
+ release_branch_name,
92
+ "Packaging for release v#{new_version}",
93
+ release_notes(new_version)
94
+ ).tap { |results| puts "Created #{repo} PR ##{results["number"]}" }
95
+ end
96
+
97
+ def ensure_correct_gem_version
98
+ response = Net::HTTP.get(URI("https://rubygems.org/api/v1/versions/shopify-cli/latest.json"))
99
+ latest_version = JSON.parse(response)["version"]
100
+ unless latest_version == new_version
101
+ raise "Attempted to update to #{new_version}, but latest on RubyGems is #{latest_version}"
102
+ end
103
+ end
104
+
105
+ def update_homebrew
106
+ ensure_updated_homebrew_repo
107
+ update_homebrew_repo
108
+ pr = create_homebrew_pr
109
+ system("open #{pr["html_url"]}")
110
+ end
111
+
112
+ def ensure_updated_homebrew_repo
113
+ unless File.exist?(homebrew_path)
114
+ system_or_fail("/opt/dev/bin/dev clone homebrew-shopify", "clone homebrew-shopify repo")
115
+ end
116
+
117
+ Dir.chdir(homebrew_path) do
118
+ system_or_fail("git checkout master && git pull", "pull latest homebrew-shopify")
119
+ system_or_fail("git checkout -b #{homebrew_release_branch}", "check out homebrew branch")
120
+ end
121
+ end
122
+
123
+ def update_homebrew_repo
124
+ source_file = File.join(package_dir, "shopify-cli.rb")
125
+ FileUtils.copy(source_file, homebrew_path)
126
+ Dir.chdir(homebrew_path) do
127
+ system_or_fail("git commit -am '#{homebrew_update_message}'", "commit homebrew update")
128
+ system_or_fail("git push -u origin #{homebrew_release_branch}", "push homebrew branch")
129
+ end
130
+ end
131
+
132
+ def create_homebrew_pr
133
+ repo = "Shopify/homebrew-shopify"
134
+ github.create_pull_request(
135
+ repo,
136
+ "master",
137
+ homebrew_release_branch,
138
+ homebrew_update_message,
139
+ homebrew_release_notes
140
+ ).tap { |results| puts "Created #{repo} PR ##{results["number"]}" }
141
+ end
142
+
143
+ def create_github_release
144
+ release = github.create_release(
145
+ "Shopify/shopify-cli",
146
+ "v#{new_version}",
147
+ {
148
+ name: "Version #{new_version}",
149
+ body: release_notes(new_version),
150
+ }
151
+ )
152
+ %w(.deb -1.noarch.rpm).each do |suffix|
153
+ github.upload_asset(
154
+ release["url"],
155
+ File.join(package_dir, "shopify-cli-#{new_version}#{suffix}")
156
+ )
157
+ end
158
+ system("open #{release["html_url"]}")
159
+ end
160
+
161
+ def homebrew_path
162
+ @homebrew_path ||= %x(/opt/dev/bin/dev project-path homebrew-shopify).chomp
163
+ end
164
+
165
+ def homebrew_update_message
166
+ @homebrew_update_message ||= "Update Shopify CLI to #{new_version}"
167
+ end
168
+
169
+ def package_dir
170
+ @package_dir ||= File.join(ShopifyCLI::ROOT, "packaging", "builds", new_version)
171
+ end
172
+
173
+ def homebrew_release_branch
174
+ "release_#{new_version.split(".").join("_")}_of_shopify-cli"
175
+ end
176
+
177
+ def homebrew_release_notes
178
+ "I'm releasing a new version of the Shopify CLI, " \
179
+ "[#{new_version}](https://github.com/Shopify/shopify-cli/releases/tag/v#{new_version})"
180
+ end
181
+
182
+ def release_branch_name
183
+ @release_branch_name ||= "release_#{new_version.split(".").join("_")}"
184
+ end
185
+
186
+ def release_notes(version)
187
+ changelog.release_notes(version)
188
+ end
189
+
190
+ def system_or_fail(command, action)
191
+ raise "Failed to #{action}!" unless system(command)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,19 @@
1
+ module ShopifyCLI
2
+ class Sed
3
+ class SedError < StandardError; end
4
+
5
+ def replace_inline(filename, pattern, output)
6
+ command =
7
+ case CLI::Kit::System.os
8
+ when :mac
9
+ "sed -i ''"
10
+ when :linux
11
+ "sed -i"
12
+ else
13
+ raise "Unrecognized system!"
14
+ end
15
+ success = system("#{command} 's/#{pattern}/#{output}/' #{filename}")
16
+ raise SedError unless success
17
+ end
18
+ end
19
+ end
@@ -108,8 +108,10 @@ module ShopifyCLI
108
108
  end
109
109
 
110
110
  def check_ruby
111
- ruby_version = Environment.ruby_version(context)
112
- return if ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.1.0")
111
+ ruby_version = Environment.ruby_version(context: context)
112
+ return if ruby_version.satisfies?("~>2.5") ||
113
+ ruby_version.satisfies?("~>3.0.0") ||
114
+ ruby_version.satisfies?("~>3.1.0")
113
115
  context.abort(context.message("core.app.create.rails.error.invalid_ruby_version"))
114
116
  end
115
117
 
@@ -205,6 +207,12 @@ module ShopifyCLI
205
207
  end
206
208
 
207
209
  def set_custom_ua
210
+ requires_ua_file = Dir.chdir(context.root) do
211
+ context.ruby_gem_version("shopify_app") < ::Semantic::Version.new("19.0.0")
212
+ end
213
+
214
+ return unless requires_ua_file
215
+
208
216
  ua_path = File.join("config", "initializers", "user_agent.rb")
209
217
  context.write(ua_path, USER_AGENT_CODE)
210
218
  end
@@ -2,32 +2,9 @@ module ShopifyCLI
2
2
  module Services
3
3
  module App
4
4
  module Serve
5
- class NodeService < BaseService
6
- attr_accessor :host, :port, :context
7
-
8
- def initialize(host:, port:, context:)
9
- @host = host
10
- @port = port
11
- @context = context
12
- super()
13
- end
14
-
5
+ class NodeService < ServeService
15
6
  def call
16
- project = ShopifyCLI::Project.current
17
- url = host || ShopifyCLI::Tunnel.start(context, port: port)
18
- raise ShopifyCLI::Abort,
19
- context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
20
- project.env.update(context, :host, url)
21
- ShopifyCLI::Tasks::UpdateDashboardURLS.call(
22
- context,
23
- url: url,
24
- callback_url: "/auth/callback",
25
- )
26
-
27
- if project.env.shop
28
- project_url = "#{project.env.host}/auth?shop=#{project.env.shop}"
29
- context.puts("\n" + context.message("core.app.serve.open_info", project_url) + "\n")
30
- end
7
+ generate_url
31
8
 
32
9
  CLI::UI::Frame.open(context.message("core.app.serve.running_server")) do
33
10
  env = project.env.to_h
@@ -2,32 +2,9 @@ module ShopifyCLI
2
2
  module Services
3
3
  module App
4
4
  module Serve
5
- class PHPService < BaseService
6
- attr_accessor :host, :port, :context
7
-
8
- def initialize(host:, port:, context:)
9
- @host = host
10
- @port = port
11
- @context = context
12
- super()
13
- end
14
-
5
+ class PHPService < ServeService
15
6
  def call
16
- project = ShopifyCLI::Project.current
17
- url = host || ShopifyCLI::Tunnel.start(context, port: port)
18
- raise ShopifyCLI::Abort,
19
- context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
20
- project.env.update(context, :host, url)
21
- ShopifyCLI::Tasks::UpdateDashboardURLS.call(
22
- context,
23
- url: url,
24
- callback_url: "/auth/callback",
25
- )
26
-
27
- if project.env.shop
28
- project_url = "#{project.env.host}/login?shop=#{project.env.shop}"
29
- context.puts("\n" + context.message("core.app.serve.open_info", project_url) + "\n")
30
- end
7
+ generate_url
31
8
 
32
9
  CLI::UI::Frame.open(context.message("core.app.serve.running_server")) do
33
10
  if ShopifyCLI::ProcessSupervision.running?(:npm_watch)
@@ -2,38 +2,18 @@ module ShopifyCLI
2
2
  module Services
3
3
  module App
4
4
  module Serve
5
- class RailsService < BaseService
6
- attr_accessor :host, :port, :context
7
-
8
- def initialize(host:, port:, context:)
9
- @host = host
10
- @port = port
11
- @context = context
12
- super()
13
- end
14
-
5
+ class RailsService < ServeService
15
6
  def call
16
- project = ShopifyCLI::Project.current
17
- url = host || ShopifyCLI::Tunnel.start(context, port: port)
18
- raise ShopifyCLI::Abort,
19
- context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
20
- project.env.update(context, :host, url)
21
- ShopifyCLI::Tasks::UpdateDashboardURLS.call(
22
- context,
23
- url: url,
24
- callback_url: "/auth/shopify/callback",
25
- )
26
-
27
- if project.env.shop
28
- project_url = "#{project.env.host}/login?shop=#{project.env.shop}"
29
- context.puts("\n" + context.message("core.app.serve.open_info", project_url) + "\n")
30
- end
7
+ generate_url
31
8
 
32
9
  CLI::UI::Frame.open(context.message("core.app.serve.running_server")) do
33
- env = ShopifyCLI::Project.current.env.to_h
34
- env.delete("HOST")
10
+ original_env = JSON.parse(ENV["ORIGINAL_ENV"] || "{}")
11
+ env = original_env.merge(ShopifyCLI::Project.current.env.to_h)
12
+ env.delete("HOST") if context.ruby_gem_version("shopify_app") < ::Semantic::Version.new("19.0.0")
35
13
  env["PORT"] = port.to_s
36
- env["GEM_PATH"] = Rails::Gem.gem_path(context)
14
+ env["GEM_PATH"] =
15
+ [env["GEM_PATH"], Rails::Gem.gem_path(context)].compact
16
+ .join(CLI::UI::OS.current.path_separator)
37
17
  if context.windows?
38
18
  context.system("ruby bin\\rails server", env: env)
39
19
  else
@@ -0,0 +1,57 @@
1
+ module ShopifyCLI
2
+ module Services
3
+ module App
4
+ module Serve
5
+ class ServeService < BaseService
6
+ attr_accessor :host, :port, :no_update, :context
7
+
8
+ def initialize(host:, port:, no_update:, context:)
9
+ @host = host
10
+ @port = port
11
+ @no_update = no_update
12
+ @context = context
13
+ super()
14
+ end
15
+
16
+ def call
17
+ raise NotImplementedError
18
+ end
19
+
20
+ private
21
+
22
+ def generate_url
23
+ create_tunnel
24
+ update_url unless no_update
25
+ show_app_url
26
+ end
27
+
28
+ def create_tunnel
29
+ url = host || ShopifyCLI::Tunnel.start(context, port: port)
30
+ raise ShopifyCLI::Abort,
31
+ context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
32
+ project.env.update(context, :host, url)
33
+ end
34
+
35
+ def update_url
36
+ ShopifyCLI::Tasks::UpdateDashboardURLS.call(
37
+ context,
38
+ url: project.env.host,
39
+ callback_url: "/auth/shopify/callback",
40
+ )
41
+ end
42
+
43
+ def show_app_url
44
+ return unless project.env.shop
45
+
46
+ project_url = "#{project.env.host}/login?shop=#{project.env.shop}"
47
+ context.puts("\n" + context.message("core.app.serve.open_info", project_url) + "\n")
48
+ end
49
+
50
+ def project
51
+ @project ||= ShopifyCLI::Project.current
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -5,6 +5,7 @@ module ShopifyCLI
5
5
 
6
6
  module App
7
7
  module Serve
8
+ autoload :ServeService, "shopify_cli/services/app/serve/serve_service"
8
9
  autoload :NodeService, "shopify_cli/services/app/serve/node_service"
9
10
  autoload :RailsService, "shopify_cli/services/app/serve/rails_service"
10
11
  autoload :PHPService, "shopify_cli/services/app/serve/php_service"
@@ -9,24 +9,22 @@ module ShopifyCLI
9
9
  api_key = project.env.api_key
10
10
  result = ShopifyCLI::PartnersAPI.query(ctx, "get_app_urls", apiKey: api_key)
11
11
  app = result["data"]["app"]
12
- consent = check_application_url(app["applicationUrl"], url)
12
+
13
+ return if app["applicationUrl"].match(url)
14
+
13
15
  constructed_urls = construct_redirect_urls(app["redirectUrlWhitelist"], url, callback_url)
14
- return if url == app["applicationUrl"]
15
16
  ShopifyCLI::PartnersAPI.query(@ctx, "update_dashboard_urls", input: {
16
- applicationUrl: consent ? url : app["applicationUrl"],
17
- redirectUrlWhitelist: constructed_urls, apiKey: api_key
17
+ applicationUrl: url,
18
+ redirectUrlWhitelist: constructed_urls,
19
+ apiKey: api_key,
18
20
  })
21
+
19
22
  @ctx.puts(@ctx.message("core.tasks.update_dashboard_urls.updated"))
20
23
  rescue
21
24
  @ctx.puts(@ctx.message("core.tasks.update_dashboard_urls.update_error", ShopifyCLI::TOOL_NAME))
22
25
  raise
23
26
  end
24
27
 
25
- def check_application_url(application_url, new_url)
26
- return false if application_url.match(new_url)
27
- CLI::UI::Prompt.confirm(@ctx.message("core.tasks.update_dashboard_urls.update_prompt"))
28
- end
29
-
30
28
  def construct_redirect_urls(urls, new_url, callback_url)
31
29
  new_urls = urls.map do |url|
32
30
  if (match = url.match(NGROK_REGEX))
@@ -15,17 +15,37 @@
15
15
  eventSource.onerror = () => eventSource.close();
16
16
  }
17
17
 
18
+ function sectionNamesByType(type) {
19
+ const namespace = window.__SHOPIFY_CLI_ENV__;
20
+ return namespace.section_names_by_type[type] || [];
21
+ }
22
+
18
23
  function reloadMode() {
19
- var namespace = window.__SHOPIFY_CLI_ENV__;
24
+ const namespace = window.__SHOPIFY_CLI_ENV__;
20
25
  return namespace.mode;
21
26
  }
22
27
 
28
+ function querySelectDOMSections(idSuffix) {
29
+ const elements = document.querySelectorAll(`[id^='shopify-section'][id$='${idSuffix}']`);
30
+ return Array.from(elements);
31
+ }
32
+
33
+ function fetchDOMSections(name) {
34
+ const domSections = sectionNamesByType(name).flatMap((n) => querySelectDOMSections(n));
35
+
36
+ if (domSections.length > 0) {
37
+ return domSections;
38
+ }
39
+
40
+ return querySelectDOMSections(name);
41
+ }
42
+
23
43
  function isFullPageReloadMode(){
24
- return reloadMode() === "full-page";
44
+ return reloadMode() === 'full-page';
25
45
  }
26
46
 
27
47
  function isReloadModeActive(){
28
- return reloadMode() !== "off";
48
+ return reloadMode() !== 'off';
29
49
  }
30
50
 
31
51
  function isRefreshRequired(files) {
@@ -104,26 +124,28 @@
104
124
  constructor(filename) {
105
125
  this.filename = filename;
106
126
  this.name = filename.split('/').pop().replace('.liquid', '');
107
- this.element = document.querySelector(`[id^='shopify-section'][id$='${this.name}']`);
127
+ this.elements = fetchDOMSections(this.name);
108
128
  }
109
129
 
110
130
  valid() {
111
- return this.filename.startsWith('sections/') && this.element;
131
+ return this.filename.startsWith('sections/') && this.elements.length > 0;
112
132
  }
113
133
 
114
- async refresh() {
115
- var url = new URL(window.location.href);
116
- url.searchParams.append('section_id', this.name);
134
+ async refreshElement(element) {
135
+
136
+ const sectionId = element.id.replace(/^shopify-section-/, '');
137
+ const url = new URL(window.location.href);
138
+
139
+ url.searchParams.append('section_id', sectionId);
140
+
141
+ const response = await fetch(url);
117
142
 
118
143
  try {
119
- const response = await fetch(url);
120
144
  if (response.headers.get('x-templates-from-params') == '1') {
121
145
  const html = await response.text();
122
- this.element.outerHTML = html;
123
-
124
- console.log(`[HotReload] Reloaded ${this.name} section`);
146
+ element.outerHTML = html;
125
147
  } else {
126
- window.location.reload()
148
+ window.location.reload();
127
149
 
128
150
  console.log(`[HotReload] Hot-reloading not supported, fully reloading ${this.name} section`);
129
151
  }
@@ -132,6 +154,11 @@
132
154
  console.log(`[HotReload] Failed to reload ${this.name} section: ${e.message}`);
133
155
  }
134
156
  }
157
+
158
+ async refresh() {
159
+ console.log(`[HotReload] Reloaded ${this.name} sections`);
160
+ this.elements.forEach(this.refreshElement);
161
+ }
135
162
  }
136
163
 
137
164
  if (isReloadModeActive()) {
@@ -51,7 +51,7 @@ module ShopifyCLI
51
51
  def fetch_asset(file)
52
52
  api_client.get(
53
53
  path: "themes/#{@theme.id}/assets.json",
54
- query: URI.encode_www_form("asset[key]" => file.relative_path.to_s),
54
+ query: URI.encode_www_form("asset[key]" => file.relative_path),
55
55
  )
56
56
  rescue ShopifyCLI::API::APIRequestNotFoundError
57
57
  [404, {}]
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ module DevServer
6
+ class HotReload
7
+ class SectionsIndex
8
+ def initialize(theme)
9
+ @theme = theme
10
+ end
11
+
12
+ def section_names_by_type
13
+ index = {}
14
+
15
+ files.each do |file|
16
+ section_hash(file).each do |key, value|
17
+ name = key
18
+ type = value&.dig("type")
19
+
20
+ next if !name || !type
21
+
22
+ index[type] = [] unless index[type]
23
+ index[type] << name
24
+ end
25
+ end
26
+
27
+ index
28
+ end
29
+
30
+ private
31
+
32
+ def section_hash(file)
33
+ content = JSON.parse(file.read)
34
+ return [] unless content.is_a?(Hash)
35
+
36
+ sections = content["sections"]
37
+ return [] if sections.nil?
38
+
39
+ sections
40
+ rescue JSON::JSONError
41
+ []
42
+ end
43
+
44
+ def files
45
+ @theme.json_files
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "hot_reload/remote_file_reloader"
4
+ require_relative "hot_reload/sections_index"
4
5
 
5
6
  module ShopifyCLI
6
7
  module Theme
@@ -13,6 +14,7 @@ module ShopifyCLI
13
14
  @mode = mode
14
15
  @streams = SSE::Streams.new
15
16
  @remote_file_reloader = RemoteFileReloader.new(ctx, theme: @theme, streams: @streams)
17
+ @sections_index = SectionsIndex.new(@theme)
16
18
  @watcher = watcher
17
19
  @watcher.add_observer(self, :notify_streams_of_file_change)
18
20
  @ignore_filter = ignore_filter
@@ -78,7 +80,10 @@ module ShopifyCLI
78
80
  end
79
81
 
80
82
  def params_js
81
- env = { mode: @mode }
83
+ env = {
84
+ mode: @mode,
85
+ section_names_by_type: @sections_index.section_names_by_type,
86
+ }
82
87
  <<~JS
83
88
  (() => {
84
89
  window.__SHOPIFY_CLI_ENV__ = #{env.to_json};
@@ -71,7 +71,7 @@ module ShopifyCLI
71
71
 
72
72
  def replace_asset_urls(body)
73
73
  replaced_body = body.join.gsub(ASSET_REGEX) do |match|
74
- path = Pathname.new(Regexp.last_match[1])
74
+ path = Regexp.last_match[1]
75
75
  if @theme.static_asset_paths.include?(path)
76
76
  "/#{path}"
77
77
  else
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/thread_pool/job"
4
+
5
+ module ShopifyCLI
6
+ module Theme
7
+ module DevServer
8
+ class RemoteWatcher
9
+ class JsonFilesUpdateJob < ShopifyCLI::ThreadPool::Job
10
+ def initialize(theme, syncer, interval)
11
+ super(interval)
12
+
13
+ @theme = theme
14
+ @syncer = syncer
15
+ end
16
+
17
+ def perform!
18
+ @syncer.fetch_checksums!
19
+ @syncer.enqueue_get(json_files)
20
+ end
21
+
22
+ private
23
+
24
+ def json_files
25
+ @theme
26
+ .json_files
27
+ .reject { |file| @syncer.pending_updates.include?(file) }
28
+ .reject { |file| @syncer.broken_file?(file) }
29
+ .reject { |file| @syncer.ignore_file?(file) }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end