shopify-cli 2.24.0 → 2.25.0

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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/Gemfile.lock +1 -1
  4. data/README.md +1 -1
  5. data/lib/project_types/extension/commands/serve.rb +57 -3
  6. data/lib/project_types/extension/extension_project.rb +8 -1
  7. data/lib/project_types/extension/loaders/project.rb +3 -2
  8. data/lib/project_types/extension/messages/messages.rb +21 -6
  9. data/lib/project_types/extension/models/server_config/development_renderer.rb +1 -1
  10. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +18 -6
  11. data/lib/project_types/theme/commands/serve.rb +15 -3
  12. data/lib/project_types/theme/messages/messages.rb +4 -2
  13. data/lib/shopify_cli/commands/logout.rb +13 -2
  14. data/lib/shopify_cli/environment.rb +1 -1
  15. data/lib/shopify_cli/file_system_listener.rb +30 -0
  16. data/lib/shopify_cli/git.rb +116 -33
  17. data/lib/shopify_cli/identity_auth.rb +1 -0
  18. data/lib/shopify_cli/project.rb +1 -1
  19. data/lib/shopify_cli/tasks/ensure_project_type.rb +3 -1
  20. data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +1 -1
  21. data/lib/shopify_cli/theme/dev_server/certificate_manager.rb +1 -1
  22. data/lib/shopify_cli/theme/dev_server/errors.rb +9 -0
  23. data/lib/shopify_cli/theme/dev_server/header_hash.rb +1 -1
  24. data/lib/shopify_cli/theme/dev_server/hooks/file_change_hook.rb +77 -0
  25. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_deleter.rb +1 -1
  26. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
  27. data/lib/shopify_cli/theme/dev_server/{hot-reload-no-script.html → hot_reload/resources/hot-reload-no-script.html} +0 -0
  28. data/lib/shopify_cli/theme/dev_server/hot_reload/resources/hot_reload.js +48 -0
  29. data/lib/shopify_cli/theme/dev_server/hot_reload/resources/sse_client.js +43 -0
  30. data/lib/shopify_cli/theme/dev_server/hot_reload/resources/theme.js +114 -0
  31. data/lib/shopify_cli/theme/dev_server/hot_reload/resources/theme_extension.js +121 -0
  32. data/lib/shopify_cli/theme/dev_server/hot_reload/script_injector.rb +57 -0
  33. data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +1 -1
  34. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +8 -76
  35. data/lib/shopify_cli/theme/dev_server/local_assets.rb +28 -28
  36. data/lib/shopify_cli/theme/dev_server/proxy.rb +33 -25
  37. data/lib/shopify_cli/theme/dev_server/proxy_param_builder.rb +82 -0
  38. data/lib/shopify_cli/theme/dev_server/reload_mode.rb +1 -1
  39. data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +1 -1
  40. data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +1 -1
  41. data/lib/shopify_cli/theme/dev_server/sse.rb +1 -1
  42. data/lib/shopify_cli/theme/dev_server/watcher.rb +8 -9
  43. data/lib/shopify_cli/theme/dev_server/web_server.rb +1 -1
  44. data/lib/shopify_cli/theme/dev_server.rb +287 -99
  45. data/lib/shopify_cli/theme/extension/app_extension.rb +40 -0
  46. data/lib/shopify_cli/theme/extension/dev_server/hooks/file_change_hook.rb +68 -0
  47. data/lib/shopify_cli/theme/extension/dev_server/hot_reload/script_injector.rb +30 -0
  48. data/lib/shopify_cli/theme/extension/dev_server/hot_reload.rb +13 -0
  49. data/lib/shopify_cli/theme/extension/dev_server/local_assets.rb +30 -0
  50. data/lib/shopify_cli/theme/{dev_server/proxy/template_param_builder.rb → extension/dev_server/proxy_param_builder.rb} +26 -16
  51. data/lib/shopify_cli/theme/extension/dev_server/watcher.rb +47 -0
  52. data/lib/shopify_cli/theme/extension/dev_server.rb +150 -0
  53. data/lib/shopify_cli/theme/extension/host_theme.rb +104 -0
  54. data/lib/shopify_cli/theme/extension/syncer/extension_serve_job.rb +133 -0
  55. data/lib/shopify_cli/theme/extension/syncer/operation.rb +21 -0
  56. data/lib/shopify_cli/theme/extension/syncer.rb +81 -0
  57. data/lib/shopify_cli/theme/extension/ui/host_theme_progress_bar.rb +35 -0
  58. data/lib/shopify_cli/theme/ignore_helper.rb +31 -0
  59. data/lib/shopify_cli/theme/root.rb +62 -0
  60. data/lib/shopify_cli/theme/syncer.rb +12 -6
  61. data/lib/shopify_cli/theme/theme.rb +10 -52
  62. data/lib/shopify_cli/version.rb +1 -1
  63. metadata +27 -7
  64. data/.github/workflows/triage.yml +0 -22
  65. data/lib/shopify_cli/theme/dev_server/hot-reload.js +0 -194
  66. data/lib/shopify_cli/theme/syncer/ignore_helper.rb +0 -33
