shopify-cli 2.24.0 → 2.25.0

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