shopify-cli 2.15.0 → 2.15.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/stale.yml +7 -2
  3. data/.vscode/settings.json +1 -2
  4. data/CHANGELOG.md +70 -19
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +39 -7
  7. data/Rakefile +48 -0
  8. data/ext/javy/hashes/javy-arm-macos-v0.3.0.gz.sha256 +1 -0
  9. data/ext/javy/hashes/javy-x86_64-linux-v0.3.0.gz.sha256 +1 -0
  10. data/ext/javy/hashes/javy-x86_64-macos-v0.3.0.gz.sha256 +1 -0
  11. data/ext/javy/hashes/javy-x86_64-windows-v0.3.0.gz.sha256 +1 -0
  12. data/ext/javy/version +1 -1
  13. data/ext/shopify-extensions/version +1 -1
  14. data/lib/project_types/extension/commands/check.rb +6 -1
  15. data/lib/project_types/extension/forms/questions/ask_template.rb +5 -8
  16. data/lib/project_types/extension/messages/messages.rb +11 -3
  17. data/lib/project_types/extension/models/development_server_requirements.rb +14 -7
  18. data/lib/project_types/extension/models/server_config/root.rb +2 -0
  19. data/lib/project_types/extension/models/specification_handlers/beacon_extension.rb +57 -0
  20. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config.rb +33 -0
  21. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config_repository.rb +75 -0
  22. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +16 -1
  23. data/lib/project_types/extension/tasks/configure_options.rb +2 -1
  24. data/lib/project_types/extension/tasks/convert_server_config.rb +13 -2
  25. data/lib/project_types/extension/tasks/merge_server_config.rb +5 -2
  26. data/lib/project_types/script/cli.rb +1 -0
  27. data/lib/project_types/script/config/extension_points.yml +18 -0
  28. data/lib/project_types/script/layers/application/create_script.rb +14 -6
  29. data/lib/project_types/script/layers/infrastructure/errors.rb +17 -0
  30. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +6 -21
  31. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  32. data/lib/project_types/script/layers/infrastructure/sparse_checkout_details.rb +35 -0
  33. data/lib/project_types/script/messages/messages.rb +3 -0
  34. data/lib/project_types/script/ui/error_handler.rb +11 -0
  35. data/lib/project_types/theme/cli.rb +1 -0
  36. data/lib/project_types/theme/commands/check.rb +4 -1
  37. data/lib/project_types/theme/commands/open.rb +2 -2
  38. data/lib/project_types/theme/commands/push.rb +1 -3
  39. data/lib/project_types/theme/commands/serve.rb +1 -0
  40. data/lib/project_types/theme/commands/share.rb +56 -0
  41. data/lib/project_types/theme/messages/messages.rb +71 -11
  42. data/lib/shopify_cli/changelog.rb +148 -0
  43. data/lib/shopify_cli/command.rb +7 -0
  44. data/lib/shopify_cli/command_options/command_serve_options.rb +10 -0
  45. data/lib/shopify_cli/commands/app/serve.rb +7 -7
  46. data/lib/shopify_cli/commands/login.rb +5 -2
  47. data/lib/shopify_cli/context.rb +13 -0
  48. data/lib/shopify_cli/git.rb +36 -0
  49. data/lib/shopify_cli/identity_auth.rb +24 -4
  50. data/lib/shopify_cli/messages/messages.rb +26 -5
  51. data/lib/shopify_cli/release.rb +194 -0
  52. data/lib/shopify_cli/sed.rb +19 -0
  53. data/lib/shopify_cli/services/app/create/rails_service.rb +10 -2
  54. data/lib/shopify_cli/services/app/serve/node_service.rb +2 -25
  55. data/lib/shopify_cli/services/app/serve/php_service.rb +2 -25
  56. data/lib/shopify_cli/services/app/serve/rails_service.rb +8 -28
  57. data/lib/shopify_cli/services/app/serve/serve_service.rb +57 -0
  58. data/lib/shopify_cli/services.rb +1 -0
  59. data/lib/shopify_cli/tasks/update_dashboard_urls.rb +7 -9
  60. data/lib/shopify_cli/theme/dev_server/hot-reload.js +40 -13
  61. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
  62. data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +51 -0
  63. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +6 -1
  64. data/lib/shopify_cli/theme/dev_server/local_assets.rb +1 -1
  65. data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +35 -0
  66. data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +44 -0
  67. data/lib/shopify_cli/theme/dev_server/watcher.rb +2 -8
  68. data/lib/shopify_cli/theme/dev_server.rb +18 -5
  69. data/lib/shopify_cli/theme/file.rb +15 -4
  70. data/lib/shopify_cli/theme/syncer/checksums.rb +60 -0
  71. data/lib/shopify_cli/theme/syncer/forms/apply_to_all.rb +39 -0
  72. data/lib/shopify_cli/theme/syncer/forms/apply_to_all_form.rb +35 -0
  73. data/lib/shopify_cli/theme/syncer/forms/base_strategy_form.rb +62 -0
  74. data/lib/shopify_cli/theme/syncer/forms/select_delete_strategy.rb +27 -0
  75. data/lib/shopify_cli/theme/syncer/forms/select_update_strategy.rb +28 -0
  76. data/lib/shopify_cli/theme/syncer/ignore_helper.rb +33 -0
  77. data/lib/shopify_cli/theme/syncer/json_delete_handler.rb +51 -0
  78. data/lib/shopify_cli/theme/syncer/json_update_handler.rb +82 -0
  79. data/lib/shopify_cli/theme/syncer/merger.rb +53 -0
  80. data/lib/shopify_cli/theme/syncer/operation.rb +1 -1
  81. data/lib/shopify_cli/theme/syncer.rb +79 -63
  82. data/lib/shopify_cli/theme/theme.rb +26 -4
  83. data/lib/shopify_cli/theme/theme_admin_api.rb +23 -8
  84. data/lib/shopify_cli/thread_pool/job.rb +10 -2
  85. data/lib/shopify_cli/thread_pool.rb +15 -3
  86. data/lib/shopify_cli/tunnel.rb +9 -0
  87. data/lib/shopify_cli/version.rb +1 -1
  88. data/shopify-cli.gemspec +3 -1
  89. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +8 -0
  90. metadata +30 -3