@@ -1,132 +1,320 @@
1
1
  # frozen_string_literal: true
2
- require_relative "development_theme"
3
- require_relative "ignore_filter"
4
- require_relative "syncer"
2
+
3
+ require "pathname"
4
+ require "singleton"
5
5
 
6
6
  require_relative "dev_server/cdn_fonts"
7
- require_relative "dev_server/hot_reload"
7
+ require_relative "dev_server/certificate_manager"
8
+ require_relative "dev_server/errors"
8
9
  require_relative "dev_server/header_hash"
9
- require_relative "dev_server/reload_mode"
10
+ require_relative "dev_server/hot_reload"
11
+ require_relative "dev_server/hot_reload/script_injector"
10
12
  require_relative "dev_server/local_assets"
13
+ require_relative "dev_server/proxy_param_builder"
11
14
  require_relative "dev_server/proxy"
15
+ require_relative "dev_server/reload_mode"
16
+ require_relative "dev_server/remote_watcher"
12
17
  require_relative "dev_server/sse"
13
18
  require_relative "dev_server/watcher"
14
- require_relative "dev_server/remote_watcher"
15
19
  require_relative "dev_server/web_server"
16
- require_relative "dev_server/certificate_manager"
20
+ require_relative "dev_server/hooks/file_change_hook"
17
21
 
18
- require "pathname"
22
+ require_relative "development_theme"
23
+ require_relative "ignore_filter"
24
+ require_relative "include_filter"
25
+ require_relative "syncer"
19
26
 
20
27
  module ShopifyCLI
21
28
  module Theme
22
- module DevServer
23
- # Errors
24
- Error = Class.new(StandardError)
25
- AddressBindingError = Class.new(Error)
29
+ class DevServer
30
+ include Singleton
31
+
32
+ attr_reader :app, :stopped, :ctx, :root, :host, :theme_identifier, :port, :poll, :editor_sync, :stable, :mode,
33
+ :block, :includes, :ignores
26
34
 
27
35
  class << self
