shopify-cli 2.9.0 → 2.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.yaml +117 -0
  3. data/.github/ISSUE_TEMPLATE/enhancement.yaml +38 -0
  4. data/.github/ISSUE_TEMPLATE/feature.yaml +47 -0
  5. data/.github/ISSUE_TEMPLATE.md +18 -0
  6. data/CHANGELOG.md +38 -5
  7. data/Gemfile.lock +1 -1
  8. data/dev.yml +3 -0
  9. data/lib/project_types/extension/commands/build.rb +3 -0
  10. data/lib/project_types/extension/commands/check.rb +3 -0
  11. data/lib/project_types/extension/commands/create.rb +3 -0
  12. data/lib/project_types/extension/commands/push.rb +3 -0
  13. data/lib/project_types/extension/commands/serve.rb +3 -0
  14. data/lib/project_types/extension/models/specification_handlers/default.rb +1 -1
  15. data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
  16. data/lib/project_types/script/cli.rb +5 -0
  17. data/lib/project_types/script/commands/connect.rb +3 -1
  18. data/lib/project_types/script/commands/create.rb +2 -0
  19. data/lib/project_types/script/commands/push.rb +6 -0
  20. data/lib/project_types/script/config/extension_points.yml +12 -0
  21. data/lib/project_types/script/graphql/app_script_set.graphql +2 -0
  22. data/lib/project_types/script/graphql/module_upload_url_generate.graphql +5 -1
  23. data/lib/project_types/script/layers/application/build_script.rb +6 -3
  24. data/lib/project_types/script/layers/application/project_dependencies.rb +1 -1
  25. data/lib/project_types/script/layers/application/push_script.rb +38 -30
  26. data/lib/project_types/script/layers/domain/errors.rb +10 -3
  27. data/lib/project_types/script/layers/domain/extension_point.rb +2 -2
  28. data/lib/project_types/script/layers/domain/push_package.rb +0 -3
  29. data/lib/project_types/script/layers/domain/script_config.rb +6 -4
  30. data/lib/project_types/script/layers/domain/script_project.rb +1 -0
  31. data/lib/project_types/script/layers/infrastructure/errors.rb +47 -24
  32. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +2 -12
  33. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +1 -0
  34. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +1 -0
  35. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +2 -12
  36. data/lib/project_types/script/layers/infrastructure/languages/wasm_project_creator.rb +15 -0
  37. data/lib/project_types/script/layers/infrastructure/languages/wasm_task_runner.rb +36 -0
  38. data/lib/project_types/script/layers/infrastructure/metadata_repository.rb +18 -0
  39. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +7 -8
  40. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +45 -54
  41. data/lib/project_types/script/layers/infrastructure/script_service.rb +35 -12
  42. data/lib/project_types/script/layers/infrastructure/script_uploader.rb +22 -9
  43. data/lib/project_types/script/loaders/project.rb +2 -1
  44. data/lib/project_types/script/messages/messages.rb +94 -88
  45. data/lib/project_types/script/ui/error_handler.rb +75 -38
  46. data/lib/project_types/theme/commands/check.rb +3 -0
  47. data/lib/project_types/theme/commands/delete.rb +3 -0
  48. data/lib/project_types/theme/commands/init.rb +3 -0
  49. data/lib/project_types/theme/commands/language_server.rb +3 -0
  50. data/lib/project_types/theme/commands/package.rb +3 -0
  51. data/lib/project_types/theme/commands/publish.rb +3 -0
  52. data/lib/project_types/theme/commands/pull.rb +12 -1
  53. data/lib/project_types/theme/commands/push.rb +12 -1
  54. data/lib/project_types/theme/commands/serve.rb +3 -0
  55. data/lib/project_types/theme/messages/messages.rb +4 -0
  56. data/lib/shopify_cli/command/sub_command.rb +2 -0
  57. data/lib/shopify_cli/command.rb +66 -0
  58. data/lib/shopify_cli/commands/app/create/node.rb +3 -0
  59. data/lib/shopify_cli/commands/app/create/rails.rb +3 -0
  60. data/lib/shopify_cli/commands/app/create.rb +3 -0
  61. data/lib/shopify_cli/commands/app/deploy.rb +3 -0
  62. data/lib/shopify_cli/commands/app/serve.rb +3 -0
  63. data/lib/shopify_cli/commands/login.rb +4 -10
  64. data/lib/shopify_cli/constants.rb +18 -2
  65. data/lib/shopify_cli/core/executor.rb +4 -4
  66. data/lib/shopify_cli/environment.rb +61 -16
  67. data/lib/shopify_cli/exception_reporter.rb +9 -0
  68. data/lib/shopify_cli/github/issue_url_generator.rb +19 -8
  69. data/lib/shopify_cli/identity_auth/env_auth_token.rb +34 -0
  70. data/lib/shopify_cli/identity_auth.rb +36 -18
  71. data/lib/shopify_cli/messages/messages.rb +2 -2
  72. data/lib/shopify_cli/method_object.rb +21 -9
  73. data/lib/shopify_cli/partners_api.rb +7 -2
  74. data/lib/shopify_cli/result.rb +61 -59
  75. data/lib/shopify_cli/services/app/create/rails_service.rb +37 -13
  76. data/lib/shopify_cli/task.rb +5 -3
  77. data/lib/shopify_cli/theme/file.rb +2 -2
  78. data/lib/shopify_cli/theme/filter/path_matcher.rb +38 -0
  79. data/lib/shopify_cli/theme/ignore_filter.rb +14 -18
  80. data/lib/shopify_cli/theme/include_filter.rb +65 -0
  81. data/lib/shopify_cli/theme/syncer.rb +17 -2
  82. data/lib/shopify_cli/utilities.rb +7 -0
  83. data/lib/shopify_cli/version.rb +1 -1
  84. data/lib/shopify_cli.rb +3 -1
  85. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +11 -6
  86. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +5 -1
  87. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +6 -4
  88. data/vendor/deps/ruby2_keywords/LICENSE +22 -0
  89. data/vendor/deps/ruby2_keywords/README.md +67 -0
  90. data/vendor/deps/ruby2_keywords/Rakefile +54 -0
  91. data/vendor/deps/ruby2_keywords/lib/ruby2_keywords.rb +57 -0
  92. data/vendor/deps/ruby2_keywords/ruby2_keywords.gemspec +18 -0
  93. data/vendor/deps/ruby2_keywords/test/test_keyword.rb +41 -0
  94. data/vendor/lib/semantic/version.rb +0 -1
  95. metadata +18 -3
  96. data/lib/project_types/rails/commands/create.rb +0 -210
