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
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
module ShopifyCLI
|
4
4
|
module Theme
|
5
|
-
|
5
|
+
class DevServer
|
6
6
|
class LocalAssets
|
7
|
-
|
7
|
+
THEME_REGEX = %r{//cdn\.shopify\.com/s/.+?/(assets/.+?\.(?:css|js))}
|
8
8
|
|
9
9
|
class FileBody
|
10
10
|
def initialize(path)
|
@@ -22,10 +22,10 @@ module ShopifyCLI
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
def initialize(ctx, app,
|
25
|
+
def initialize(ctx, app, target)
|
26
26
|
@ctx = ctx
|
27
27
|
@app = app
|
28
|
-
@
|
28
|
+
@target = target
|
29
29
|
end
|
30
30
|
|
31
31
|
def call(env)
|
@@ -42,23 +42,20 @@ module ShopifyCLI
|
|
42
42
|
|
43
43
|
private
|
44
44
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
},
|
54
|
-
FileBody.new(path),
|
55
|
-
]
|
56
|
-
else
|
57
|
-
fail(404, "Not found")
|
45
|
+
def replace_asset_urls(body)
|
46
|
+
replaced_body = body.join.gsub(THEME_REGEX) do |match|
|
47
|
+
path = Regexp.last_match[1]
|
48
|
+
if @target.static_asset_paths.include?(path)
|
49
|
+
"/#{path}"
|
50
|
+
else
|
51
|
+
match
|
52
|
+
end
|
58
53
|
end
|
54
|
+
|
55
|
+
[replaced_body]
|
59
56
|
end
|
60
57
|
|
61
|
-
def
|
58
|
+
def serve_fail(status, body)
|
62
59
|
[
|
63
60
|
status,
|
64
61
|
{
|
@@ -69,17 +66,20 @@ module ShopifyCLI
|
|
69
66
|
]
|
70
67
|
end
|
71
68
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
69
|
+
def serve_file(path_info)
|
70
|
+
path = @target.root.join(path_info[1..-1])
|
71
|
+
if path.file? && path.readable? && @target.static_asset_file?(path)
|
72
|
+
[
|
73
|
+
200,
|
74
|
+
{
|
75
|
+
"Content-Type" => MimeType.by_filename(path).to_s,
|
76
|
+
"Content-Length" => path.size.to_s,
|
77
|
+
},
|
78
|
+
FileBody.new(path),
|
79
|
+
]
|
80
|
+
else
|
81
|
+
serve_fail(404, "Not found")
|
80
82
|
end
|
81
|
-
|
82
|
-
[replaced_body]
|
83
83
|
end
|
84
84
|
end
|
85
85
|
end
|
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "net/http"
|
3
2
|
require "stringio"
|
4
3
|
require "time"
|
5
4
|
require "cgi"
|
6
|
-
|
5
|
+
require "net/http"
|
6
|
+
|
7
|
+
require_relative "header_hash"
|
8
|
+
require_relative "proxy_param_builder"
|
7
9
|
|
8
10
|
module ShopifyCLI
|
9
11
|
module Theme
|
10
|
-
|
12
|
+
class DevServer
|
11
13
|
HOP_BY_HOP_HEADERS = [
|
12
14
|
"connection",
|
13
15
|
"keep-alive",
|
@@ -25,27 +27,27 @@ module ShopifyCLI
|
|
25
27
|
SESSION_COOKIE_REGEXP = /#{SESSION_COOKIE_NAME}=(\h+)/
|
26
28
|
SESSION_COOKIE_MAX_AGE = 60 * 60 * 23 # 1 day - leeway of 1h
|
27
29
|
|
28
|
-
def initialize(ctx, theme
|
30
|
+
def initialize(ctx, theme, param_builder)
|
29
31
|
@ctx = ctx
|
30
32
|
@theme = theme
|
31
|
-
@
|
32
|
-
@core_endpoints = Set.new
|
33
|
+
@param_builder = param_builder
|
33
34
|
|
35
|
+
@core_endpoints = Set.new
|
34
36
|
@secure_session_id = nil
|
35
37
|
@last_session_cookie_refresh = nil
|
36
38
|
end
|
37
39
|
|
38
40
|
def call(env)
|
39
41
|
headers = extract_http_request_headers(env)
|
40
|
-
headers["Host"] =
|
42
|
+
headers["Host"] = shop
|
41
43
|
headers["Cookie"] = add_session_cookie(headers["Cookie"])
|
42
44
|
headers["Accept-Encoding"] = "none"
|
43
45
|
headers["User-Agent"] = "Shopify CLI"
|
44
|
-
|
45
46
|
query = URI.decode_www_form(env["QUERY_STRING"])
|
46
|
-
replace_templates =
|
47
|
+
replace_templates = build_replacement_param(env)
|
47
48
|
response = if replace_templates.any?
|
48
|
-
# Pass to SFR the recently modified templates in `replace_templates`
|
49
|
+
# Pass to SFR the recently modified templates in `replace_templates` or
|
50
|
+
# `replace_extension_templates` body param
|
49
51
|
headers["Authorization"] = "Bearer #{bearer_token}"
|
50
52
|
form_data = URI.decode_www_form(env["rack.input"].read).to_h
|
51
53
|
request(
|
@@ -63,7 +65,7 @@ module ShopifyCLI
|
|
63
65
|
)
|
64
66
|
end
|
65
67
|
|
66
|
-
headers = get_response_headers(response)
|
68
|
+
headers = get_response_headers(response, env)
|
67
69
|
|
68
70
|
unless headers["x-storefront-renderer-rendered"]
|
69
71
|
@core_endpoints << env["PATH_INFO"]
|
@@ -79,7 +81,7 @@ module ShopifyCLI
|
|
79
81
|
def patch_body(env, body)
|
80
82
|
return [""] unless body
|
81
83
|
|
82
|
-
body.gsub(%r{(data-.+=(["']))(http:|https:)?//#{
|
84
|
+
body.gsub(%r{(data-.+=(["']))(http:|https:)?//#{shop}(.*)(\2)}) do |_|
|
83
85
|
match = Regexp.last_match
|
84
86
|
"#{match[1]}http://#{host(env)}#{match[4]}#{match[5]}"
|
85
87
|
end
|
@@ -127,15 +129,6 @@ module ShopifyCLI
|
|
127
129
|
name.sub(/^HTTP_/, "").gsub("_", "-")
|
128
130
|
end
|
129
131
|
|
130
|
-
def build_replace_templates_param(env)
|
131
|
-
TemplateParamBuilder.new
|
132
|
-
.with_core_endpoints(@core_endpoints)
|
133
|
-
.with_syncer(@syncer)
|
134
|
-
.with_theme(@theme)
|
135
|
-
.with_rack_env(env)
|
136
|
-
.build
|
137
|
-
end
|
138
|
-
|
139
132
|
def add_session_cookie(cookie_header)
|
140
133
|
cookie_header = if cookie_header
|
141
134
|
cookie_header.dup
|
@@ -170,7 +163,7 @@ module ShopifyCLI
|
|
170
163
|
def secure_session_id
|
171
164
|
if secure_session_id_expired?
|
172
165
|
@ctx.debug("Refreshing preview _secure_session_id cookie")
|
173
|
-
response = request("HEAD", "/", query: [[:preview_theme_id,
|
166
|
+
response = request("HEAD", "/", query: [[:preview_theme_id, theme_id]])
|
174
167
|
@secure_session_id = extract_secure_session_id_from_response_headers(response)
|
175
168
|
@last_session_cookie_refresh = Time.now
|
176
169
|
end
|
@@ -178,7 +171,7 @@ module ShopifyCLI
|
|
178
171
|
@secure_session_id
|
179
172
|
end
|
180
173
|
|
181
|
-
def get_response_headers(response)
|
174
|
+
def get_response_headers(response, env)
|
182
175
|
response_headers = normalize_headers(
|
183
176
|
response.respond_to?(:headers) ? response.headers : response.to_hash
|
184
177
|
)
|
@@ -188,7 +181,7 @@ module ShopifyCLI
|
|
188
181
|
response_headers.reject! { |k| HOP_BY_HOP_HEADERS.include?(k.downcase) }
|
189
182
|
|
190
183
|
if response_headers["location"]&.include?("myshopify.com")
|
191
|
-
response_headers["location"].gsub!(%r{(https://#{
|
184
|
+
response_headers["location"].gsub!(%r{(https://#{shop})}, "http://#{host(env)}")
|
192
185
|
end
|
193
186
|
|
194
187
|
new_session_id = extract_secure_session_id_from_response_headers(response_headers)
|
@@ -202,7 +195,7 @@ module ShopifyCLI
|
|
202
195
|
end
|
203
196
|
|
204
197
|
def request(method, path, headers: nil, query: [], form_data: nil, body_stream: nil)
|
205
|
-
uri = URI.join("https://#{
|
198
|
+
uri = URI.join("https://#{shop}", path)
|
206
199
|
uri.query = URI.encode_www_form(query + [[:_fd, 0], [:pb, 0]])
|
207
200
|
|
208
201
|
@ctx.debug("Proxying #{method} #{uri}")
|
@@ -218,6 +211,21 @@ module ShopifyCLI
|
|
218
211
|
response
|
219
212
|
end
|
220
213
|
end
|
214
|
+
|
215
|
+
def shop
|
216
|
+
@shop ||= @theme.shop
|
217
|
+
end
|
218
|
+
|
219
|
+
def theme_id
|
220
|
+
@theme_id ||= @theme.id
|
221
|
+
end
|
222
|
+
|
223
|
+
def build_replacement_param(env)
|
224
|
+
@param_builder
|
225
|
+
.with_core_endpoints(@core_endpoints)
|
226
|
+
.with_rack_env(env)
|
227
|
+
.build
|
228
|
+
end
|
221
229
|
end
|
222
230
|
end
|
223
231
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
module Theme
|
7
|
+
class DevServer
|
8
|
+
class ProxyParamBuilder
|
9
|
+
def build
|
10
|
+
# Core doesn't support replace_templates
|
11
|
+
return {} if core?(current_path)
|
12
|
+
|
13
|
+
(syncer_templates + request_templates)
|
14
|
+
.select { |file| file.liquid? || file.json? }
|
15
|
+
.uniq(&:relative_path)
|
16
|
+
.map { |file| as_param(file) }
|
17
|
+
.to_h
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_core_endpoints(core_endpoints)
|
21
|
+
@core_endpoints = core_endpoints
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_syncer(syncer)
|
26
|
+
@syncer = syncer
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_rack_env(rack_env)
|
31
|
+
@rack_env = rack_env
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_theme(theme)
|
36
|
+
@theme = theme
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def as_param(file)
|
43
|
+
["replace_templates[#{file.relative_path}]", file.read]
|
44
|
+
end
|
45
|
+
|
46
|
+
def syncer_templates
|
47
|
+
@syncer&.pending_updates || []
|
48
|
+
end
|
49
|
+
|
50
|
+
def request_templates
|
51
|
+
cookie_sections
|
52
|
+
.map { |section| @theme[section] unless @theme.nil? }
|
53
|
+
.compact
|
54
|
+
end
|
55
|
+
|
56
|
+
def cookie_sections
|
57
|
+
CGI::Cookie.parse(cookie)["hot_reload_files"].join.split(",") || []
|
58
|
+
end
|
59
|
+
|
60
|
+
def core?(path)
|
61
|
+
core_endpoints.include?(path)
|
62
|
+
end
|
63
|
+
|
64
|
+
def current_path
|
65
|
+
rack_env["PATH_INFO"]
|
66
|
+
end
|
67
|
+
|
68
|
+
def cookie
|
69
|
+
rack_env["HTTP_COOKIE"]
|
70
|
+
end
|
71
|
+
|
72
|
+
def core_endpoints
|
73
|
+
@core_endpoints || []
|
74
|
+
end
|
75
|
+
|
76
|
+
def rack_env
|
77
|
+
@rack_env || {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -1,24 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
require "
|
3
|
-
require "
|
2
|
+
require "shopify_cli/file_system_listener"
|
3
|
+
require "forwardable"
|
4
4
|
|
5
5
|
module ShopifyCLI
|
6
6
|
module Theme
|
7
|
-
|
7
|
+
class DevServer
|
8
8
|
# Watches for file changes and publish events to the theme
|
9
9
|
class Watcher
|
10
|
-
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def_delegators :@listener, :add_observer, :changed, :notify_observers
|
11
13
|
|
12
14
|
def initialize(ctx, theme:, syncer:, ignore_filter: nil, poll: false)
|
13
15
|
@ctx = ctx
|
14
16
|
@theme = theme
|
15
17
|
@syncer = syncer
|
16
18
|
@ignore_filter = ignore_filter
|
17
|
-
@listener =
|
18
|
-
|
19
|
-
changed
|
20
|
-
notify_observers(modified, added, removed)
|
21
|
-
end
|
19
|
+
@listener = FileSystemListener.new(root: @theme.root, force_poll: poll,
|
20
|
+
ignore_regex: @ignore_filter&.regexes)
|
22
21
|
|
23
22
|
add_observer(self, :upload_files_when_changed)
|
24
23
|
end
|
@@ -5,7 +5,7 @@ require "stringio"
|
|
5
5
|
|
6
6
|
module ShopifyCLI
|
7
7
|
module Theme
|
8
|
-
|
8
|
+
class DevServer
|
9
9
|
# WEBrick will sometimes cause a fatal deadlock error on shutdown.
|
10
10
|
# The error happens because `Thread#join` is called without a timeout argument.
|
11
11
|
# We monkey-patch WEBrick to call `Thread#join(timeout)` before the existing
|