28
- attr_accessor :ctx
29
-
30
- def start(ctx, root, host: "127.0.0.1", theme: nil, port: 9292, poll: false, editor_sync: false,
31
- mode: ReloadMode.default, stable: false)
32
- @ctx = ctx
33
- theme = find_theme(root, theme)
34
- ignore_filter = IgnoreFilter.from_path(root)
35
- @syncer = Syncer.new(ctx, theme: theme, ignore_filter: ignore_filter, overwrite_json: !editor_sync,
36
- stable: stable)
37
- watcher = Watcher.new(ctx, theme: theme, ignore_filter: ignore_filter, syncer: @syncer, poll: poll)
38
- remote_watcher = RemoteWatcher.to(theme: theme, syncer: @syncer)
39
-
40
- # Setup the middleware stack. Mimics Rack::Builder / config.ru, but in reverse order
41
- @app = Proxy.new(ctx, theme: theme, syncer: @syncer)
42
- @app = CdnFonts.new(@app, theme: theme)
43
- @app = LocalAssets.new(ctx, @app, theme: theme)
44
- @app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, mode: mode, ignore_filter: ignore_filter)
45
- stopped = false
46
- address = "http://#{host}:#{port}"
47
-
48
- trap("INT") do
49
- stopped = true
50
- stop
51
- end
36
+ def start(
37
+ ctx,
38
+ root,
39
+ host: "127.0.0.1",
40
+ theme: nil,
41
+ port: 9292,
42
+ poll: false,
43
+ editor_sync: false,
44
+ stable: false,
45
+ mode: ReloadMode.default,
46
+ includes: nil,
47
+ ignores: nil,
48
+ &block
49
+ )
50
+ instance.setup(
51
+ ctx,
52
+ root,
53
+ host,
54
+ theme,
55
+ port,
56
+ poll,
57
+ editor_sync,
58
+ stable,
59
+ mode,
60
+ includes,
61
+ ignores,
62
+ &block
63
+ )
64
+ instance.start
65
+ end
52
66
 
53
- CLI::UI::Frame.open(@ctx.message("theme.serve.viewing_theme")) do
54
- ctx.print_task(ctx.message("theme.serve.syncing_theme", theme.id, theme.shop))
55
- @syncer.start_threads
56
- if block_given?
57
- yield @syncer
58
- else
59
- @syncer.upload_theme!(delay_low_priority_files: true)
60
- end
61
-
62
- return if stopped
63
-
64
- preview_suffix = editor_sync ? "" : ctx.message("theme.serve.download_changes")
65
- preview_message = ctx.message(
66
- "theme.serve.customize_or_preview",
67
- preview_suffix,
68
- theme.editor_url,
69
- theme.preview_url
70
- )
71
-
72
- ctx.puts(ctx.message("theme.serve.serving", theme.root))
73
- ctx.open_url!(address)
74
- ctx.puts(preview_message)
75
- end
67
+ def stop
68
+ instance.stop
69
+ end
70
+ end
71
+
72
+ # rubocop:disable Metrics/ParameterLists
73
+ def setup(
74
+ ctx,
75
+ root,
76
+ host,
77
+ theme_identifier,
78
+ port,
79
+ poll,
80
+ editor_sync,
81
+ stable,
82
+ mode,
83
+ includes,
84
+ ignores,
85
+ &block
86
+ )
87
+ @ctx = ctx
88
+ @root = root
89
+ @host = host
90
+ @theme_identifier = theme_identifier
91
+ @port = port
92
+ @poll = poll
93
+ @editor_sync = editor_sync
94
+ @stable = stable
95
+ @mode = mode
96
+ @includes = includes
97
+ @ignores = ignores
98
+ @block = block
99
+ end
100
+
101
+ def start
102
+ sync_theme
103
+
104
+ # Handle process stop
105
+ trap("INT") { stop("SIGINT") }
106
+ trap("TERM") { stop("SIGTERM") }
107
+
108
+ # Setup the middleware stack. Mimics Rack::Builder / config.ru, but in reverse order
109
+ @app = middleware_stack
110
+
111
+ # Start development server
112
+ setup_server
113
+ start_server
114
+ teardown_server
115
+
116
+ rescue ShopifyCLI::API::APIRequestForbiddenError,
117
+ ShopifyCLI::API::APIRequestUnauthorizedError
118
+ ctx.abort(ensure_user_message)
119
+ rescue Errno::EADDRINUSE
120
+ ctx.abort(port_error_message, port_error_help_message)
121
+ rescue Errno::EADDRNOTAVAIL
122
+ ctx.abort(binding_error_message)
123
+ end
124
+
125
+ def stop(signal = nil)
126
+ ctx.debug(stop_signal(signal)) unless signal.nil?
127
+
128
+ @stopped = true
129
+
130
+ ctx.puts(stopping_message)
131
+ app.close
132
+ WebServer.shutdown
133
+ end
134
+
135
+ private
136
+
137
+ def setup_server
138
+ watcher.start
139
+ remote_watcher.start if editor_sync
140
+ end
141
+
142
+ def teardown_server
143
+ # Use instance variables, so we don't build components
144
+ # at the teardown phase.
145
+ @remote_watcher&.stop if editor_sync
146
+ @watcher&.stop
147
+ @syncer&.shutdown
148
+ end
149
+
150
+ def start_server
151
+ WebServer.run(
152
+ app,
153
+ BindAddress: host,
154
+ Port: port,
155
+ Logger: logger,
156
+ AccessLog: [],
157
+ )
158
+ end
159
+
160
+ def middleware_stack
161
+ @app = Proxy.new(ctx, theme, param_builder)
162
+ @app = CdnFonts.new(@app, theme: theme)
163
+ @app = LocalAssets.new(ctx, @app, theme)
164
+ @app = HotReload.new(ctx, @app, broadcast_hooks: broadcast_hooks, watcher: watcher, mode: mode,
165
+ script_injector: script_injector)
166
+ end
167
+
168
+ def sync_theme
169
+ CLI::UI::Frame.open(viewing_theme_message) do
170
+ ctx.print_task(syncing_theme_message)
171
+ syncer.start_threads
76
172
 
