shopify-cli 2.7.4 → 2.10.1

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/Gemfile.lock +1 -1
  4. data/RELEASING.md +4 -3
  5. data/ext/javy/javy.rb +1 -1
  6. data/lib/project_types/extension/commands/push.rb +2 -2
  7. data/lib/project_types/extension/messages/messages.rb +1 -1
  8. data/lib/project_types/extension/models/development_server.rb +2 -4
  9. data/lib/project_types/rails/gem.rb +1 -2
  10. data/lib/project_types/script/cli.rb +5 -0
  11. data/lib/project_types/script/commands/connect.rb +1 -1
  12. data/lib/project_types/script/commands/create.rb +8 -2
  13. data/lib/project_types/script/commands/push.rb +35 -12
  14. data/lib/project_types/script/graphql/app_script_set.graphql +2 -0
  15. data/lib/project_types/script/layers/application/build_script.rb +0 -1
  16. data/lib/project_types/script/layers/application/connect_app.rb +11 -5
  17. data/lib/project_types/script/layers/application/extension_points.rb +50 -26
  18. data/lib/project_types/script/layers/application/push_script.rb +6 -3
  19. data/lib/project_types/script/layers/domain/errors.rb +3 -2
  20. data/lib/project_types/script/layers/domain/extension_point.rb +14 -0
  21. data/lib/project_types/script/layers/domain/push_package.rb +0 -3
  22. data/lib/project_types/script/layers/domain/script_config.rb +6 -4
  23. data/lib/project_types/script/layers/domain/script_project.rb +1 -0
  24. data/lib/project_types/script/layers/infrastructure/errors.rb +38 -23
  25. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -4
  26. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +0 -4
  27. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +6 -7
  28. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +45 -54
  29. data/lib/project_types/script/layers/infrastructure/script_service.rb +25 -6
  30. data/lib/project_types/script/loaders/project.rb +44 -0
  31. data/lib/project_types/script/loaders/specification_handler.rb +22 -0
  32. data/lib/project_types/script/messages/messages.rb +28 -16
  33. data/lib/project_types/script/ui/error_handler.rb +46 -29
  34. data/lib/project_types/theme/commands/pull.rb +45 -17
  35. data/lib/project_types/theme/commands/push.rb +62 -27
  36. data/lib/project_types/theme/commands/serve.rb +5 -0
  37. data/lib/project_types/theme/messages/messages.rb +33 -18
  38. data/lib/shopify_cli/commands/login.rb +1 -1
  39. data/lib/shopify_cli/commands/switch.rb +1 -1
  40. data/lib/shopify_cli/constants.rb +7 -2
  41. data/lib/shopify_cli/context.rb +66 -12
  42. data/lib/shopify_cli/core/executor.rb +4 -4
  43. data/lib/shopify_cli/environment.rb +50 -20
  44. data/lib/shopify_cli/identity_auth.rb +4 -3
  45. data/lib/shopify_cli/messages/messages.rb +2 -0
  46. data/lib/shopify_cli/method_object.rb +21 -9
  47. data/lib/shopify_cli/resources/env_file.rb +5 -1
  48. data/lib/shopify_cli/result.rb +61 -59
  49. data/lib/shopify_cli/task.rb +5 -3
  50. data/lib/shopify_cli/theme/dev_server/hot-reload.js +19 -1
  51. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +18 -2
  52. data/lib/shopify_cli/theme/dev_server/proxy.rb +1 -0
  53. data/lib/shopify_cli/theme/dev_server/reload_mode.rb +34 -0
  54. data/lib/shopify_cli/theme/dev_server.rb +6 -21
  55. data/lib/shopify_cli/theme/file.rb +2 -2
  56. data/lib/shopify_cli/theme/filter/path_matcher.rb +38 -0
  57. data/lib/shopify_cli/theme/ignore_filter.rb +14 -18
  58. data/lib/shopify_cli/theme/include_filter.rb +43 -0
  59. data/lib/shopify_cli/theme/syncer.rb +17 -2
  60. data/lib/shopify_cli/theme/theme.rb +26 -4
  61. data/lib/shopify_cli/version.rb +1 -1
  62. data/lib/shopify_cli.rb +6 -1
  63. data/vendor/deps/ruby2_keywords/LICENSE +22 -0
  64. data/vendor/deps/ruby2_keywords/README.md +67 -0
  65. data/vendor/deps/ruby2_keywords/Rakefile +54 -0
  66. data/vendor/deps/ruby2_keywords/lib/ruby2_keywords.rb +57 -0
  67. data/vendor/deps/ruby2_keywords/ruby2_keywords.gemspec +18 -0
  68. data/vendor/deps/ruby2_keywords/test/test_keyword.rb +41 -0
  69. metadata +13 -2
