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
@@ -103,51 +103,65 @@ module Script
103
103
  cause_of_error: ShopifyCLI::Context.message("script.error.metadata_not_found_cause"),
104
104
  help_suggestion: ShopifyCLI::Context.message("script.error.metadata_not_found_help"),
105
105
  }
106
+ when Layers::Domain::Errors::MissingScriptConfigFieldError
107
+ {
108
+ cause_of_error: ShopifyCLI::Context.message(
109
+ "script.error.missing_script_config_field_cause",
110
+ field: e.field,
111
+ filename: e.filename,
112
+ ),
113
+ help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_config_field_help"),
114
+ }
106
115
  when Layers::Infrastructure::Errors::BuildError
107
116
  {
108
117
  cause_of_error: ShopifyCLI::Context.message("script.error.build_error_cause"),
109
118
  help_suggestion: ShopifyCLI::Context.message("script.error.build_error_help"),
110
119
  }
111
- when Layers::Infrastructure::Errors::InvalidScriptConfigYmlDefinitionError
112
- {
113
- cause_of_error: ShopifyCLI::Context.message("script.error.invalid_script_config_yml_definition_cause"),
114
- help_suggestion: ShopifyCLI::Context.message("script.error.invalid_script_config_yml_definition_help"),
115
- }
116
- when Layers::Infrastructure::Errors::InvalidScriptJsonDefinitionError
117
- {
118
- cause_of_error: ShopifyCLI::Context.message("script.error.invalid_script_json_definition_cause"),
119
- help_suggestion: ShopifyCLI::Context.message("script.error.invalid_script_json_definition_help"),
120
- }
121
- when Layers::Infrastructure::Errors::MissingScriptConfigYmlFieldError
122
- {
123
- cause_of_error: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_cause", e.field),
124
- help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_help"),
125
- }
126
- when Layers::Infrastructure::Errors::MissingScriptConfigYmlFieldError
120
+ when Layers::Infrastructure::Errors::ScriptConfigParseError
127
121
  {
128
- cause_of_error: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_cause", e.field),
129
- help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_config_yml_field_help"),
122
+ cause_of_error: ShopifyCLI::Context.message(
123
+ "script.error.script_config_parse_error_cause",
124
+ filename: e.filename,
125
+ serialization_format: e.serialization_format,
126
+ ),
127
+ help_suggestion: ShopifyCLI::Context.message("script.error.script_config_parse_error_help"),
130
128
  }
131
- when Layers::Infrastructure::Errors::MissingScriptJsonFieldError
129
+ when Layers::Infrastructure::Errors::NoScriptConfigFileError
132
130
  {
133
- cause_of_error: ShopifyCLI::Context.message("script.error.missing_script_json_field_cause", e.field),
134
- help_suggestion: ShopifyCLI::Context.message("script.error.missing_script_json_field_help"),
131
+ cause_of_error: ShopifyCLI::Context.message(
132
+ "script.error.no_script_config_file_cause",
133
+ filename: e.filename,
134
+ ),
135
+ help_suggestion: ShopifyCLI::Context.message("script.error.no_script_config_file_help"),
135
136
  }
136
- when Layers::Infrastructure::Errors::NoScriptConfigYmlFileError
137
+ when Layers::Infrastructure::Errors::ScriptConfigurationDefinitionError
137
138
  {
138
- cause_of_error: ShopifyCLI::Context.message("script.error.no_script_config_yml_file_cause"),
139
- help_suggestion: ShopifyCLI::Context.message("script.error.no_script_config_yml_file_help"),
139
+ cause_of_error: ShopifyCLI::Context.message(
140
+ "script.error.configuration_definition_error_cause",
141
+ message: e.message,
142
+ filename: e.filename,
143
+ ),
144
+ help_suggestion: ShopifyCLI::Context.message("script.error.configuration_definition_error_help"),
140
145
  }
141
146
  when Layers::Infrastructure::Errors::ScriptConfigSyntaxError