77
- logger = if ctx.debug?
78
- WEBrick::Log.new(nil, WEBrick::BasicLog::INFO)
173
+ if block
174
+ block.call(syncer)
79
175
  else
80
- WEBrick::Log.new(nil, WEBrick::BasicLog::FATAL)
176
+ syncer.upload_theme!(delay_low_priority_files: true)
81
177
  end
82
178
 
83
- watcher.start
84
- remote_watcher.start if editor_sync
85
- WebServer.run(
86
- @app,
87
- BindAddress: host,
88
- Port: port,
89
- Logger: logger,
90
- AccessLog: [],
91
- )
92
- remote_watcher.stop if editor_sync
93
- watcher.stop
94
-
95
- rescue ShopifyCLI::API::APIRequestForbiddenError,
96
- ShopifyCLI::API::APIRequestUnauthorizedError
97
- shop = ShopifyCLI::AdminAPI.get_shop_or_abort(@ctx)
98
- raise ShopifyCLI::Abort, @ctx.message("theme.serve.ensure_user", shop)
99
- rescue Errno::EADDRINUSE
100
- error_message = @ctx.message("theme.serve.address_already_in_use", address)
101
- help_message = @ctx.message("theme.serve.try_port_option")
102
- @ctx.abort(error_message, help_message)
103
- rescue Errno::EADDRNOTAVAIL
104
- raise AddressBindingError, "Error binding to the address #{host}."
179
+ return if stopped
180
+
181
+ ctx.puts(serving_theme_message)
182
+ ctx.open_url!(address)
183
+ ctx.puts(preview_message)
105
184
  end
185
+ end
106
186
 
107
- def stop
108
- @ctx.puts("Stopping…")
109
- @app.close
110
- @syncer.shutdown
111
- WebServer.shutdown
187
+ def theme
188
+ @theme ||= if theme_identifier
189
+ theme = ShopifyCLI::Theme::Theme.find_by_identifier(ctx, root: root, identifier: theme_identifier)
190
+ theme || ctx.abort(not_found_error_message)
191
+ else
192
+ DevelopmentTheme.find_or_create!(ctx, root: root)
112
193
  end
194
+ end
195
+
196
+ def syncer
197
+ @syncer ||= Syncer.new(
198
+ ctx,
199
+ theme: theme,
200
+ include_filter: include_filter,
201
+ ignore_filter: ignore_filter,
202
+ overwrite_json: !editor_sync,
203
+ stable: stable
204
+ )
205
+ end
113
206
 
