shopify-cli 2.7.2 → 2.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +2 -2
- data/.github/workflows/shopify.yml +1 -1
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +52 -0
- data/Codespace.dockerfile +2 -2
- data/Gemfile.lock +4 -4
- data/RELEASING.md +4 -3
- data/Tests.dockerfile +2 -2
- data/dev.yml +3 -3
- data/ext/javy/hashes/javy-arm-macos-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-linux-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-macos-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-windows-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/javy.rb +31 -13
- data/lib/graphql/get_extension_registrations.graphql +27 -0
- data/lib/project_types/extension/cli.rb +27 -2
- data/lib/project_types/extension/commands/build.rb +10 -10
- data/lib/project_types/extension/commands/create.rb +2 -3
- data/lib/project_types/extension/commands/push.rb +36 -8
- data/lib/project_types/extension/extension_project.rb +1 -1
- data/lib/project_types/extension/features/argo_serve.rb +6 -5
- data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
- data/lib/project_types/extension/loaders/project.rb +29 -0
- data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
- data/lib/project_types/extension/messages/messages.rb +4 -0
- data/lib/project_types/extension/models/app.rb +1 -1
- data/lib/project_types/extension/models/development_server.rb +2 -4
- data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
- data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
- data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
- data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
- data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
- data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
- data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
- data/lib/project_types/rails/commands/create.rb +1 -1
- data/lib/project_types/rails/gem.rb +1 -2
- data/lib/project_types/script/cli.rb +8 -1
- data/lib/project_types/script/commands/connect.rb +19 -0
- data/lib/project_types/script/commands/create.rb +8 -2
- data/lib/project_types/script/commands/push.rb +35 -12
- data/lib/project_types/script/config/extension_points.yml +10 -2
- data/lib/project_types/script/graphql/app_script_set.graphql +2 -2
- data/lib/project_types/script/layers/application/connect_app.rb +15 -3
- data/lib/project_types/script/layers/application/create_script.rb +17 -17
- data/lib/project_types/script/layers/application/extension_points.rb +50 -26
- data/lib/project_types/script/layers/application/push_script.rb +6 -3
- data/lib/project_types/script/layers/domain/errors.rb +1 -4
- data/lib/project_types/script/layers/domain/extension_point.rb +14 -0
- data/lib/project_types/script/layers/domain/push_package.rb +3 -3
- data/lib/project_types/script/layers/domain/{script_json.rb → script_config.rb} +2 -2
- data/lib/project_types/script/layers/domain/script_project.rb +1 -1
- data/lib/project_types/script/layers/infrastructure/errors.rb +30 -5
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -2
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +125 -27
- data/lib/project_types/script/layers/infrastructure/script_service.rb +11 -11
- data/lib/project_types/script/loaders/project.rb +44 -0
- data/lib/project_types/script/loaders/specification_handler.rb +22 -0
- data/lib/project_types/script/messages/messages.rb +34 -3
- data/lib/project_types/script/ui/error_handler.rb +35 -15
- data/lib/project_types/theme/commands/pull.rb +39 -16
- data/lib/project_types/theme/commands/push.rb +60 -29
- data/lib/project_types/theme/commands/serve.rb +5 -0
- data/lib/project_types/theme/messages/messages.rb +30 -18
- data/lib/shopify_cli/command.rb +6 -0
- data/lib/shopify_cli/commands/login.rb +11 -5
- data/lib/shopify_cli/commands/switch.rb +1 -1
- data/lib/shopify_cli/constants.rb +5 -0
- data/lib/shopify_cli/context.rb +66 -11
- data/lib/shopify_cli/environment.rb +15 -4
- data/lib/shopify_cli/form.rb +2 -0
- data/lib/shopify_cli/git.rb +2 -0
- data/lib/shopify_cli/identity_auth.rb +1 -0
- data/lib/shopify_cli/messages/messages.rb +10 -2
- data/lib/shopify_cli/partners_api/app_extensions/job.rb +36 -0
- data/lib/shopify_cli/partners_api/app_extensions.rb +46 -0
- data/lib/shopify_cli/partners_api/organizations.rb +2 -5
- data/lib/shopify_cli/partners_api.rb +1 -0
- data/lib/shopify_cli/project.rb +8 -7
- data/lib/shopify_cli/resources/env_file.rb +18 -6
- data/lib/shopify_cli/services/app/create/rails_service.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +73 -0
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +57 -10
- data/lib/shopify_cli/theme/dev_server/hot_reload.rb +18 -2
- data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
- data/lib/shopify_cli/theme/dev_server/proxy.rb +10 -15
- data/lib/shopify_cli/theme/dev_server/reload_mode.rb +34 -0
- data/lib/shopify_cli/theme/dev_server.rb +8 -21
- data/lib/shopify_cli/theme/theme.rb +26 -4
- data/lib/shopify_cli/thread_pool/job.rb +27 -0
- data/lib/shopify_cli/thread_pool.rb +37 -0
- data/lib/shopify_cli/tunnel.rb +1 -0
- data/lib/shopify_cli/version.rb +1 -1
- data/lib/shopify_cli.rb +4 -0
- data/shopify-cli.gemspec +1 -1
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
- metadata +26 -7
- data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
- data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
data/lib/shopify_cli/git.rb
CHANGED
@@ -229,6 +229,7 @@ module ShopifyCLI
|
|
229
229
|
uri = URI.parse("#{auth_url}#{endpoint}")
|
230
230
|
https = Net::HTTP.new(uri.host, uri.port)
|
231
231
|
https.use_ssl = true
|
232
|
+
https.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV["SSL_VERIFY_NONE"]
|
232
233
|
request = Net::HTTP::Post.new(uri.path)
|
233
234
|
request["User-Agent"] = "Shopify CLI #{::ShopifyCLI::VERSION}"
|
234
235
|
request.body = URI.encode_www_form(params)
|
@@ -14,6 +14,12 @@ module ShopifyCLI
|
|
14
14
|
},
|
15
15
|
},
|
16
16
|
core: {
|
17
|
+
errors: {
|
18
|
+
option_parser: {
|
19
|
+
invalid_option: "The option {{command:%s}} is not supported.",
|
20
|
+
missing_argument: "The required argument {{command:%s}} is missing.",
|
21
|
+
},
|
22
|
+
},
|
17
23
|
app: {
|
18
24
|
help: <<~HELP,
|
19
25
|
Suite of commands for developing apps. See {{command:%1$s app <command> --help}} for usage of each command.
|
@@ -409,7 +415,7 @@ module ShopifyCLI
|
|
409
415
|
Usage: {{command:%s login [--store=STORE]}}
|
410
416
|
HELP
|
411
417
|
invalid_shop: <<~MESSAGE,
|
412
|
-
Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
|
418
|
+
Invalid store provided (%s). Please make sure that the store belongs to your partner organization, and provide the store in the following format: my-store.myshopify.com
|
413
419
|
MESSAGE
|
414
420
|
shop_prompt: <<~PROMPT,
|
415
421
|
What store are you connecting to? (e.g. my-store.myshopify.com; do {{bold:NOT}} include protocol part, e.g., https://)
|
@@ -719,7 +725,7 @@ module ShopifyCLI
|
|
719
725
|
signup_suggestion: <<~MESSAGE,
|
720
726
|
{{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
|
721
727
|
account at {{underline:https://ngrok.com/signup}}. After you signup, install your
|
722
|
-
personalized authorization token using {{command:%s
|
728
|
+
personalized authorization token using {{command:%s app tunnel auth <token>}}.
|
723
729
|
MESSAGE
|
724
730
|
start: "{{v}} ngrok tunnel running at {{underline:%s}}",
|
725
731
|
start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
|
@@ -784,6 +790,8 @@ module ShopifyCLI
|
|
784
790
|
logged_in_partner_only: "Logged into partner organization {{green:%s}}",
|
785
791
|
logged_in_partner_and_shop: "Logged into store {{green:%s}} in partner organization {{green:%s}}",
|
786
792
|
},
|
793
|
+
error: "Error",
|
794
|
+
try_this: "Try this",
|
787
795
|
},
|
788
796
|
}.freeze
|
789
797
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shopify_cli/thread_pool/job"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
class PartnersAPI
|
7
|
+
class AppExtensions
|
8
|
+
class Job < ShopifyCLI::ThreadPool::Job
|
9
|
+
attr_reader :result
|
10
|
+
|
11
|
+
def initialize(ctx, app, type)
|
12
|
+
super()
|
13
|
+
@ctx = ctx
|
14
|
+
@app = app
|
15
|
+
@api_key = @app["apiKey"]
|
16
|
+
@type = type
|
17
|
+
end
|
18
|
+
|
19
|
+
def perform!
|
20
|
+
resp = PartnersAPI.query(@ctx, "get_extension_registrations", **params)
|
21
|
+
@result = resp&.dig("data", "app") || {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def patch_app_with_extensions!
|
25
|
+
@app.merge!(result)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def params
|
31
|
+
{ api_key: @api_key, type: @type }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shopify_cli/thread_pool"
|
4
|
+
|
5
|
+
require_relative "app_extensions/job"
|
6
|
+
|
7
|
+
module ShopifyCLI
|
8
|
+
class PartnersAPI
|
9
|
+
class AppExtensions
|
10
|
+
class << self
|
11
|
+
def fetch_apps_extensions(ctx, orgs, type)
|
12
|
+
jobs = apps(orgs).map { |app| AppExtensions::Job.new(ctx, app, type) }
|
13
|
+
|
14
|
+
consume_jobs!(jobs)
|
15
|
+
patch_apps_with_extensions!(jobs)
|
16
|
+
|
17
|
+
orgs
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def apps(orgs)
|
23
|
+
orgs.flat_map { |org| org["apps"] }
|
24
|
+
end
|
25
|
+
|
26
|
+
def consume_jobs!(jobs)
|
27
|
+
thread_pool = ShopifyCLI::ThreadPool.new
|
28
|
+
jobs.each do |job|
|
29
|
+
thread_pool.schedule(job)
|
30
|
+
end
|
31
|
+
thread_pool.shutdown
|
32
|
+
|
33
|
+
raise_if_any_error(jobs)
|
34
|
+
end
|
35
|
+
|
36
|
+
def patch_apps_with_extensions!(jobs)
|
37
|
+
jobs.each(&:patch_app_with_extensions!)
|
38
|
+
end
|
39
|
+
|
40
|
+
def raise_if_any_error(jobs)
|
41
|
+
jobs.find(&:error?).tap { |job| raise job.error if job }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -28,11 +28,8 @@ module ShopifyCLI
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def fetch_with_extensions(ctx, type)
|
31
|
-
|
32
|
-
(
|
33
|
-
org["apps"] = (org.dig("apps", "nodes") || [])
|
34
|
-
org
|
35
|
-
end
|
31
|
+
orgs = fetch_with_app(ctx)
|
32
|
+
AppExtensions.fetch_apps_extensions(ctx, orgs, type)
|
36
33
|
end
|
37
34
|
end
|
38
35
|
end
|
data/lib/shopify_cli/project.rb
CHANGED
@@ -107,13 +107,6 @@ module ShopifyCLI
|
|
107
107
|
@dir = nil
|
108
108
|
end
|
109
109
|
|
110
|
-
private
|
111
|
-
|
112
|
-
def directory(dir)
|
113
|
-
@dir ||= Hash.new { |h, k| h[k] = __directory(k) }
|
114
|
-
@dir[dir]
|
115
|
-
end
|
116
|
-
|
117
110
|
def at(dir)
|
118
111
|
proj_dir = directory(dir)
|
119
112
|
unless proj_dir
|
@@ -123,6 +116,13 @@ module ShopifyCLI
|
|
123
116
|
@at[proj_dir]
|
124
117
|
end
|
125
118
|
|
119
|
+
private
|
120
|
+
|
121
|
+
def directory(dir)
|
122
|
+
@dir ||= Hash.new { |h, k| h[k] = __directory(k) }
|
123
|
+
@dir[dir]
|
124
|
+
end
|
125
|
+
|
126
126
|
def __directory(curr)
|
127
127
|
loop do
|
128
128
|
return nil if curr == "/" || /^[A-Z]:\/$/.match?(curr)
|
@@ -134,6 +134,7 @@ module ShopifyCLI
|
|
134
134
|
end
|
135
135
|
|
136
136
|
property :directory # :nodoc:
|
137
|
+
property :env # :nodoc:
|
137
138
|
|
138
139
|
##
|
139
140
|
# will read, parse and return the envfile for the project
|
@@ -14,13 +14,21 @@ module ShopifyCLI
|
|
14
14
|
}
|
15
15
|
|
16
16
|
class << self
|
17
|
-
def
|
18
|
-
|
17
|
+
def path(directory)
|
18
|
+
File.join(directory, FILENAME)
|
19
|
+
end
|
20
|
+
|
21
|
+
def read(_directory = Dir.pwd, overrides: {})
|
22
|
+
input = parse_external_env(overrides: overrides)
|
19
23
|
new(input)
|
20
24
|
end
|
21
25
|
|
26
|
+
def from_hash(hash)
|
27
|
+
new(env_input(hash))
|
28
|
+
end
|
29
|
+
|
22
30
|
def parse(directory)
|
23
|
-
File.read(
|
31
|
+
File.read(path(directory))
|
24
32
|
.gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
|
25
33
|
match = /\A([A-Za-z_0-9]+)\s*=\s*(.*)\z/.match(line)
|
26
34
|
if match
|
@@ -37,10 +45,14 @@ module ShopifyCLI
|
|
37
45
|
end
|
38
46
|
end
|
39
47
|
|
40
|
-
def parse_external_env(directory = Dir.pwd)
|
48
|
+
def parse_external_env(directory = Dir.pwd, overrides: {})
|
49
|
+
env_input(parse(directory), overrides: overrides)
|
50
|
+
end
|
51
|
+
|
52
|
+
def env_input(parsed_source, overrides: {})
|
41
53
|
env_details = {}
|
42
54
|
extra = {}
|
43
|
-
|
55
|
+
parsed_source.merge(overrides).each do |key, value|
|
44
56
|
if KEY_MAP[key]
|
45
57
|
env_details[KEY_MAP[key]] = value
|
46
58
|
else
|
@@ -53,7 +65,7 @@ module ShopifyCLI
|
|
53
65
|
end
|
54
66
|
|
55
67
|
property :api_key, required: true
|
56
|
-
property :secret
|
68
|
+
property :secret
|
57
69
|
property :shop
|
58
70
|
property :scopes
|
59
71
|
property :host
|
@@ -159,10 +159,10 @@ module ShopifyCLI
|
|
159
159
|
|
160
160
|
CLI::UI::Frame.open(context.message("core.app.create.rails.generating_app", name)) do
|
161
161
|
new_command = %w(rails new)
|
162
|
+
new_command << name
|
162
163
|
new_command += DEFAULT_RAILS_FLAGS
|
163
164
|
new_command << "--database=#{db}"
|
164
165
|
new_command += rails_opts.split unless rails_opts.nil?
|
165
|
-
new_command << name
|
166
166
|
|
167
167
|
syscall(new_command)
|
168
168
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
module DevServer
|
6
|
+
class CdnFonts
|
7
|
+
FONTS_PATH = "/fonts"
|
8
|
+
FONTS_CDN = "https://fonts.shopifycdn.com"
|
9
|
+
FONTS_REGEX = %r{#{FONTS_CDN}}
|
10
|
+
|
11
|
+
def initialize(app, theme:)
|
12
|
+
@app = app
|
13
|
+
@theme = theme
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(env)
|
17
|
+
path = env["PATH_INFO"]
|
18
|
+
|
19
|
+
# Serve from fonts CDN
|
20
|
+
return serve_font(env) if path.start_with?(FONTS_PATH)
|
21
|
+
|
22
|
+
# Proxy the request, and replace the URLs in the response
|
23
|
+
status, headers, body = @app.call(env)
|
24
|
+
body = replace_font_urls(body)
|
25
|
+
[status, headers, body]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def serve_font(env)
|
31
|
+
parameters = %w(PATH_INFO QUERY_STRING REQUEST_METHOD rack.input)
|
32
|
+
path, query, method, body_stream = *env.slice(*parameters).values
|
33
|
+
|
34
|
+
uri = fonts_cdn_uri(path, query)
|
35
|
+
|
36
|
+
response = Net::HTTP.start(uri.host, 443, use_ssl: true) do |http|
|
37
|
+
req_class = Net::HTTP.const_get(method.capitalize)
|
38
|
+
req = req_class.new(uri)
|
39
|
+
req.initialize_http_header(fonts_cdn_headers)
|
40
|
+
req.body_stream = body_stream
|
41
|
+
http.request(req)
|
42
|
+
end
|
43
|
+
|
44
|
+
[
|
45
|
+
response.code.to_s,
|
46
|
+
{
|
47
|
+
"Content-Type" => response.content_type,
|
48
|
+
"Content-Length" => response.content_length.to_s,
|
49
|
+
},
|
50
|
+
[response.body],
|
51
|
+
]
|
52
|
+
end
|
53
|
+
|
54
|
+
def fonts_cdn_headers
|
55
|
+
{
|
56
|
+
"Referer" => "https://#{@theme.shop}",
|
57
|
+
"Transfer-Encoding" => "chunked",
|
58
|
+
}
|
59
|
+
end
|
60
|
+
|
61
|
+
def fonts_cdn_uri(path, query)
|
62
|
+
uri = URI.join("#{FONTS_CDN}/", path.gsub(%r{^#{FONTS_PATH}\/}, ""))
|
63
|
+
uri.query = query.split("&").last
|
64
|
+
uri
|
65
|
+
end
|
66
|
+
|
67
|
+
def replace_font_urls(body)
|
68
|
+
[body.join.gsub(FONTS_REGEX, FONTS_PATH)]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -15,21 +15,64 @@
|
|
15
15
|
eventSource.onerror = () => eventSource.close();
|
16
16
|
}
|
17
17
|
|
18
|
-
|
18
|
+
function reloadMode() {
|
19
|
+
var namespace = window.__SHOPIFY_CLI_ENV__;
|
20
|
+
return namespace.mode;
|
21
|
+
}
|
22
|
+
|
23
|
+
function isFullPageReloadMode(){
|
24
|
+
return reloadMode() === "full-page";
|
25
|
+
}
|
26
|
+
|
27
|
+
function isReloadModeActive(){
|
28
|
+
return reloadMode() !== "off";
|
29
|
+
}
|
30
|
+
|
31
|
+
function isRefreshRequired(files) {
|
32
|
+
if (isFullPageReloadMode()) {
|
33
|
+
return true;
|
34
|
+
}
|
35
|
+
return files.some((file) => !isCssFile(file) && !isSectionFile(file));
|
36
|
+
}
|
37
|
+
|
38
|
+
function refreshFile(file) {
|
39
|
+
if (isCssFile(file)) {
|
40
|
+
reloadCssFile(file);
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
|
44
|
+
if (isSectionFile(file)) {
|
45
|
+
reloadSection(file);
|
46
|
+
return;
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
function setHotReloadCookie(files) {
|
51
|
+
var date = new Date();
|
52
|
+
|
53
|
+
// Hot reload cookie expires in 3 seconds
|
54
|
+
date.setSeconds(date.getSeconds() + 3);
|
55
|
+
|
56
|
+
var sections = files.join(',');
|
57
|
+
var expires = date.toUTCString();
|
58
|
+
|
59
|
+
document.cookie = `hot_reload_sections=${sections}; expires=${expires}; path=/`;
|
60
|
+
}
|
61
|
+
|
62
|
+
function refreshPage(files) {
|
63
|
+
setHotReloadCookie(files);
|
64
|
+
console.log('[HotReload] Refreshing entire page');
|
65
|
+
window.location.reload();
|
66
|
+
}
|
19
67
|
|
20
68
|
function handleUpdate(message) {
|
21
69
|
var data = JSON.parse(message.data);
|
70
|
+
var modifiedFiles = data.modified;
|
22
71
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
if (isCssFile(modified)) {
|
27
|
-
reloadCssFile(modified)
|
28
|
-
} else if (isSectionFile(modified)) {
|
29
|
-
reloadSection(modified);
|
72
|
+
if (isRefreshRequired(modifiedFiles)) {
|
73
|
+
refreshPage(modifiedFiles);
|
30
74
|
} else {
|
31
|
-
|
32
|
-
window.location.reload();
|
75
|
+
modifiedFiles.forEach(refreshFile);
|
33
76
|
}
|
34
77
|
}
|
35
78
|
|
@@ -90,4 +133,8 @@
|
|
90
133
|
}
|
91
134
|
}
|
92
135
|
}
|
136
|
+
|
137
|
+
if (isReloadModeActive()) {
|
138
|
+
connect();
|
139
|
+
}
|
93
140
|
})();
|
@@ -4,10 +4,11 @@ module ShopifyCLI
|
|
4
4
|
module Theme
|
5
5
|
module DevServer
|
6
6
|
class HotReload
|
7
|
-
def initialize(ctx, app, theme:, watcher:, ignore_filter: nil)
|
7
|
+
def initialize(ctx, app, theme:, watcher:, mode:, ignore_filter: nil)
|
8
8
|
@ctx = ctx
|
9
9
|
@app = app
|
10
10
|
@theme = theme
|
11
|
+
@mode = mode
|
11
12
|
@streams = SSE::Streams.new
|
12
13
|
@watcher = watcher
|
13
14
|
@watcher.add_observer(self, :notify_streams_of_file_change)
|
@@ -48,12 +49,27 @@ module ShopifyCLI
|
|
48
49
|
|
49
50
|
def inject_hot_reload_javascript(body)
|
50
51
|
hot_reload_js = ::File.read("#{__dir__}/hot-reload.js")
|
51
|
-
hot_reload_script =
|
52
|
+
hot_reload_script = [
|
53
|
+
"<script>",
|
54
|
+
params_js,
|
55
|
+
hot_reload_js,
|
56
|
+
"</script>",
|
57
|
+
].join("\n")
|
58
|
+
|
52
59
|
body = body.join.gsub("</body>", "#{hot_reload_script}\n</body>")
|
53
60
|
|
54
61
|
[body]
|
55
62
|
end
|
56
63
|
|
64
|
+
def params_js
|
65
|
+
env = { mode: @mode }
|
66
|
+
<<~JS
|
67
|
+
(() => {
|
68
|
+
window.__SHOPIFY_CLI_ENV__ = #{env.to_json};
|
69
|
+
})();
|
70
|
+
JS
|
71
|
+
end
|
72
|
+
|
57
73
|
def create_stream
|
58
74
|
stream = @streams.new
|
59
75
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
module Theme
|
7
|
+
module DevServer
|
8
|
+
class Proxy
|
9
|
+
class TemplateParamBuilder
|
10
|
+
def build
|
11
|
+
# Core doesn't support replace_templates
|
12
|
+
return {} if core?(current_path)
|
13
|
+
|
14
|
+
(syncer_templates + request_templates)
|
15
|
+
.select { |file| file.liquid? || file.json? }
|
16
|
+
.uniq(&:relative_path)
|
17
|
+
.map { |file| as_param(file) }
|
18
|
+
.to_h
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_core_endpoints(core_endpoints)
|
22
|
+
@core_endpoints = core_endpoints
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_syncer(syncer)
|
27
|
+
@syncer = syncer
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_rack_env(rack_env)
|
32
|
+
@rack_env = rack_env
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def with_theme(theme)
|
37
|
+
@theme = theme
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def as_param(file)
|
44
|
+
["replace_templates[#{file.relative_path}]", file.read]
|
45
|
+
end
|
46
|
+
|
47
|
+
def syncer_templates
|
48
|
+
@syncer&.pending_updates || []
|
49
|
+
end
|
50
|
+
|
51
|
+
def request_templates
|
52
|
+
cookie_sections
|
53
|
+
.map { |section| @theme[section] unless @theme.nil? }
|
54
|
+
.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def cookie_sections
|
58
|
+
CGI::Cookie.parse(cookie)["hot_reload_sections"].join.split(",") || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def core?(path)
|
62
|
+
core_endpoints.include?(path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_path
|
66
|
+
rack_env["PATH_INFO"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def cookie
|
70
|
+
rack_env["HTTP_COOKIE"]
|
71
|
+
end
|
72
|
+
|
73
|
+
def core_endpoints
|
74
|
+
@core_endpoints || []
|
75
|
+
end
|
76
|
+
|
77
|
+
def rack_env
|
78
|
+
@rack_env || {}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -2,6 +2,9 @@
|
|
2
2
|
require "net/http"
|
3
3
|
require "stringio"
|
4
4
|
require "time"
|
5
|
+
require "cgi"
|
6
|
+
|
7
|
+
require_relative "proxy/template_param_builder"
|
5
8
|
|
6
9
|
module ShopifyCLI
|
7
10
|
module Theme
|
@@ -15,6 +18,7 @@ module ShopifyCLI
|
|
15
18
|
"trailer",
|
16
19
|
"transfer-encoding",
|
17
20
|
"upgrade",
|
21
|
+
"content-security-policy",
|
18
22
|
]
|
19
23
|
|
20
24
|
class Proxy
|
@@ -112,21 +116,12 @@ module ShopifyCLI
|
|
112
116
|
end
|
113
117
|
|
114
118
|
def build_replace_templates_param(env)
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Only replace Liquid or JSON files
|
122
|
-
file.liquid? || file.json?
|
123
|
-
end
|
124
|
-
|
125
|
-
pending_templates.each do |path|
|
126
|
-
params["replace_templates[#{path.relative_path}]"] = path.read
|
127
|
-
end
|
128
|
-
|
129
|
-
params
|
119
|
+
TemplateParamBuilder.new
|
120
|
+
.with_core_endpoints(@core_endpoints)
|
121
|
+
.with_syncer(@syncer)
|
122
|
+
.with_theme(@theme)
|
123
|
+
.with_rack_env(env)
|
124
|
+
.build
|
130
125
|
end
|
131
126
|
|
132
127
|
def add_session_cookie(cookie_header)
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
module DevServer
|
6
|
+
class ReloadMode
|
7
|
+
MODES = [:"hot-reload", :"full-page", :off]
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def default
|
11
|
+
:"hot-reload"
|
12
|
+
end
|
13
|
+
|
14
|
+
def get!(mode)
|
15
|
+
MODES.find { |m| m == mode.to_sym } || raise_error(mode)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def raise_error(invalid_mode)
|
21
|
+
error_message = ShopifyCLI::Context.message("theme.serve.reload_mode_is_not_valid", invalid_mode)
|
22
|
+
help_message = ShopifyCLI::Context.message("theme.serve.try_a_valid_reload_mode", valid_modes)
|
23
|
+
|
24
|
+
ShopifyCLI::Context.abort(error_message, help_message)
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid_modes
|
28
|
+
MODES.map { |v| "`#{v}`" }.join(", ")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|