@@ -4,6 +4,21 @@ module ShopifyCLI
4
4
  module Environment
5
5
  TRUTHY_ENV_VARIABLE_VALUES = ["1", "true", "TRUE", "yes", "YES"]
6
6
 
7
+ def self.interactive=(interactive)
8
+ @interactive = interactive
9
+ end
10
+
11
+ def self.interactive?(env_variables: ENV)
12
+ if env_variables.key?(Constants::EnvironmentVariables::TTY)
13
+ env_variable_truthy?(
14
+ Constants::EnvironmentVariables::TTY,
15
+ env_variables: env_variables
16
+ )
17
+ else
18
+ @interactive ||= STDIN.tty?
19
+ end
20
+ end
21
+
7
22
  def self.development?(env_variables: ENV)
8
23
  env_variable_truthy?(
9
24
  Constants::EnvironmentVariables::DEVELOPMENT,
@@ -11,10 +26,6 @@ module ShopifyCLI
11
26
  )
12
27
  end
13
28
 
14
- def self.interactive?
15
- ShopifyCLI::Context.new.tty?
16
- end
17
-
18
29
  def self.use_local_partners_instance?(env_variables: ENV)
19
30
  env_variable_truthy?(
20
31
  Constants::EnvironmentVariables::LOCAL_PARTNERS,
@@ -50,17 +61,10 @@ module ShopifyCLI
50
61
  )
51
62
  end
52
63
 
53
- def self.use_spin_partners_instance?(env_variables: ENV)
54
- env_variable_truthy?(
55
- Constants::EnvironmentVariables::SPIN_PARTNERS,
56
- env_variables: env_variables
57
- )
58
- end
59
-
60
64
  def self.partners_domain(env_variables: ENV)
61
65
  if use_local_partners_instance?(env_variables: env_variables)
62
66
  "partners.myshopify.io"
63
- elsif use_spin_partners_instance?(env_variables: env_variables)
67
+ elsif use_spin?(env_variables: env_variables)
64
68
  "partners.#{spin_url(env_variables: env_variables)}"
65
69
  else
66
70
  "partners.shopify.com"
@@ -68,15 +72,31 @@ module ShopifyCLI
68
72
  end
69
73
 
70
74
  def self.use_spin?(env_variables: ENV)
71
- !env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE].nil? &&
72
- !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
+ )
73
89
  end
74
90
 
75
91
  def self.spin_url(env_variables: ENV)
76
- spin_workspace = spin_workspace(env_variables: env_variables)
77
- spin_namespace = spin_namespace(env_variables: env_variables)
78
- spin_host = spin_host(env_variables: env_variables)
79
- "#{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
80
100
  end
81
101
 
82
102
  def self.send_monorail_events?(env_variables: ENV)
@@ -95,11 +115,21 @@ module ShopifyCLI
95
115
  end
96
116
 
97
117
  def self.spin_workspace(env_variables: ENV)
98
- 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
99
124
  end
100
125
 
101
126
  def self.spin_namespace(env_variables: ENV)
102
- 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
103
133
  end
104
134
 
105
135
  def self.spin_host(env_variables: ENV)
@@ -229,6 +229,7 @@ module ShopifyCLI
229
229
  uri = URI.parse("#{auth_url}#{endpoint}")
230
230
  https = Net::HTTP.new(uri.host, uri.port)
231
231
  https.use_ssl = true
232
+ https.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV["SSL_VERIFY_NONE"]
232
233
  request = Net::HTTP::Post.new(uri.path)
233
234
  request["User-Agent"] = "Shopify CLI #{::ShopifyCLI::VERSION}"
234
235
  request.body = URI.encode_www_form(params)
@@ -255,7 +256,7 @@ module ShopifyCLI
255
256
  def auth_url
256
257
  if Environment.use_local_partners_instance?
257
258
  "https://identity.myshopify.io/oauth"
258
- elsif Environment.use_spin_partners_instance?
259
+ elsif Environment.use_spin?
259
260
  "https://identity.#{Environment.spin_url}/oauth"
260
261
  else
261
262
  "https://accounts.shopify.com/oauth"
@@ -263,7 +264,7 @@ module ShopifyCLI
263
264
  end
264
265
 
265
266
  def client_id_for_application(application_name)
266
- 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?
267
268
  DEV_APPLICATION_CLIENT_IDS
268
269
  else
269
270
  APPLICATION_CLIENT_IDS
@@ -279,7 +280,7 @@ module ShopifyCLI
279
280
  end
280
281
 
281
282
  def client_id
282
- if Environment.use_local_partners_instance? || Environment.use_spin_partners_instance?
283
+ if Environment.use_local_partners_instance? || Environment.use_spin?
283
284
  Constants::Identity::CLIENT_ID_DEV