114
- private
207
+ def watcher
208
+ @watcher ||= Watcher.new(
209
+ ctx,
210
+ theme: theme,
211
+ ignore_filter: ignore_filter,
212
+ syncer: syncer,
213
+ poll: poll
214
+ )
215
+ end
115
216
 
116
- def find_theme(root, identifier)
117
- return theme_by_identifier(root, identifier) if identifier
118
- DevelopmentTheme.find_or_create!(@ctx, root: root)
119
- end
217
+ def remote_watcher
218
+ @remote_watcher ||= RemoteWatcher.to(
219
+ theme: theme,
220
+ syncer: syncer
221
+ )
222
+ end
223
+
224
+ def param_builder
225
+ @param_builder ||= ProxyParamBuilder
226
+ .new
227
+ .with_theme(theme)
228
+ .with_syncer(syncer)
229
+ end
120
230
 
121
- def theme_by_identifier(root, identifier)
122
- theme = ShopifyCLI::Theme::Theme.find_by_identifier(@ctx, root: root, identifier: identifier)
123
- theme || not_found_error(identifier)
231
+ def ignore_filter
232
+ @ignore_filter ||= IgnoreFilter.from_path(root).tap do |filter|
233
+ filter.add_patterns(ignores) if ignores
124
234
  end
235
+ end
236
+
237
+ def include_filter
238
+ @include_filter ||= ShopifyCLI::Theme::IncludeFilter.new(root, includes)
239
+ end
125
240
 
126
- def not_found_error(identifier)
127
- @ctx.abort(@ctx.message("theme.serve.theme_not_found", identifier))
241
+ def logger
242
+ @logger ||= if ctx.debug?
243
+ WEBrick::Log.new(nil, WEBrick::BasicLog::INFO)
244
+ else
245
+ WEBrick::Log.new(nil, WEBrick::BasicLog::FATAL)
128
246
  end
129
247
  end
248
+
249
+ # Hooks
250
+
251
+ def broadcast_hooks
252
+ file_handler = Hooks::FileChangeHook.new(ctx, theme: theme, include_filter: include_filter,
253
+ ignore_filter: ignore_filter)
254
+ [file_handler]
255
+ end
256
+
257
+ def script_injector
258
+ HotReload::ScriptInjector.new(ctx, theme: theme)
259
+ end
260
+
261
+ def address
262
+ @address ||= "http://#{host}:#{port}"
263
+ end
264
+
265
+ # Messages
266
+
267
+ def ensure_user_message
268
+ shop = ShopifyCLI::AdminAPI.get_shop_or_abort(ctx)
269
+ ctx.message("theme.serve.ensure_user", shop)
270
+ end
271
+
272
+ def port_error_message
273
+ ctx.message("theme.serve.address_already_in_use", address)
274
+ end
275
+
276
+ def port_error_help_message
277
+ ctx.message("theme.serve.try_port_option")
278
+ end
279
+
280
+ def binding_error_message
281
+ ctx.message("theme.serve.binding_error", ShopifyCLI::TOOL_NAME)
282
+ end
283
+
284
+ def viewing_theme_message
285
+ ctx.message("theme.serve.viewing_theme")
286
+ end
287
+
288
+ def syncing_theme_message
289
+ ctx.message("theme.serve.syncing_theme", theme.id, theme.shop)
290
+ end
291
+
292
+ def serving_theme_message
293
+ ctx.message("theme.serve.serving", theme.root)
294
+ end
295
+
296
+ def stopping_message
297
+ ctx.message("theme.serve.stopping")
298
+ end
299
+
300
+ def stop_signal(signal)
301
+ ctx.message("theme.serve.stop_signal", signal)
302
+ end
303
+
304
+ def not_found_error_message
305
+ ctx.message("theme.serve.theme_not_found", theme_identifier)
306
+ end
307
+
308
+ def preview_message
309
+ preview_suffix = editor_sync ? "" : ctx.message("theme.serve.download_changes")
310
+
311
+ ctx.message(
312
+ "theme.serve.customize_or_preview",
313
+ preview_suffix,
314
+ theme.editor_url,
315
+ theme.preview_url
316
+ )
317
+ end
130
318
  end
