shopify-cli 2.7.0 → 2.7.4
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 +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
|