@@ -2,11 +2,15 @@
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
8
9
  class Command
9
10
  class Push < ShopifyCLI::Command::SubCommand
11
+ recommend_default_node_range
12
+ recommend_default_ruby_range
13
+
10
14
  options do |parser, flags|
11
15
  parser.on("-n", "--nodelete") { flags[:nodelete] = true }
12
16
  parser.on("-i", "--themeid=ID") { |theme_id| flags[:theme_id] = theme_id }
@@ -17,6 +21,10 @@ module Theme
17
21
  parser.on("-j", "--json") { flags[:json] = true }
18
22
  parser.on("-a", "--allow-live") { flags[:allow_live] = true }
19
23
  parser.on("-p", "--publish") { flags[:publish] = true }
24
+ parser.on("-o", "--only=PATTERN") do |pattern|
25
+ flags[:includes] ||= []
26
+ flags[:includes] << pattern
27
+ end
20
28
  parser.on("-x", "--ignore=PATTERN") do |pattern|
21
29
  flags[:ignores] ||= []
22
30
  flags[:ignores] << pattern
@@ -35,10 +43,13 @@ module Theme
35
43
  return unless CLI::UI::Prompt.confirm(question)
36
44
  end
37
45
 
46
+ include_filter = ShopifyCLI::Theme::IncludeFilter.new(options.flags[:includes])
38
47
  ignore_filter = ShopifyCLI::Theme::IgnoreFilter.from_path(root)
39
48
  ignore_filter.add_patterns(options.flags[:ignores]) if options.flags[:ignores]
40
49
 
41
- syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme, ignore_filter: ignore_filter)
50
+ syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme,
51
+ include_filter: include_filter,
52
+ ignore_filter: ignore_filter)
42
53
  begin
43
54
  syncer.start_threads
44
55
  if options.flags[:json]
@@ -4,6 +4,9 @@ require "shopify_cli/theme/dev_server"
4
4
  module Theme
5
5
  class Command
6
6
  class Serve < ShopifyCLI::Command::SubCommand
