shopify-cli 2.9.0 → 2.10.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 -2
- data/Gemfile.lock +1 -1
- data/lib/project_types/script/layers/domain/errors.rb +3 -2
- data/lib/project_types/script/layers/domain/script_config.rb +6 -4
- data/lib/project_types/script/layers/infrastructure/errors.rb +37 -24
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +28 -28
- data/lib/project_types/script/layers/infrastructure/script_service.rb +22 -5
- data/lib/project_types/script/messages/messages.rb +15 -17
- data/lib/project_types/script/ui/error_handler.rb +41 -29
- data/lib/project_types/theme/commands/pull.rb +6 -1
- data/lib/project_types/theme/commands/push.rb +6 -1
- data/lib/project_types/theme/messages/messages.rb +4 -0
- data/lib/shopify_cli/commands/login.rb +4 -10
- data/lib/shopify_cli/constants.rb +6 -2
- data/lib/shopify_cli/core/executor.rb +4 -4
- data/lib/shopify_cli/environment.rb +35 -16
- data/lib/shopify_cli/identity_auth.rb +3 -3
- data/lib/shopify_cli/messages/messages.rb +1 -1
- data/lib/shopify_cli/method_object.rb +21 -9
- data/lib/shopify_cli/result.rb +61 -59
- data/lib/shopify_cli/task.rb +5 -3
- data/lib/shopify_cli/theme/dev_server/cdn/cdn_helper.rb +49 -0
- data/lib/shopify_cli/theme/dev_server/cdn_assets.rb +69 -0
- data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +8 -28
- data/lib/shopify_cli/theme/dev_server/local_assets.rb +4 -0
- data/lib/shopify_cli/theme/dev_server.rb +2 -0
- data/lib/shopify_cli/theme/file.rb +2 -2
- data/lib/shopify_cli/theme/filter/path_matcher.rb +38 -0
- data/lib/shopify_cli/theme/ignore_filter.rb +14 -18
- data/lib/shopify_cli/theme/include_filter.rb +43 -0
- data/lib/shopify_cli/theme/syncer.rb +17 -2
- data/lib/shopify_cli/version.rb +1 -1
- data/lib/shopify_cli.rb +2 -1
- data/vendor/deps/ruby2_keywords/LICENSE +22 -0
- data/vendor/deps/ruby2_keywords/README.md +67 -0
- data/vendor/deps/ruby2_keywords/Rakefile +54 -0
- data/vendor/deps/ruby2_keywords/lib/ruby2_keywords.rb +57 -0
- data/vendor/deps/ruby2_keywords/ruby2_keywords.gemspec +18 -0
- data/vendor/deps/ruby2_keywords/test/test_keyword.rb +41 -0
- metadata +12 -2
@@ -15,6 +15,8 @@ module ShopifyCLI
|
|
15
15
|
|
16
16
|
def call(*)
|
17
17
|
shop = (options.flags[:shop] || @ctx.getenv("SHOPIFY_SHOP" || nil))
|
18
|
+
ShopifyCLI::DB.set(shop: self.class.validate_shop(shop, context: @ctx)) unless shop.nil?
|
19
|
+
|
18
20
|
if shop.nil? && Shopifolk.check
|
19
21
|
Shopifolk.reset
|
20
22
|
@ctx.puts(@ctx.message("core.tasks.select_org_and_shop.identified_as_shopify"))
|
@@ -31,25 +33,17 @@ module ShopifyCLI
|
|
31
33
|
IdentityAuth.new(ctx: @ctx).authenticate
|
32
34
|
org = select_organization
|
33
35
|
ShopifyCLI::DB.set(organization_id: org["id"].to_i) unless org.nil?
|
34
|
-
|
36
|
+
Whoami.call([], "whoami")
|
35
37
|
end
|
36
|
-
# validate that shop belongs to organization
|
37
|
-
ShopifyCLI::DB.set(shop: self.class.validate_shop(shop: shop, org: org, context: @ctx)) unless shop.nil?
|
38
|
-
Whoami.call([], "whoami")
|
39
38
|
end
|
40
39
|
|
41
40
|
def self.help
|
42
41
|
ShopifyCLI::Context.message("core.login.help", ShopifyCLI::TOOL_NAME)
|
43
42
|
end
|
44
43
|
|
45
|
-
def self.validate_shop(shop
|
44
|
+
def self.validate_shop(shop, context:)
|
46
45
|
permanent_domain = shop_to_permanent_domain(shop)
|
47
46
|
context.abort(context.message("core.login.invalid_shop", shop)) unless permanent_domain
|
48
|
-
if org
|
49
|
-
stores_owned = org["stores"]
|
50
|
-
is_verified = stores_owned.any? { |store| store["shopDomain"] == permanent_domain }
|
51
|
-
context.abort(context.message("core.login.invalid_shop", shop)) unless is_verified
|
52
|
-
end
|
53
47
|
permanent_domain
|
54
48
|
end
|
55
49
|
|
@@ -36,12 +36,16 @@ module ShopifyCLI
|
|
36
36
|
# the partners dashboard and identity.
|
37
37
|
LOCAL_PARTNERS = "SHOPIFY_APP_CLI_LOCAL_PARTNERS"
|
38
38
|
|
39
|
-
# When true the CLI points to
|
40
|
-
|
39
|
+
# When true the CLI points to spin instances of services
|
40
|
+
SPIN = "SPIN"
|
41
|
+
INFER_SPIN = "INFER_SPIN"
|
41
42
|
SPIN_WORKSPACE = "SPIN_WORKSPACE"
|
42
43
|
SPIN_NAMESPACE = "SPIN_NAMESPACE"
|
43
44
|
SPIN_HOST = "SPIN_HOST"
|
44
45
|
|
46
|
+
# Deprecated, equivalent to using SPIN=1
|
47
|
+
SPIN_PARTNERS = "SHOPIFY_APP_CLI_SPIN_PARTNERS"
|
48
|
+
|
45
49
|
# Environments
|
46
50
|
TEST = "SHOPIFY_CLI_TEST"
|
47
51
|
ACCEPTANCE_TEST = "SHOPIFY_CLI_ACCEPTANCE_TEST"
|
@@ -3,10 +3,10 @@ require "shopify_cli"
|
|
3
3
|
module ShopifyCLI
|
4
4
|
module Core
|
5
5
|
class Executor < CLI::Kit::Executor
|
6
|
-
def initialize(ctx, task_registry, *args
|
7
|
-
@ctx = ctx ||
|
8
|
-
@task_registry = task_registry ||
|
9
|
-
super(*args
|
6
|
+
ruby2_keywords def initialize(ctx, task_registry, *args)
|
7
|
+
@ctx = ctx || ShopifyCli::Context.new
|
8
|
+
@task_registry = task_registry || ShopifyCli::Tasks::TaskRegistry.new
|
9
|
+
super(*args)
|
10
10
|
end
|
11
11
|
|
12
12
|
def call(command, command_name, args)
|
@@ -61,17 +61,10 @@ module ShopifyCLI
|
|
61
61
|
)
|
62
62
|
end
|
63
63
|
|
64
|
-
def self.use_spin_partners_instance?(env_variables: ENV)
|
65
|
-
env_variable_truthy?(
|
66
|
-
Constants::EnvironmentVariables::SPIN_PARTNERS,
|
67
|
-
env_variables: env_variables
|
68
|
-
)
|
69
|
-
end
|
70
|
-
|
71
64
|
def self.partners_domain(env_variables: ENV)
|
72
65
|
if use_local_partners_instance?(env_variables: env_variables)
|
73
66
|
"partners.myshopify.io"
|
74
|
-
elsif
|
67
|
+
elsif use_spin?(env_variables: env_variables)
|
75
68
|
"partners.#{spin_url(env_variables: env_variables)}"
|
76
69
|
else
|
77
70
|
"partners.shopify.com"
|
@@ -79,15 +72,31 @@ module ShopifyCLI
|
|
79
72
|
end
|
80
73
|
|
81
74
|
def self.use_spin?(env_variables: ENV)
|
82
|
-
|
83
|
-
|
75
|
+
env_variable_truthy?(
|
76
|
+
Constants::EnvironmentVariables::SPIN,
|
77
|
+
env_variables: env_variables
|
78
|
+
) || env_variable_truthy?(
|
79
|
+
Constants::EnvironmentVariables::SPIN_PARTNERS,
|
80
|
+
env_variables: env_variables
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.infer_spin?(env_variables: ENV)
|
85
|
+
env_variable_truthy?(
|
86
|
+
Constants::EnvironmentVariables::INFER_SPIN,
|
87
|
+
env_variables: env_variables
|
88
|
+
)
|
84
89
|
end
|
85
90
|
|
86
91
|
def self.spin_url(env_variables: ENV)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
92
|
+
if infer_spin?(env_variables: env_variables)
|
93
|
+
%x(spin info fqdn 2> /dev/null).strip
|
94
|
+
else
|
95
|
+
spin_workspace = spin_workspace(env_variables: env_variables)
|
96
|
+
spin_namespace = spin_namespace(env_variables: env_variables)
|
97
|
+
spin_host = spin_host(env_variables: env_variables)
|
98
|
+
"#{spin_workspace}.#{spin_namespace}.#{spin_host}"
|
99
|
+
end
|
91
100
|
end
|
92
101
|
|
93
102
|
def self.send_monorail_events?(env_variables: ENV)
|
@@ -106,11 +115,21 @@ module ShopifyCLI
|
|
106
115
|
end
|
107
116
|
|
108
117
|
def self.spin_workspace(env_variables: ENV)
|
109
|
-
env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE]
|
118
|
+
env_value = env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE]
|
119
|
+
return env_value unless env_value.nil?
|
120
|
+
|
121
|
+
if env_value.nil?
|
122
|
+
raise "No value set for #{Constants::EnvironmentVariables::SPIN_WORKSPACE}"
|
123
|
+
end
|
110
124
|
end
|
111
125
|
|
112
126
|
def self.spin_namespace(env_variables: ENV)
|
113
|
-
env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE]
|
127
|
+
env_value = env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE]
|
128
|
+
return env_value unless env_value.nil?
|
129
|
+
|
130
|
+
if env_value.nil?
|
131
|
+
raise "No value set for #{Constants::EnvironmentVariables::SPIN_NAMESPACE}"
|
132
|
+
end
|
114
133
|
end
|
115
134
|
|
116
135
|
def self.spin_host(env_variables: ENV)
|
@@ -256,7 +256,7 @@ module ShopifyCLI
|
|
256
256
|
def auth_url
|
257
257
|
if Environment.use_local_partners_instance?
|
258
258
|
"https://identity.myshopify.io/oauth"
|
259
|
-
elsif Environment.
|
259
|
+
elsif Environment.use_spin?
|
260
260
|
"https://identity.#{Environment.spin_url}/oauth"
|
261
261
|
else
|
262
262
|
"https://accounts.shopify.com/oauth"
|
@@ -264,7 +264,7 @@ module ShopifyCLI
|
|
264
264
|
end
|
265
265
|
|
266
266
|
def client_id_for_application(application_name)
|
267
|
-
client_ids = if Environment.use_local_partners_instance? || Environment.
|
267
|
+
client_ids = if Environment.use_local_partners_instance? || Environment.use_spin?
|
268
268
|
DEV_APPLICATION_CLIENT_IDS
|
269
269
|
else
|
270
270
|
APPLICATION_CLIENT_IDS
|
@@ -280,7 +280,7 @@ module ShopifyCLI
|
|
280
280
|
end
|
281
281
|
|
282
282
|
def client_id
|
283
|
-
if Environment.use_local_partners_instance? || Environment.
|
283
|
+
if Environment.use_local_partners_instance? || Environment.use_spin?
|
284
284
|
Constants::Identity::CLIENT_ID_DEV
|
285
285
|
else
|
286
286
|
# In the future we might want to use Identity's dynamic
|
@@ -415,7 +415,7 @@ module ShopifyCLI
|
|
415
415
|
Usage: {{command:%s login [--store=STORE]}}
|
416
416
|
HELP
|
417
417
|
invalid_shop: <<~MESSAGE,
|
418
|
-
Invalid store provided (%s). Please
|
418
|
+
Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
|
419
419
|
MESSAGE
|
420
420
|
shop_prompt: <<~PROMPT,
|
421
421
|
What store are you connecting to? (e.g. my-store.myshopify.com; do {{bold:NOT}} include protocol part, e.g., https://)
|
@@ -66,15 +66,27 @@ module ShopifyCLI
|
|
66
66
|
# initializer or to `call`. If the keyword argument matches the name of
|
67
67
|
# property, it is forwarded to the initializer, otherwise to call.
|
68
68
|
#
|
69
|
-
def call(*args,
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
69
|
+
ruby2_keywords def call(*args, &block)
|
70
|
+
# This is an extremely complicated case of delegation. The method wants
|
71
|
+
# to delegate arguments, but to have control over which keyword
|
72
|
+
# arguments are delegated. I'm not sure the forward and backward
|
73
|
+
# compatibility of this unusual form of delegation has really been
|
74
|
+
# explored or there's any good way to support it. So I have done
|
75
|
+
# done something hacky here and I'm looking at the last argument and
|
76
|
+
# modifying the package of arguments to be delegated in-place.
|
77
|
+
if args.last.is_a?(Hash)
|
78
|
+
kwargs = args.last
|
79
|
+
|
80
|
+
initializer_kwargs = kwargs.slice(*properties.keys)
|
81
|
+
instance = new(**initializer_kwargs)
|
82
|
+
|
83
|
+
kwargs.reject! { |key| initializer_kwargs.key?(key) }
|
84
|
+
args.pop if kwargs.empty?
|
85
|
+
instance.call(*args, &block)
|
86
|
+
else
|
87
|
+
# Since the former is so complicated - let's have a fast path that
|
88
|
+
# is much simpler.
|
89
|
+
new.call(*args, &block)
|
78
90
|
end
|
79
91
|
end
|
80
92
|
|
data/lib/shopify_cli/result.rb
CHANGED
@@ -363,70 +363,72 @@ module ShopifyCLI
|
|
363
363
|
Result::Failure.new(error)
|
364
364
|
end
|
365
365
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
366
|
+
class << self
|
367
|
+
##
|
368
|
+
# takes either a value or a block and chooses the appropriate result
|
369
|
+
# container based on the type of the value or the type of the block's return
|
370
|
+
# value. If the type is an exception, it is wrapped in a
|
371
|
+
# `ShopifyCli::Result::Failure` and otherwise in a
|
372
|
+
# `ShopifyCli::Result::Success`. If a block was provided instead of value, a
|
373
|
+
# `Proc` is returned and the result wrapping doesn't occur until the block
|
374
|
+
# is invoked.
|
375
|
+
#
|
376
|
+
# #### Parameters
|
377
|
+
#
|
378
|
+
# * `*args` should be an `Array` with zero or one element
|
379
|
+
# * `&block` should be a `Proc` that takes zero or one argument
|
380
|
+
#
|
381
|
+
# #### Returns
|
382
|
+
#
|
383
|
+
# Returns either a `Result::Success`, `Result::Failure` or a `Proc` that
|
384
|
+
# produces one of the former when invoked.
|
385
|
+
#
|
386
|
+
# #### Examples
|
387
|
+
#
|
388
|
+
# Result.wrap(1) # => ShopifyCli::Result::Success
|
389
|
+
# Result.wrap(RuntimeError.new) # => ShopifyCli::Result::Failure
|
390
|
+
#
|
391
|
+
# Result.wrap { 1 } # => Proc
|
392
|
+
# Result.wrap { 1 }.call # => ShopifyCli::Result::Success
|
393
|
+
# Result.wrap { raise }.call # => ShopifyCli::Result::Failure
|
394
|
+
#
|
395
|
+
# Result.wrap { |s| s.upcase }.call("hello").tap do |result|
|
396
|
+
# result # => Result::Success
|
397
|
+
# result.value # => "HELLO"
|
398
|
+
# end
|
399
|
+
#
|
400
|
+
ruby2_keywords def wrap(*values, &block)
|
401
|
+
raise ArgumentError, "expected either a value or a block" unless (values.length == 1) ^ block
|
401
402
|
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
end
|
413
|
-
else
|
414
|
-
->(*args) do
|
415
|
-
begin
|
416
|
-
wrap(block.call(*args))
|
417
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
418
|
-
wrap(error)
|
403
|
+
if values.length == 1
|
404
|
+
values.pop.yield_self do |value|
|
405
|
+
case value
|
406
|
+
when Result::Success, Result::Failure
|
407
|
+
value
|
408
|
+
when NilClass, Exception
|
409
|
+
Result.failure(value)
|
410
|
+
else
|
411
|
+
Result.success(value)
|
412
|
+
end
|
419
413
|
end
|
414
|
+
else
|
415
|
+
->(*args) do
|
416
|
+
begin
|
417
|
+
wrap(block.call(*args))
|
418
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
419
|
+
wrap(error)
|
420
|
+
end
|
421
|
+
end.ruby2_keywords
|
420
422
|
end
|
421
423
|
end
|
422
|
-
end
|
423
424
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
425
|
+
##
|
426
|
+
# Wraps the given block and invokes it with the passed arguments.
|
427
|
+
#
|
428
|
+
ruby2_keywords def call(*args, &block)
|
429
|
+
raise ArgumentError, "expected a block" unless block
|
430
|
+
wrap(&block).call(*args)
|
431
|
+
end
|
430
432
|
end
|
431
433
|
end
|
432
434
|
end
|
data/lib/shopify_cli/task.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
module DevServer
|
6
|
+
module Cdn
|
7
|
+
module CdnHelper
|
8
|
+
def proxy_request(env, uri, theme)
|
9
|
+
response = Net::HTTP.start(uri.host, 443, use_ssl: true) do |http|
|
10
|
+
req_class = Net::HTTP.const_get(method(env))
|
11
|
+
|
12
|
+
req = req_class.new(uri)
|
13
|
+
req.initialize_http_header(req_headers(theme))
|
14
|
+
req.body_stream = req_body(env)
|
15
|
+
|
16
|
+
http.request(req)
|
17
|
+
end
|
18
|
+
|
19
|
+
[
|
20
|
+
response.code.to_s,
|
21
|
+
{
|
22
|
+
"Content-Type" => response.content_type,
|
23
|
+
"Content-Length" => response.content_length.to_s,
|
24
|
+
},
|
25
|
+
[response.body],
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def method(env)
|
32
|
+
env["REQUEST_METHOD"].capitalize
|
33
|
+
end
|
34
|
+
|
35
|
+
def req_body(env)
|
36
|
+
env["rack.input"]
|
37
|
+
end
|
38
|
+
|
39
|
+
def req_headers(theme)
|
40
|
+
{
|
41
|
+
"Referer" => "https://#{theme.shop}",
|
42
|
+
"Transfer-Encoding" => "chunked",
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "cdn/cdn_helper"
|
4
|
+
require_relative "local_assets"
|
5
|
+
|
6
|
+
module ShopifyCLI
|
7
|
+
module Theme
|
8
|
+
module DevServer
|
9
|
+
class CdnAssets
|
10
|
+
include Cdn::CdnHelper
|
11
|
+
|
12
|
+
ASSETS_PROXY_PATH = "/cdn_asset"
|
13
|
+
ASSETS_CDN = "//cdn.shopify.com"
|
14
|
+
ASSETS_CDN_REGEX = %r{(https?:)?#{ASSETS_CDN}}
|
15
|
+
ASSETS_SOURCE_MAP_REGEX = /\/[\/|\*]# sourceMappingURL\=(\/.*)/
|
16
|
+
|
17
|
+
def initialize(app, theme:)
|
18
|
+
@app = app
|
19
|
+
@theme = theme
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
path = env["PATH_INFO"]
|
24
|
+
|
25
|
+
# Serve assets from CDN
|
26
|
+
return serve_asset(env, path) if path.start_with?(ASSETS_PROXY_PATH)
|
27
|
+
|
28
|
+
# Proxy the request, and replace the URLs in the response
|
29
|
+
status, headers, body = @app.call(env)
|
30
|
+
body = replace_asset_urls(body)
|
31
|
+
[status, headers, body]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def serve_asset(env, path)
|
37
|
+
path = path.gsub(%r{^#{ASSETS_PROXY_PATH}}, "")
|
38
|
+
query = env["QUERY_STRING"]
|
39
|
+
uri = asset_cdn_uri(path, query)
|
40
|
+
|
41
|
+
status, headers, body = proxy_request(env, uri, @theme)
|
42
|
+
|
43
|
+
[status, headers, replace_source_map_url(body)]
|
44
|
+
end
|
45
|
+
|
46
|
+
def asset_cdn_uri(path, query)
|
47
|
+
uri = URI.join("https:#{ASSETS_CDN}", path)
|
48
|
+
uri.query = query.split("&").last
|
49
|
+
uri
|
50
|
+
end
|
51
|
+
|
52
|
+
def replace_asset_urls(body)
|
53
|
+
[body.join.gsub(ASSETS_CDN_REGEX, ASSETS_PROXY_PATH)]
|
54
|
+
end
|
55
|
+
|
56
|
+
def replace_source_map_url(body)
|
57
|
+
body_content = body.join
|
58
|
+
map_regex_match = body_content.match(ASSETS_SOURCE_MAP_REGEX)
|
59
|
+
return body if map_regex_match.nil?
|
60
|
+
|
61
|
+
map_url = map_regex_match[1]
|
62
|
+
return body if map_url.nil?
|
63
|
+
|
64
|
+
[body_content.gsub(map_url, "#{ASSETS_PROXY_PATH}#{map_url}")]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,9 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "cdn/cdn_helper"
|
4
|
+
|
3
5
|
module ShopifyCLI
|
4
6
|
module Theme
|
5
7
|
module DevServer
|
6
8
|
class CdnFonts
|
9
|
+
include Cdn::CdnHelper
|
10
|
+
|
7
11
|
FONTS_PATH = "/fonts"
|
8
12
|
FONTS_CDN = "https://fonts.shopifycdn.com"
|
9
13
|
FONTS_REGEX = %r{#{FONTS_CDN}}
|
@@ -17,7 +21,7 @@ module ShopifyCLI
|
|
17
21
|
path = env["PATH_INFO"]
|
18
22
|
|
19
23
|
# Serve from fonts CDN
|
20
|
-
return serve_font(env) if path.start_with?(FONTS_PATH)
|
24
|
+
return serve_font(env, path) if path.start_with?(FONTS_PATH)
|
21
25
|
|
22
26
|
# Proxy the request, and replace the URLs in the response
|
23
27
|
status, headers, body = @app.call(env)
|
@@ -27,35 +31,11 @@ module ShopifyCLI
|
|
27
31
|
|
28
32
|
private
|
29
33
|
|
30
|
-
def serve_font(env)
|
31
|
-
|
32
|
-
path, query, method, body_stream = *env.slice(*parameters).values
|
33
|
-
|
34
|
+
def serve_font(env, path)
|
35
|
+
query = env["QUERY_STRING"]
|
34
36
|
uri = fonts_cdn_uri(path, query)
|
35
37
|
|
36
|
-
|
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
|
-
}
|
38
|
+
proxy_request(env, uri, @theme)
|
59
39
|
end
|
60
40
|
|
61
41
|
def fonts_cdn_uri(path, query)
|
@@ -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_assets"
|
6
7
|
require_relative "dev_server/cdn_fonts"
|
7
8
|
require_relative "dev_server/hot_reload"
|
8
9
|
require_relative "dev_server/header_hash"
|
@@ -37,6 +38,7 @@ module ShopifyCLI
|
|
37
38
|
@app = Proxy.new(ctx, theme: theme, syncer: @syncer)
|
38
39
|
@app = CdnFonts.new(@app, theme: theme)
|
39
40
|
@app = LocalAssets.new(ctx, @app, theme: theme)
|
41
|
+
@app = CdnAssets.new(@app, theme: theme)
|
40
42
|
@app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, mode: mode, ignore_filter: ignore_filter)
|
41
43
|
stopped = false
|
42
44
|
address = "http://#{host}:#{port}"
|
@@ -18,7 +18,7 @@ module ShopifyCLI
|
|
18
18
|
|
19
19
|
def read
|
20
20
|
if text?
|
21
|
-
path.read
|
21
|
+
path.read(universal_newline: true)
|
22
22
|
else
|
23
23
|
path.read(mode: "rb")
|
24
24
|
end
|
@@ -27,7 +27,7 @@ module ShopifyCLI
|
|
27
27
|
def write(content)
|
28
28
|
path.parent.mkpath unless path.parent.directory?
|
29
29
|
if text?
|
30
|
-
path.write(content)
|
30
|
+
path.write(content, universal_newline: true)
|
31
31
|
else
|
32
32
|
path.write(content, 0, mode: "wb")
|
33
33
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
module Filter
|
6
|
+
module PathMatcher
|
7
|
+
def regex_match?(regex, path)
|
8
|
+
regex.match?(path)
|
9
|
+
rescue StandardError
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def glob_match?(glob, path)
|
14
|
+
!!::File.fnmatch?(glob, path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def regex?(pattern)
|
18
|
+
pattern.start_with?("/") && pattern.end_with?("/")
|
19
|
+
end
|
20
|
+
|
21
|
+
def as_regex(pattern)
|
22
|
+
Regexp.new(pattern.gsub(%r{^\/|\/$}, ""))
|
23
|
+
end
|
24
|
+
|
25
|
+
def as_glob(pattern)
|
26
|
+
# if specifying a directory, match everything below it
|
27
|
+
pattern += "*" if pattern.end_with?("/")
|
28
|
+
|
29
|
+
# The pattern will be scoped to root directory, so it should match anything
|
30
|
+
# within that space
|
31
|
+
pattern.prepend("*") unless pattern.start_with?("*")
|
32
|
+
|
33
|
+
pattern
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|