131
319
  end
132
320
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require "forwardable"
3
+ require "shopify_cli/theme/root"
4
+
5
+ module ShopifyCLI
6
+ module Theme
7
+ module Extension
8
+ class AppExtension
9
+ extend Forwardable
10
+
11
+ attr_reader :app_id, :location, :registration_id
12
+ def_delegators :@root_obj,
13
+ :root,
14
+ :static_asset_files,
15
+ :liquid_files,
16
+ :json_files,
17
+ :glob,
18
+ :static_asset_file?,
19
+ :static_asset_paths,
20
+ :[],
21
+ :file?
22
+
23
+ def initialize(ctx, root:, app_id: nil, location: nil, registration_id: nil)
24
+ @app_id = app_id
25
+ @location = location
26
+ @registration_id = registration_id
27
+ @root_obj = Root.new(ctx, root: root)
28
+ end
29
+
30
+ def extension_files
31
+ (glob(["**/*.liquid", "**/*.json"]) + static_asset_files).uniq
32
+ end
33
+
34
+ def extension_file?(file)
35
+ extension_files.include?(self[file])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ module Extension
6
+ class DevServer
7
+ module Hooks
8
+ class FileChangeHook
9
+ attr_reader :ctx, :extension, :syncer, :streams
10
+
11
+ def initialize(ctx, extension:, syncer:)
12
+ @ctx = ctx
13
+ @extension = extension
14
+ @syncer = syncer
15
+ end
16
+
17
+ def call(modified, added, removed, streams: nil)
18
+ @streams = streams
19
+
20
+ modified = paths(modified).select { |file| @extension.extension_file?(file) }
21
+ added = paths(added).select { |file| @extension.extension_file?(file) }
22
+ removed = paths(removed)
23
+
24
+ hot_reload(modified) unless modified.empty?
25
+ reload_page(added, removed) unless (added + removed).empty?
26
+ end
27
+
28
+ private
29
+
30
+ def hot_reload(modified)
31
+ broadcast(modified: modified)
32
+
33
+ ctx.debug("[HotReload] Modified: #{modified.join(", ")}")
34
+ end
35
+
36
+ def reload_page(added, removed)
37
+ wait_blocking_operations
38
+
39
+ broadcast(reload_page: true)
40
+
41
+ ctx.debug("[ReloadPage] Added: #{added.join(", ")}")
42
+ ctx.debug("[ReloadPage] Removed: #{removed.join(", ")}")
43
+ end
44
+
45
+ def wait_blocking_operations
46
+ retries = 10
47
+ while syncer.any_blocking_operation? && !retries.zero?
48
+ sleep(0.5)
49
+ retries -= 1
50
+ end
51
+ end
52
+
53
+ def paths(files)
54
+ files
55
+ .map { |file| extension[file] }
56
+ .reject(&:liquid_css?)
57
+ .map(&:relative_path)
58
+ end
59
+
60
+ def broadcast(message)
61
+ streams&.broadcast(JSON.generate(message))
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/theme/dev_server/hot_reload/script_injector"
4
+
5
+ module ShopifyCLI
6
+ module Theme
7
+ module Extension
8
+ class DevServer < ShopifyCLI::Theme::DevServer
9
+ class HotReload < ShopifyCLI::Theme::DevServer::HotReload
10
+ class ScriptInjector < ShopifyCLI::Theme::DevServer::HotReload::ScriptInjector
11
+ private
12
+
13
+ def javascript_files
14
+ %w(hot_reload.js sse_client.js theme_extension.js)
15
+ end
16
+
17
+ def javascript_inline
18
+ env = { mode: @mode }
19
+ <<~JS
20
+ (() => {
21
+ window.__SHOPIFY_CLI_ENV__ = #{env.to_json};
22
+ })();
23
+ JS
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/theme/dev_server/hot_reload"
4
+
5
+ module ShopifyCLI
6
+ module Theme
7
+ module Extension
8
+ class DevServer < ShopifyCLI::Theme::DevServer
9
+ class HotReload < ShopifyCLI::Theme::DevServer::HotReload; end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/theme/dev_server/local_assets"
4
+
5
+ module ShopifyCLI
6
+ module Theme
7
+ module Extension
8
+ class DevServer < ShopifyCLI::Theme::DevServer
9
+ class LocalAssets < ShopifyCLI::Theme::DevServer::LocalAssets
10
+ TAE_ASSET_REGEX = %r{(http:|https:)?//cdn\.shopify\.com/extensions/.+?/(assets/.+?\.(?:css|js))}
11
+
12
+ private
13
+
14
+ def replace_asset_urls(body)
15
+ replaced_body = body.join.gsub(TAE_ASSET_REGEX) do |match|
16
+ path = Regexp.last_match[2]
17
+ if @target.static_asset_paths.include?(path)
18
+ "/#{path}"
19
+ else
20
+ match
21
+ end
22
+ end
23
+
24
+ [replaced_body]
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,19 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "cgi"
4
+ require "shopify_cli/theme/dev_server"
4
5
 
5
6
  module ShopifyCLI
6
7
  module Theme
7
- module DevServer
8
- class Proxy
9
- class TemplateParamBuilder
8
+ module Extension
9
+ class DevServer < ShopifyCLI::Theme::DevServer
10
+ class ProxyParamBuilder
10
11
  def build
11
- # Core doesn't support replace_templates
12
+ # Core doesn't support replace_extension_templates
12
13
  return {} if core?(current_path)
13
14
 
14
- (syncer_templates + request_templates)
15
- .select { |file| file.liquid? || file.json? }
15
+ (request_templates + syncer_templates)
16
+ .select(&:liquid?)
16
17
  .uniq(&:relative_path)
18
+ .reject { |file| proxy_cannot_render?(file) }
17
19
  .map { |file| as_param(file) }
18
20
  .to_h
19
21
  end
@@ -33,29 +35,37 @@ module ShopifyCLI
33
35
  self
34
36
  end
35
37
 
36
- def with_theme(theme)
37
- @theme = theme
38
+ def with_extension(extension)
39
+ @extension = extension
38
40
  self
39
41
  end
40
42
 
41
43
  private
42
44
 
43
- def as_param(file)
44
- ["replace_templates[#{file.relative_path}]", file.read]
45
+ def proxy_cannot_render?(file)
46
+ !(file&.relative_path&.include?("blocks/") || file&.relative_path&.include?("snippets/"))
45
47
  end
46
48
 
47
- def syncer_templates
48
- @syncer&.pending_updates || []
49
+ def as_param(file)
50
+ if file&.relative_path&.include?("blocks/")
51
+ ["replace_extension_templates[blocks][#{file.relative_path}]", file.read]
52
+ elsif file&.relative_path&.include?("snippets/")
53
+ ["replace_extension_templates[snippets][#{file.relative_path}]", file.read]
54
+ end
49
55
  end
50
56
 
51
57
  def request_templates
52
- cookie_sections
53
- .map { |section| @theme[section] unless @theme.nil? }
58
+ cookie_files
59
+ .map { |file_path| @extension[file_path] unless @extension.nil? }
54
60
  .compact
55
61
  end
56
62
 
57
- def cookie_sections
58
- CGI::Cookie.parse(cookie)["hot_reload_sections"].join.split(",") || []
63
+ def syncer_templates
64
+ @syncer&.pending_files || []
65
+ end
66
+
67
+ def cookie_files
68
+ CGI::Cookie.parse(cookie)["hot_reload_files"].join.split(",") || []
59
69
  end
60
70
 
61
71
  def core?(path)