7
+ recommend_default_node_range
8
+ recommend_default_ruby_range
9
+
7
10
  DEFAULT_HTTP_HOST = "127.0.0.1"
8
11
 
9
12
  options do |parser, flags|
@@ -65,6 +65,8 @@ module Theme
65
65
  {{command:-j, --json}} Output JSON instead of a UI.
66
66
  {{command:-a, --allow-live}} Allow push to a live theme.
67
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
@@ -200,6 +202,8 @@ module Theme
200
202
  {{command:-l, --live}} Pull theme files from your remote live theme.
201
203
  {{command:-d, --development}} Pull theme files from your remote development theme.
202
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.
203
207
 
204
208
  Run without options to select theme from a list.
205
209
  HELP
@@ -9,6 +9,8 @@ module ShopifyCLI
9
9
  cmd = new(@ctx)
10
10
  args = cmd.options.parse(@_options, args || [])
11
11
  return call_help(parent_command, command_name) if cmd.options.help
12
+ check_ruby_version
13
+ check_node_version
12
14
  run_prerequisites
13
15
 
14
16
  cmd.call(args, command_name)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  require "shopify_cli"
3
+ require "semantic/semantic"
3
4
 
4
5
  module ShopifyCLI
5
6
  class Command < CLI::Kit::BaseCommand
@@ -7,6 +8,8 @@ module ShopifyCLI
7
8
  autoload :AppSubCommand, "shopify_cli/command/app_sub_command"
8
9
  autoload :ProjectCommand, "shopify_cli/command/project_command"
9
10
 
11
+ VersionRange = Struct.new(:from, :to, keyword_init: true)
12
+
10
13
  extend Feature::Set
11
14
 
12
15
  attr_writer :ctx
@@ -26,6 +29,8 @@ module ShopifyCLI
26
29
  cmd = new(@ctx)
27
30
  cmd.options.parse(@_options, args)
28
31
  return call_help(command_name) if cmd.options.help
32
+ check_ruby_version
33
+ check_node_version
29
34
  run_prerequisites
30
35
  cmd.call(args, command_name)
31
36
  end
@@ -58,6 +63,67 @@ module ShopifyCLI
58
63
  )
59
64
  end
60
65
 
66
+ def recommend_ruby(from:, to:)
67
+ @compatible_ruby_range = VersionRange.new(
68
+ from: Semantic::Version.new(from),
69
+ to: Semantic::Version.new(to)
70
+ )
71
+ end
72
+
73
+ def recommend_default_ruby_range
74
+ recommend_ruby(
75
+ from: Constants::SupportedVersions::Ruby::FROM,
76
+ to: Constants::SupportedVersions::Ruby::TO
77
+ )
78
+ end
79
+
80
+ def check_ruby_version
81
+ check_version(
82
+ Environment.ruby_version,
83
+ range: @compatible_ruby_range,
84
+ runtime: "Ruby"
85
+ )
86
+ end
87
+
88
+ def recommend_node(from:, to:)
89
+ @compatible_node_range = VersionRange.new(
90
+ from: Semantic::Version.new(from),
91
+ to: Semantic::Version.new(to)
92
+ )
93
+ end
94
+
95
+ def recommend_default_node_range
96
+ recommend_node(
97
+ from: Constants::SupportedVersions::Node::FROM,
98
+ to: Constants::SupportedVersions::Node::TO
99
+ )
100
+ end
101
+
102
+ def check_node_version
103
+ check_version(
104
+ Environment.node_version,
105
+ range: @compatible_node_range,
106
+ runtime: "Node"
107
+ )
108
+ end
109
+
110
+ def check_version(version, range:, runtime:)
111
+ return if Environment.test?
112
+ return if range.nil?
113
+
114
+ version_without_pre_nor_build = Utilities.version_dropping_pre_and_build(version)
115
+ is_higher_than_bottom = version_without_pre_nor_build >= Utilities.version_dropping_pre_and_build(range.from)
116
+ is_lower_than_top = version_without_pre_nor_build < Utilities.version_dropping_pre_and_build(range.to)
117
+ return if is_higher_than_bottom && is_lower_than_top
118
+
119
+ Context.new.warn("Your environment #{runtime} version, #{version},"\
120
+ " is outside of the range supported by the CLI,"\
121
+ " #{range.from}..<#{range.to},"\
122
+ " and might cause incompatibility issues.")
123
+ rescue StandardError => error
124
+ ExceptionReporter.report_error_silently(error)
125
+ end
126
+
61
127
  def prerequisite_task(*tasks_without_args, **tasks_with_args)