142
147
  {
143
- cause_of_error: ShopifyCLI::Context.message("script.error.configuration_syntax_error_cause"),
148
+ cause_of_error: ShopifyCLI::Context.message(
149
+ "script.error.configuration_syntax_error_cause",
150
+ filename: e.filename,
151
+ ),
144
152
  help_suggestion: ShopifyCLI::Context.message("script.error.configuration_syntax_error_help"),
145
153
  }
154
+ when Layers::Infrastructure::Errors::ScriptEnvAppNotConnectedError
155
+ {
156
+ cause_of_error: ShopifyCLI::Context.message("script.error.app_not_connected_cause"),
157
+ help_suggestion: ShopifyCLI::Context.message("script.error.app_not_connected_help"),
158
+ }
146
159
  when Layers::Infrastructure::Errors::ScriptConfigMissingKeysError
147
160
  {
148
161
  cause_of_error: ShopifyCLI::Context.message(
149
162
  "script.error.configuration_missing_keys_error_cause",
150
- missing_keys: e.missing_keys
163
+ missing_keys: e.missing_keys,
164
+ filename: e.filename,
151
165
  ),
152
166
  help_suggestion: ShopifyCLI::Context.message("script.error.configuration_missing_keys_error_help"),
153
167
  }
@@ -155,7 +169,8 @@ module Script
155
169
  {
156
170
  cause_of_error: ShopifyCLI::Context.message(
157
171
  "script.error.configuration_invalid_value_error_cause",
158
- valid_input_modes: e.valid_input_modes
172
+ valid_input_modes: e.valid_input_modes,
173
+ filename: e.filename,
159
174
  ),
160
175
  help_suggestion: ShopifyCLI::Context.message("script.error.configuration_invalid_value_error_help"),
161
176
  }