284
285
  else
285
286
  # In the future we might want to use Identity's dynamic
@@ -790,6 +790,8 @@ module ShopifyCLI
790
790
  logged_in_partner_only: "Logged into partner organization {{green:%s}}",
791
791
  logged_in_partner_and_shop: "Logged into store {{green:%s}} in partner organization {{green:%s}}",
792
792
  },
793
+ error: "Error",
794
+ try_this: "Try this",
793
795
  },
794
796
  }.freeze
795
797
  end
@@ -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
 
@@ -14,6 +14,10 @@ module ShopifyCLI
14
14
  }
15
15
 
16
16
  class << self
17
+ def path(directory)
18
+ File.join(directory, FILENAME)
19
+ end
20
+
17
21
  def read(_directory = Dir.pwd, overrides: {})
18
22
  input = parse_external_env(overrides: overrides)
19
23
  new(input)
@@ -24,7 +28,7 @@ module ShopifyCLI
24
28
  end
25
29
 
26
30
  def parse(directory)
27
- File.read(File.join(directory, FILENAME))
31
+ File.read(path(directory))
28
32
  .gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
29
33
  match = /\A([A-Za-z_0-9]+)\s*=\s*(.*)\z/.match(line)
30
34
  if match
@@ -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
@@ -15,9 +15,23 @@
15
15
  eventSource.onerror = () => eventSource.close();
16
16
  }
17
17
 
18
- connect();
18
+ function reloadMode() {
19
+ var namespace = window.__SHOPIFY_CLI_ENV__;
20
+ return namespace.mode;
21
+ }
22
+
23
+ function isFullPageReloadMode(){
24
+ return reloadMode() === "full-page";
25
+ }
26
+
27
+ function isReloadModeActive(){
28
+ return reloadMode() !== "off";
29
+ }
19
30
 
20
31
  function isRefreshRequired(files) {
32
+ if (isFullPageReloadMode()) {
33
+ return true;
34
+ }
21
35
  return files.some((file) => !isCssFile(file) && !isSectionFile(file));
22
36
  }
23
37
 
@@ -119,4 +133,8 @@
119
133
  }
120
134
  }
121
135
  }
136
+
137
+ if (isReloadModeActive()) {
138
+ connect();
139
+ }
122
140
  })();
@@ -4,10 +4,11 @@ module ShopifyCLI
4
4
  module Theme
5
5
  module DevServer
6
6
  class HotReload
7
- def initialize(ctx, app, theme:, watcher:, ignore_filter: nil)
7
+ def initialize(ctx, app, theme:, watcher:, mode:, ignore_filter: nil)
8
8
  @ctx = ctx
9
9
  @app = app
10
10
  @theme = theme
11
+ @mode = mode
11
12
  @streams = SSE::Streams.new
12
13
  @watcher = watcher
13
14
  @watcher.add_observer(self, :notify_streams_of_file_change)
@@ -48,12 +49,27 @@ module ShopifyCLI
48
49
 
49
50
  def inject_hot_reload_javascript(body)
50
51
  hot_reload_js = ::File.read("#{__dir__}/hot-reload.js")
51
- hot_reload_script = "<script>\n#{hot_reload_js}</script>"
52
+ hot_reload_script = [
53
+ "<script>",
54
+ params_js,
55
+ hot_reload_js,
56
+ "</script>",
57
+ ].join("\n")
58
+
52
59
  body = body.join.gsub("</body>", "#{hot_reload_script}\n</body>")
53
60
 
54
61
  [body]
55
62
  end
56
63
 
64
+ def params_js
65
+ env = { mode: @mode }
66
+ <<~JS
67
+ (() => {
68
+ window.__SHOPIFY_CLI_ENV__ = #{env.to_json};
69
+ })();
70
+ JS
71
+ end
72
+
57
73
  def create_stream
58
74
  stream = @streams.new
59
75
 
@@ -18,6 +18,7 @@ module ShopifyCLI
18
18
  "trailer",
19
19
  "transfer-encoding",
20
20
  "upgrade",
21
+ "content-security-policy",
21
22
  ]
22
23
 
23
24
  class Proxy
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ module DevServer
6
+ class ReloadMode
7
+ MODES = [:"hot-reload", :"full-page", :off]
8
+
9
+ class << self
10
+ def default
11
+ :"hot-reload"
12
+ end
13
+
14
+ def get!(mode)
15
+ MODES.find { |m| m == mode.to_sym } || raise_error(mode)
16
+ end
17
+
18
+ private
19
+
20
+ def raise_error(invalid_mode)
21
+ error_message = ShopifyCLI::Context.message("theme.serve.reload_mode_is_not_valid", invalid_mode)
22
+ help_message = ShopifyCLI::Context.message("theme.serve.try_a_valid_reload_mode", valid_modes)
23
+
24
+ ShopifyCLI::Context.abort(error_message, help_message)
25
+ end
26
+
27
+ def valid_modes
28
+ MODES.map { |v| "`#{v}`" }.join(", ")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -6,6 +6,7 @@ require_relative "syncer"
6
6
  require_relative "dev_server/cdn_fonts"