@@ -0,0 +1,75 @@
1
+ require_relative "script_config"
2
+ module Extension
3
+ module Models
4
+ module SpecificationHandlers
5
+ module BeaconExtensionUtils
6
+ class ScriptConfigRepository
7
+ include SmartProperties
8
+ property! :ctx, accepts: ShopifyCLI::Context
9
+
10
+ def active?
11
+ ctx.file_exist?(filename)
12
+ end
13
+
14
+ def get!
15
+ raise RuntimeError.new("NoScriptConfigFile"), filename unless active?
16
+
17
+ content = ctx.read(filename)
18
+ hash = file_content_to_hash(content)
19
+
20
+ from_h(hash)
21
+ end
22
+
23
+ def filename
24
+ raise NotImplementedError
25
+ end
26
+
27
+ private
28
+
29
+ def from_h(hash)
30
+ Extension::Models::SpecificationHandlers::BeaconExtensionUtils::ScriptConfig.new(content: hash,
31
+ filename: filename)
32
+ end
33
+
34
+ def file_content_to_hash(file_content)
35
+ raise NotImplementedError
36
+ end
37
+
38
+ def hash_to_file_content(hash)
39
+ raise NotImplementedError
40
+ end
41
+ end
42
+
43
+ class ScriptConfigYmlRepository < ScriptConfigRepository
44
+ def self.filename
45
+ "extension.config.yml"
46
+ end
47
+
48
+ def filename
49
+ ScriptConfigYmlRepository.filename
50
+ end
51
+
52
+ private
53
+
54
+ def file_content_to_hash(file_content)
55
+ begin
56
+ hash = YAML.load(file_content)
57
+ rescue Psych::SyntaxError
58
+ raise parse_error
59
+ end
60
+ raise parse_error unless hash.is_a?(Hash)
61
+ hash
62
+ end
63
+
64
+ def hash_to_file_content(hash)
65
+ YAML.dump(hash)
66
+ end
67
+
68
+ def parse_error
69
+ RuntimeError.new("ScriptConfigParseError #{filename}, serialization_format: \"YAML\" ")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -49,7 +49,7 @@ module Extension
49
49
 
