shopify-cli 2.9.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -2
  3. data/Gemfile.lock +1 -1
  4. data/lib/project_types/script/layers/domain/errors.rb +3 -2
  5. data/lib/project_types/script/layers/domain/script_config.rb +6 -4
  6. data/lib/project_types/script/layers/infrastructure/errors.rb +37 -24
  7. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +28 -28
  8. data/lib/project_types/script/layers/infrastructure/script_service.rb +22 -5
  9. data/lib/project_types/script/messages/messages.rb +15 -17
  10. data/lib/project_types/script/ui/error_handler.rb +41 -29
  11. data/lib/project_types/theme/commands/pull.rb +6 -1
  12. data/lib/project_types/theme/commands/push.rb +6 -1
  13. data/lib/project_types/theme/messages/messages.rb +4 -0
  14. data/lib/shopify_cli/commands/login.rb +4 -10
  15. data/lib/shopify_cli/constants.rb +6 -2
  16. data/lib/shopify_cli/core/executor.rb +4 -4
  17. data/lib/shopify_cli/environment.rb +35 -16
  18. data/lib/shopify_cli/identity_auth.rb +3 -3
  19. data/lib/shopify_cli/messages/messages.rb +1 -1
  20. data/lib/shopify_cli/method_object.rb +21 -9
  21. data/lib/shopify_cli/result.rb +61 -59
  22. data/lib/shopify_cli/task.rb +5 -3
  23. data/lib/shopify_cli/theme/dev_server/cdn/cdn_helper.rb +49 -0
  24. data/lib/shopify_cli/theme/dev_server/cdn_assets.rb +69 -0
  25. data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +8 -28
  26. data/lib/shopify_cli/theme/dev_server/local_assets.rb +4 -0
  27. data/lib/shopify_cli/theme/dev_server.rb +2 -0
  28. data/lib/shopify_cli/theme/file.rb +2 -2
  29. data/lib/shopify_cli/theme/filter/path_matcher.rb +38 -0
  30. data/lib/shopify_cli/theme/ignore_filter.rb +14 -18
  31. data/lib/shopify_cli/theme/include_filter.rb +43 -0
  32. data/lib/shopify_cli/theme/syncer.rb +17 -2
  33. data/lib/shopify_cli/version.rb +1 -1
  34. data/lib/shopify_cli.rb +2 -1
  35. data/vendor/deps/ruby2_keywords/LICENSE +22 -0
  36. data/vendor/deps/ruby2_keywords/README.md +67 -0
  37. data/vendor/deps/ruby2_keywords/Rakefile +54 -0
  38. data/vendor/deps/ruby2_keywords/lib/ruby2_keywords.rb +57 -0
  39. data/vendor/deps/ruby2_keywords/ruby2_keywords.gemspec +18 -0
  40. data/vendor/deps/ruby2_keywords/test/test_keyword.rb +41 -0
  41. 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:, org:, context:)
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 a spin instance of spin
40
- SPIN_PARTNERS = "SHOPIFY_APP_CLI_SPIN_PARTNERS"
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, **kwargs)
7
- @ctx = ctx || ShopifyCLI::Context.new
8
- @task_registry = task_registry || ShopifyCLI::Tasks::TaskRegistry.new
9
- super(*args, **kwargs)
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 use_spin_partners_instance?(env_variables: env_variables)
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
- !env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE].nil? &&
83
- !env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE].nil?
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
- spin_workspace = spin_workspace(env_variables: env_variables)
88
- spin_namespace = spin_namespace(env_variables: env_variables)
89
- spin_host = spin_host(env_variables: env_variables)
90
- "#{spin_workspace}.#{spin_namespace}.#{spin_host}"
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.use_spin_partners_instance?
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.use_spin_partners_instance?
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.use_spin_partners_instance?
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 make sure that the store belongs to your partner organization, and provide the store in the following format: my-store.myshopify.com
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, **kwargs, &block)
70
- properties.keys.yield_self do |properties|
71
- instance = new(**kwargs.slice(*properties))
72
- kwargs = kwargs.slice(*(kwargs.keys - properties))
73
- if kwargs.any?
74
- instance.call(*args, **kwargs, &block)
75
- else
76
- instance.call(*args, &block)
77
- end
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
 
@@ -363,70 +363,72 @@ module ShopifyCLI
363
363
  Result::Failure.new(error)
364
364
  end
365
365
 
366
- ##
367
- # takes either a value or a block and chooses the appropriate result
368
- # container based on the type of the value or the type of the block's return
369
- # value. If the type is an exception, it is wrapped in a
370
- # `ShopifyCLI::Result::Failure` and otherwise in a
371
- # `ShopifyCLI::Result::Success`. If a block was provided instead of value, a
372
- # `Proc` is returned and the result wrapping doesn't occur until the block
373
- # is invoked.
374
- #
375
- # #### Parameters
376
- #
377
- # * `*args` should be an `Array` with zero or one element
378
- # * `&block` should be a `Proc` that takes zero or one argument
379
- #
380
- # #### Returns
381
- #
382
- # Returns either a `Result::Success`, `Result::Failure` or a `Proc` that
383
- # produces one of the former when invoked.
384
- #
385
- # #### Examples
386
- #
387
- # Result.wrap(1) # => ShopifyCLI::Result::Success
388
- # Result.wrap(RuntimeError.new) # => ShopifyCLI::Result::Failure
389
- #
390
- # Result.wrap { 1 } # => Proc
391
- # Result.wrap { 1 }.call # => ShopifyCLI::Result::Success
392
- # Result.wrap { raise }.call # => ShopifyCLI::Result::Failure
393
- #
394
- # Result.wrap { |s| s.upcase }.call("hello").tap do |result|
395
- # result # => Result::Success
396
- # result.value # => "HELLO"
397
- # end
398
- #
399
- def self.wrap(*values, &block)
400
- raise ArgumentError, "expected either a value or a block" unless (values.length == 1) ^ block
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
- if values.length == 1
403
- values.pop.yield_self do |value|
404
- case value
405
- when Result::Success, Result::Failure
406
- value
407
- when NilClass, Exception
408
- Result.failure(value)
409
- else
410
- Result.success(value)
411
- end
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
- # Wraps the given block and invokes it with the passed arguments.
426
- #
427
- def self.call(*args, &block)
428
- raise ArgumentError, "expected a block" unless block
429
- wrap(&block).call(*args)
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
@@ -2,9 +2,11 @@ require "shopify_cli"
2
2
 
3
3
  module ShopifyCLI
4
4
  class Task
5
- def self.call(*args, **kwargs)
6
- task = new
7
- task.call(*args, **kwargs)
5
+ class << self
6
+ ruby2_keywords def call(*args)
7
+ task = new
8
+ task.call(*args)
9
+ end
8
10
  end
9
11
 
10
12
  private
@@ -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
- parameters = %w(PATH_INFO QUERY_STRING REQUEST_METHOD rack.input)
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
- 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
- }
38
+ proxy_request(env, uri, @theme)
59
39
  end
60
40
 
61
41
  def fonts_cdn_uri(path, query)
@@ -11,6 +11,10 @@ module ShopifyCLI
11
11
  @path = path
12
12
  end
13
13
 
14
+ def join
15
+ @path.read
16
+ end
17
+
14
18
  # Naive implementation. Only used in unit tests.
15
19
  def each
16
20
  yield @path.read
@@ -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