7
7
  require_relative "dev_server/hot_reload"
8
8
  require_relative "dev_server/header_hash"
9
+ require_relative "dev_server/reload_mode"
9
10
  require_relative "dev_server/local_assets"
10
11
  require_relative "dev_server/proxy"
11
12
  require_relative "dev_server/sse"
@@ -25,7 +26,7 @@ module ShopifyCLI
25
26
  class << self
26
27
  attr_accessor :ctx
27
28
 
28
- def start(ctx, root, host: "127.0.0.1", port: 9292, poll: false)
29
+ def start(ctx, root, host: "127.0.0.1", port: 9292, poll: false, mode: ReloadMode.default)
29
30
  @ctx = ctx
30
31
  theme = DevelopmentTheme.new(ctx, root: root)
31
32
  ignore_filter = IgnoreFilter.from_path(root)
@@ -36,7 +37,7 @@ module ShopifyCLI
36
37
  @app = Proxy.new(ctx, theme: theme, syncer: @syncer)
37
38
  @app = CdnFonts.new(@app, theme: theme)
38
39
  @app = LocalAssets.new(ctx, @app, theme: theme)
39
- @app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, ignore_filter: ignore_filter)
40
+ @app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, mode: mode, ignore_filter: ignore_filter)
40
41
  stopped = false
41
42
  address = "http://#{host}:#{port}"
42
43
 
@@ -83,7 +84,9 @@ module ShopifyCLI
83
84
  ShopifyCLI::API::APIRequestUnauthorizedError
84
85
  raise ShopifyCLI::Abort, @ctx.message("theme.serve.ensure_user", theme.shop)
85
86
  rescue Errno::EADDRINUSE
86
- abort_address_already_in_use(address)
87
+ error_message = @ctx.message("theme.serve.address_already_in_use", address)
88
+ help_message = @ctx.message("theme.serve.try_port_option")
89
+ @ctx.abort(error_message, help_message)
87
90
  rescue Errno::EADDRNOTAVAIL
88
91
  raise AddressBindingError, "Error binding to the address #{host}."
89
92
  end
@@ -94,24 +97,6 @@ module ShopifyCLI
94
97
  @syncer.shutdown
95
98
  WebServer.shutdown
96
99
  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
115
100
  end
116
101
  end
117
102
  end
@@ -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
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "filter/path_matcher"
4
+
3
5
  module ShopifyCLI
4
6
  module Theme
5
7
  class IgnoreFilter
8
+ include Filter::PathMatcher
9
+
6
10
  FILE = ".shopifyignore"
7
11
 
8
12
  DEFAULT_REGEXES = [
@@ -72,11 +76,11 @@ module ShopifyCLI
72
76
  return true if path.empty?
73
77
 
74
78
  regexes.each do |regex|
75
- return true if regex.match(path)
79
+ return true if regex_match?(regex, path)
76
80
  end
77
81
 
78
82
  globs.each do |glob|
79
- return true if ::File.fnmatch?(glob, path)
83
+ return true if glob_match?(glob, path)
80
84
  end
81
85
 
82
86
  false
@@ -91,24 +95,16 @@ module ShopifyCLI
91
95
  new_regexes = DEFAULT_REGEXES.dup
92
96
  new_globs = DEFAULT_GLOBS.dup
93
97
 
94
- patterns.each do |pattern|
95
- pattern = pattern.strip
96
-
97
- if pattern.start_with?("/") && pattern.end_with?("/")
98
- new_regexes << Regexp.new(pattern.gsub(%r{^\/|\/$}, ""))
99
- next
98
+ patterns
99
+ .map(&:strip)
100
+ .each do |pattern|
101
+ if regex?(pattern)
102
+ new_regexes << as_regex(pattern)
103
+ else
104
+ new_globs << as_glob(pattern)
105
+ end
100
106
  end
101
107
 
102
- # if specifying a directory, match everything below it
103
- pattern += "*" if pattern.end_with?("/")
104
-
105
- # The pattern will be scoped to root directory, so it should match anything
106
- # within that space
107
- pattern.prepend("*") unless pattern.start_with?("*")
108
-
109
- new_globs << pattern
110
- end
111
-
112
108
  [new_regexes, new_globs]
113
109
  end
114
110
  end