62
128
  @prerequisite_tasks ||= []
63
129
  @prerequisite_tasks += tasks_without_args.map { |t| PrerequisiteTask.new(t) }
@@ -5,6 +5,9 @@ module ShopifyCLI
5
5
  class Node < ShopifyCLI::Command::AppSubCommand
6
6
  prerequisite_task :ensure_authenticated
7
7
 
8
+ recommend_default_node_range
9
+ recommend_default_ruby_range
10
+
8
11
  options do |parser, flags|
9
12
  parser.on("--name=NAME") { |t| flags[:name] = t }
10
13
  parser.on("--organization-id=ID") { |id| flags[:organization_id] = id }
@@ -5,6 +5,9 @@ module ShopifyCLI
5
5
  class Rails < ShopifyCLI::Command::AppSubCommand
6
6
  prerequisite_task :ensure_authenticated
7
7
 
8
+ recommend_default_node_range
9
+ recommend_default_ruby_range
10
+
8
11
  options do |parser, flags|
9
12
  parser.on("--name=NAME") { |t| flags[:name] = t }
10
13
  parser.on("--organization-id=ID") { |id| flags[:organization_id] = id }
@@ -6,6 +6,9 @@ module ShopifyCLI
6
6
  subcommand :PHP, "php", "shopify_cli/commands/app/create/php"
7
7
  subcommand :Node, "node", "shopify_cli/commands/app/create/node"
8
8
 
9
+ recommend_default_node_range
10
+ recommend_default_ruby_range
11
+
9
12
  def call(_args, _command_name)
10
13
  @ctx.puts(self.class.help)
11
14
  end
@@ -4,6 +4,9 @@ module ShopifyCLI
4
4
  class Deploy < ShopifyCLI::Command::AppSubCommand
5
5
  subcommand :Heroku, "heroku", "shopify_cli/commands/app/deploy/heroku"
6
6
 
7
+ recommend_default_node_range
8
+ recommend_default_ruby_range
9
+
7
10
  def call(args, _name)
8
11
  platform = args.shift
9
12
  case platform
@@ -6,6 +6,9 @@ module ShopifyCLI
6
6
 
7
7
  prerequisite_task :ensure_env, :ensure_dev_store
8
8
 
9
+ recommend_default_ruby_range
10
+ recommend_default_node_range
11
+
9
12
  options do |parser, flags|
10
13
  parser.on("--host=HOST") do |h|
11
14
  flags[:host] = h.gsub('"', "")
@@ -15,6 +15,8 @@ module ShopifyCLI
15
15
 
16
16
  def call(*)
17
17
  shop = (options.flags[:shop] || @ctx.getenv("SHOPIFY_SHOP" || nil))
18
+ ShopifyCLI::DB.set(shop: self.class.validate_shop(shop, context: @ctx)) unless shop.nil?
19
+
18
20
  if shop.nil? && Shopifolk.check
19
21
  Shopifolk.reset
20
22
  @ctx.puts(@ctx.message("core.tasks.select_org_and_shop.identified_as_shopify"))
@@ -31,25 +33,17 @@ module ShopifyCLI
31
33
  IdentityAuth.new(ctx: @ctx).authenticate
32
34
  org = select_organization
33
35
  ShopifyCLI::DB.set(organization_id: org["id"].to_i) unless org.nil?
34
-
36
+ Whoami.call([], "whoami")
35
37
  end
36
- # validate that shop belongs to organization
37
- ShopifyCLI::DB.set(shop: self.class.validate_shop(shop: shop, org: org, context: @ctx)) unless shop.nil?
38
- Whoami.call([], "whoami")
39
38
  end
40
39
 
41
40
  def self.help
42
41
  ShopifyCLI::Context.message("core.login.help", ShopifyCLI::TOOL_NAME)
43
42
  end
44
43
 
45
- def self.validate_shop(shop:, org:, context:)
44
+ def self.validate_shop(shop, context:)
46
45
  permanent_domain = shop_to_permanent_domain(shop)
47
46
  context.abort(context.message("core.login.invalid_shop", shop)) unless permanent_domain
48
- if org
49
- stores_owned = org["stores"]
50
- is_verified = stores_owned.any? { |store| store["shopDomain"] == permanent_domain }
51
- context.abort(context.message("core.login.invalid_shop", shop)) unless is_verified
52
- end
53
47
  permanent_domain
