shopify-cli 1.1.1 → 1.4.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CONTRIBUTING.md +1 -1
  3. data/.gitignore +0 -1
  4. data/.rubocop.yml +6 -2
  5. data/.rubocop_todo.yml +20 -0
  6. data/CHANGELOG.md +24 -0
  7. data/Gemfile +1 -2
  8. data/Gemfile.lock +11 -11
  9. data/RELEASING.md +22 -10
  10. data/docs/core/index.md +16 -0
  11. data/lib/project_types/extension/cli.rb +6 -1
  12. data/lib/project_types/extension/commands/register.rb +1 -1
  13. data/lib/project_types/extension/features/argo/admin.rb +20 -0
  14. data/lib/project_types/extension/features/argo/base.rb +129 -0
  15. data/lib/project_types/extension/features/argo/checkout.rb +20 -0
  16. data/lib/project_types/extension/features/argo_config.rb +60 -0
  17. data/lib/project_types/extension/messages/messages.rb +11 -2
  18. data/lib/project_types/extension/models/type.rb +4 -0
  19. data/lib/project_types/extension/models/types/checkout_post_purchase.rb +6 -3
  20. data/lib/project_types/extension/models/types/product_subscription.rb +24 -0
  21. data/lib/project_types/node/commands/create.rb +6 -1
  22. data/lib/project_types/node/commands/serve.rb +5 -5
  23. data/lib/project_types/node/messages/messages.rb +4 -1
  24. data/lib/project_types/rails/commands/create.rb +10 -2
  25. data/lib/project_types/rails/commands/serve.rb +5 -5
  26. data/lib/project_types/rails/messages/messages.rb +5 -1
  27. data/lib/project_types/script/config/extension_points.yml +8 -8
  28. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +2 -2
  29. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +36 -1
  30. data/lib/project_types/script/layers/infrastructure/errors.rb +7 -0
  31. data/lib/project_types/script/messages/messages.rb +12 -37
  32. data/lib/project_types/script/ui/error_handler.rb +13 -5
  33. data/lib/shopify-cli/api.rb +5 -9
  34. data/lib/shopify-cli/commands/config.rb +33 -1
  35. data/lib/shopify-cli/commands/system.rb +9 -0
  36. data/lib/shopify-cli/context.rb +40 -0
  37. data/lib/shopify-cli/core/entry_point.rb +3 -0
  38. data/lib/shopify-cli/git.rb +1 -1
  39. data/lib/shopify-cli/http_request.rb +15 -0
  40. data/lib/shopify-cli/js_system.rb +22 -5
  41. data/lib/shopify-cli/messages/messages.rb +39 -11
  42. data/lib/shopify-cli/project.rb +3 -3
  43. data/lib/shopify-cli/shopifolk.rb +67 -0
  44. data/lib/shopify-cli/tunnel.rb +1 -1
  45. data/lib/shopify-cli/version.rb +1 -1
  46. data/lib/shopify_cli.rb +1 -0
  47. metadata +10 -4
  48. data/lib/project_types/extension/features/argo.rb +0 -48
  49. data/lib/project_types/extension/models/types/subscription_management.rb +0 -20
@@ -1,5 +1,4 @@
1
1
  require 'shopify_cli'
2
- require 'net/http'
3
2
 
4
3
  module ShopifyCli
5
4
  class API
@@ -32,8 +31,9 @@ module ShopifyCli
32
31
  )
33
32
  ctx.debug(resp)
34
33
  resp
35
- rescue API::APIRequestServerError, API::APIRequestUnexpectedError
34
+ rescue API::APIRequestServerError, API::APIRequestUnexpectedError => e
36
35
  ctx.puts(ctx.message('core.api.error.internal_server_error'))
36
+ ctx.debug(ctx.message('core.api.error.internal_server_error_debug', e.message))
37
37
  end
38
38
 
39
39
  protected
@@ -58,14 +58,10 @@ module ShopifyCli
58
58
  unless uri.is_a?(URI::HTTP)
59
59
  ctx.abort("Invalid URL: #{graphql_url}")
60
60
  end
61
- http = ::Net::HTTP.new(uri.host, uri.port)
62
- http.use_ssl = true
63
61
 
64
- req = ::Net::HTTP::Post.new(uri.request_uri)
65
- req.body = JSON.dump(query: body.tr("\n", ""), variables: variables)
66
- req['Content-Type'] = 'application/json'
67
- headers.each { |header, value| req[header] = value }
68
- response = http.request(req)
62
+ # we delay this require so as to avoid a performance hit on starting the CLI
63
+ require 'shopify-cli/http_request'
64
+ response = HttpRequest.call(uri, body, variables, headers)
69
65
 
70
66
  case response.code.to_i
71
67
  when 200..399
@@ -6,6 +6,7 @@ module ShopifyCli
6
6
  hidden_feature(feature_set: :debug)
7
7
 
8
8
  subcommand :Feature, 'feature'
9
+ subcommand :Analytics, 'analytics'
9
10
 
10
11
  def call(*)
11
12
  @ctx.puts(self.class.help)
@@ -16,6 +17,10 @@ module ShopifyCli
16
17
  end
17
18
 
18
19
  class Feature < ShopifyCli::SubCommand
20
+ def self.help
21
+ ShopifyCli::Context.message('core.config.feature.help', ShopifyCli::TOOL_NAME)
22
+ end
23
+
19
24
  options do |parser, flags|
20
25
  parser.on('--enable') { flags[:action] = 'enable' }
21
26
  parser.on('--disable') { flags[:action] = 'disable' }
@@ -28,7 +33,7 @@ module ShopifyCli
28
33
  is_enabled = ShopifyCli::Feature.enabled?(feature_name)
29
34
  if options.flags[:action] == 'disable' && is_enabled
30
35
  ShopifyCli::Feature.disable(feature_name)
31
- @ctx.puts(@ctx.message('core.config.feature.disabled', is_enabled))
36
+ @ctx.puts(@ctx.message('core.config.feature.disabled', feature_name))
32
37
  elsif options.flags[:action] == 'enable' && !is_enabled
33
38
  ShopifyCli::Feature.enable(feature_name)
34
39
  @ctx.puts(@ctx.message('core.config.feature.enabled', feature_name))
@@ -39,6 +44,33 @@ module ShopifyCli
39
44
  end
40
45
  end
41
46
  end
47
+
48
+ class Analytics < ShopifyCli::SubCommand
49
+ def self.help
50
+ ShopifyCli::Context.message('core.config.analytics.help', ShopifyCli::TOOL_NAME)
51
+ end
52
+
53
+ options do |parser, flags|
54
+ parser.on('--enable') { flags[:action] = 'enable' }
55
+ parser.on('--disable') { flags[:action] = 'disable' }
56
+ parser.on('--status') { flags[:action] = 'status' }
57
+ end
58
+
59
+ def call(_args, _name)
60
+ is_enabled = ShopifyCli::Config.get_bool('analytics', 'enabled')
61
+ if options.flags[:action] == 'disable' && is_enabled
62
+ ShopifyCli::Config.set('analytics', 'enabled', false)
63
+ @ctx.puts(@ctx.message('core.config.analytics.disabled'))
64
+ elsif options.flags[:action] == 'enable' && !is_enabled
65
+ ShopifyCli::Config.set('analytics', 'enabled', true)
66
+ @ctx.puts(@ctx.message('core.config.analytics.enabled'))
67
+ elsif is_enabled
68
+ @ctx.puts(@ctx.message('core.config.analytics.is_enabled'))
69
+ else
70
+ @ctx.puts(@ctx.message('core.config.analytics.is_disabled'))
71
+ end
72
+ end
73
+ end
42
74
  end
43
75
  end
44
76
  end
@@ -23,6 +23,7 @@ module ShopifyCli
23
23
  display_cli_ruby(show_all_details)
24
24
  display_utility_commands(show_all_details)
25
25
  display_project_commands(show_all_details)
26
+ display_shopify_staff_identity if show_all_details
26
27
  end
27
28
 
28
29
  def self.help
@@ -139,6 +140,14 @@ module ShopifyCli
139
140
  end
140
141
  @ctx.puts("")
141
142
  end
143
+
144
+ def display_shopify_staff_identity
145
+ is_shopifolk = ShopifyCli::Shopifolk.check
146
+ if is_shopifolk
147
+ @ctx.puts("\n" + @ctx.message('core.system.identity_header'))
148
+ @ctx.puts(" " + @ctx.message('core.system.identity_is_shopifolk'))
149
+ end
150
+ end
142
151
  end
143
152
  end
144
153
  end
@@ -2,6 +2,8 @@
2
2
  require 'shopify_cli'
3
3
  require 'fileutils'
4
4
  require 'rbconfig'
5
+ require 'net/http'
6
+ require 'json'
5
7
 
6
8
  module ShopifyCli
7
9
  ##
@@ -11,6 +13,11 @@ module ShopifyCli
11
13
  # resoures.
12
14
  #
13
15
  class Context
16
+ GEM_LATEST_URI = URI.parse('https://rubygems.org/api/v1/versions/shopify-cli/latest.json')
17
+ VERSION_CHECK_SECTION = 'versioncheck'
18
+ LAST_CHECKED_AT_FIELD = 'last_checked_at'
19
+ VERSION_CHECK_INTERVAL = 86400
20
+
14
21
  class << self
15
22
  attr_reader :messages
16
23
 
@@ -446,6 +453,23 @@ module ShopifyCli
446
453
  nil
447
454
  end
448
455
 
456
+ # Checks if there's a newer version of the CLI available and returns version string if
457
+ # this should be conveyed to the user (i.e., if it's been over 24 hours since last check)
458
+ #
459
+ # #### Parameters
460
+ #
461
+ # #### Returns
462
+ # - `version`: string of newer version available, IFF new version is available AND it's time to inform user,
463
+ # : nil otherwise
464
+ #
465
+ def new_version
466
+ if (time_of_last_check + VERSION_CHECK_INTERVAL) < (now = Time.now.to_i)
467
+ update_time_of_last_check(now)
468
+ latest_version = retrieve_latest_gem_version
469
+ latest_version unless latest_version == ShopifyCli::VERSION
470
+ end
471
+ end
472
+
449
473
  private
450
474
 
451
475
  def ctx_path(fname)
@@ -456,5 +480,21 @@ module ShopifyCli
456
480
  File.join(root, fname)
457
481
  end
458
482
  end
483
+
484
+ def retrieve_latest_gem_version
485
+ response = Net::HTTP.get_response(GEM_LATEST_URI)
486
+ latest = JSON.parse(response.body)
487
+ latest["version"]
488
+ rescue
489
+ nil
490
+ end
491
+
492
+ def time_of_last_check
493
+ (val = ShopifyCli::Config.get(VERSION_CHECK_SECTION, LAST_CHECKED_AT_FIELD)) ? val.to_i : 0
494
+ end
495
+
496
+ def update_time_of_last_check(time)
497
+ ShopifyCli::Config.set(VERSION_CHECK_SECTION, LAST_CHECKED_AT_FIELD, time)
498
+ end
459
499
  end
460
500
  end
@@ -28,6 +28,9 @@ module ShopifyCli
28
28
  ctx.puts(
29
29
  ctx.message('core.warning.development_version', File.join(ShopifyCli::ROOT, 'bin', ShopifyCli::TOOL_NAME))
30
30
  )
31
+ elsif !ctx.testing?
32
+ new_version = ctx.new_version
33
+ ctx.puts(ctx.message('core.warning.new_version', ShopifyCli::VERSION, new_version)) unless new_version.nil?
31
34
  end
32
35
 
33
36
  ProjectType.load_type(Project.current_project_type)
@@ -125,13 +125,13 @@ module ShopifyCli
125
125
 
126
126
  success = Open3.popen3('git', *git_command, '--progress') do |_stdin, _stdout, stderr, thread|
127
127
  while (line = stderr.gets)
128
+ msg << line.chomp
128
129
  next unless line.strip.start_with?('Receiving objects:')
129
130
  percent = (line.match(/Receiving objects:\s+(\d+)/)[1].to_f / 100).round(2)
130
131
  bar.tick(set_percent: percent)
131
132
  next
132
133
  end
133
134
 
134
- msg << stderr
135
135
  thread.value
136
136
  end.success?
137
137
 
@@ -0,0 +1,15 @@
1
+ require 'net/http'
2
+
3
+ module ShopifyCli
4
+ class HttpRequest
5
+ def self.call(uri, body, variables, headers)
6
+ http = ::Net::HTTP.new(uri.host, uri.port)
7
+ http.use_ssl = true
8
+ req = ::Net::HTTP::Post.new(uri.request_uri)
9
+ req.body = JSON.dump(query: body.tr("\n", ""), variables: variables)
10
+ req['Content-Type'] = 'application/json'
11
+ headers.each { |header, value| req[header] = value }
12
+ http.request(req)
13
+ end
14
+ end
15
+ end
@@ -76,23 +76,40 @@ module ShopifyCli
76
76
  # - `ctx`: running context from your command
77
77
  # - `yarn`: The proc, array, or string command to run if yarn is available
78
78
  # - `npm`: The proc, array, or string command to run if npm is available
79
+ # - `capture_response`: The boolean flag to capture the output of the running command if it is set to true
79
80
  #
80
81
  # #### Example
81
82
  #
82
- # ShopifyCli::JsSystem.new(ctx: ctx).call(yarn: ['install', '--silent'], npm: ['install', '--no-audit'])
83
+ # ShopifyCli::JsSystem.new(ctx: ctx).call(
84
+ # yarn: ['install', '--silent'],
85
+ # npm: ['install', '--no-audit'],
86
+ # capture_response: false
87
+ # )
83
88
  #
84
- def call(yarn:, npm:)
85
- yarn? ? call_command(yarn, YARN_CORE_COMMAND) : call_command(npm, NPM_CORE_COMMAND)
89
+ def call(yarn:, npm:, capture_response: false)
90
+ if yarn?
91
+ call_command(yarn, YARN_CORE_COMMAND, capture_response)
92
+ else
93
+ call_command(npm, NPM_CORE_COMMAND, capture_response)
94
+ end
86
95
  end
87
96
 
88
97
  private
89
98
 
90
- def call_command(command, core_command)
99
+ def call_command(command, core_command, capture_response)
91
100
  if command.is_a?(String) || command.is_a?(Array)
92
- CLI::Kit::System.system(core_command, *command, chdir: ctx.root).success?
101
+ capture_response ? call_with_capture(command, core_command) : call_without_capture(command, core_command)
93
102
  else
94
103
  command.call
95
104
  end
96
105
  end
106
+
107
+ def call_with_capture(command, core_command)
108
+ CLI::Kit::System.capture3(core_command, *command, chdir: ctx.root)
109
+ end
110
+
111
+ def call_without_capture(command, core_command)
112
+ CLI::Kit::System.system(core_command, *command, chdir: ctx.root).success?
113
+ end
97
114
  end
98
115
  end
@@ -48,13 +48,27 @@ module ShopifyCli
48
48
  config: {
49
49
  help: <<~HELP,
50
50
  Change configuration of how the CLI operates
51
- Usage: {{command:%s config [ feature ] [ feature_name ] }}
51
+ Usage: {{command:%s config [ feature | analytics ] }}
52
52
  HELP
53
53
  feature: {
54
- enabled: "{{v}} feature {{green:%s}} was enabled",
55
- disabled: "{{v}} feature {{green:%s}} was disabled",
56
- is_enabled: "{{v}} feature {{green:%s}} is enabled",
57
- is_disabled: "{{v}} feature {{green:%s}} is disabled",
54
+ help: <<~HELP,
55
+ Change configuration of various features
56
+ Usage: {{command:%s config [ feature ] [ feature_name ] }}
57
+ HELP
58
+ enabled: "{{v}} feature {{green:%s}} has been enabled",
59
+ disabled: "{{v}} feature {{green:%s}} has been disabled",
60
+ is_enabled: "{{v}} feature {{green:%s}} is currently enabled",
61
+ is_disabled: "{{v}} feature {{green:%s}} is currently disabled",
62
+ },
63
+ analytics: {
64
+ help: <<~HELP,
65
+ Opt in/out of anonymous usage reporting
66
+ Usage: {{command:%s config [ analytics ] }}
67
+ HELP
68
+ enabled: "{{v}} analytics have been enabled",
69
+ disabled: "{{v}} analytics have been disabled",
70
+ is_enabled: "{{v}} analytics are currently enabled",
71
+ is_disabled: "{{v}} analytics are currently disabled",
58
72
  },
59
73
  },
60
74
 
@@ -163,6 +177,7 @@ module ShopifyCli
163
177
  api: {
164
178
  error: {
165
179
  internal_server_error: '{{red:{{x}} An unexpected error occurred on Shopify.}}',
180
+ internal_server_error_debug: "\n{{red:Response details:}}\n%s\n\n",
166
181
  },
167
182
  },
168
183
 
@@ -184,11 +199,14 @@ module ShopifyCli
184
199
  {{x}} You are not in a Shopify app project
185
200
  {{yellow:{{*}}}}{{reset: Run}}{{cyan: shopify create}}{{reset: to create your app}}
186
201
  MESSAGE
187
- cli_yaml: {
188
- not_hash: "{{x}} .shopify-cli.yml was not a proper YAML file. Expecting a hash.",
189
- invalid: "{{x}} %s contains invalid YAML: %s",
190
- not_found: "{{x}} %s not found",
191
- },
202
+ },
203
+ },
204
+
205
+ yaml: {
206
+ error: {
207
+ not_hash: "{{x}} %s was not a proper YAML file. Expecting a hash.",
208
+ invalid: "{{x}} %s contains invalid YAML: %s",
209
+ not_found: "{{x}} %s not found",
192
210
  },
193
211
  },
194
212
 
@@ -234,6 +252,8 @@ module ShopifyCli
234
252
  },
235
253
  environment_header: "{{bold:Environment}}",
236
254
  env: "%-17s = %s",
255
+ identity_header: "{{bold:Identity}}",
256
+ identity_is_shopifolk: "{{v}} Checked user settings: you’re Shopify staff!",
237
257
  },
238
258
 
239
259
  tasks: {
@@ -317,7 +337,7 @@ module ShopifyCli
317
337
  warning: {
318
338
  development_version: <<~DEVELOPMENT,
319
339
  {{*}} {{yellow:You are running a development version of the CLI at:}}
320
- {{yellow:%s}}
340
+ {{yellow:%s}}
321
341
 
322
342
  DEVELOPMENT
323
343
 
@@ -328,6 +348,14 @@ module ShopifyCli
328
348
  {{underline:https://shopify.github.io/shopify-app-cli/migrate/}}
329
349
 
330
350
  MESSAGE
351
+
352
+ new_version: <<~MESSAGE,
353
+ {{*}} {{yellow:A new version of the Shopify App CLI is available! You have version %s and the latest version is %s.
354
+
355
+ To upgrade, follow the instructions for the package manager you’re using:
356
+ {{underline:https://shopify.github.io/shopify-app-cli/getting-started/upgrade/}}}}
357
+
358
+ MESSAGE
331
359
  },
332
360
  },
333
361
  }.freeze
@@ -166,7 +166,7 @@ module ShopifyCli
166
166
  @config ||= begin
167
167
  config = load_yaml_file('.shopify-cli.yml')
168
168
  unless config.is_a?(Hash)
169
- raise ShopifyCli::Abort, Context.message('core.project.error.cli_yaml.not_hash')
169
+ raise ShopifyCli::Abort, Context.message('core.yaml.error.not_hash', '.shopify-cli.yml')
170
170
  end
171
171
 
172
172
  # The app_type key was deprecated in favour of project_type, so replace it
@@ -187,12 +187,12 @@ module ShopifyCli
187
187
  begin
188
188
  YAML.load_file(f)
189
189
  rescue Psych::SyntaxError => e
190
- raise(ShopifyCli::Abort, Context.message('core.project.error.cli_yaml.invalid', relative_path, e.message))
190
+ raise(ShopifyCli::Abort, Context.message('core.yaml.error.invalid', relative_path, e.message))
191
191
  # rescue Errno::EACCES => e
192
192
  # TODO
193
193
  # Dev::Helpers::EaccesHandler.diagnose_and_raise(f, e, mode: :read)
194
194
  rescue Errno::ENOENT
195
- raise ShopifyCli::Abort, Context.message('core.project.error.cli_yaml.not_found', f)
195
+ raise ShopifyCli::Abort, Context.message('core.yaml.error.not_found', f)
196
196
  end
197
197
  end
198
198
  end
@@ -0,0 +1,67 @@
1
+ module ShopifyCli
2
+ ##
3
+ # ShopifyCli::Shopifolk contains the logic to determine if the user appears to be a Shopify staff
4
+ #
5
+ # The Shopifolk Feature flag will persist between runs so if the flag is enabled or disabled,
6
+ # it will still be in that same state until the next class invocation.
7
+ class Shopifolk
8
+ GCLOUD_CONFIG_FILE = File.expand_path('~/.config/gcloud/configurations/config_default')
9
+ DEV_PATH = '/opt/dev'
10
+ SECTION = 'core'
11
+ FEATURE_NAME = 'shopifolk'
12
+
13
+ ##
14
+ # will return if the user appears to be a Shopify employee, based on several heuristics
15
+ #
16
+ # #### Returns
17
+ #
18
+ # * `is_shopifolk` - returns true if the user is a Shopify Employee
19
+ #
20
+ # #### Example
21
+ #
22
+ # ShopifyCli::Shopifolk.check
23
+ #
24
+ def self.check
25
+ ShopifyCli::Shopifolk.new.shopifolk?
26
+ end
27
+
28
+ ##
29
+ # will return if the user is a Shopify employee
30
+ #
31
+ # #### Returns
32
+ #
33
+ # * `is_shopifolk` - returns true if the user has `dev` installed and
34
+ # a valid google cloud config file with email ending in "@shopify.com"
35
+ #
36
+ def shopifolk?
37
+ return true if Feature.enabled?(FEATURE_NAME)
38
+ if shopifolk_by_gcloud? && shopifolk_by_dev?
39
+ ShopifyCli::Feature.enable(FEATURE_NAME)
40
+ true
41
+ else
42
+ ShopifyCli::Feature.disable(FEATURE_NAME)
43
+ false
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def shopifolk_by_gcloud?
50
+ ini&.dig("[#{SECTION}]", 'account')&.match?(/@shopify.com\z/)
51
+ end
52
+
53
+ def shopifolk_by_dev?
54
+ File.exist?("#{DEV_PATH}/bin/dev") && File.exist?("#{DEV_PATH}/.shopify-build")
55
+ end
56
+
57
+ def ini
58
+ @ini ||= begin
59
+ if File.exist?(GCLOUD_CONFIG_FILE)
60
+ CLI::Kit::Ini
61
+ .new(GCLOUD_CONFIG_FILE, default_section: "[#{SECTION}]", convert_types: false)
62
+ .tap(&:parse).ini
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end