shopify-cli 2.7.3 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +44 -0
  4. data/Gemfile.lock +1 -1
  5. data/RELEASING.md +4 -3
  6. data/dev.yml +2 -2
  7. data/ext/javy/javy.rb +8 -9
  8. data/lib/graphql/get_extension_registrations.graphql +27 -0
  9. data/lib/project_types/extension/cli.rb +27 -2
  10. data/lib/project_types/extension/commands/build.rb +10 -10
  11. data/lib/project_types/extension/commands/create.rb +2 -3
  12. data/lib/project_types/extension/commands/push.rb +36 -8
  13. data/lib/project_types/extension/extension_project.rb +1 -1
  14. data/lib/project_types/extension/features/argo_serve.rb +6 -5
  15. data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
  16. data/lib/project_types/extension/loaders/project.rb +29 -0
  17. data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
  18. data/lib/project_types/extension/messages/messages.rb +4 -0
  19. data/lib/project_types/extension/models/app.rb +1 -1
  20. data/lib/project_types/extension/models/development_server.rb +2 -4
  21. data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
  22. data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
  23. data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
  24. data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
  25. data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
  26. data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
  27. data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
  28. data/lib/project_types/rails/gem.rb +1 -2
  29. data/lib/project_types/script/cli.rb +7 -0
  30. data/lib/project_types/script/commands/connect.rb +19 -0
  31. data/lib/project_types/script/commands/create.rb +8 -2
  32. data/lib/project_types/script/commands/push.rb +35 -12
  33. data/lib/project_types/script/layers/application/connect_app.rb +15 -3
  34. data/lib/project_types/script/layers/application/create_script.rb +16 -16
  35. data/lib/project_types/script/layers/application/extension_points.rb +50 -26
  36. data/lib/project_types/script/layers/application/push_script.rb +5 -2
  37. data/lib/project_types/script/layers/domain/errors.rb +3 -2
  38. data/lib/project_types/script/layers/domain/extension_point.rb +14 -0
  39. data/lib/project_types/script/layers/domain/script_config.rb +6 -4
  40. data/lib/project_types/script/layers/infrastructure/errors.rb +38 -23
  41. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +49 -28
  42. data/lib/project_types/script/layers/infrastructure/script_service.rb +22 -5
  43. data/lib/project_types/script/loaders/project.rb +44 -0
  44. data/lib/project_types/script/loaders/specification_handler.rb +22 -0
  45. data/lib/project_types/script/messages/messages.rb +39 -16
  46. data/lib/project_types/script/ui/error_handler.rb +46 -29
  47. data/lib/project_types/theme/commands/pull.rb +45 -17
  48. data/lib/project_types/theme/commands/push.rb +65 -28
  49. data/lib/project_types/theme/commands/serve.rb +5 -0
  50. data/lib/project_types/theme/messages/messages.rb +34 -18
  51. data/lib/shopify_cli/command.rb +6 -0
  52. data/lib/shopify_cli/commands/login.rb +1 -1
  53. data/lib/shopify_cli/commands/switch.rb +1 -1
  54. data/lib/shopify_cli/constants.rb +11 -2
  55. data/lib/shopify_cli/context.rb +66 -12
  56. data/lib/shopify_cli/core/executor.rb +4 -4
  57. data/lib/shopify_cli/environment.rb +50 -20
  58. data/lib/shopify_cli/form.rb +2 -0
  59. data/lib/shopify_cli/identity_auth.rb +4 -3
  60. data/lib/shopify_cli/messages/messages.rb +9 -1
  61. data/lib/shopify_cli/method_object.rb +21 -9
  62. data/lib/shopify_cli/partners_api/app_extensions/job.rb +36 -0
  63. data/lib/shopify_cli/partners_api/app_extensions.rb +46 -0
  64. data/lib/shopify_cli/partners_api/organizations.rb +2 -5
  65. data/lib/shopify_cli/partners_api.rb +1 -0
  66. data/lib/shopify_cli/project.rb +8 -7
  67. data/lib/shopify_cli/resources/env_file.rb +18 -6
  68. data/lib/shopify_cli/result.rb +61 -59
  69. data/lib/shopify_cli/task.rb +5 -3
  70. data/lib/shopify_cli/theme/dev_server/cdn/cdn_helper.rb +49 -0
  71. data/lib/shopify_cli/theme/dev_server/cdn_assets.rb +69 -0
  72. data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +8 -28
  73. data/lib/shopify_cli/theme/dev_server/hot-reload.js +34 -3
  74. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +18 -2
  75. data/lib/shopify_cli/theme/dev_server/local_assets.rb +4 -0
  76. data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
  77. data/lib/shopify_cli/theme/dev_server/proxy.rb +10 -15
  78. data/lib/shopify_cli/theme/dev_server/reload_mode.rb +34 -0
  79. data/lib/shopify_cli/theme/dev_server.rb +8 -21
  80. data/lib/shopify_cli/theme/file.rb +2 -2
  81. data/lib/shopify_cli/theme/filter/path_matcher.rb +38 -0
  82. data/lib/shopify_cli/theme/ignore_filter.rb +14 -18
  83. data/lib/shopify_cli/theme/include_filter.rb +43 -0
  84. data/lib/shopify_cli/theme/syncer.rb +17 -2
  85. data/lib/shopify_cli/theme/theme.rb +26 -4
  86. data/lib/shopify_cli/thread_pool/job.rb +27 -0
  87. data/lib/shopify_cli/thread_pool.rb +37 -0
  88. data/lib/shopify_cli/version.rb +1 -1
  89. data/lib/shopify_cli.rb +6 -1
  90. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
  91. data/vendor/deps/ruby2_keywords/LICENSE +22 -0
  92. data/vendor/deps/ruby2_keywords/README.md +67 -0
  93. data/vendor/deps/ruby2_keywords/Rakefile +54 -0
  94. data/vendor/deps/ruby2_keywords/lib/ruby2_keywords.rb +57 -0
  95. data/vendor/deps/ruby2_keywords/ruby2_keywords.gemspec +18 -0
  96. data/vendor/deps/ruby2_keywords/test/test_keyword.rb +41 -0
  97. metadata +28 -4
  98. data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
  99. data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
@@ -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,39 +28,22 @@ 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
- return unless CLI::UI::Prompt.confirm(@ctx.message("theme.push.live"))
35
+ question = @ctx.message("theme.push.live")
36
+ question += @ctx.message("theme.push.theme", theme.name, theme.id) if options.flags[:live]
37
+ return unless CLI::UI::Prompt.confirm(question)
55
38
  end
56
39
 
40
+ include_filter = ShopifyCLI::Theme::IncludeFilter.new(options.flags[:includes])
57
41
  ignore_filter = ShopifyCLI::Theme::IgnoreFilter.from_path(root)
58
42
  ignore_filter.add_patterns(options.flags[:ignores]) if options.flags[:ignores]
59
43
 
60
- 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)
61
47
  begin
62
48
  syncer.start_threads
63
49
  if options.flags[:json]
@@ -76,16 +62,67 @@ module Theme
76
62
  end
77
63
  end
78
64
  raise ShopifyCLI::AbortSilent if syncer.has_any_error?
79
- rescue ShopifyCLI::API::APIRequestNotFoundError
80
- @ctx.abort(@ctx.message("theme.push.theme_not_found", theme.id))
81
65
  ensure
82
66
  syncer.shutdown
83
67
  end
68
+ rescue ShopifyCLI::API::APIRequestNotFoundError
69
+ @ctx.abort(@ctx.message("theme.push.theme_not_found", "##{theme.id}"))
84
70
  end
85
71
 
86
72
  def self.help
87
73
  ShopifyCLI::Context.message("theme.push.help", ShopifyCLI::TOOL_NAME, ShopifyCLI::TOOL_NAME)
88
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
89
126
  end
90
127
  end
91
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
@@ -74,7 +76,11 @@ module Theme
74
76
  push: "Pushing theme files to Shopify",
75
77
  select: "Select theme to push to",
76
78
  live: "Are you sure you want to push to your live theme?",
77
- theme_not_found: "Theme #%s doesn't exist",
79
+ theme: "\n Theme: {{blue:%s #%s}} {{green:[live]}}",
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",
78
84
  done: <<~DONE,
79
85
  {{green:Your theme was pushed successfully}}
80
86
 
@@ -93,10 +99,16 @@ module Theme
93
99
  Usage: {{command:%s theme serve}}
94
100
 
95
101
  Options:
96
- {{command:--port=PORT}} Local port to serve theme preview from
97
- {{command:--poll}} Force polling to detect file changes
98
- {{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
99
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.",
100
112
  viewing_theme: "Viewing theme…",
101
113
  syncing_theme: "Syncing theme #%s on %s",
102
114
  open_fail: "Couldn't open the theme",
@@ -130,9 +142,7 @@ module Theme
130
142
  You are not authorized to edit themes on %s.
131
143
  Make sure you are a user of that store, and allowed to edit themes.
132
144
  ENSURE_USER
133
- already_in_use_error: "Error",
134
145
  address_already_in_use: "The address \"%s\" is already in use.",
135
- try_this: "Try this",
136
146
  try_port_option: "Use the --port=PORT option to serve the theme in a different port.",
137
147
  },
138
148
  check: {
@@ -188,16 +198,22 @@ module Theme
188
198
  Usage: {{command:%s theme pull [ ROOT ]}}
189
199
 
190
200
  Options:
191
- {{command:-i, --themeid=THEMEID}} The Theme ID. Must be an existing theme on your store.
192
- {{command:-l, --live}} Pull theme files from your remote live theme.
193
- {{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.
194
207
 
195
208
  Run without options to select theme from a list.
196
209
  HELP
197
210
  select: "Select a theme to pull from",
198
211
  pulling: "Pulling theme files from %s (#%s) on %s",
199
212
  done: "Theme pulled successfully",
200
- 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",
201
217
  },
202
218
  },
203
219
  }.freeze
@@ -29,6 +29,12 @@ module ShopifyCLI
29
29
  run_prerequisites
30
30
  cmd.call(args, command_name)
31
31
  end
32
+ rescue OptionParser::InvalidOption => error
33
+ arg = error.args.first
34
+ raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.invalid_option", arg)
35
+ rescue OptionParser::MissingArgument => error
36
+ arg = error.args.first
37
+ raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.missing_argument", arg)
32
38
  end
33
39
 
34
40
  def options(&block)
@@ -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"
@@ -61,5 +66,9 @@ module ShopifyCLI
61
66
  module Links
62
67
  NEW_ISSUE = "https://github.com/Shopify/shopify-cli/issues/new"
63
68
  end
69
+
70
+ module Extension
71
+ DEFAULT_PORT = 39351
72
+ end
64
73
  end
65
74
  end
@@ -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
- $stdin.tty? && !testing?
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)
@@ -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)
@@ -15,6 +15,8 @@ module ShopifyCLI
15
15
  rescue ShopifyCLI::Abort => err
16
16
  ctx.puts(err.message)
17
17
  nil
18
+ rescue ShopifyCLI::AbortSilent
19
+ nil
18
20
  end
19
21
  end
20
22
 
@@ -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
@@ -14,6 +14,12 @@ module ShopifyCLI
14
14
  },
15
15
  },
16
16
  core: {
17
+ errors: {
18
+ option_parser: {
19
+ invalid_option: "The option {{command:%s}} is not supported.",
20
+ missing_argument: "The required argument {{command:%s}} is missing.",
21
+ },
22
+ },
17
23
  app: {
18
24
  help: <<~HELP,
19
25
  Suite of commands for developing apps. See {{command:%1$s app <command> --help}} for usage of each command.
@@ -719,7 +725,7 @@ module ShopifyCLI
719
725
  signup_suggestion: <<~MESSAGE,
720
726
  {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
721
727
  account at {{underline:https://ngrok.com/signup}}. After you signup, install your
722
- personalized authorization token using {{command:%s [ node | rails ] tunnel auth <token>}}.
728
+ personalized authorization token using {{command:%s app tunnel auth <token>}}.
723
729
  MESSAGE
724
730
  start: "{{v}} ngrok tunnel running at {{underline:%s}}",
725
731
  start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
@@ -784,6 +790,8 @@ module ShopifyCLI
784
790
  logged_in_partner_only: "Logged into partner organization {{green:%s}}",
785
791
  logged_in_partner_and_shop: "Logged into store {{green:%s}} in partner organization {{green:%s}}",
786
792
  },
793
+ error: "Error",
794
+ try_this: "Try this",
787
795
  },
788
796
  }.freeze
789
797
  end