shopify-cli 2.7.4 → 2.10.1

Sign up to get free protection for your applications and to get access to all the features.
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