@@ -163,7 +178,8 @@ module Script
163
178
  {
164
179
  cause_of_error: ShopifyCLI::Context.message(
165
180
  "script.error.configuration_schema_field_missing_keys_error_cause",
166
- missing_keys: e.missing_keys
181
+ missing_keys: e.missing_keys,
182
+ filename: e.filename,
167
183
  ),
168
184
  help_suggestion: ShopifyCLI::Context.message(
169
185
  "script.error.configuration_definition_schema_field_missing_keys_error_help"
@@ -173,7 +189,8 @@ module Script
173
189
  {
174
190
  cause_of_error: ShopifyCLI::Context.message(
175
191
  "script.error.configuration_schema_field_invalid_value_error_cause",
176
- valid_types: e.valid_types
192
+ valid_types: e.valid_types,
193
+ filename: e.filename,
177
194
  ),
178
195
  help_suggestion: ShopifyCLI::Context.message(
179
196
  "script.error.configuration_schema_field_invalid_value_error_help"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "shopify_cli/theme/theme"
3
3
  require "shopify_cli/theme/ignore_filter"
4
+ require "shopify_cli/theme/include_filter"
4
5
  require "shopify_cli/theme/syncer"
5
6
 
6
7
  module Theme
@@ -9,7 +10,10 @@ module Theme
9
10
  options do |parser, flags|
10
11
  parser.on("-n", "--nodelete") { flags[:nodelete] = true }
11
12
  parser.on("-i", "--themeid=ID") { |theme_id| flags[:theme_id] = theme_id }
13
+ parser.on("-t", "--theme=NAME_OR_ID") { |theme| flags[:theme] = theme }
12
14
  parser.on("-l", "--live") { flags[:live] = true }
15
+ parser.on("-d", "--development") { flags[:development] = true }
16
+ parser.on("-o", "--only=PATTERN") { |pattern| flags[:includes] = pattern }
13
17
  parser.on("-x", "--ignore=PATTERN") do |pattern|
14
18
  flags[:ignores] ||= []
15
19
  flags[:ignores] << pattern
@@ -19,26 +23,16 @@ module Theme
19
23
  def call(args, _name)
20
24
  root = args.first || "."
21
25
  delete = !options.flags[:nodelete]
26
+ theme = find_theme(root, **options.flags)
27
+ return if theme.nil?
22
28
 
23
- theme = if (theme_id = options.flags[:theme_id])
24
- ShopifyCLI::Theme::Theme.new(@ctx, root: root, id: theme_id)
25
- elsif options.flags[:live]
26
- ShopifyCLI::Theme::Theme.live(@ctx, root: root)
27
- else
28
- form = Forms::Select.ask(
29
- @ctx,
30
- [],
31
- title: @ctx.message("theme.pull.select"),
32
- root: root,
33
- )
34
- return unless form
35
- form.theme
36
- end
37
-
29
+ include_filter = ShopifyCLI::Theme::IncludeFilter.new(options.flags[:includes])
38
30
  ignore_filter = ShopifyCLI::Theme::IgnoreFilter.from_path(root)
39
31
  ignore_filter.add_patterns(options.flags[:ignores]) if options.flags[:ignores]
40
32
 
41
- syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme, ignore_filter: ignore_filter)
33
+ syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme,
34
+ include_filter: include_filter,
35
+ ignore_filter: ignore_filter)
42
36
  begin
43
37
  syncer.start_threads
44
38
  CLI::UI::Frame.open(@ctx.message("theme.pull.pulling", theme.name, theme.id, theme.shop)) do
@@ -46,7 +40,7 @@ module Theme
46
40
  end
47
41
  @ctx.done(@ctx.message("theme.pull.done"))
48
42
  rescue ShopifyCLI::API::APIRequestNotFoundError
49
- @ctx.abort(@ctx.message("theme.pull.theme_not_found", theme.id))
43
+ @ctx.abort(@ctx.message("theme.pull.theme_not_found", "##{theme.id}"))
50
44
  ensure
51
45
  syncer.shutdown
52
46
  end
@@ -55,6 +49,40 @@ module Theme
55
49
  def self.help
56
50
  ShopifyCLI::Context.message("theme.pull.help", ShopifyCLI::TOOL_NAME, ShopifyCLI::TOOL_NAME)
57
51
  end
52
+
53
+ private
54
+
55
+ def find_theme(root, theme_id: nil, theme: nil, live: nil, development: nil, **_args)
56
+ if theme_id
57
+ @ctx.warn(@ctx.message("theme.pull.deprecated_themeid"))
58
+ return ShopifyCLI::Theme::Theme.new(@ctx, root: root, id: theme_id)
59
+ end
60
+
61
+ if theme
62
+ selected_theme = ShopifyCLI::Theme::Theme.find_by_identifier(@ctx, root: root, identifier: theme)
63
+ return selected_theme || @ctx.abort(@ctx.message("theme.pull.theme_not_found", theme))
64
+ end
65
+
66
+ if live
67
+ return ShopifyCLI::Theme::Theme.live(@ctx, root: root)
68
+ end
69
+
70
+ if development
71
+ return ShopifyCLI::Theme::Theme.development(@ctx, root: root)
72
+ end
73
+
74
+ select_theme(root)
75
+ end
76
+
77
+ def select_theme(root)
78
+ form = Forms::Select.ask(
79
+ @ctx,
80
+ [],
81
+ title: @ctx.message("theme.pull.select"),
82
+ root: root,
83
+ )
84
+ form&.theme
85
+ end
58
86
  end
59
87
  end
60
88
  end
@@ -2,6 +2,7 @@
2
2
  require "shopify_cli/theme/theme"
3
3
  require "shopify_cli/theme/development_theme"
4
4
  require "shopify_cli/theme/ignore_filter"
5
+ require "shopify_cli/theme/include_filter"
5
6
  require "shopify_cli/theme/syncer"
6
7
 
7
8
  module Theme
@@ -10,12 +11,14 @@ module Theme
10
11
  options do |parser, flags|
11
12
  parser.on("-n", "--nodelete") { flags[:nodelete] = true }
12
13
  parser.on("-i", "--themeid=ID") { |theme_id| flags[:theme_id] = theme_id }
14
+ parser.on("-t", "--theme=NAME_OR_ID") { |theme| flags[:theme] = theme }
13
15
  parser.on("-l", "--live") { flags[:live] = true }
14
16
  parser.on("-d", "--development") { flags[:development] = true }
15
17
  parser.on("-u", "--unpublished") { flags[:unpublished] = true }
16
18
  parser.on("-j", "--json") { flags[:json] = true }
17
19
  parser.on("-a", "--allow-live") { flags[:allow_live] = true }
18
20
  parser.on("-p", "--publish") { flags[:publish] = true }
21
+ parser.on("-o", "--only=PATTERN") { |pattern| flags[:includes] = pattern }
19
22
  parser.on("-x", "--ignore=PATTERN") do |pattern|
20
23
  flags[:ignores] ||= []
21
24
  flags[:ignores] << pattern
@@ -25,30 +28,8 @@ module Theme
25
28
  def call(args, _name)
26
29
  root = args.first || "."
27
30
  delete = !options.flags[:nodelete]
28
-
29
- theme = if (theme_id = options.flags[:theme_id])
30
- ShopifyCLI::Theme::Theme.new(@ctx, root: root, id: theme_id)
31
- elsif options.flags[:live]
32
- ShopifyCLI::Theme::Theme.live(@ctx, root: root)
33
- elsif options.flags[:development]
34
- theme = ShopifyCLI::Theme::DevelopmentTheme.new(@ctx, root: root)
35
- theme.ensure_exists!
36
- theme
37
- elsif options.flags[:unpublished]
38
- name = CLI::UI::Prompt.ask(@ctx.message("theme.push.name"), allow_empty: false)
39
- theme = ShopifyCLI::Theme::Theme.new(@ctx, root: root, name: name, role: "unpublished")
40
- theme.create
41
- theme
42
- else
43
- form = Forms::Select.ask(
44
- @ctx,
45
- [],
46
- title: @ctx.message("theme.push.select"),
47
- root: root,
48
- )
49
- return unless form
50
- form.theme
51
- end
31
+ theme = find_theme(root, **options.flags)
32
+ return if theme.nil?
52
33
 
53
34
  if theme.live? && !options.flags[:allow_live]
54
35
  question = @ctx.message("theme.push.live")
@@ -56,10 +37,13 @@ module Theme
56
37
  return unless CLI::UI::Prompt.confirm(question)
57
38
  end
58
39
 
40
+ include_filter = ShopifyCLI::Theme::IncludeFilter.new(options.flags[:includes])
59
41
  ignore_filter = ShopifyCLI::Theme::IgnoreFilter.from_path(root)
60
42
  ignore_filter.add_patterns(options.flags[:ignores]) if options.flags[:ignores]
61
43
 
62
- syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme, ignore_filter: ignore_filter)
44
+ syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme,
45
+ include_filter: include_filter,
46
+ ignore_filter: ignore_filter)
63
47
  begin
