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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -0
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/lib/project_types/extension/commands/serve.rb +57 -3
- data/lib/project_types/extension/extension_project.rb +8 -1
- data/lib/project_types/extension/loaders/project.rb +3 -2
- data/lib/project_types/extension/messages/messages.rb +21 -6
- data/lib/project_types/extension/models/server_config/development_renderer.rb +1 -1
- data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +18 -6
- data/lib/project_types/theme/commands/serve.rb +15 -3
- data/lib/project_types/theme/messages/messages.rb +4 -2
- data/lib/shopify_cli/commands/logout.rb +13 -2
- data/lib/shopify_cli/environment.rb +1 -1
- data/lib/shopify_cli/file_system_listener.rb +30 -0
- data/lib/shopify_cli/git.rb +116 -33
- data/lib/shopify_cli/identity_auth.rb +1 -0
- data/lib/shopify_cli/project.rb +1 -1
- data/lib/shopify_cli/tasks/ensure_project_type.rb +3 -1
- data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/certificate_manager.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/errors.rb +9 -0
- data/lib/shopify_cli/theme/dev_server/header_hash.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/hooks/file_change_hook.rb +77 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_deleter.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/{hot-reload-no-script.html → hot_reload/resources/hot-reload-no-script.html} +0 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/resources/hot_reload.js +48 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/resources/sse_client.js +43 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/resources/theme.js +114 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/resources/theme_extension.js +121 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/script_injector.rb +57 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/hot_reload.rb +8 -76
- data/lib/shopify_cli/theme/dev_server/local_assets.rb +28 -28
- data/lib/shopify_cli/theme/dev_server/proxy.rb +33 -25
- data/lib/shopify_cli/theme/dev_server/proxy_param_builder.rb +82 -0
- data/lib/shopify_cli/theme/dev_server/reload_mode.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/sse.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/watcher.rb +8 -9
- data/lib/shopify_cli/theme/dev_server/web_server.rb +1 -1
- data/lib/shopify_cli/theme/dev_server.rb +287 -99
- data/lib/shopify_cli/theme/extension/app_extension.rb +40 -0
- data/lib/shopify_cli/theme/extension/dev_server/hooks/file_change_hook.rb +68 -0
- data/lib/shopify_cli/theme/extension/dev_server/hot_reload/script_injector.rb +30 -0
- data/lib/shopify_cli/theme/extension/dev_server/hot_reload.rb +13 -0
- data/lib/shopify_cli/theme/extension/dev_server/local_assets.rb +30 -0
- data/lib/shopify_cli/theme/{dev_server/proxy/template_param_builder.rb → extension/dev_server/proxy_param_builder.rb} +26 -16
- data/lib/shopify_cli/theme/extension/dev_server/watcher.rb +47 -0
- data/lib/shopify_cli/theme/extension/dev_server.rb +150 -0
- data/lib/shopify_cli/theme/extension/host_theme.rb +104 -0
- data/lib/shopify_cli/theme/extension/syncer/extension_serve_job.rb +133 -0
- data/lib/shopify_cli/theme/extension/syncer/operation.rb +21 -0
- data/lib/shopify_cli/theme/extension/syncer.rb +81 -0
- data/lib/shopify_cli/theme/extension/ui/host_theme_progress_bar.rb +35 -0
- data/lib/shopify_cli/theme/ignore_helper.rb +31 -0
- data/lib/shopify_cli/theme/root.rb +62 -0
- data/lib/shopify_cli/theme/syncer.rb +12 -6
- data/lib/shopify_cli/theme/theme.rb +10 -52
- data/lib/shopify_cli/version.rb +1 -1
- metadata +27 -7
- data/.github/workflows/triage.yml +0 -22
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +0 -194
- data/lib/shopify_cli/theme/syncer/ignore_helper.rb +0 -33
@@ -1,132 +1,320 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
|
4
|
-
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "singleton"
|
5
5
|
|
6
6
|
require_relative "dev_server/cdn_fonts"
|
7
|
-
require_relative "dev_server/
|
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/
|
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/
|
20
|
+
require_relative "dev_server/hooks/file_change_hook"
|
17
21
|
|
18
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
78
|
-
|
173
|
+
if block
|
174
|
+
block.call(syncer)
|
79
175
|
else
|
80
|
-
|
176
|
+
syncer.upload_theme!(delay_low_priority_files: true)
|
81
177
|
end
|
82
178
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
-
|
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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
122
|
-
|
123
|
-
|
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
|
-
|
127
|
-
|
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
|
8
|
-
class
|
9
|
-
class
|
8
|
+
module Extension
|
9
|
+
class DevServer < ShopifyCLI::Theme::DevServer
|
10
|
+
class ProxyParamBuilder
|
10
11
|
def build
|
11
|
-
# Core doesn't support
|
12
|
+
# Core doesn't support replace_extension_templates
|
12
13
|
return {} if core?(current_path)
|
13
14
|
|
14
|
-
(
|
15
|
-
.select
|
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
|
37
|
-
@
|
38
|
+
def with_extension(extension)
|
39
|
+
@extension = extension
|
38
40
|
self
|
39
41
|
end
|
40
42
|
|
41
43
|
private
|
42
44
|
|
43
|
-
def
|
44
|
-
|
45
|
+
def proxy_cannot_render?(file)
|
46
|
+
!(file&.relative_path&.include?("blocks/") || file&.relative_path&.include?("snippets/"))
|
45
47
|
end
|
46
48
|
|
47
|
-
def
|
48
|
-
|
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
|
-
|
53
|
-
.map { |
|
58
|
+
cookie_files
|
59
|
+
.map { |file_path| @extension[file_path] unless @extension.nil? }
|
54
60
|
.compact
|
55
61
|
end
|
56
62
|
|
57
|
-
def
|
58
|
-
|
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)
|