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.
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