shopify-cli 2.15.0 → 2.15.3

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