shopify-cli 2.15.1 → 2.15.2

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile.lock +1 -1
  4. data/Rakefile +13 -0
  5. data/lib/project_types/extension/messages/messages.rb +1 -1
  6. data/lib/project_types/script/layers/infrastructure/errors.rb +17 -0
  7. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  8. data/lib/project_types/script/messages/messages.rb +3 -0
  9. data/lib/project_types/script/ui/error_handler.rb +11 -0
  10. data/lib/project_types/theme/commands/serve.rb +1 -0
  11. data/lib/project_types/theme/messages/messages.rb +40 -8
  12. data/lib/shopify_cli/git.rb +36 -0
  13. data/lib/shopify_cli/messages/messages.rb +5 -4
  14. data/lib/shopify_cli/release.rb +120 -20
  15. data/lib/shopify_cli/theme/dev_server/hot-reload.js +40 -13
  16. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
  17. data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +51 -0
  18. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +6 -1
  19. data/lib/shopify_cli/theme/dev_server/local_assets.rb +1 -1
  20. data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +34 -0
  21. data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +44 -0
  22. data/lib/shopify_cli/theme/dev_server/watcher.rb +1 -1
  23. data/lib/shopify_cli/theme/dev_server.rb +15 -3
  24. data/lib/shopify_cli/theme/file.rb +15 -4
  25. data/lib/shopify_cli/theme/syncer/checksums.rb +60 -0
  26. data/lib/shopify_cli/theme/syncer/forms/apply_to_all.rb +39 -0
  27. data/lib/shopify_cli/theme/syncer/forms/apply_to_all_form.rb +35 -0
  28. data/lib/shopify_cli/theme/syncer/forms/base_strategy_form.rb +62 -0
  29. data/lib/shopify_cli/theme/syncer/forms/select_delete_strategy.rb +27 -0
  30. data/lib/shopify_cli/theme/syncer/forms/select_update_strategy.rb +28 -0
  31. data/lib/shopify_cli/theme/syncer/ignore_helper.rb +33 -0
  32. data/lib/shopify_cli/theme/syncer/json_delete_handler.rb +51 -0
  33. data/lib/shopify_cli/theme/syncer/json_update_handler.rb +82 -0
  34. data/lib/shopify_cli/theme/syncer/merger.rb +53 -0
  35. data/lib/shopify_cli/theme/syncer/operation.rb +1 -1
  36. data/lib/shopify_cli/theme/syncer.rb +79 -63
  37. data/lib/shopify_cli/thread_pool/job.rb +10 -2
  38. data/lib/shopify_cli/thread_pool.rb +15 -3
  39. data/lib/shopify_cli/version.rb +1 -1
  40. metadata +15 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 141760dcf4bd3da2b9e92a238c72ee4612fc9e72ce4f522db84f190028439175
4
- data.tar.gz: f16bc2db60656219f7f1484ce455380822fb7d7da4c65faeceeaf065953076c9
3
+ metadata.gz: a06f3c09c3e5e000956befefcbb7b8108096236d9b03cd021959881324c7cfe5
4
+ data.tar.gz: b1daa60127f1b93ceed1f00d151e67f9709bca8ae77a962913f6fd1edfaac0c9
5
5
  SHA512:
6
- metadata.gz: 1df93436fa11a84a2df90f354a59277fbf29bd2445b097da8c2ddd6d727dc9d2d4792602de476f3479329e58873dfd222ea185fd096271896bd4ee8533f5b242
7
- data.tar.gz: ada360c3f4529d52a0315c70b1c4fea63ad69f0da1ca7aab384958adb8bf342003d4c4dced65ddc4984e5732fb5e2604f77ba82702908242b1d1427570362626
6
+ metadata.gz: 31e29e63fd9f7e569f7d4834549645b1ca206489274709aa7577f57b6888b3bd28d06eb5bd5899721cf9478239fa524b7937c009575a93c0ac096cd56c911fa6
7
+ data.tar.gz: '09f19940da497bf3fab8d8c78c801fc127c2a8cad8fe49887f86b05de373dc96acb805c8c282ce76c72d7ea89526d65f0ec0ef3732d6854110a50d02de2f240b'
data/CHANGELOG.md CHANGED
@@ -2,11 +2,17 @@ From version 2.6.0, the sections in this file adhere to the [keep a changelog](h
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## Version 2.15.2
6
+
7
+ ### Fixed
8
+ * [#2121](https://github.com/Shopify/shopify-cli/pull/2121): Fix the hot-reload to work when the section name is not equal to the type
9
+
5
10
  ## Version 2.15.1
6
11
 
7
12
  ### Added
8
13
  * [#1934](https://github.com/Shopify/shopify-cli/pull/1934): Block directories in theme assets
9
14
  * [#1880](https://github.com/Shopify/shopify-cli/pull/1880): Recognize attempts to pass a store name and suggest correction
15
+ * [#2174](https://github.com/Shopify/shopify-cli/pull/2174): Add optional 2-way sync between the CLI (`theme serve`) and the Theme Editor
10
16
 
11
17
  ### Fixed
12
18
  * [#1874](https://github.com/Shopify/shopify-cli/pull/1874): Make ngrok errors more robust and helpful
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify-cli (2.15.1)
4
+ shopify-cli (2.15.2)
5
5
  bugsnag (~> 6.22)
6
6
  listen (~> 3.7.0)
7
7
  theme-check (~> 1.10.1)
data/Rakefile CHANGED
@@ -154,6 +154,19 @@ namespace :release do
154
154
  ShopifyCLI::Release.new(new_version, github_access_token).prepare!
155
155
  puts "Completed!"
156
156
  end
157
+
158
+ task :package do
159
+ github_access_token = ENV["GITHUB_ACCESS_TOKEN"]
160
+ unless github_access_token
161
+ raise <<~NO_GITHUB_ACCESS_TOKEN
162
+ GitHub access token must be provided, e.g.:
163
+
164
+ $ GITHUB_ACCESS_TOKEN=abcdef rake release:package
165
+ NO_GITHUB_ACCESS_TOKEN
166
+ end
167
+ ShopifyCLI::Release.new(ShopifyCLI::VERSION, github_access_token).package!
168
+ puts "Completed!"
169
+ end
157
170
  end
158
171
 
159
172
  namespace :extensions do
@@ -41,7 +41,7 @@ module Extension
41
41
  invalid_api_key: "The API key %s does not match any of your apps.",
42
42
  ask_app: "Which app would you like to register this extension with?",
43
43
  no_apps: "{{x}} You don’t have any apps.",
44
- learn_about_apps: "{{*}} Learn more about building apps at <https://shopify.dev/concepts/apps>, " \
44
+ learn_about_apps: "{{*}} Learn more about building apps at <https://shopify.dev/apps>, " \
45
45
  "or try creating a new app using {{command:shopify [node|rails] create}}.",
46
46
  loading_apps: "Loading your apps…",
47
47
  no_available_extensions: "{{x}} There are no available extensions for this app.",
@@ -180,6 +180,23 @@ module Script
180
180
  @max_size = max_size
181
181
  end
182
182
  end
183
+
184
+ class InvalidInputQueryErrors < ScriptProjectError
185
+ attr_reader :messages
186
+
187
+ def initialize(messages)
188
+ @messages = messages
189
+ super()
190
+ end
191
+
192
+ def input_query_path
193
+ ScriptProjectRepository::INPUT_QUERY_PATH
194
+ end
195
+
196
+ def message
197
+ messages.join("\n")
198
+ end
199
+ end
183
200
  end
184
201
  end
185
202
  end
@@ -83,6 +83,8 @@ module Script
83
83
  valid_types: e["message"],
84
84
  filename: script_config.filename,
85
85
  )
86
+ elsif (errors = user_errors.filter { |err| err["tag"] == "input_query_validation_error" }).any?
87
+ raise Errors::InvalidInputQueryErrors, errors.map { |err| err["message"] }
86
88
  elsif user_errors.find { |err| %w(not_use_msgpack_error schema_version_argument_error).include?(err["tag"]) }
87
89
  raise Domain::Errors::MetadataValidationError
88
90
  else
@@ -86,6 +86,9 @@ module Script
86
86
  "type(s): %{valid_types}.",
87
87
  configuration_schema_field_invalid_value_error_help: "Change the value of the type.",
88
88
 
89
+ input_query_error_cause: "Input query is invalid:\n%{messages}\n\n",
90
+ input_query_error_help: "Fix the query in the `%{input_query_path}` file.",
91
+
89
92
  script_not_found_cause: "Can't find script %s for Script API %s",
90
93
 
91
94
  system_call_failure_cause: "Something went wrong while running: {{command:%{cmd}}}.",
@@ -216,6 +216,17 @@ module Script
216
216
  "script.error.configuration_schema_field_invalid_value_error_help"
217
217
  ),
218
218
  }
219
+ when Layers::Infrastructure::Errors::InvalidInputQueryErrors
220
+ {
221
+ cause_of_error: ShopifyCLI::Context.message(
222
+ "script.error.input_query_error_cause",
223
+ messages: e.messages.map { |message| " {{x}} #{message}." }.join("\n"),
224
+ ),
225
+ help_suggestion: ShopifyCLI::Context.message(
226
+ "script.error.input_query_error_help",
227
+ input_query_path: e.input_query_path,
228
+ ),
229
+ }
219
230
  when Layers::Infrastructure::Errors::DependencyInstallError
220
231
  {
221
232
  cause_of_error: ShopifyCLI::Context.message("script.error.dependency_install_cause"),
@@ -16,6 +16,7 @@ module Theme
16
16
  parser.on("--port=PORT") { |port| flags[:port] = port.to_i }
17
17
  parser.on("--poll") { flags[:poll] = true }
18
18
  parser.on("--live-reload=MODE") { |mode| flags[:mode] = as_reload_mode(mode) }
19
+ parser.on("--theme-editor-sync") { flags[:editor_sync] = true }
19
20
  end
20
21
 
21
22
  def call(_args, name)
@@ -101,13 +101,14 @@ module Theme
101
101
  Usage: {{command:%s theme serve [ ROOT ]}}
102
102
 
103
103
  Options:
104
- {{command:--port=PORT}} Local port to serve theme preview from.
105
- {{command:--poll}} Force polling to detect file changes.
106
- {{command:--host=HOST}} Set which network interface the web server listens on. The default value is 127.0.0.1.
107
- {{command:--live-reload=MODE}} The live reload mode switches the server behavior when a file is modified:
108
- - {{command:hot-reload}} Hot reloads local changes to CSS and sections (default)
109
- - {{command:full-page}} Always refreshes the entire page
110
- - {{command:off}} Deactivate live reload
104
+ {{command:--port=PORT}} Local port to serve theme preview from.
105
+ {{command:--poll}} Force polling to detect file changes.
106
+ {{command:--host=HOST}} Set which network interface the web server listens on. The default value is 127.0.0.1.
107
+ {{command:--theme-editor-sync}} Synchronize Theme Editor updates in the local theme files.
108
+ {{command:--live-reload=MODE}} The live reload mode switches the server behavior when a file is modified:
109
+ - {{command:hot-reload}} Hot reloads local changes to CSS and sections (default)
110
+ - {{command:full-page}} Always refreshes the entire page
111
+ - {{command:off}} Deactivate live reload
111
112
  HELP
112
113
  reload_mode_is_not_valid: "The live reload mode `%s` is not valid.",
113
114
  try_a_valid_reload_mode: "Try a valid live reload mode: %s.",
@@ -121,6 +122,36 @@ module Theme
121
122
  fixed: "Fixed",
122
123
  },
123
124
  },
125
+ syncer: {
126
+ forms: {
127
+ apply_to_all: {
128
+ title: "Would like apply this to all the other %s files?",
129
+ yes: "Yes",
130
+ no: "No",
131
+ },
132
+ update_strategy: {
133
+ title_context: <<~TITLE,
134
+
135
+ The local file {{command:%s}} is different from the remote version in the development theme."
136
+ TITLE
137
+ title_question: "What would you like to do?",
138
+ keep_remote: "Keep the remote version",
139
+ keep_local: "Keep the local version",
140
+ union_merge: "Merge files (it may break the local file)",
141
+ exit: "Exit",
142
+ },
143
+ delete_strategy: {
144
+ title_context: <<~TITLE,
145
+
146
+ The local file {{command:%s}} has been recently removed, but it's present on your remote development theme.",
147
+ TITLE
148
+ title_question: "What would you like to do?",
149
+ delete: "Delete permanently",
150
+ restore: "Restore with the remote version",
151
+ exit: "Exit",
152
+ },
153
+ },
154
+ },
124
155
  error: {
125
156
  address_binding_error: "Couldn't bind to localhost."\
126
157
  " To serve your theme, set a different address with {{command:%s theme serve --host=<address>}}",
@@ -137,9 +168,10 @@ module Theme
137
168
  Serving %s
138
169
 
139
170
  SERVING
171
+ download_changes: ", and use 'theme pull' to get the changes",
140
172
  customize_or_preview: <<~CUSTOMIZE_OR_PREVIEW,
141
173
 
142
- Customize this theme in the Theme Editor:
174
+ Customize this theme in the Theme Editor%s:
143
175
  {{green:%s}}
144
176
 
145
177
  Share this theme preview:
@@ -102,6 +102,42 @@ module ShopifyCLI
102
102
  branches
103
103
  end
104
104
 
105
+ ##
106
+ # Run git three-way file merge (it doesn't require an initialized git repository)
107
+ #
108
+ # #### Parameters
109
+ #
110
+ # * `current_file - string path of the current file
111
+ # * `base_file` - string path of the base file
112
+ # * `other_file` - string path of the other file
113
+ # * `opts` - list of "git merge-file" options. Valid values:
114
+ # - "-q" - do not warn about conflicts
115
+ # - "--diff3" - show conflicts
116
+ # - "--ours" - resolve conflicts favoring lines from `current_file`
117
+ # - "--theirs" - resolve conflicts favoring lines from `other_file`
118
+ # - "--union" - resolve conflicts favoring lines from both files
119
+ # - "-p" - send results to standard output instead of
120
+ # overwriting the `current_file`
121
+ # * `ctx` - the current running context of your command, defaults to a new context
122
+ #
123
+ # #### Returns
124
+ #
125
+ # * standard output from git
126
+ #
127
+ # #### Example
128
+ #
129
+ # output = ShopifyCLI::Git.merge_file(current_file, base_file, other_file, opts, ctx: ctx)
130
+ #
131
+ def merge_file(current_file, base_file, other_file, opts = [], ctx: Context.new)
132
+ output, status = ctx.capture2e("git", "merge-file", current_file, base_file, other_file, *opts)
133
+
134
+ unless status.success?
135
+ ctx.abort(ctx.message("core.git.error.merge_failed"))
136
+ end
137
+
138
+ output
139
+ end
140
+
105
141
  ##
106
142
  # will initialize a new repo in the current directory. This will output
107
143
  # if it was successful or not.
@@ -388,6 +388,7 @@ module ShopifyCLI
388
388
  sparse_checkout_not_set: "Sparse checkout set command failed.",
389
389
  pull_failed: "Pull failed.",
390
390
  pull_failed_bad_branch: "Pull failed. Branch %s cannot be found. Check the branch name and try again.",
391
+ merge_failed: "The file %s merge failed.",
391
392
  },
392
393
 
393
394
  cloning: "Cloning %s into %s…",
@@ -438,7 +439,7 @@ module ShopifyCLI
438
439
  login: {
439
440
  help: <<~HELP,
440
441
  Log in to the Shopify CLI by authenticating with a store or partner organization
441
- Usage: {{command:%s login [--store=STORE]}}
442
+ Usage: {{command:%s login [--store STORE]}}
442
443
  HELP
443
444
  invalid_shop: <<~MESSAGE,
444
445
  Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
@@ -460,7 +461,7 @@ module ShopifyCLI
460
461
  switch: {
461
462
  help: <<~HELP,
462
463
  Switch between development stores in your partner organization
463
- Usage: {{command:%s switch [--store=STORE]}}
464
+ Usage: {{command:%s switch [--store STORE]}}
464
465
  HELP
465
466
  disabled_as_shopify_org: "Can't switch development stores logged in as {{green:Shopify partners org}}",
466
467
  success: "Switched development store to {{green:%s}}",
@@ -573,7 +574,7 @@ module ShopifyCLI
573
574
  HELP
574
575
 
575
576
  error: {
576
- no_shop: "No store found. Please run {{command:%s login --store=STORE}} to login to a specific store",
577
+ no_shop: "No store found. Please run {{command:%s login --store STORE}} to login to a specific store",
577
578
  },
578
579
 
579
580
  customer: {
@@ -809,7 +810,7 @@ module ShopifyCLI
809
810
  not_logged_in: <<~MESSAGE,
810
811
  It doesn't appear that you're logged in. You must log into a partner organization or a store staff account.
811
812
 
812
- If trying to log into a store staff account, please use {{command:%s login --store=STORE}} to log in.
813
+ If trying to log into a store staff account, please use {{command:%s login --store STORE}} to log in.
813
814
  MESSAGE
814
815
  logged_in_shop_only: <<~MESSAGE,
815
816
  Logged into store {{green:%s}} as staff (no partner organizations available for this login)
@@ -1,3 +1,5 @@
1
+ require "net/http"
2
+ require "fileutils"
1
3
  require "shopify_cli/sed"
2
4
  require "shopify_cli/changelog"
3
5
  require "octokit"
@@ -15,20 +17,34 @@ module ShopifyCLI
15
17
  create_release_branch
16
18
  update_changelog
17
19
  update_versions_in_files
18
- commit
20
+ commit_packaging
19
21
  pr = create_pr
20
22
  system("open #{pr["html_url"]}")
21
23
  end
22
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
+
23
33
  private
24
34
 
25
35
  attr_reader :new_version, :changelog, :github
26
36
 
27
37
  def ensure_updated_main
28
- current_branch = %x(git branch --show-current)
29
- unless current_branch == "main"
30
- raise "Must be on the main branch to package a release!"
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
31
46
  end
47
+ system_or_fail("git checkout main", "check out main branch")
32
48
  unless system("git pull")
33
49
  raise "git pull failed, cannot be sure there aren't new commits!"
34
50
  end
@@ -36,13 +52,11 @@ module ShopifyCLI
36
52
 
37
53
  def create_release_branch
38
54
  puts "Checking out release branch"
39
- unless system("git checkout -b #{release_branch_name}")
40
- puts "Cannot check out release branch!"
41
- end
55
+ system_or_fail("git checkout -b #{release_branch_name}", "check out release branch")
42
56
  end
43
57
 
44
58
  def update_changelog
45
- if release_notes.empty?
59
+ if release_notes("Unreleased").empty?
46
60
  puts "No unreleased CHANGELOG updates found!"
47
61
  else
48
62
  puts "Updating CHANGELOG"
@@ -63,32 +77,118 @@ module ShopifyCLI
63
77
  )
64
78
  end
65
79
 
66
- def commit
80
+ def commit_packaging
67
81
  puts "Committing"
68
- unless system("git commit -am 'Packaging for release v#{new_version}'")
69
- puts "Commit failed!"
70
- end
71
- unless system("git push -u origin #{release_branch_name}")
72
- puts "Failed to push branch!"
73
- end
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")
74
84
  end
75
85
 
76
86
  def create_pr
87
+ repo = "Shopify/shopify-cli"
77
88
  github.create_pull_request(
78
- "Shopify/shopify-cli",
89
+ repo,
79
90
  "main",
80
91
  release_branch_name,
81
92
  "Packaging for release v#{new_version}",
82
- release_notes
83
- ).tap { |results| puts "Created PR ##{results["number"]}" }
93
+ release_notes("Unreleased")
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})"
84
180
  end
85
181
 
86
182
  def release_branch_name
87
183
  @release_branch_name ||= "release_#{new_version.split(".").join("_")}"
88
184
  end
89
185
 
90
- def release_notes
91
- @release_notes ||= changelog.release_notes("Unreleased")
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)
92
192
  end
93
193
  end
94
194
  end
@@ -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