54
48
  end
55
49
 
@@ -36,12 +36,16 @@ module ShopifyCLI
36
36
  # the partners dashboard and identity.
37
37
  LOCAL_PARTNERS = "SHOPIFY_APP_CLI_LOCAL_PARTNERS"
38
38
 
39
- # When true the CLI points to a spin instance of spin
40
- 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"
41
42
  SPIN_WORKSPACE = "SPIN_WORKSPACE"
42
43
  SPIN_NAMESPACE = "SPIN_NAMESPACE"
43
44
  SPIN_HOST = "SPIN_HOST"
44
45
 
46
+ # Deprecated, equivalent to using SPIN=1
47
+ SPIN_PARTNERS = "SHOPIFY_APP_CLI_SPIN_PARTNERS"
48
+
45
49
  # Environments
46
50
  TEST = "SHOPIFY_CLI_TEST"
47
51
  ACCEPTANCE_TEST = "SHOPIFY_CLI_ACCEPTANCE_TEST"
@@ -54,6 +58,18 @@ module ShopifyCLI
54
58
  MONORAIL_REAL_EVENTS = "MONORAIL_REAL_EVENTS"
55
59
  end
56
60
 
61
+ module SupportedVersions
62
+ module Ruby
63
+ FROM = "2.6.6"
64
+ TO = "3.1.0"
65
+ end
66
+
67
+ module Node
68
+ FROM = "12.0.0"
69
+ TO = "17.0.0"
70
+ end
71
+ end
72
+
57
73
  module Identity
58
74
  CLIENT_ID_DEV = "e5380e02-312a-7408-5718-e07017e9cf52"
59
75
  CLIENT_ID = "fbdb2649-e327-4907-8f67-908d24cfd7e3"
@@ -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)
@@ -1,9 +1,25 @@
1
+ require "semantic/semantic"
2
+
1
3
  module ShopifyCLI
2
4
  # The environment module provides an interface to get information from
3
5
  # the environment in which the CLI runs
4
6
  module Environment
5
7
  TRUTHY_ENV_VARIABLE_VALUES = ["1", "true", "TRUE", "yes", "YES"]
6
8
 
9
+ def self.ruby_version(context: Context.new)
10
+ out, err, stat = context.capture3('ruby -e "puts RUBY_VERSION"')
11
+ raise ShopifyCLI::Abort, err unless stat.success?
12
+ out = out.gsub('"', "")
13
+ ::Semantic::Version.new(out.chomp)
14
+ end
15
+
16
+ def self.node_version(context: Context.new)
17
+ out, err, stat = context.capture3("node", "--version")
18
+ raise ShopifyCLI::Abort, err unless stat.success?
19
+ out = out.gsub("v", "")
20
+ ::Semantic::Version.new(out.chomp)
21
+ end
22
+
7
23
  def self.interactive=(interactive)
8
24
  @interactive = interactive
9
25
  end
@@ -61,17 +77,10 @@ module ShopifyCLI
61
77
  )
62
78
  end
63
79
 
64
- def self.use_spin_partners_instance?(env_variables: ENV)
65
- env_variable_truthy?(
66
- Constants::EnvironmentVariables::SPIN_PARTNERS,
67
- env_variables: env_variables
68
- )
69
- end
70
-
71
80
  def self.partners_domain(env_variables: ENV)
72
81
  if use_local_partners_instance?(env_variables: env_variables)
73
82
  "partners.myshopify.io"
74
- elsif use_spin_partners_instance?(env_variables: env_variables)
83
+ elsif use_spin?(env_variables: env_variables)
75
84
  "partners.#{spin_url(env_variables: env_variables)}"
76
85
  else
77
86
  "partners.shopify.com"
@@ -79,15 +88,41 @@ module ShopifyCLI
79
88
  end
80
89
 
81
90
  def self.use_spin?(env_variables: ENV)
82
- !env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE].nil? &&
83
- !env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE].nil?
91
+ env_variable_truthy?(
92
+ Constants::EnvironmentVariables::SPIN,
93
+ env_variables: env_variables
94
+ ) || env_variable_truthy?(
95
+ Constants::EnvironmentVariables::SPIN_PARTNERS,
96
+ env_variables: env_variables
97
+ )
98
+ end
99
+
100
+ def self.infer_spin?(env_variables: ENV)
101
+ env_variable_truthy?(
102
+ Constants::EnvironmentVariables::INFER_SPIN,
103
+ env_variables: env_variables
104
+ )
84
105
  end