64
48
  syncer.start_threads
65
49
  if options.flags[:json]
@@ -78,16 +62,67 @@ module Theme
78
62
  end
79
63
  end
80
64
  raise ShopifyCLI::AbortSilent if syncer.has_any_error?
81
- rescue ShopifyCLI::API::APIRequestNotFoundError
82
- @ctx.abort(@ctx.message("theme.push.theme_not_found", theme.id))
83
65
  ensure
84
66
  syncer.shutdown
85
67
  end
68
+ rescue ShopifyCLI::API::APIRequestNotFoundError
69
+ @ctx.abort(@ctx.message("theme.push.theme_not_found", "##{theme.id}"))
86
70
  end
87
71
 
88
72
  def self.help
89
73
  ShopifyCLI::Context.message("theme.push.help", ShopifyCLI::TOOL_NAME, ShopifyCLI::TOOL_NAME)
90
74
  end
75
+
76
+ private
77
+
78
+ def find_theme(root, theme_id: nil, theme: nil, live: nil, development: nil, unpublished: nil, **_args)
79
+ if theme_id
80
+ @ctx.warn(@ctx.message("theme.push.deprecated_themeid"))
81
+ return ShopifyCLI::Theme::Theme.new(@ctx, root: root, id: theme_id)
82
+ end
83
+
84
+ if live
85
+ return ShopifyCLI::Theme::Theme.live(@ctx, root: root)
86
+ end
87
+
88
+ if development
89
+ new_theme = ShopifyCLI::Theme::DevelopmentTheme.new(@ctx, root: root)
90
+ new_theme.ensure_exists!
91
+ return new_theme
92
+ end
93
+
94
+ if unpublished
95
+ name = theme || ask_theme_name
96
+ new_theme = ShopifyCLI::Theme::Theme.new(@ctx, root: root, name: name, role: "unpublished")
97
+ new_theme.create
98
+ return new_theme
99
+ end
100
+
101
+ if theme
102
+ selected_theme = ShopifyCLI::Theme::Theme.find_by_identifier(@ctx, root: root, identifier: theme)
103
+ return selected_theme || @ctx.abort(@ctx.message("theme.push.theme_not_found", theme))
104
+ end
105
+
106
+ select_theme(root)
107
+ end
108
+
109
+ def ask_theme_name
110
+ CLI::UI::Prompt.ask(@ctx.message("theme.push.name"), allow_empty: false)
111
+ end
112
+
113
+ def select_theme(root)
114
+ form = Forms::Select.ask(
115
+ @ctx,
116
+ [],
117
+ title: @ctx.message("theme.push.select"),
118
+ root: root,
119
+ )
120
+ form&.theme
121
+ end
122
+
123
+ def themes(root)
124
+ ShopifyCLI::Theme::Theme.all(@ctx, root: root)
125
+ end
91
126
  end