50
50
  locale_filenames.map do |filename|
51
51
  locale = basename_for_locale_filename(filename)
52
- [locale.to_sym, Base64.strict_encode64(File.read(filename, mode: "rt", encoding: "UTF-8").strip)]
52
+ [locale.to_sym, read_locale_file(filename)]
53
53
  end
54
54
  .yield_self do |encoded_files_by_locale|
55
55
  {
@@ -62,6 +62,14 @@ module Extension
62
62
  end
63
63
  end
64
64
 
65
+ def read_locale_file(filename)
66
+ content = File.read(filename, mode: "rt", encoding: "bom|utf-8").strip
67
+ raise_invalid_encoding_error(filename) unless content.valid_encoding?
68
+ Base64.strict_encode64(content)
69
+ rescue ArgumentError
70
+ raise_invalid_encoding_error(filename)
71
+ end
72
+
65
73
  def validate_no_duplicate_locale(locale_filenames)
66
74
  duplicate_locale = locale_filenames
67
75
  .map { |filename| basename_for_locale_filename(filename.downcase) }
@@ -149,6 +157,13 @@ module Extension
149
157
  def basename_for_locale_filename(filename)
150
158
  File.basename(File.basename(filename, ".json"), ".default")
151
159
  end
160
+
161
+ def raise_invalid_encoding_error(filename)
162
+ raise(
163
+ ShopifyCLI::Abort,
164
+ ShopifyCLI::Context.message("#{L10N_ERROR_PREFIX}.invalid_file_encoding", filename)
165
+ )
166
+ end
152
167
  end
153
168
  end
154
169
  end
@@ -13,7 +13,8 @@ module Extension
13
13
  private
14
14
 
15
15
  def configure_skip_build(attributes)
16
- attributes[:options].merge!(skip_build: attributes[:identifier] == "theme_app_extension")
16
+ attributes[:options].merge!(skip_build: attributes[:identifier] == "theme_app_extension" ||
17
+ attributes[:identifier] == "beacon_extension")
17
18
  end
18
19
  end
19
20
  end
@@ -18,7 +18,6 @@ module Extension
18
18
  property! :type, accepts: String
19
19
 
20
20
  DEFAULT_BUILD_DIR = "build"
21
- DEFAULT_MAIN = Dir["src/*"].lazy.grep(/index.[jt]sx?/).first
22
21
 
23
22
  def self.call(*args)
24
23
  new(*args).call
@@ -37,7 +36,7 @@ module Extension
37
36
  build_dir: hash.dig("development", "build_dir") || DEFAULT_BUILD_DIR,
38
37
  renderer: renderer,
39
38
  entries: Models::ServerConfig::DevelopmentEntries.new(
40
- main: hash.dig("development", "entries", "main") || DEFAULT_MAIN
39
+ main: hash.dig("development", "entries", "main") || determine_default_entry_main(project_directory),
41
40
  )
42
41
  ),
43
42
  extension_points: hash.dig("extension_points"),
@@ -65,6 +64,18 @@ module Extension
65
64
  def version(renderer, context)
66
65
  Tasks::FindPackageFromJson.call(renderer, context: context).version
67
66
  end