85
106
 
86
107
  def self.spin_url(env_variables: ENV)
87
- spin_workspace = spin_workspace(env_variables: env_variables)
88
- spin_namespace = spin_namespace(env_variables: env_variables)
89
- spin_host = spin_host(env_variables: env_variables)
90
- "#{spin_workspace}.#{spin_namespace}.#{spin_host}"
108
+ if infer_spin?(env_variables: env_variables)
109
+ # TODO: Remove version check and delete spin-legacy branch
110
+ # once spin2 becomes the installed "spin" binary by default
111
+ spin_version = %x(spin version 2> /dev/null).strip
112
+ if spin_version.start_with?("spin-")
113
+ # spin2
114
+ raise ShopifyCLI:: Abort, "SPIN_INSTANCE must be specified" unless ENV.key?("SPIN_INSTANCE")
115
+ %x(spin show -o fqdn 2> /dev/null).strip
116
+ else
117
+ # spin-legacy
118
+ %x(spin info fqdn 2> /dev/null).strip
119
+ end
120
+ else
121
+ spin_workspace = spin_workspace(env_variables: env_variables)
122
+ spin_namespace = spin_namespace(env_variables: env_variables)
123
+ spin_host = spin_host(env_variables: env_variables)
124
+ "#{spin_workspace}.#{spin_namespace}.#{spin_host}"
125
+ end
91
126
  end
92
127
 
93
128
  def self.send_monorail_events?(env_variables: ENV)
@@ -106,11 +141,21 @@ module ShopifyCLI
106
141
  end
107
142
 
108
143
  def self.spin_workspace(env_variables: ENV)
109
- env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE]
144
+ env_value = env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE]
145
+ return env_value unless env_value.nil?
146
+
147
+ if env_value.nil?
148
+ raise "No value set for #{Constants::EnvironmentVariables::SPIN_WORKSPACE}"
149
+ end
110
150
  end
111
151
 
112
152
  def self.spin_namespace(env_variables: ENV)
113
- env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE]
153
+ env_value = env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE]
154
+ return env_value unless env_value.nil?
155
+
156
+ if env_value.nil?
157
+ raise "No value set for #{Constants::EnvironmentVariables::SPIN_NAMESPACE}"
158
+ end
114
159
  end
115
160
 
116
161
  def self.spin_host(env_variables: ENV)
@@ -1,5 +1,10 @@
1
1
  module ShopifyCLI
2
2
  module ExceptionReporter
3
+ def self.report_error_silently(error)
4
+ return unless ReportingConfigurationController.reporting_enabled?
5
+ report_to_bugsnag(error: error)
6
+ end
7
+
3
8
  def self.report(error, _logs = nil, _api_key = nil, custom_metadata = {})
4
9
  context = ShopifyCLI::Context.new
5
10
  unless ShopifyCLI::Environment.development?
@@ -19,6 +24,10 @@ module ShopifyCLI
19
24
  return unless reportable_error?(error)
20
25
 
21
26
  return unless report?(context: context)
27
+ report_to_bugsnag(error: error, custom_metadata: custom_metadata)
28
+ end
29
+
30
+ def self.report_to_bugsnag(error:, custom_metadata: {})
22
31
  ENV["BUGSNAG_DISABLE_AUTOCONFIGURE"] = "1"
23
32
  require "bugsnag"
24
33
 
@@ -2,17 +2,28 @@ module ShopifyCLI
2
2
  module GitHub
3
3
  module IssueURLGenerator
4
4
  def self.error_url(error)
5
- title = "#{error.class}: #{error.message}"
5
+ title = "[Bug]: #{error.class}: #{error.message}"
6
6
  labels = "type:bug"
7
- content = File.read(File.join(ShopifyCLI::ROOT, ".github/ISSUE_TEMPLATE.md"))
8
7
 
9
8
  # take at most 5 lines from backtrace