92
127
  end
93
128
  end
@@ -10,6 +10,7 @@ module Theme
10
10
  parser.on("--host=HOST") { |host| flags[:host] = host.to_s }
11
11
  parser.on("--port=PORT") { |port| flags[:port] = port.to_i }
12
12
  parser.on("--poll") { flags[:poll] = true }
13
+ parser.on("--live-reload=MODE") { |mode| flags[:mode] = as_reload_mode(mode) }
13
14
  end
14
15
 
15
16
  def call(*)
@@ -23,6 +24,10 @@ module Theme
23
24
  ShopifyCLI::Context.message("theme.serve.error.address_binding_error", ShopifyCLI::TOOL_NAME)
24
25
  end
25
26
 
27
+ def self.as_reload_mode(mode)
28
+ ShopifyCLI::Theme::DevServer::ReloadMode.get!(mode)
29
+ end
30
+
26
31
  def self.help
27
32
  ShopifyCLI::Context.message("theme.serve.help", ShopifyCLI::TOOL_NAME)
28
33
  end
@@ -57,14 +57,16 @@ module Theme
57
57
  Usage: {{command:%s theme push [ ROOT ]}}
58
58
 
59
59
  Options:
60
- {{command:-i, --themeid=THEMEID}} Theme ID. Must be an existing theme on your store.
61
- {{command:-l, --live}} Push to your remote live theme, and update your live store.
62
- {{command:-d, --development}} Push to your remote development theme, and create it if needed.
63
- {{command:-u, --unpublished}} Create a new unpublished theme and push to it.
64
- {{command:-n, --nodelete}} Runs the push command without deleting remote files from Shopify.
65
- {{command:-j, --json}} Output JSON instead of a UI.
66
- {{command:-a, --allow-live}} Allow push to a live theme.
67
- {{command:-p, --publish}} Publish as the live theme after uploading.
60
+ {{command:-t, --theme=NAME_OR_ID}} Theme ID or name of the remote theme.
61
+ {{command:-l, --live}} Push to your remote live theme, and update your live store.
62
+ {{command:-d, --development}} Push to your remote development theme, and create it if needed.
63
+ {{command:-u, --unpublished}} Create a new unpublished theme and push to it.
64
+ {{command:-n, --nodelete}} Runs the push command without deleting remote files from Shopify.
65
+ {{command:-j, --json}} Output JSON instead of a UI.
66
+ {{command:-a, --allow-live}} Allow push to a live theme.
67
+ {{command:-p, --publish}} Publish as the live theme after uploading.
68
+ {{command:-o, --only}} Upload only the specified files.
69
+ {{command:-x, --ignore}} Skip uploading the specified files.
68
70
 
69
71
  Run without options to select theme from a list.
70
72
  HELP
@@ -75,7 +77,10 @@ module Theme
75
77
  select: "Select theme to push to",
76
78
  live: "Are you sure you want to push to your live theme?",
77
79
  theme: "\n Theme: {{blue:%s #%s}} {{green:[live]}}",
78
- theme_not_found: "Theme #%s doesn't exist",
80
+ deprecated_themeid: <<~WARN,
81
+ {{warning:The {{command:-i, --themeid}} flag is deprecated. Use {{command:-t, --theme}} instead.}}
82
+ WARN
83
+ theme_not_found: "Theme \"%s\" doesn't exist",
79
84
  done: <<~DONE,
80
85
  {{green:Your theme was pushed successfully}}
81
86
 
@@ -94,10 +99,16 @@ module Theme
94
99
  Usage: {{command:%s theme serve}}
95
100
 
96
101
  Options:
97
- {{command:--port=PORT}} Local port to serve theme preview from
98
- {{command:--poll}} Force polling to detect file changes
99
- {{command:--host=HOST}} Set which network interface the web server listens on. The default value is 127.0.0.1.
102
+ {{command:--port=PORT}} Local port to serve theme preview from.
103
+ {{command:--poll}} Force polling to detect file changes.
104
+ {{command:--host=HOST}} Set which network interface the web server listens on. The default value is 127.0.0.1.
105
+ {{command:--live-reload=MODE}} The live reload mode switches the server behavior when a file is modified:
106
+ - {{command:hot-reload}} Hot reloads local changes to CSS and sections (default)
107
+ - {{command:full-page}} Always refreshes the entire page
108
+ - {{command:off}} Deactivate live reload
100
109
  HELP
110
+ reload_mode_is_not_valid: "The live reload mode `%s` is not valid.",
111
+ try_a_valid_reload_mode: "Try a valid live reload mode: %s.",
101
112
  viewing_theme: "Viewing theme…",
102
113
  syncing_theme: "Syncing theme #%s on %s",
103
114
  open_fail: "Couldn't open the theme",
@@ -131,9 +142,7 @@ module Theme
131
142
  You are not authorized to edit themes on %s.
132
143
  Make sure you are a user of that store, and allowed to edit themes.
133
144
  ENSURE_USER
134
- already_in_use_error: "Error",
135
145
  address_already_in_use: "The address \"%s\" is already in use.",
136
- try_this: "Try this",
137
146
  try_port_option: "Use the --port=PORT option to serve the theme in a different port.",
138
147
  },
139
148
  check: {
@@ -189,16 +198,22 @@ module Theme
189
198
  Usage: {{command:%s theme pull [ ROOT ]}}
190
199
 
191
200
  Options:
192
- {{command:-i, --themeid=THEMEID}} The Theme ID. Must be an existing theme on your store.
193
- {{command:-l, --live}} Pull theme files from your remote live theme.
194
- {{command:-n, --nodelete}} Runs the pull command without deleting local files.
201
+ {{command:-t, --theme=NAME_OR_ID}} Theme ID or name of the remote theme.
202
+ {{command:-l, --live}} Pull theme files from your remote live theme.
203
+ {{command:-d, --development}} Pull theme files from your remote development theme.
204
+ {{command:-n, --nodelete}} Runs the pull command without deleting local files.
205
+ {{command:-o, --only}} Download only the specified files.
206
+ {{command:-x, --ignore}} Skip downloading the specified files.
195
207
 
196
208
  Run without options to select theme from a list.
197
209
  HELP
198
210
  select: "Select a theme to pull from",
199
211
  pulling: "Pulling theme files from %s (#%s) on %s",
200
212
  done: "Theme pulled successfully",
201
- not_found: "{{x}} Theme #%s doesn't exist",
213
+ deprecated_themeid: <<~WARN,
214
+ {{warning:The {{command:-i, --themeid}} flag is deprecated. Use {{command:-t, --theme}} instead.}}
215
+ WARN
216
+ theme_not_found: "Theme \"%s\" doesn't exist",
202
217
  },
203
218
  },
204
219
  }.freeze
@@ -7,7 +7,7 @@ module ShopifyCLI
7
7
  PERMANENT_DOMAIN_SUFFIX = /\.myshopify\.(com|io)$/
8
8
 
9
9
  options do |parser, flags|
10
- parser.on("--store=STORE") { |url| flags[:shop] = url }
10
+ parser.on("-s", "--store=STORE") { |url| flags[:shop] = url }
11
11
  # backwards compatibility allow 'shop' for now
12
12
  parser.on("--shop=SHOP") { |url| flags[:shop] = url }
13
13
  parser.on("--password=PASSWORD") { |password| flags[:password] = password }
@@ -4,7 +4,7 @@ module ShopifyCLI
4
4
  module Commands
5
5
  class Switch < ShopifyCLI::Command
6
6
  options do |parser, flags|
7
- parser.on("--store=STORE") { |url| flags[:shop] = url }
7
+ parser.on("-s", "--store=STORE") { |url| flags[:shop] = url }
8
8
  # backwards compatibility allow 'shop' for now
9
9
  parser.on("--shop=SHOP") { |url| flags[:shop] = url }
10
10
  end
@@ -30,17 +30,22 @@ module ShopifyCLI
30
30
 
31
31
  module EnvironmentVariables
32
32
  STACKTRACE = "SHOPIFY_CLI_STACKTRACE"
33
+ TTY = "SHOPIFY_CLI_TTY"
33
34
 
34
35
  # When true the CLI points to a local instance of
35
36
  # the partners dashboard and identity.
36
37
  LOCAL_PARTNERS = "SHOPIFY_APP_CLI_LOCAL_PARTNERS"
37
38
 
38
- # When true the CLI points to a spin instance of spin
39
- SPIN_PARTNERS = "SHOPIFY_APP_CLI_SPIN_PARTNERS"
39
+ # When true the CLI points to spin instances of services
40
+ SPIN = "SPIN"
41
+ INFER_SPIN = "INFER_SPIN"
40
42
  SPIN_WORKSPACE = "SPIN_WORKSPACE"
41
43
  SPIN_NAMESPACE = "SPIN_NAMESPACE"
42
44
  SPIN_HOST = "SPIN_HOST"
43
45
 
46
+ # Deprecated, equivalent to using SPIN=1
47
+ SPIN_PARTNERS = "SHOPIFY_APP_CLI_SPIN_PARTNERS"
48
+
44
49
  # Environments
45
50
  TEST = "SHOPIFY_CLI_TEST"
46
51
  ACCEPTANCE_TEST = "SHOPIFY_CLI_ACCEPTANCE_TEST"
@@ -44,6 +44,56 @@ module ShopifyCLI
44
44
  str = Context.messages.dig(*key_parts)
45
45
  str ? str % params : key
46
46
  end
47
+
48
+ # a wrapper around Kernel.puts to allow for easy formatting
49
+ #
50
+ # #### Parameters
51
+ # * `text` - a string message to output
52
+ def puts(*args)
53
+ Kernel.puts(CLI::UI.fmt(*args))
54
+ end
55
+
56
+ # aborts the current running command and outputs an error message:
57
+ # - when the `help_message` is not provided, the error message appears in
58
+ # a red frame, prefixed by an ✗ icon
59
+ # - when the `help_message` is provided, the error message appears in a
60
+ # red frame, and the help message appears in a green frame
61
+ #
62
+ # #### Parameters
63
+ # * `error_message` - an error message to output
64
+ # * `help_message` - an optional help message
65
+ #
66
+ # #### Example
67
+ #
68
+ # ShopifyCLI::Context.abort("Execution error")
69
+ # # Output:
70
+ # # ┏━━ Error ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
71
+ # # ┃ ✗ Execution error
72
+ # # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
73
+ #
74
+ # ShopifyCLI::Context.abort("Execution error", "export EXECUTION=1")
75
+ # # Output:
76
+ # # ┏━━ Error ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
77
+ # # ┃ Execution error
78
+ # # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
79
+ # # ┏━━ Try this ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
80
+ # # ┃ export EXECUTION=1
81
+ # # ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
82
+ #
83
+ def abort(error_message, help_message = nil)
84
+ raise ShopifyCLI::Abort, "{{x}} #{error_message}" if help_message.nil?
85
+
86
+ frame(message("core.error"), color: :red) { self.puts(error_message) }
87
+ frame(message("core.try_this"), color: :green) { self.puts(help_message) }
88
+
89
+ raise ShopifyCLI::AbortSilent
90
+ end
91
+
92
+ private
93
+
94
+ def frame(title, color:, &block)
95
+ CLI::UI::Frame.open(title, color: CLI::UI.resolve_color(color), timing: false, &block)
96
+ end
47
97
  end
48
98
 
49
99
  # is the directory root that the current command is running in. If you want to
@@ -61,14 +111,19 @@ module ShopifyCLI
61
111
  # will return which operating system that the cli is running on [:mac, :linux]
62
112
  def os
63
113
  host = uname
64
- return :mac_m1 if /arm64-apple-darwin/i.match(host)
114
+ return :mac_m1 if /arm64.*darwin/i.match(host)
65
115
  return :mac if /darwin/i.match(host)
66
116
  return :windows if /mswin|mingw|cygwin/i.match(host)
67
117
  return :linux if /linux|bsd/i.match(host)
68
118
  :unknown
69
119
  end
70
120
 
71
- # will return true if the cli is running on an apple computer.
121
+ # will return true if the cli is running on an ARM Apple computer.
122
+ def mac_m1?
123
+ os == :mac_m1
124
+ end
125
+
126
+ # will return true if the cli is running on a Intel x86 Apple computer.
72
127
  def mac?
73
128
  os == :mac
74
129
  end
@@ -90,7 +145,7 @@ module ShopifyCLI
90
145
 
91
146
  # will return true if being launched from a tty
92
147
  def tty?
93
- !testing? && $stdin.tty?
148
+ $stdin.tty?
94
149
  end
95
150
 
96
151
  # will return true if the cli is being run from an installation, and not a
@@ -329,7 +384,7 @@ module ShopifyCLI
329
384
  system("xdg-open", uri.to_s)
330
385
  elsif windows?
331
386
  system("start \"\" \"#{uri}\"")
332
- elsif mac?
387
+ elsif mac? || mac_m1?
333
388
  system("open", uri.to_s)
334
389
  else
335
390
  open_url!(uri)
@@ -349,13 +404,13 @@ module ShopifyCLI
349
404
  puts "{{yellow:*}} #{text}"
350
405
  end
351
406
 
352
- # a wrapper around Kernel.puts to allow for easy formatting
407
+ # proxy call to Context.puts.
353
408
  #
354
409
  # #### Parameters
355
410
  # * `text` - a string message to output
356
411
  #
357
412
  def puts(*args)
358
- Kernel.puts(CLI::UI.fmt(*args))
413
+ Context.puts(*args)
359
414
  end
360
415
 
361
416
  # a wrapper around $stderr.puts to allow for easy formatting
@@ -385,14 +440,13 @@ module ShopifyCLI
385
440
  puts("{{v}} #{text}")
386
441
  end
387
442
 
388
- # aborts the current running command and outputs an error message, prefixed
389
- # by a red x
443
+ # proxy call to Context.abort.
390
444
  #
391
445
  # #### Parameters
392
- # * `text` - a string message to output
393
- #
394
- def abort(text)
395
- raise ShopifyCLI::Abort, "{{x}} #{text}"
446
+ # * `error_message` - an error message to output
447
+ # * `help_message` - an optional help message
448
+ def abort(error_message, help_message = nil)
449
+ Context.abort(error_message, help_message)
396
450
  end
397
451
 
398
452
  # outputs a message, prefixed by a red `DEBUG` tag. This will only output to
@@ -3,10 +3,10 @@ require "shopify_cli"
3
3
  module ShopifyCLI
4
4
  module Core
5
5
  class Executor < CLI::Kit::Executor
6
- def initialize(ctx, task_registry, *args, **kwargs)
7
- @ctx = ctx || ShopifyCLI::Context.new
8
- @task_registry = task_registry || ShopifyCLI::Tasks::TaskRegistry.new
9
- super(*args, **kwargs)
6
+ ruby2_keywords def initialize(ctx, task_registry, *args)
7
+ @ctx = ctx || ShopifyCli::Context.new
8
+ @task_registry = task_registry || ShopifyCli::Tasks::TaskRegistry.new
9
+ super(*args)
10
10
  end
11
11
 
12
12
  def call(command, command_name, args)