67
+
68
+ private
69
+
70
+ def determine_default_entry_main(project_directory)
71
+ Dir.chdir(project_directory) do
72
+ Dir["src/*"].lazy.grep(/index.[jt]sx?/).first
73
+ end
74
+ end
75
+
76
+ def project_directory
77
+ ExtensionProject.current.directory
78
+ end
68
79
  end
69
80
  end
70
81
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "shopify_cli"
3
3
  require "yaml"
4
+ require "pathname"
4
5
 
5
6
  module Extension
6
7
  module Tasks
@@ -8,7 +9,7 @@ module Extension
8
9
  include SmartProperties
9
10
 
10
11
  property! :context, accepts: ShopifyCLI::Context
11
- property! :file_path, accepts: ->(path) { Pathname(path).yield_self { |pn| pn.absolute? && pn.file? } }
12
+ property! :file_path, accepts: ->(path) { Pathname(path).yield_self(&:absolute?) }
12
13
  property :port, accepts: Integer, default: ShopifyCLI::Constants::Extension::DEFAULT_PORT
13
14
  property :resource_url, accepts: String
14
15
  property :tunnel_url, accepts: String
@@ -19,7 +20,9 @@ module Extension
19
20
  end
20
21
 
21
22
  def call
22
- config = YAML.load_file(file_path)
23
+ config = YAML.load_file(file_path) if File.file?(file_path)
24
+ config ||= {}
25
+
23
26
  project = ExtensionProject.current
24
27
  Tasks::ConvertServerConfig.call(
25
28
  api_key: project.env.api_key,
@@ -62,6 +62,7 @@ module Script
62
62
  autoload :ScriptService, Project.project_filepath("layers/infrastructure/script_service")
63
63
  autoload :ScriptUploader, Project.project_filepath("layers/infrastructure/script_uploader")
64
64
  autoload :ServiceLocator, Project.project_filepath("layers/infrastructure/service_locator")
65
+ autoload :SparseCheckoutDetails, Project.project_filepath("layers/infrastructure/sparse_checkout_details")
65
66
 
66
67
  module Languages
67
68
  autoload :ProjectCreator, Project.project_filepath("layers/infrastructure/languages/project_creator")
@@ -36,3 +36,21 @@ delivery_discount_types:
36
36
  repo: "https://github.com/Shopify/scripts-apis-examples"
37
37
  wasm:
38
38
  repo: "https://github.com/Shopify/scripts-apis-examples"
39
+ product_discount_type:
40
+ beta: true
41
+ domain: 'discounts'
42
+ libraries:
43
+ wasm:
44
+ repo: "https://github.com/Shopify/scripts-apis-examples"
45
+ order_discount_type:
46
+ beta: true
47
+ domain: 'discounts'
48
+ libraries:
49
+ wasm:
50
+ repo: "https://github.com/Shopify/scripts-apis-examples"
51
+ shipping_discount_type:
52
+ beta: true
53
+ domain: 'discounts'
54
+ libraries:
55
+ wasm:
56
+ repo: "https://github.com/Shopify/scripts-apis-examples"
@@ -23,19 +23,23 @@ module Script
23
23
  )
24
24
 
25
25
  # remove the need to pass the whole extension-point object to the infra layer
26
- sparse_checkout_repo = extension_point.libraries.for(language).repo
27
26
  type = extension_point.dasherize_type
28
27
  domain = extension_point.domain
29
28
 
29
+ sparse_checkout_details = Infrastructure::SparseCheckoutDetails.new(
30
+ repo: extension_point.libraries.for(language).repo,
31
+ branch: sparse_checkout_branch,
32
+ path: "#{domain}/#{language}/#{type}/default",
33
+ input_queries_enabled: input_queries_enabled?,
34
+ )
35
+
30
36
  project_creator = Infrastructure::Languages::ProjectCreator.for(
31
37
  ctx: ctx,
32
38
  language: language,
33
39
  type: type,
34
40
  project_name: title,
35
41
  path_to_project: project.id,
36
- sparse_checkout_repo: sparse_checkout_repo,
37
- sparse_checkout_branch: sparse_checkout_branch,
38
- sparse_checkout_set_path: "#{domain}/#{language}/#{type}/default"
42
+ sparse_checkout_details: sparse_checkout_details,
39
43
  )
40
44
 
41
45
  install_dependencies(ctx, language, title, project_creator)
@@ -49,12 +53,12 @@ module Script
49
53
  task_runner = Infrastructure::Languages::TaskRunner.for(ctx, language)
50
54
  CLI::UI::Frame.open(ctx.message(
51
55
  "core.git.pulling_from_to",
52
- project_creator.sparse_checkout_repo,
56
+ project_creator.sparse_checkout_details.repo,
53
57
  title,
54
58
  )) do
55
59
  UI::StrictSpinner.spin(ctx.message(
56
60
  "core.git.pulling",
57
- project_creator.sparse_checkout_repo,
61
+ project_creator.sparse_checkout_details.repo,
58
62
  title,
59
63
  )) do |spinner|
60
64
  project_creator.setup_dependencies
@@ -75,6 +79,10 @@ module Script
75
79
  ensure
76
80
  script_project_repo.change_to_initial_directory
77
81
  end
82
+
83
+ def input_queries_enabled?
84
+ ShopifyCLI::Feature.enabled?(:scripts_beta_input_queries)
85
+ end
78
86
  end
79
87
  end
80
88
  end
@@ -180,6 +180,23 @@ module Script
180
180
  @max_size = max_size
181
181
  end
182
182
  end
183
+
184
+ class InvalidInputQueryErrors < ScriptProjectError
185
+ attr_reader :messages
186
+
187
+ def initialize(messages)
188
+ @messages = messages
189
+ super()
190
+ end
191
+
192
+ def input_query_path
193
+ ScriptProjectRepository::INPUT_QUERY_PATH
194
+ end
195
+
196
+ def message
197
+ messages.join("\n")
198
+ end
199
+ end
183
200
  end
184
201
  end
185
202
  end
@@ -10,9 +10,7 @@ module Script
10
10
  property! :type, accepts: String
11
11
  property! :project_name, accepts: String
12
12
  property! :path_to_project, accepts: String
13
- property! :sparse_checkout_repo, accepts: String
14
- property! :sparse_checkout_branch, accepts: String
15
- property! :sparse_checkout_set_path, accepts: String
13
+ property! :sparse_checkout_details, accepts: SparseCheckoutDetails
16
14
 
17
15
  def self.for(
18
16
  ctx:,
@@ -20,9 +18,7 @@ module Script
20
18
  type:,
21
19
  project_name:,
22
20
  path_to_project:,
23
- sparse_checkout_repo:,
24
- sparse_checkout_branch:,
25
- sparse_checkout_set_path:
21
+ sparse_checkout_details:
26
22
  )
27
23
 
28
24
  project_creators = {
@@ -36,33 +32,22 @@ module Script
36
32
  type: type,
37
33
  project_name: project_name,
38
34
  path_to_project: path_to_project,
39
- sparse_checkout_repo: sparse_checkout_repo,
40
- sparse_checkout_branch: sparse_checkout_branch,
41
- sparse_checkout_set_path: sparse_checkout_set_path
35
+ sparse_checkout_details: sparse_checkout_details,
42
36
  )
43
37
  end
44
38
 
45
39
  # the sparse checkout process is common to all script types
46
40
  def setup_dependencies
47
- setup_sparse_checkout
41
+ sparse_checkout_details.setup(ctx)
48
42
  clean
49
43
  end
50
44
 
51
45
  private
52
46
 
53
- def setup_sparse_checkout
54
- ShopifyCLI::Git.sparse_checkout(
55
- sparse_checkout_repo,
56
- sparse_checkout_set_path,
57
- sparse_checkout_branch,
58
- ctx
59
- )
60
- end
61
-
62
47
  def clean
63
- source = File.join(path_to_project, sparse_checkout_set_path, ".")
48
+ source = File.join(path_to_project, sparse_checkout_details.path, ".")
64
49
  FileUtils.cp_r(source, path_to_project)
65
- ctx.rm_rf(sparse_checkout_set_path.split("/")[0])
50
+ ctx.rm_rf(sparse_checkout_details.path.split("/")[0])
66
51
  ctx.rm_rf(".git")
67
52
  end
68
53
 
@@ -83,6 +83,8 @@ module Script
83
83
  valid_types: e["message"],
84
84
  filename: script_config.filename,
85
85
  )
86
+ elsif (errors = user_errors.filter { |err| err["tag"] == "input_query_validation_error" }).any?
87
+ raise Errors::InvalidInputQueryErrors, errors.map { |err| err["message"] }
86
88
  elsif user_errors.find { |err| %w(not_use_msgpack_error schema_version_argument_error).include?(err["tag"]) }
87
89
  raise Domain::Errors::MetadataValidationError
88
90
  else
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Script
4
+ module Layers
5
+ module Infrastructure
6
+ class SparseCheckoutDetails
7
+ include SmartProperties
8
+ property! :repo, accepts: String
9
+ property! :branch, accepts: String
10
+ property! :path, accepts: String
11
+ property! :input_queries_enabled, accepts: [true, false]
12
+
13
+ def ==(other)
14
+ self.class == other.class &&
15
+ self.class.properties.all? { |name, _| self[name] == other[name] }
16
+ end
17
+
18
+ def setup(ctx)
19
+ ShopifyCLI::Git.sparse_checkout(repo, patterns_to_checkout, branch, ctx)
20
+ end
21
+
22
+ private
23
+
24
+ def patterns_to_checkout
25
+ paths = [path]
26
+ unless input_queries_enabled
27
+ paths << "!#{path}/#{ScriptProjectRepository::INPUT_QUERY_PATH}"
28
+ paths << "!#{path}/schema.graphql"
29
+ end
30
+ paths.join(" ")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -86,6 +86,9 @@ module Script
86
86
  "type(s): %{valid_types}.",
87
87
  configuration_schema_field_invalid_value_error_help: "Change the value of the type.",
88
88
 
89
+ input_query_error_cause: "Input query is invalid:\n%{messages}\n\n",
90
+ input_query_error_help: "Fix the query in the `%{input_query_path}` file.",
91
+
89
92
  script_not_found_cause: "Can't find script %s for Script API %s",
90
93
 
91
94
  system_call_failure_cause: "Something went wrong while running: {{command:%{cmd}}}.",
@@ -216,6 +216,17 @@ module Script
216
216
  "script.error.configuration_schema_field_invalid_value_error_help"
217
217
  ),
218
218
  }
219
+ when Layers::Infrastructure::Errors::InvalidInputQueryErrors
220
+ {
221
+ cause_of_error: ShopifyCLI::Context.message(
222
+ "script.error.input_query_error_cause",
223
+ messages: e.messages.map { |message| " {{x}} #{message}." }.join("\n"),
224
+ ),
225
+ help_suggestion: ShopifyCLI::Context.message(
226
+ "script.error.input_query_error_help",
227
+ input_query_path: e.input_query_path,
228
+ ),
229
+ }
219
230
  when Layers::Infrastructure::Errors::DependencyInstallError
220
231
  {
221
232
  cause_of_error: ShopifyCLI::Context.message("script.error.dependency_install_cause"),
@@ -16,6 +16,7 @@ module Theme
16
16
  subcommand :Package, "package", Project.project_filepath("commands/package")
17
17
  subcommand :Open, "open", Project.project_filepath("commands/open")
18
18
  subcommand :List, "list", Project.project_filepath("commands/list")
19
+ subcommand :Share, "share", Project.project_filepath("commands/share")
19
20
  subcommand :LanguageServer, "language-server", Project.project_filepath("commands/language_server")
20
21
  end
21
22
  ShopifyCLI::Commands.register("Theme::Command", "theme")
@@ -24,7 +24,10 @@ module Theme
24
24
  end
25
25
 
26
26
  def call(*)
27
- @theme_check.run
27
+ @theme_check.run!
28
+ rescue ThemeCheck::Cli::Abort, ThemeCheck::ThemeCheckError => e
29
+ raise ShopifyCLI::Abort,
30
+ ShopifyCLI::Context.message("theme.check.error", e.full_message)
28
31
  end
29
32
 
30
33
  def self.help
@@ -17,8 +17,8 @@ module Theme
17
17
  def call(_args, _name)
18
18
  theme = find_theme(**options.flags)
19
19
 
20
- @ctx.puts(@ctx.message("theme.open.details", theme.name, theme.editor_url))
21
- @ctx.open_url!(theme.preview_url)
20
+ @ctx.puts(@ctx.message("theme.open.details", theme.name, theme.preview_url, theme.editor_url))
21
+ @ctx.open_browser_url!(theme.preview_url)
22
22
  end
23
23
 
24
24
  def self.help
@@ -104,9 +104,7 @@ module Theme
104
104
 
105
105
  if unpublished
106
106
  name = theme || ask_theme_name
107
- new_theme = ShopifyCLI::Theme::Theme.new(@ctx, root: root, name: name, role: "unpublished")
108
- new_theme.create
109
- return new_theme
107
+ return ShopifyCLI::Theme::Theme.create_unpublished(@ctx, root: root, name: name)
110
108
  end
111
109
 
112
110
  if theme
@@ -16,6 +16,7 @@ module Theme
16
16
  parser.on("--port=PORT") { |port| flags[:port] = port.to_i }
17
17
  parser.on("--poll") { flags[:poll] = true }
18
18
  parser.on("--live-reload=MODE") { |mode| flags[:mode] = as_reload_mode(mode) }
19
+ parser.on("--theme-editor-sync") { flags[:editor_sync] = true }
19
20
  end
20
21
 
21
22
  def call(_args, name)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/theme/theme"
4
+ require "shopify_cli/theme/syncer"
5
+ require "project_types/theme/commands/common/root_helper"
6
+
7
+ module Theme
8
+ class Command
9
+ class Share < ShopifyCLI::Command::SubCommand
10
+ include Common::RootHelper
11
+
12
+ recommend_default_ruby_range
13
+
14
+ def call(_args, name)
15
+ root = root_value(options, name)
16
+ theme = create_theme(root)
17
+
18
+ upload(theme)
19
+
20
+ @ctx.done(done_message(theme))
21
+ end
22
+
23
+ def self.help
24
+ tool = ShopifyCLI::TOOL_NAME
25
+ @ctx.message("theme.share.help", tool, tool)
26
+ end
27
+
28
+ private
29
+
30
+ def create_theme(root)
31
+ ShopifyCLI::Theme::Theme.create_unpublished(@ctx, root: root)
32
+ end
33
+
34
+ def upload(theme)
35
+ syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme)
36
+ syncer.start_threads
37
+
38
+ CLI::UI::Frame.open(upload_message(theme)) do
39
+ UI::SyncProgressBar.new(syncer).progress(:upload_theme!)
40
+ end
41
+
42
+ raise ShopifyCLI::AbortSilent if syncer.has_any_error?
43
+ ensure
44
+ syncer.shutdown
45
+ end
46
+
47
+ def upload_message(theme)
48
+ @ctx.message("theme.share.upload", theme.name, theme.id, theme.shop)
49
+ end
50
+
51
+ def done_message(theme)
52
+ @ctx.message("theme.share.done", theme.name, theme.preview_url, theme.editor_url)
53
+ end
54
+ end
55
+ end
56
+ end