10
- stacktrace = error.backtrace.length < 5 ? error.backtrace : error.backtrace[0..4]
11
- body = stacktrace.join("\n").to_s
12
- output = content.gsub(/<!--Stacktrace(.|\n)*-->/, body)
13
- query = URI.encode_www_form({ title: title, body: output, labels: labels })
14
- url = "#{ShopifyCLI::Constants::Links::NEW_ISSUE}?#{query}"
15
- url
9
+ stacktrace_text =
10
+ if error.backtrace # Sometimes errors seem to appear without backtrace, see https://github.com/Shopify/shopify-cli/issues/1972#issuecomment-1028013630
11
+ stacktrace = error.backtrace.length < 5 ? error.backtrace : error.backtrace[0..4]
12
+ stacktrace.join("\n").to_s
13
+ else
14
+ ""
15
+ end
16
+ query = URI.encode_www_form({
17
+ title: title,
18
+ labels: labels,
19
+ template: "bug_report.yaml",
20
+ stack_trace: stacktrace_text,
21
+ os: RUBY_PLATFORM,
22
+ cli_version: ShopifyCLI::VERSION,
23
+ ruby_version: "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}",
24
+ shell: ENV["SHELL"],
25
+ })
26
+ "#{ShopifyCLI::Constants::Links::NEW_ISSUE}?#{query}"
16
27
  end
17
28
  end
18
29
  end
@@ -0,0 +1,34 @@
1
+ module ShopifyCLI
2
+ class IdentityAuth
3
+ class EnvAuthToken
4
+ Token = Struct.new(:token, :expires_at, keyword_init: true)
5
+
6
+ class << self
7
+ attr_accessor :exchanged_partners_token
8
+
9
+ def partners_token_present?
10
+ Environment.auth_token
11
+ end
12
+
13
+ def fetch_exchanged_partners_token
14
+ current_time = Time.now.to_i
15
+
16
+ # If we have an in-memory token that hasn't expired yet, we reuse it.
17
+ if exchanged_partners_token && current_time < exchanged_partners_token.expires_at.to_i
18
+ return exchanged_partners_token.token
19
+ end
20
+
21
+ new_exchanged_token = yield(Environment.auth_token)
22
+ token = new_exchanged_token["access_token"]
23
+ expires_in = new_exchanged_token["expires_in"].to_i
24
+ expires_at = Time.at(current_time + expires_in)
25
+
26
+ token = Token.new(token: token, expires_at: expires_at)
27
+
28
+ self.exchanged_partners_token = token
29
+ token.token
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -13,6 +13,7 @@ module ShopifyCLI
13
13
  include SmartProperties
14
14
 
15
15
  autoload :Servlet, "shopify_cli/identity_auth/servlet"
16
+ autoload :EnvAuthToken, "shopify_cli/identity_auth/env_auth_token"
16
17
 
17
18
  class Error < StandardError; end
18
19
  class Timeout < StandardError; end
@@ -68,9 +69,12 @@ module ShopifyCLI
68
69
  request_exchange_tokens
69
70
  end
70
71
 
71
- def self.fetch_or_auth_partners_token(ctx:)
72
- env_var_auth_token = Environment.auth_token
73
- return env_var_auth_token if env_var_auth_token
72
+ def fetch_or_auth_partners_token
73
+ if EnvAuthToken.partners_token_present?
74
+ return EnvAuthToken.fetch_exchanged_partners_token do |env_token|
75
+ exchange_partners_auth_token(env_token)
76
+ end
77
+ end
74
78
 
75
79
  ShopifyCLI::DB.get(:partners_exchange_token) do
76
80
  IdentityAuth.new(ctx: ctx).authenticate
@@ -78,6 +82,15 @@ module ShopifyCLI
78
82
  end
79
83
  end
80
84
 
85
+ def exchange_partners_auth_token(subject_token)
86
+ application = "partners"
87
+ request_exchange_token(
88
+ audience: client_id_for_application(application),
89
+ scopes: APPLICATION_SCOPES[application],
90
+ subject_token: subject_token,
91
+ )
92
+ end
93
+
81
94
  def self.environment_auth_token?
82
95
  !!Environment.auth_token
83
96
  end
@@ -195,30 +208,35 @@ module ShopifyCLI
195
208
 
196
209
  def request_exchange_tokens
197
210
  APPLICATION_SCOPES.each do |key, scopes|
198
- request_exchange_token(key, client_id_for_application(key), scopes)
211
+ request_and_save_exchange_token(key, client_id_for_application(key), scopes)
199
212
  end
200
213
  end
201
214
 
202
- def request_exchange_token(name, audience, additional_scopes)
215
+ def request_and_save_exchange_token(name, audience, additional_scopes)
203
216
  return if name == "shopify" && !store.exists?(:shop)
217
+ access_token = request_exchange_token(
218
+ audience: audience,
219
+ scopes: scopes(additional_scopes),
220
+ subject_token: store.get(:identity_access_token),
221
+ destination: name == "shopify" ? "https://#{store.get(:shop)}/admin" : nil
222
+ )["access_token"]
223
+ store.set("#{name}_exchange_token".to_sym => access_token)
224
+ ctx.debug("#{name}_exchange_token: " + access_token)
225
+ end
204
226
 
227
+ def request_exchange_token(audience:, scopes:, subject_token:, destination: nil)
205
228
  params = {
206
229
  grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
207
230
  requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
208
231
  subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
209
232
  client_id: client_id,
210
233
  audience: audience,
211
- scope: scopes(additional_scopes),
212
- subject_token: store.get(:identity_access_token),
213
- }.tap do |result|
214
- if name == "shopify"
215
- result[:destination] = "https://#{store.get(:shop)}/admin"
216
- end
217
- end
234
+ scope: scopes,
235
+ subject_token: subject_token,
236
+ destination: destination,
237
+ }.compact
218
238
  # ctx.debug(params)
219
- resp = post_token_request(params)
220
- store.set("#{name}_exchange_token".to_sym => resp["access_token"])
221
- ctx.debug("#{name}_exchange_token: " + resp["access_token"])
239
+ post_token_request(params)
222
240
  end
223
241
 
224
242
  def post_token_request(params)
@@ -256,7 +274,7 @@ module ShopifyCLI
256
274
  def auth_url
257
275
  if Environment.use_local_partners_instance?
258
276
  "https://identity.myshopify.io/oauth"
259
- elsif Environment.use_spin_partners_instance?
277
+ elsif Environment.use_spin?
260
278
  "https://identity.#{Environment.spin_url}/oauth"
261
279
  else
262
280
  "https://accounts.shopify.com/oauth"
@@ -264,7 +282,7 @@ module ShopifyCLI
264
282
  end
265
283
 
266
284
  def client_id_for_application(application_name)
267
- client_ids = if Environment.use_local_partners_instance? || Environment.use_spin_partners_instance?
285
+ client_ids = if Environment.use_local_partners_instance? || Environment.use_spin?
268
286
  DEV_APPLICATION_CLIENT_IDS
269
287
  else
270
288
  APPLICATION_CLIENT_IDS
@@ -280,7 +298,7 @@ module ShopifyCLI
280
298
  end
281
299
 
282
300
  def client_id
283
- if Environment.use_local_partners_instance? || Environment.use_spin_partners_instance?
301
+ if Environment.use_local_partners_instance? || Environment.use_spin?
284
302
  Constants::Identity::CLIENT_ID_DEV
285
303
  else
286
304
  # In the future we might want to use Identity's dynamic
@@ -53,7 +53,7 @@ module ShopifyCLI
53
53
  {{command:--organization-id=ID}} Partner organization ID. Must be an existing organization.
54
54
  {{command:--store-domain=MYSHOPIFYDOMAIN }} Development store URL. Must be an existing development store.
55
55
  {{command:--db=DB}} Database type. Must be one of: mysql, postgresql, sqlite3, oracle, frontbase, ibm_db, sqlserver, jdbcmysql, jdbcsqlite3, jdbcpostgresql, jdbc.
56
- {{command:--rails-opts=RAILSOPTS}} Additional options. Must be string containing one or more valid Rails options, separated by spaces.
56
+ {{command:--rails-opts=RAILSOPTS}} Additional options. Must be a string containing one or more valid Rails options, separated by spaces.
57
57
  HELP
58
58
 
59
59
  error: {
@@ -415,7 +415,7 @@ module ShopifyCLI
415
415
  Usage: {{command:%s login [--store=STORE]}}
416
416
  HELP
417
417
  invalid_shop: <<~MESSAGE,
418
- Invalid store provided (%s). Please make sure that the store belongs to your partner organization, and provide the store in the following format: my-store.myshopify.com
418
+ Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
419
419
  MESSAGE
420
420
  shop_prompt: <<~PROMPT,
421
421
  What store are you connecting to? (e.g. my-store.myshopify.com; do {{bold:NOT}} include protocol part, e.g., https://)