shopify-cli 2.11.0 → 2.11.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -1
  3. data/Gemfile.lock +1 -1
  4. data/lib/project_types/extension/commands/push.rb +13 -0
  5. data/lib/project_types/extension/loaders/project.rb +28 -8
  6. data/lib/project_types/extension/messages/messages.rb +10 -2
  7. data/lib/project_types/script/layers/application/create_script.rb +1 -1
  8. data/lib/project_types/script/layers/application/push_script.rb +1 -1
  9. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +1 -8
  10. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +35 -9
  11. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +1 -8
  12. data/lib/project_types/script/layers/infrastructure/languages/wasm_task_runner.rb +3 -7
  13. data/lib/project_types/script/messages/messages.rb +6 -6
  14. data/lib/project_types/theme/commands/check.rb +0 -1
  15. data/lib/project_types/theme/commands/delete.rb +0 -1
  16. data/lib/project_types/theme/commands/init.rb +0 -1
  17. data/lib/project_types/theme/commands/language_server.rb +0 -1
  18. data/lib/project_types/theme/commands/package.rb +0 -1
  19. data/lib/project_types/theme/commands/publish.rb +0 -1
  20. data/lib/project_types/theme/commands/pull.rb +0 -1
  21. data/lib/project_types/theme/commands/push.rb +0 -1
  22. data/lib/project_types/theme/commands/serve.rb +0 -1
  23. data/lib/shopify_cli/command.rb +9 -3
  24. data/lib/shopify_cli/commands/app/create/rails.rb +1 -1
  25. data/lib/shopify_cli/commands/app/create.rb +0 -3
  26. data/lib/shopify_cli/commands/app/deploy.rb +0 -1
  27. data/lib/shopify_cli/commands/app/serve.rb +0 -1
  28. data/lib/shopify_cli/constants.rb +1 -1
  29. data/lib/shopify_cli/environment.rb +39 -45
  30. data/lib/shopify_cli/messages/messages.rb +1 -0
  31. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +63 -0
  32. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +22 -6
  33. data/lib/shopify_cli/theme/dev_server/proxy.rb +4 -5
  34. data/lib/shopify_cli/theme/file.rb +4 -0
  35. data/lib/shopify_cli/version.rb +1 -1
  36. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9944385de970a522f54fcf4e8feffa5bcab8ad61498095a1794c5abcf5918063
4
- data.tar.gz: 62b23eb1ab82cf0783ccab14d77b55c3e71595d87581795fff19227c4bcada57
3
+ metadata.gz: b4c9bb44a8b7b750acbf3364201705d93dfb39ce9772976ba2af04183cfc5748
4
+ data.tar.gz: fa32248af15c17d01d80132b4caf42ea224edba35146098954f51b4453db50de
5
5
  SHA512:
6
- metadata.gz: 72ec8ee4a02bb2b7cd6f9d58c094625bba0e1971a1d2e81d86920e8b326ff8fb52bac73b2a7afec43f7e67991b239bd7ac11c43a2df70790ecd82cf6c4614db2
7
- data.tar.gz: e456bb338bfa6102cf8643455b983a147a0fdef6fb820a5ee46dd210e6cbde788de6053727c48ab44057a7ad920a8b765651bfd09a26735b4db8525dc6f247b1
6
+ metadata.gz: 3446d1a595e343f6566b7ec087c187d31518398ad57be75ff519a394133c75e4f0a5f8670d78913271091632d8ef9fdeef4fb1bb3c1b4ac5cb99d0bc12aef765
7
+ data.tar.gz: 48ad3c64a367e80facb7aecc393295fb8fa838949a51c7a71310c50a40e6bf2c648b57dbcaf53ade5e15af90ff911593e38865cbb8e2b8dce6b70a8918d83a03
data/CHANGELOG.md CHANGED
@@ -2,8 +2,14 @@ From version 2.6.0, the sections in this file adhere to the [keep a changelog](h
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
- ## Version 2.11.0
5
+ ## Version 2.11.1
6
+ ### Fixed
7
+ * [#1973](https://github.com/Shopify/shopify-cli/pull/1973): Fix `theme serve` to preview generated files (`*.css.liquid`)
8
+ * [#2034](https://github.com/Shopify/shopify-cli/pull/2034): Fix `theme serve` to accept parameters with multiple values
9
+ * [#2033](https://github.com/Shopify/shopify-cli/pull/2033): Pin Homebrew Ruby to 3.0
10
+ * [#2032](https://github.com/Shopify/shopify-cli/pull/2032): Runtime error checking the Node version if Node is not present in the environment.
6
11
 
12
+ ## Version 2.11.0
7
13
  ### Fixed
8
14
  * [#2005](https://github.com/Shopify/shopify-cli/pull/2005): Fix PHP app serve on Windows environments
9
15
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shopify-cli (2.11.0)
4
+ shopify-cli (2.11.1)
5
5
  bugsnag (~> 6.22)
6
6
  listen (~> 3.7.0)
7
7
  theme-check (~> 1.9.0)
@@ -27,6 +27,10 @@ module Extension
27
27
  api_secret: options.flags[:api_secret],
28
28
  registration_id: options.flags[:registration_id]
29
29
  )
30
+ # on ci, registration id must be present
31
+ registration_id = options.flags[:registration_id]
32
+ check_registration(registration_id: registration_id, context: @ctx)
33
+
30
34
  specification_handler = Extension::Loaders::SpecificationHandler.load(project: project, context: @ctx)
31
35
  register_if_necessary(project: project, args: args, name: name)
32
36
 
@@ -43,6 +47,15 @@ module Extension
43
47
  end
44
48
  end
45
49
 
50
+ def check_registration(registration_id:, context:)
51
+ if !ShopifyCLI::Environment.interactive? && (!registration_id || registration_id.empty?)
52
+ message = context.message("errors.missing_push_options_ci", "--registration-id")
53
+ message += context.message("errors.missing_push_options_ci_solution", ShopifyCLI::TOOL_NAME)
54
+ raise ShopifyCLI::Abort,
55
+ message
56
+ end
57
+ end
58
+
46
59
  def self.help
47
60
  ShopifyCLI::Context.new.message("push.help", ShopifyCLI::TOOL_NAME)
48
61
  end
@@ -9,20 +9,40 @@ module Extension
9
9
  "SHOPIFY_API_SECRET" => api_secret,
10
10
  "EXTENSION_ID" => registration_id,
11
11
  }.compact
12
- env =
13
- begin
14
- ShopifyCLI::Resources::EnvFile.read(directory, overrides: env_overrides)
15
- rescue Errno::ENOENT
16
- ShopifyCLI::Resources::EnvFile.from_hash(env_overrides)
17
- end
12
+ env_file_present = env_file_exists?(directory)
13
+ env = if env_file_present
14
+ ShopifyCLI::Resources::EnvFile.read(directory, overrides: env_overrides)
15
+ else
16
+ ShopifyCLI::Resources::EnvFile.from_hash(env_overrides)
17
+ end
18
18
  # This is a somewhat uncomfortable hack we use because `Project::at` is
19
19
  # a global cache and we can't rely on this class loading the project
20
20
  # first. Long-term we should move away from that global cache.
21
21
  project = ExtensionProject.at(directory)
22
22
  project.env = env
23
23
  project
24
- rescue SmartProperties::InitializationError, SmartProperties::MissingValueError
25
- context.abort(context.message("errors.missing_api_key"))
24
+ rescue SmartProperties::InitializationError, SmartProperties::MissingValueError => error
25
+ handle_error(error, context: context)
26
+ end
27
+
28
+ def self.handle_error(error, context:)
29
+ if ShopifyCLI::Environment.interactive?
30
+ properties_hash = { api_key: "SHOPIFY_API_KEY", secret: "SHOPIFY_API_SECRET" }
31
+ missing_env_variables = error.properties.map { |p| properties_hash[p.name] }.compact.join(", ")
32
+ message = context.message("errors.missing_env_file_variables", missing_env_variables)
33
+ message += context.message("errors.missing_env_file_variables_solution", ShopifyCLI::TOOL_NAME)
34
+ else
35
+ properties_hash = { api_key: "--api-key", secret: "--api-secret" }
36
+ missing_options = error.properties.map { |p| properties_hash[p.name] }.compact.join(", ")
37
+ message = context.message("errors.missing_push_options_ci", missing_options)
38
+ message += context.message("errors.missing_push_options_ci_solution", ShopifyCLI::TOOL_NAME)
39
+ end
40
+ raise ShopifyCLI::Abort,
41
+ message
42
+ end
43
+
44
+ def self.env_file_exists?(directory)
45
+ File.exist?(ShopifyCLI::Resources::EnvFile.path(directory))
26
46
  end
27
47
  end
28
48
  end
@@ -174,9 +174,17 @@ module Extension
174
174
  },
175
175
  },
176
176
  errors: {
177
- unknown_type: "Unknown extension type %s",
177
+ unknown_type: "Unknown extension type %s. Valid extension types include: CHECKOUT_POST_PURCHASE, " \
178
+ "CHECKOUT_UI_EXTENSION, THEME_APP_EXTENSION, and PRODUCT_SUBSCRIPTION.",
178
179
  package_not_found: "`%s` package not found.",
179
- missing_api_key: "Missing api_key.",
180
+ missing_push_options_ci: "The following are missing: %s. ",
181
+ missing_push_options_ci_solution: "To add them to a CI environment:\n\t1. Run a connect command " \
182
+ "({{command:%1$s extension connect}})\n\t2. Navigate to the .env file at the root of your project\n\t" \
183
+ "3. Copy the missing values and pass them through as arguments in {{command:%1$s extension push}}",
184
+ missing_env_file_variables: "The following are missing in the .env file: %s. ",
185
+ missing_env_file_variables_solution: "To add it, connect your extension with " \
186
+ "{{command:%1$s extension connect}} " \
187
+ "or run {{command:%1$s extension register}} to register a new extension.",
180
188
  module_not_found: "Unable to find module %s. Ensure your dependencies are up-to-date and try again.",
181
189
  },
182
190
  warnings: {
@@ -47,7 +47,7 @@ module Script
47
47
  private
48
48
 
49
49
  def install_dependencies(ctx, language, script_name, project_creator)
50
- task_runner = Infrastructure::Languages::TaskRunner.for(ctx, language, script_name)
50
+ task_runner = Infrastructure::Languages::TaskRunner.for(ctx, language)
51
51
  CLI::UI::Frame.open(ctx.message(
52
52
  "core.git.pulling_from_to",
53
53
  project_creator.sparse_checkout_repo,
@@ -10,7 +10,7 @@ module Script
10
10
  script_project = script_project_repo.get
11
11
  script_project.env = project.env
12
12
  task_runner = Infrastructure::Languages::TaskRunner
13
- .for(ctx, script_project.language, script_project.script_name)
13
+ .for(ctx, script_project.language)
14
14
 
15
15
  extension_point = ExtensionPoints.get(type: script_project.extension_point_type)
16
16
 
@@ -4,18 +4,11 @@ module Script
4
4
  module Layers
5
5
  module Infrastructure
6
6
  module Languages
7
- class AssemblyScriptTaskRunner
7
+ class AssemblyScriptTaskRunner < TaskRunner
8
8
  BYTECODE_FILE = "build/script.wasm"
9
9
  METADATA_FILE = "build/metadata.json"
10
10
  SCRIPT_SDK_BUILD = "npm run build"
11
11
 
12
- attr_reader :ctx, :script_name
13
-
14
- def initialize(ctx, script_name)
15
- @ctx = ctx
16
- @script_name = script_name
17
- end
18
-
19
12
  def build
20
13
  compile
21
14
  bytecode
@@ -5,15 +5,41 @@ module Script
5
5
  module Infrastructure
6
6
  module Languages
7
7
  class TaskRunner
8
- TASK_RUNNERS = {
9
- "assemblyscript" => AssemblyScriptTaskRunner,
10
- "typescript" => TypeScriptTaskRunner,
11
- "wasm" => WasmTaskRunner,
12
- }
13
-
14
- def self.for(ctx, language, script_name)
15
- raise Errors::TaskRunnerNotFoundError unless TASK_RUNNERS[language]
16
- TASK_RUNNERS[language].new(ctx, script_name)
8
+ attr_reader :ctx
9
+
10
+ def self.for(ctx, language)
11
+ task_runners = {
12
+ "assemblyscript" => AssemblyScriptTaskRunner,
13
+ "typescript" => TypeScriptTaskRunner,
14
+ "wasm" => WasmTaskRunner,
15
+ }
16
+
17
+ raise Errors::TaskRunnerNotFoundError unless task_runners[language]
18
+ task_runners[language].new(ctx)
19
+ end
20
+
21
+ def initialize(ctx)
22
+ @ctx = ctx
23
+ end
24
+
25
+ def build
26
+ raise NotImplementedError
27
+ end
28
+
29
+ def dependencies_installed?
30
+ raise NotImplementedError
31
+ end
32
+
33
+ def install_dependencies
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def metadata_file_location
38
+ raise NotImplementedError
39
+ end
40
+
41
+ def library_version(_library_name)
42
+ raise NotImplementedError
17
43
  end
18
44
  end
19
45
  end
@@ -4,19 +4,12 @@ module Script
4
4
  module Layers
5
5
  module Infrastructure
6
6
  module Languages
7
- class TypeScriptTaskRunner
7
+ class TypeScriptTaskRunner < TaskRunner
8
8
  BYTECODE_FILE = "build/index.wasm"
9
9
  METADATA_FILE = "build/metadata.json"
10
10
  SCRIPT_SDK_BUILD = "npm run build"
11
11
  GEN_METADATA = "npm run gen-metadata"
12
12
 
13
- attr_reader :ctx, :script_name
14
-
15
- def initialize(ctx, script_name)
16
- @ctx = ctx
17
- @script_name = script_name
18
- end
19
-
20
13
  def build
21
14
  compile
22
15
  bytecode
@@ -4,19 +4,15 @@ module Script
4
4
  module Layers
5
5
  module Infrastructure
6
6
  module Languages
7
- class WasmTaskRunner
7
+ class WasmTaskRunner < TaskRunner
8
8
  BYTECODE_FILE = "script.wasm"
9
- attr_reader :ctx, :script_name
10
-
11
- def initialize(ctx, script_name)
12
- @ctx = ctx
13
- @script_name = script_name
14
- end
15
9
 
16
10
  def dependencies_installed?
17
11
  true
18
12
  end
19
13
 
14
+ def install_dependencies; end
15
+
20
16
  def library_version(_library_name)
21
17
  nil
22
18
  end
@@ -149,12 +149,12 @@ module Script
149
149
 
150
150
  language_library_for_api_not_found_cause: "Script can’t be pushed because the %{language} library for API %{api} is missing.",
151
151
  language_library_for_api_not_found_help: "Make sure extension_point.yml contains the correct API library.",
152
- no_scripts_found_in_app: "The selected apps have no scripts. Create them first on the partners' dashboard.",
153
- missing_env_file_variables: "The following variables are missing in the .env file: %s."\
154
- " This can occur when the script hasn't been connected to an app."\
155
- " To connect the script to an app, run {{command:%s script connect}}",
156
- missing_push_options: "The following options are missing from .env: %s."\
157
- " Run {{command:%s script connect}} to connect the script to an app and generate these options.",
152
+ no_scripts_found_in_app: "The selected apps have no scripts. Please, create them first on the partners' dashboard.",
153
+ missing_env_file_variables: "The following are missing in the .env file: %s."\
154
+ " To add it, run {{command:%s script connect}}",
155
+ missing_push_options: "The following are missing: %s. "\
156
+ "To add them to a CI environment:\n\t1. Run a connect command {{command:%s script connect}}\n\t2. Navigate to the .env file at the root of your project\n\t"\
157
+ "3. Copy the missing values, then pass them through as arguments.",
158
158
  },
159
159
 
160
160
  create: {
@@ -4,7 +4,6 @@ require "theme_check"
4
4
  module Theme
5
5
  class Command
6
6
  class Check < ShopifyCLI::Command::SubCommand
7
- recommend_default_node_range
8
7
  recommend_default_ruby_range
9
8
 
10
9
  class Options < ShopifyCLI::Options
@@ -5,7 +5,6 @@ require "shopify_cli/theme/development_theme"
5
5
  module Theme
6
6
  class Command
7
7
  class Delete < ShopifyCLI::Command::SubCommand
8
- recommend_default_node_range
9
8
  recommend_default_ruby_range
10
9
 
11
10
  options do |parser, flags|
@@ -3,7 +3,6 @@
3
3
  module Theme
4
4
  class Command
5
5
  class Init < ShopifyCLI::Command::SubCommand
6
- recommend_default_node_range
7
6
  recommend_default_ruby_range
8
7
 
9
8
  options do |parser, flags|
@@ -4,7 +4,6 @@ require "theme_check"
4
4
  module Theme
5
5
  class Command
6
6
  class LanguageServer < ShopifyCLI::Command::SubCommand
7
- recommend_default_node_range
8
7
  recommend_default_ruby_range
9
8
 
10
9
  def call(*)
@@ -5,7 +5,6 @@ require "json"
5
5
  module Theme
6
6
  class Command
7
7
  class Package < ShopifyCLI::Command::SubCommand
8
- recommend_default_node_range
9
8
  recommend_default_ruby_range
10
9
 
11
10
  THEME_DIRECTORIES = %w[
@@ -4,7 +4,6 @@ require "shopify_cli/theme/theme"
4
4
  module Theme
5
5
  class Command
6
6
  class Publish < ShopifyCLI::Command::SubCommand
7
- recommend_default_node_range
8
7
  recommend_default_ruby_range
9
8
 
10
9
  options do |parser, flags|
@@ -7,7 +7,6 @@ require "shopify_cli/theme/syncer"
7
7
  module Theme
8
8
  class Command
9
9
  class Pull < ShopifyCLI::Command::SubCommand
10
- recommend_default_node_range
11
10
  recommend_default_ruby_range
12
11
 
13
12
  options do |parser, flags|
@@ -8,7 +8,6 @@ require "shopify_cli/theme/syncer"
8
8
  module Theme
9
9
  class Command
10
10
  class Push < ShopifyCLI::Command::SubCommand
11
- recommend_default_node_range
12
11
  recommend_default_ruby_range
13
12
 
14
13
  options do |parser, flags|
@@ -4,7 +4,6 @@ require "shopify_cli/theme/dev_server"
4
4
  module Theme
5
5
  class Command
6
6
  class Serve < ShopifyCLI::Command::SubCommand
7
- recommend_default_node_range
8
7
  recommend_default_ruby_range
9
8
 
10
9
  DEFAULT_HTTP_HOST = "127.0.0.1"
@@ -100,14 +100,20 @@ module ShopifyCLI
100
100
  end
101
101
 
102
102
  def check_node_version
103
+ context = Context.new
104
+ if @compatible_node_range && context.which("node").nil?
105
+ raise ShopifyCLI::Abort, context.message("core.errors.missing_node")
106
+ end
107
+
103
108
  check_version(
104
109
  Environment.node_version,
105
110
  range: @compatible_node_range,
106
- runtime: "Node"
111
+ runtime: "Node",
112
+ context: context
107
113
  )
108
114
  end
109
115
 
110
- def check_version(version, range:, runtime:)
116
+ def check_version(version, range:, runtime:, context: Context.new)
111
117
  return if Environment.test?
112
118
  return if range.nil?
113
119
 
@@ -116,7 +122,7 @@ module ShopifyCLI
116
122
  is_lower_than_top = version_without_pre_nor_build < Utilities.version_dropping_pre_and_build(range.to)
117
123
  return if is_higher_than_bottom && is_lower_than_top
118
124
 
119
- Context.new.warn("Your environment #{runtime} version, #{version},"\
125
+ context.warn("Your environment #{runtime} version, #{version},"\
120
126
  " is outside of the range supported by the CLI,"\
121
127
  " #{range.from}..<#{range.to},"\
122
128
  " and might cause incompatibility issues.")
@@ -5,8 +5,8 @@ module ShopifyCLI
5
5
  class Rails < ShopifyCLI::Command::AppSubCommand
6
6
  prerequisite_task :ensure_authenticated
7
7
 
8
- recommend_default_node_range
9
8
  recommend_default_ruby_range
9
+ recommend_default_node_range
10
10
 
11
11
  options do |parser, flags|
12
12
  parser.on("--name=NAME") { |t| flags[:name] = t }
@@ -6,9 +6,6 @@ module ShopifyCLI
6
6
  subcommand :PHP, "php", "shopify_cli/commands/app/create/php"
7
7
  subcommand :Node, "node", "shopify_cli/commands/app/create/node"
8
8
 
9
- recommend_default_node_range
10
- recommend_default_ruby_range
11
-
12
9
  def call(_args, _command_name)
13
10
  @ctx.puts(self.class.help)
14
11
  end
@@ -4,7 +4,6 @@ module ShopifyCLI
4
4
  class Deploy < ShopifyCLI::Command::AppSubCommand
5
5
  subcommand :Heroku, "heroku", "shopify_cli/commands/app/deploy/heroku"
6
6
 
7
- recommend_default_node_range
8
7
  recommend_default_ruby_range
9
8
 
10
9
  def call(args, _name)
@@ -7,7 +7,6 @@ module ShopifyCLI
7
7
  prerequisite_task :ensure_env, :ensure_dev_store
8
8
 
9
9
  recommend_default_ruby_range
10
- recommend_default_node_range
11
10
 
12
11
  options do |parser, flags|
13
12
  parser.on("--host=HOST") do |h|
@@ -38,7 +38,7 @@ module ShopifyCLI
38
38
 
39
39
  # When true the CLI points to spin instances of services
40
40
  SPIN = "SPIN"
41
- INFER_SPIN = "INFER_SPIN"
41
+ SPIN_INSTANCE = "SPIN_INSTANCE"
42
42
  SPIN_WORKSPACE = "SPIN_WORKSPACE"
43
43
  SPIN_NAMESPACE = "SPIN_NAMESPACE"
44
44
  SPIN_HOST = "SPIN_HOST"
@@ -5,6 +5,11 @@ module ShopifyCLI
5
5
  # the environment in which the CLI runs
6
6
  module Environment
7
7
  TRUTHY_ENV_VARIABLE_VALUES = ["1", "true", "TRUE", "yes", "YES"]
8
+ SPIN_OVERRIDE_ENV_NAMES = [
9
+ Constants::EnvironmentVariables::SPIN_WORKSPACE,
10
+ Constants::EnvironmentVariables::SPIN_NAMESPACE,
11
+ Constants::EnvironmentVariables::SPIN_HOST,
12
+ ]
8
13
 
9
14
  def self.ruby_version(context: Context.new)
10
15
  out, err, stat = context.capture3('ruby -e "puts RUBY_VERSION"')
@@ -87,6 +92,20 @@ module ShopifyCLI
87
92
  end
88
93
  end
89
94
 
95
+ def self.spin_url_override(env_variables: ENV)
96
+ tokens = SPIN_OVERRIDE_ENV_NAMES.map do |name|
97
+ env_variables[name]
98
+ end
99
+
100
+ return if tokens.all?(&:nil?)
101
+
102
+ if tokens.any?(&:nil?)
103
+ raise "To manually target a spin instance, you must set #{SPIN_OVERRIDE_ENV_NAMES}"
104
+ else
105
+ tokens.join(".")
106
+ end
107
+ end
108
+
90
109
  def self.use_spin?(env_variables: ENV)
91
110
  env_variable_truthy?(
92
111
  Constants::EnvironmentVariables::SPIN,
@@ -97,32 +116,29 @@ module ShopifyCLI
97
116
  )
98
117
  end
99
118
 
100
- def self.infer_spin?(env_variables: ENV)
101
- env_variable_truthy?(
102
- Constants::EnvironmentVariables::INFER_SPIN,
103
- env_variables: env_variables
104
- )
105
- end
106
-
107
119
  def self.spin_url(env_variables: ENV)
108
- if infer_spin?(env_variables: env_variables)
109
- # TODO: Remove version check and delete spin-legacy branch
110
- # once spin2 becomes the installed "spin" binary by default
111
- spin_version = %x(spin version 2> /dev/null).strip
112
- if spin_version.start_with?("spin-")
113
- # spin2
114
- raise ShopifyCLI:: Abort, "SPIN_INSTANCE must be specified" unless ENV.key?("SPIN_INSTANCE")
115
- %x(spin show -o fqdn 2> /dev/null).strip
116
- else
117
- # spin-legacy
118
- %x(spin info fqdn 2> /dev/null).strip
119
- end
120
+ override = spin_url_override(env_variables: env_variables)
121
+ return override unless override.nil?
122
+
123
+ spin_response = if env_variables.key?(
124
+ Constants::EnvironmentVariables::SPIN_INSTANCE
125
+ )
126
+ spin_show
120
127
  else
121
- spin_workspace = spin_workspace(env_variables: env_variables)
122
- spin_namespace = spin_namespace(env_variables: env_variables)
123
- spin_host = spin_host(env_variables: env_variables)
124
- "#{spin_workspace}.#{spin_namespace}.#{spin_host}"
128
+ spin_show(latest: true)
125
129
  end
130
+
131
+ begin
132
+ instance = JSON.parse(spin_response)
133
+ raise "Missing key 'fqdn' from spin show. Actual response: #{instance}" unless instance.include?("fqdn")
134
+ instance["fqdn"]
135
+ rescue => e
136
+ raise "Failed to infer spin environment from spin show response #{spin_response}: #{e}"
137
+ end
138
+ end
139
+
140
+ def self.spin_show(latest: false)
141
+ latest ? %x(spin show --latest --json) : %x(spin show --json)
126
142
  end
127
143
 
128
144
  def self.send_monorail_events?(env_variables: ENV)
@@ -139,27 +155,5 @@ module ShopifyCLI
139
155
  def self.env_variable_truthy?(variable_name, env_variables: ENV)
140
156
  TRUTHY_ENV_VARIABLE_VALUES.include?(env_variables[variable_name.to_s])
141
157
  end
142
-
143
- def self.spin_workspace(env_variables: ENV)
144
- env_value = env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE]
145
- return env_value unless env_value.nil?
146
-
147
- if env_value.nil?
148
- raise "No value set for #{Constants::EnvironmentVariables::SPIN_WORKSPACE}"
149
- end
150
- end
151
-
152
- def self.spin_namespace(env_variables: ENV)
153
- env_value = env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE]
154
- return env_value unless env_value.nil?
155
-
156
- if env_value.nil?
157
- raise "No value set for #{Constants::EnvironmentVariables::SPIN_NAMESPACE}"
158
- end
159
- end
160
-
161
- def self.spin_host(env_variables: ENV)
162
- env_variables[Constants::EnvironmentVariables::SPIN_HOST] || "us.spin.dev"
163
- end
164
158
  end
165
159
  end
@@ -15,6 +15,7 @@ module ShopifyCLI
15
15
  },
16
16
  core: {
17
17
  errors: {
18
+ missing_node: "Node is necessary for this command and was not found in the environment.",
18
19
  option_parser: {
19
20
  invalid_option: "The option {{command:%s}} is not supported.",
20
21
  missing_argument: "The required argument {{command:%s}} is missing.",
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ module DevServer
6
+ class HotReload
7
+ class RemoteFileReloader
8
+ def initialize(ctx, theme:, streams:)
9
+ @ctx = ctx
10
+ @theme = theme
11
+ @streams = streams
12
+ end
13
+
14
+ def reload(file)
15
+ retries = 6
16
+
17
+ until retries.zero?
18
+ retries -= 1
19
+
20
+ _status, body = fetch_asset(file)
21
+ retries = 0 if updated_file?(body, file)
22
+
23
+ wait
24
+ end
25
+
26
+ notify(file)
27
+ end
28
+
29
+ private
30
+
31
+ def updated_file?(body, file)
32
+ remote_checksum = body.dig("asset", "checksum")
33
+ local_checksum = file.checksum
34
+
35
+ remote_checksum == local_checksum
36
+ end
37
+
38
+ def notify(file)
39
+ @streams.broadcast(JSON.generate(modified: [file]))
40
+ @ctx.debug("[RemoteFileReloader] Modified #{file}")
41
+ end
42
+
43
+ def wait
44
+ sleep(1)
45
+ end
46
+
47
+ def fetch_asset(file)
48
+ ShopifyCLI::AdminAPI.rest_request(
49
+ @ctx,
50
+ shop: @theme.shop,
51
+ path: "themes/#{@theme.id}/assets.json",
52
+ method: "GET",
53
+ api_version: "unstable",
54
+ query: URI.encode_www_form("asset[key]" => file.relative_path.to_s),
55
+ )
56
+ rescue ShopifyCLI::API::APIRequestNotFoundError
57
+ [404, {}]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "hot_reload/remote_file_reloader"
4
+
3
5
  module ShopifyCLI
4
6
  module Theme
5
7
  module DevServer
@@ -10,6 +12,7 @@ module ShopifyCLI
10
12
  @theme = theme
11
13
  @mode = mode
12
14
  @streams = SSE::Streams.new
15
+ @remote_file_reloader = RemoteFileReloader.new(ctx, theme: @theme, streams: @streams)
13
16
  @watcher = watcher
14
17
  @watcher.add_observer(self, :notify_streams_of_file_change)
15
18
  @ignore_filter = ignore_filter
@@ -32,17 +35,30 @@ module ShopifyCLI
32
35
  end
33
36
 
34
37
  def notify_streams_of_file_change(modified, added, _removed)
35
- files = (modified + added).reject { |file| @ignore_filter&.ignore?(file) }
36
- .map { |file| @theme[file].relative_path }
38
+ files = (modified + added)
39
+ .reject { |file| @ignore_filter&.ignore?(file) }
40
+ .map { |file| @theme[file] }
37
41
 
38
- unless files.empty?
39
- @streams.broadcast(JSON.generate(modified: files))
40
- @ctx.debug("[HotReload] Modified #{files.join(", ")}")
41
- end
42
+ files -= liquid_css_files = files.select(&:liquid_css?)
43
+
44
+ hot_reload(files) unless files.empty?
45
+ remote_reload(liquid_css_files)
42
46
  end
43
47
 
44
48
  private
45
49
 
50
+ def hot_reload(files)
51
+ paths = files.map(&:relative_path)
52
+ @streams.broadcast(JSON.generate(modified: paths))
53
+ @ctx.debug("[HotReload] Modified #{paths.join(", ")}")
54
+ end
55
+
56
+ def remote_reload(files)
57
+ files.each do |file|
58
+ @remote_file_reloader.reload(file)
59
+ end
60
+ end
61
+
46
62
  def request_is_html?(headers)
47
63
  headers["content-type"]&.start_with?("text/html")
48
64
  end
@@ -43,9 +43,8 @@ module ShopifyCLI
43
43
  headers["Accept-Encoding"] = "none"
44
44
  headers["User-Agent"] = "Shopify CLI"
45
45
 
46
- query = URI.decode_www_form(env["QUERY_STRING"]).to_h
46
+ query = URI.decode_www_form(env["QUERY_STRING"])
47
47
  replace_templates = build_replace_templates_param(env)
48
-
49
48
  response = if replace_templates.any?
50
49
  # Pass to SFR the recently modified templates in `replace_templates` body param
51
50
  headers["Authorization"] = "Bearer #{bearer_token}"
@@ -158,7 +157,7 @@ module ShopifyCLI
158
157
  def secure_session_id
159
158
  if secure_session_id_expired?
160
159
  @ctx.debug("Refreshing preview _secure_session_id cookie")
161
- response = request("HEAD", "/", query: { preview_theme_id: @theme.id })
160
+ response = request("HEAD", "/", query: [[:preview_theme_id, @theme.id]])
162
161
  @secure_session_id = extract_secure_session_id_from_response_headers(response)
163
162
  @last_session_cookie_refresh = Time.now
164
163
  end
@@ -189,9 +188,9 @@ module ShopifyCLI
189
188
  response_headers
190
189
  end
191
190
 
192
- def request(method, path, headers: nil, query: {}, form_data: nil, body_stream: nil)
191
+ def request(method, path, headers: nil, query: [], form_data: nil, body_stream: nil)
193
192
  uri = URI.join("https://#{@theme.shop}", path)
194
- uri.query = URI.encode_www_form(query.merge(_fd: 0, pb: 0))
193
+ uri.query = URI.encode_www_form(query + [[:_fd, 0], [:pb, 0]])
195
194
 
196
195
  @ctx.debug("Proxying #{method} #{uri}")
197
196
 
@@ -53,6 +53,10 @@ module ShopifyCLI
53
53
  path.extname == ".liquid"
54
54
  end
55
55
 
56
+ def liquid_css?
57
+ relative_path.to_s.end_with?(".css.liquid")
58
+ end
59
+
56
60
  def json?
57
61
  path.extname == ".json"
58
62
  end
@@ -1,3 +1,3 @@
1
1
  module ShopifyCLI
2
- VERSION = "2.11.0"
2
+ VERSION = "2.11.1"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0
4
+ version: 2.11.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-07 00:00:00.000000000 Z
11
+ date: 2022-02-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -469,6 +469,7 @@ files:
469
469
  - lib/shopify_cli/theme/dev_server/header_hash.rb
470
470
  - lib/shopify_cli/theme/dev_server/hot-reload.js
471
471
  - lib/shopify_cli/theme/dev_server/hot_reload.rb
472
+ - lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb
472
473
  - lib/shopify_cli/theme/dev_server/local_assets.rb
473
474
  - lib/shopify_cli/theme/dev_server/proxy.rb
474
475
  - lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb