shopify-cli 1.1.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
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