shopify-cli 2.7.0 → 2.7.4
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/.github/CODEOWNERS +2 -2
- data/.github/workflows/shopify.yml +1 -1
- data/.gitignore +2 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +46 -0
- data/Codespace.dockerfile +2 -2
- data/Gemfile.lock +4 -4
- data/Rakefile +27 -0
- 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 +204 -0
- data/ext/javy/version +1 -0
- 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 -14
- data/lib/project_types/extension/commands/create.rb +3 -6
- 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 -2
- data/lib/project_types/extension/models/app.rb +1 -1
- data/lib/project_types/extension/models/development_server.rb +2 -2
- 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 +2 -4
- data/lib/project_types/script/cli.rb +9 -1
- data/lib/project_types/script/commands/connect.rb +19 -0
- data/lib/project_types/script/commands/create.rb +1 -3
- data/lib/project_types/script/commands/javy.rb +29 -0
- data/lib/project_types/script/commands/push.rb +2 -1
- data/lib/project_types/script/config/extension_points.yml +12 -30
- data/lib/project_types/script/forms/ask_app.rb +32 -0
- data/lib/project_types/script/forms/ask_org.rb +30 -0
- data/lib/project_types/script/forms/ask_script_uuid.rb +22 -0
- data/lib/project_types/script/forms/run_against_shopify_org.rb +14 -0
- data/lib/project_types/script/graphql/app_script_set.graphql +2 -2
- data/lib/project_types/script/layers/application/build_script.rb +0 -1
- data/lib/project_types/script/layers/application/connect_app.rb +79 -0
- data/lib/project_types/script/layers/application/create_script.rb +17 -17
- data/lib/project_types/script/layers/application/push_script.rb +1 -1
- data/lib/project_types/script/layers/domain/errors.rb +1 -4
- 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 +5 -1
- data/lib/project_types/script/layers/infrastructure/errors.rb +36 -7
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -4
- data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +0 -4
- 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/messages/messages.rb +32 -4
- data/lib/project_types/script/ui/error_handler.rb +31 -21
- data/lib/project_types/theme/commands/pull.rb +3 -0
- data/lib/project_types/theme/commands/push.rb +7 -1
- data/lib/project_types/theme/commands/serve.rb +1 -1
- data/lib/project_types/theme/messages/messages.rb +35 -1
- data/lib/project_types/theme/ui/sync_progress_bar.rb +2 -2
- data/lib/shopify_cli/command/project_command.rb +20 -7
- data/lib/shopify_cli/command.rb +6 -0
- data/lib/shopify_cli/commands/app/create/node.rb +1 -3
- data/lib/shopify_cli/commands/app/create/rails.rb +1 -3
- data/lib/shopify_cli/constants.rb +7 -0
- data/lib/shopify_cli/context.rb +11 -1
- data/lib/shopify_cli/environment.rb +4 -0
- data/lib/shopify_cli/form.rb +2 -0
- data/lib/shopify_cli/git.rb +2 -0
- data/lib/shopify_cli/identity_auth.rb +18 -0
- data/lib/shopify_cli/messages/messages.rb +9 -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 +2 -8
- data/lib/shopify_cli/project.rb +8 -7
- data/lib/shopify_cli/resources/env_file.rb +13 -5
- data/lib/shopify_cli/services/app/create/node_service.rb +2 -0
- data/lib/shopify_cli/services/app/create/php_service.rb +1 -1
- data/lib/shopify_cli/services/app/create/rails_service.rb +3 -1
- data/lib/shopify_cli/services/app/serve/node_service.rb +1 -1
- data/lib/shopify_cli/services/app/serve/rails_service.rb +1 -1
- data/lib/shopify_cli/tasks/ensure_authenticated.rb +9 -3
- data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +73 -0
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +38 -9
- data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
- data/lib/shopify_cli/theme/dev_server/proxy.rb +9 -15
- data/lib/shopify_cli/theme/dev_server.rb +32 -19
- data/lib/shopify_cli/theme/syncer/error_reporter.rb +45 -0
- data/lib/shopify_cli/theme/syncer/operation.rb +56 -0
- data/lib/shopify_cli/theme/syncer/standard_reporter.rb +32 -0
- data/lib/shopify_cli/theme/syncer.rb +40 -39
- data/lib/shopify_cli/theme/theme.rb +31 -19
- 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 +26 -22
- data/lib/shopify_cli/version.rb +1 -1
- data/shopify-cli.gemspec +1 -1
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
- data/vendor/deps/cli-kit/lib/cli/kit/system.rb +1 -1
- metadata +34 -8
- data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
- data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
- data/lib/project_types/script/tasks/ensure_env.rb +0 -106
|
@@ -7,6 +7,7 @@ module ShopifyCLI
|
|
|
7
7
|
#
|
|
8
8
|
class PartnersAPI < API
|
|
9
9
|
autoload :Organizations, "shopify_cli/partners_api/organizations"
|
|
10
|
+
autoload :AppExtensions, "shopify_cli/partners_api/app_extensions"
|
|
10
11
|
|
|
11
12
|
class << self
|
|
12
13
|
##
|
|
@@ -61,18 +62,11 @@ module ShopifyCLI
|
|
|
61
62
|
def api_client(ctx)
|
|
62
63
|
new(
|
|
63
64
|
ctx: ctx,
|
|
64
|
-
token:
|
|
65
|
+
token: IdentityAuth.fetch_or_auth_partners_token(ctx: ctx),
|
|
65
66
|
url: "https://#{Environment.partners_domain}/api/cli/graphql",
|
|
66
67
|
)
|
|
67
68
|
end
|
|
68
69
|
|
|
69
|
-
def access_token(ctx)
|
|
70
|
-
ShopifyCLI::DB.get(:partners_exchange_token) do
|
|
71
|
-
IdentityAuth.new(ctx: ctx).authenticate
|
|
72
|
-
ShopifyCLI::DB.get(:partners_exchange_token)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
|
|
76
70
|
def auth_failure_info(ctx, error)
|
|
77
71
|
if error.response
|
|
78
72
|
headers = %w(www-authenticate x-request-id)
|
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,11 +14,15 @@ module ShopifyCLI
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
def read(_directory = Dir.pwd)
|
|
18
|
-
input = parse_external_env
|
|
17
|
+
def read(_directory = Dir.pwd, overrides: {})
|
|
18
|
+
input = parse_external_env(overrides: overrides)
|
|
19
19
|
new(input)
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def from_hash(hash)
|
|
23
|
+
new(env_input(hash))
|
|
24
|
+
end
|
|
25
|
+
|
|
22
26
|
def parse(directory)
|
|
23
27
|
File.read(File.join(directory, FILENAME))
|
|
24
28
|
.gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
|
|
@@ -37,10 +41,14 @@ module ShopifyCLI
|
|
|
37
41
|
end
|
|
38
42
|
end
|
|
39
43
|
|
|
40
|
-
def parse_external_env(directory = Dir.pwd)
|
|
44
|
+
def parse_external_env(directory = Dir.pwd, overrides: {})
|
|
45
|
+
env_input(parse(directory), overrides: overrides)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def env_input(parsed_source, overrides: {})
|
|
41
49
|
env_details = {}
|
|
42
50
|
extra = {}
|
|
43
|
-
|
|
51
|
+
parsed_source.merge(overrides).each do |key, value|
|
|
44
52
|
if KEY_MAP[key]
|
|
45
53
|
env_details[KEY_MAP[key]] = value
|
|
46
54
|
else
|
|
@@ -53,7 +61,7 @@ module ShopifyCLI
|
|
|
53
61
|
end
|
|
54
62
|
|
|
55
63
|
property :api_key, required: true
|
|
56
|
-
property :secret
|
|
64
|
+
property :secret
|
|
57
65
|
property :shop
|
|
58
66
|
property :scopes
|
|
59
67
|
property :host
|
|
@@ -39,6 +39,8 @@ module ShopifyCLI
|
|
|
39
39
|
form_options[:rails_opts] = rails_opts unless rails_opts.nil?
|
|
40
40
|
form = form_data(form_options)
|
|
41
41
|
|
|
42
|
+
raise ShopifyCLI::AbortSilent if form.nil?
|
|
43
|
+
|
|
42
44
|
ruby_version = Rails::Ruby.version(context)
|
|
43
45
|
context.abort(context.message("core.app.create.rails.error.invalid_ruby_version")) unless
|
|
44
46
|
ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.0.0")
|
|
@@ -157,10 +159,10 @@ module ShopifyCLI
|
|
|
157
159
|
|
|
158
160
|
CLI::UI::Frame.open(context.message("core.app.create.rails.generating_app", name)) do
|
|
159
161
|
new_command = %w(rails new)
|
|
162
|
+
new_command << name
|
|
160
163
|
new_command += DEFAULT_RAILS_FLAGS
|
|
161
164
|
new_command << "--database=#{db}"
|
|
162
165
|
new_command += rails_opts.split unless rails_opts.nil?
|
|
163
|
-
new_command << name
|
|
164
166
|
|
|
165
167
|
syscall(new_command)
|
|
166
168
|
end
|
|
@@ -14,7 +14,7 @@ module ShopifyCLI
|
|
|
14
14
|
|
|
15
15
|
def call
|
|
16
16
|
project = ShopifyCLI::Project.current
|
|
17
|
-
url = host || ShopifyCLI::Tunnel.start(context)
|
|
17
|
+
url = host || ShopifyCLI::Tunnel.start(context, port: port)
|
|
18
18
|
raise ShopifyCLI::Abort,
|
|
19
19
|
context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
|
|
20
20
|
project.env.update(context, :host, url)
|
|
@@ -14,7 +14,7 @@ module ShopifyCLI
|
|
|
14
14
|
|
|
15
15
|
def call
|
|
16
16
|
project = ShopifyCLI::Project.current
|
|
17
|
-
url = host || ShopifyCLI::Tunnel.start(context)
|
|
17
|
+
url = host || ShopifyCLI::Tunnel.start(context, port: port)
|
|
18
18
|
raise ShopifyCLI::Abort,
|
|
19
19
|
context.message("core.app.serve.error.host_must_be_https") if url.match(/^https/i).nil?
|
|
20
20
|
project.env.update(context, :host, url)
|
|
@@ -4,9 +4,15 @@ module ShopifyCLI
|
|
|
4
4
|
module Tasks
|
|
5
5
|
class EnsureAuthenticated < ShopifyCLI::Task
|
|
6
6
|
def call(ctx)
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
return if ShopifyCLI::Environment.acceptance_test?
|
|
8
|
+
unless ShopifyCLI::IdentityAuth.authenticated?
|
|
9
|
+
raise ShopifyCLI::Abort,
|
|
10
|
+
ctx.message("core.identity_auth.login_prompt", ShopifyCLI::TOOL_NAME)
|
|
11
|
+
end
|
|
12
|
+
if ShopifyCLI::IdentityAuth.environment_auth_token?
|
|
13
|
+
ctx.puts(ctx.message("core.identity_auth.token_authentication",
|
|
14
|
+
ShopifyCLI::Constants::EnvironmentVariables::AUTH_TOKEN))
|
|
15
|
+
end
|
|
10
16
|
end
|
|
11
17
|
end
|
|
12
18
|
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
|
|
@@ -17,19 +17,48 @@
|
|
|
17
17
|
|
|
18
18
|
connect();
|
|
19
19
|
|
|
20
|
+
function isRefreshRequired(files) {
|
|
21
|
+
return files.some((file) => !isCssFile(file) && !isSectionFile(file));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function refreshFile(file) {
|
|
25
|
+
if (isCssFile(file)) {
|
|
26
|
+
reloadCssFile(file);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (isSectionFile(file)) {
|
|
31
|
+
reloadSection(file);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setHotReloadCookie(files) {
|
|
37
|
+
var date = new Date();
|
|
38
|
+
|
|
39
|
+
// Hot reload cookie expires in 3 seconds
|
|
40
|
+
date.setSeconds(date.getSeconds() + 3);
|
|
41
|
+
|
|
42
|
+
var sections = files.join(',');
|
|
43
|
+
var expires = date.toUTCString();
|
|
44
|
+
|
|
45
|
+
document.cookie = `hot_reload_sections=${sections}; expires=${expires}; path=/`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function refreshPage(files) {
|
|
49
|
+
setHotReloadCookie(files);
|
|
50
|
+
console.log('[HotReload] Refreshing entire page');
|
|
51
|
+
window.location.reload();
|
|
52
|
+
}
|
|
53
|
+
|
|
20
54
|
function handleUpdate(message) {
|
|
21
55
|
var data = JSON.parse(message.data);
|
|
56
|
+
var modifiedFiles = data.modified;
|
|
22
57
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (isCssFile(modified)) {
|
|
27
|
-
reloadCssFile(modified)
|
|
28
|
-
} else if (isSectionFile(modified)) {
|
|
29
|
-
reloadSection(modified);
|
|
58
|
+
if (isRefreshRequired(modifiedFiles)) {
|
|
59
|
+
refreshPage(modifiedFiles);
|
|
30
60
|
} else {
|
|
31
|
-
|
|
32
|
-
window.location.reload();
|
|
61
|
+
modifiedFiles.forEach(refreshFile);
|
|
33
62
|
}
|
|
34
63
|
}
|
|
35
64
|
|
|
@@ -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
|
|
@@ -112,21 +115,12 @@ module ShopifyCLI
|
|
|
112
115
|
end
|
|
113
116
|
|
|
114
117
|
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
|
|
118
|
+
TemplateParamBuilder.new
|
|
119
|
+
.with_core_endpoints(@core_endpoints)
|
|
120
|
+
.with_syncer(@syncer)
|
|
121
|
+
.with_theme(@theme)
|
|
122
|
+
.with_rack_env(env)
|
|
123
|
+
.build
|
|
130
124
|
end
|
|
131
125
|
|
|
132
126
|
def add_session_cookie(cookie_header)
|
|
@@ -3,6 +3,7 @@ require_relative "development_theme"
|
|
|
3
3
|
require_relative "ignore_filter"
|
|
4
4
|
require_relative "syncer"
|
|
5
5
|
|
|
6
|
+
require_relative "dev_server/cdn_fonts"
|
|
6
7
|
require_relative "dev_server/hot_reload"
|
|
7
8
|
require_relative "dev_server/header_hash"
|
|
8
9
|
require_relative "dev_server/local_assets"
|
|
@@ -24,7 +25,7 @@ module ShopifyCLI
|
|
|
24
25
|
class << self
|
|
25
26
|
attr_accessor :ctx
|
|
26
27
|
|
|
27
|
-
def start(ctx, root,
|
|
28
|
+
def start(ctx, root, host: "127.0.0.1", port: 9292, poll: false)
|
|
28
29
|
@ctx = ctx
|
|
29
30
|
theme = DevelopmentTheme.new(ctx, root: root)
|
|
30
31
|
ignore_filter = IgnoreFilter.from_path(root)
|
|
@@ -33,9 +34,11 @@ module ShopifyCLI
|
|
|
33
34
|
|
|
34
35
|
# Setup the middleware stack. Mimics Rack::Builder / config.ru, but in reverse order
|
|
35
36
|
@app = Proxy.new(ctx, theme: theme, syncer: @syncer)
|
|
37
|
+
@app = CdnFonts.new(@app, theme: theme)
|
|
36
38
|
@app = LocalAssets.new(ctx, @app, theme: theme)
|
|
37
39
|
@app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, ignore_filter: ignore_filter)
|
|
38
40
|
stopped = false
|
|
41
|
+
address = "http://#{host}:#{port}"
|
|
39
42
|
|
|
40
43
|
theme.ensure_exists!
|
|
41
44
|
|
|
@@ -44,8 +47,8 @@ module ShopifyCLI
|
|
|
44
47
|
stop
|
|
45
48
|
end
|
|
46
49
|
|
|
47
|
-
CLI::UI::Frame.open(@ctx.message("theme.serve.
|
|
48
|
-
ctx.print_task("
|
|
50
|
+
CLI::UI::Frame.open(@ctx.message("theme.serve.viewing_theme")) do
|
|
51
|
+
ctx.print_task(ctx.message("theme.serve.syncing_theme", theme.id, theme.shop))
|
|
49
52
|
@syncer.start_threads
|
|
50
53
|
if block_given?
|
|
51
54
|
yield @syncer
|
|
@@ -55,18 +58,9 @@ module ShopifyCLI
|
|
|
55
58
|
|
|
56
59
|
return if stopped
|
|
57
60
|
|
|
58
|
-
ctx.puts("")
|
|
59
|
-
ctx.
|
|
60
|
-
ctx.puts("")
|
|
61
|
-
ctx.open_url!("http://127.0.0.1:#{port}")
|
|
62
|
-
ctx.puts("")
|
|
63
|
-
ctx.puts("Customize this theme in the Online Store Editor:")
|
|
64
|
-
ctx.puts("{{green:#{theme.editor_url}}}")
|
|
65
|
-
ctx.puts("")
|
|
66
|
-
ctx.puts("Share this theme preview:")
|
|
67
|
-
ctx.puts("{{green:#{theme.preview_url}}}")
|
|
68
|
-
ctx.puts("")
|
|
69
|
-
ctx.puts("(Use Ctrl-C to stop)")
|
|
61
|
+
ctx.puts(ctx.message("theme.serve.serving", theme.root))
|
|
62
|
+
ctx.open_url!(address)
|
|
63
|
+
ctx.puts(ctx.message("theme.serve.customize_or_preview", theme.editor_url, theme.preview_url))
|
|
70
64
|
end
|
|
71
65
|
|
|
72
66
|
logger = if ctx.debug?
|
|
@@ -78,7 +72,7 @@ module ShopifyCLI
|
|
|
78
72
|
watcher.start
|
|
79
73
|
WebServer.run(
|
|
80
74
|
@app,
|
|
81
|
-
BindAddress:
|
|
75
|
+
BindAddress: host,
|
|
82
76
|
Port: port,
|
|
83
77
|
Logger: logger,
|
|
84
78
|
AccessLog: [],
|
|
@@ -87,10 +81,11 @@ module ShopifyCLI
|
|
|
87
81
|
|
|
88
82
|
rescue ShopifyCLI::API::APIRequestForbiddenError,
|
|
89
83
|
ShopifyCLI::API::APIRequestUnauthorizedError
|
|
90
|
-
@ctx.
|
|
91
|
-
|
|
84
|
+
raise ShopifyCLI::Abort, @ctx.message("theme.serve.ensure_user", theme.shop)
|
|
85
|
+
rescue Errno::EADDRINUSE
|
|
86
|
+
abort_address_already_in_use(address)
|
|
92
87
|
rescue Errno::EADDRNOTAVAIL
|
|
93
|
-
raise AddressBindingError, "Error binding to the address #{
|
|
88
|
+
raise AddressBindingError, "Error binding to the address #{host}."
|
|
94
89
|
end
|
|
95
90
|
|
|
96
91
|
def stop
|
|
@@ -99,6 +94,24 @@ module ShopifyCLI
|
|
|
99
94
|
@syncer.shutdown
|
|
100
95
|
WebServer.shutdown
|
|
101
96
|
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def abort_address_already_in_use(address)
|
|
101
|
+
open_frame(@ctx.message("theme.serve.already_in_use_error"), color: :red) do
|
|
102
|
+
@ctx.puts(@ctx.message("theme.serve.address_already_in_use", address))
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
open_frame(@ctx.message("theme.serve.try_this"), color: :green) do
|
|
106
|
+
@ctx.puts(@ctx.message("theme.serve.try_port_option"))
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
raise ShopifyCLI::AbortSilent
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def open_frame(title, color:, &block)
|
|
113
|
+
CLI::UI::Frame.open(title, color: CLI::UI.resolve_color(color), timing: false, &block)
|
|
114
|
+
end
|
|
102
115
|
end
|
|
103
116
|
end
|
|
104
117
|
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShopifyCLI
|
|
4
|
+
module Theme
|
|
5
|
+
class Syncer
|
|
6
|
+
##
|
|
7
|
+
# ShopifyCLI::Theme::Syncer::ErrorReporter allows delaying log of errors,
|
|
8
|
+
# mainly to not break the progress bar.
|
|
9
|
+
#
|
|
10
|
+
class ErrorReporter
|
|
11
|
+
attr_reader :ctx, :delayed_errors
|
|
12
|
+
|
|
13
|
+
def initialize(ctx)
|
|
14
|
+
@ctx = ctx
|
|
15
|
+
@has_any_error = false
|
|
16
|
+
@delay_errors = false
|
|
17
|
+
@delayed_errors = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def disable!
|
|
21
|
+
@delay_errors = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def enable!
|
|
25
|
+
@delay_errors = false
|
|
26
|
+
@delayed_errors.each { |error| report(error) }
|
|
27
|
+
@delayed_errors.clear
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def report(error_message)
|
|
31
|
+
if @delay_errors
|
|
32
|
+
@delayed_errors << error_message
|
|
33
|
+
else
|
|
34
|
+
@has_any_error = true
|
|
35
|
+
@ctx.error(error_message)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def has_any_error?
|
|
40
|
+
@has_any_error
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShopifyCLI
|
|
4
|
+
module Theme
|
|
5
|
+
class Syncer
|
|
6
|
+
class Operation
|
|
7
|
+
attr_accessor :method, :file
|
|
8
|
+
|
|
9
|
+
COLOR_BY_STATUS = {
|
|
10
|
+
error: :red,
|
|
11
|
+
synced: :green,
|
|
12
|
+
fixed: :cyan,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
def initialize(ctx, method, file)
|
|
16
|
+
@ctx = ctx
|
|
17
|
+
@method = method
|
|
18
|
+
@file = file
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
"#{method} #{file_path}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def as_error_message
|
|
26
|
+
as_message_with(status: :error)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def as_synced_message
|
|
30
|
+
as_message_with(status: :synced)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def as_fix_message
|
|
34
|
+
as_message_with(status: :fixed)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def file_path
|
|
38
|
+
file&.relative_path.to_s
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def as_message_with(status:)
|
|
44
|
+
status_color = COLOR_BY_STATUS[status]
|
|
45
|
+
status_text = @ctx.message("theme.serve.operation.status.#{status}").ljust(6)
|
|
46
|
+
|
|
47
|
+
"#{timestamp} {{#{status_color}:#{status_text}}} {{>}} {{blue:#{self}}}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def timestamp
|
|
51
|
+
Time.now.strftime("%T")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShopifyCLI
|
|
4
|
+
module Theme
|
|
5
|
+
class Syncer
|
|
6
|
+
##
|
|
7
|
+
# ShopifyCLI::Theme::Syncer::StdReporter allows disabling/enabling
|
|
8
|
+
# messages reported in the standard output (ShopifyCLI::Context#puts).
|
|
9
|
+
#
|
|
10
|
+
class StandardReporter
|
|
11
|
+
attr_reader :ctx
|
|
12
|
+
|
|
13
|
+
def initialize(ctx)
|
|
14
|
+
@enabled = true
|
|
15
|
+
@ctx = ctx
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def disable!
|
|
19
|
+
@enabled = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def enable!
|
|
23
|
+
@enabled = true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def report(message)
|
|
27
|
+
ctx.puts(message) if @enabled
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|