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.
- 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
|