shopify-cli 2.15.1 → 2.15.2

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