shopify-cli 2.0.0 → 2.2.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +29 -0
  3. data/Gemfile.lock +4 -4
  4. data/README.md +3 -1
  5. data/RELEASING.md +2 -0
  6. data/THEMEKIT_MIGRATION.md +18 -0
  7. data/lib/graphql/api_versions.graphql +1 -1
  8. data/lib/graphql/get_variant_id.graphql +16 -0
  9. data/lib/project_types/extension/cli.rb +10 -1
  10. data/lib/project_types/extension/commands/check.rb +44 -0
  11. data/lib/project_types/extension/commands/serve.rb +8 -2
  12. data/lib/project_types/extension/extension_project.rb +39 -1
  13. data/lib/project_types/extension/extension_project_keys.rb +1 -0
  14. data/lib/project_types/extension/features/argo_runtime.rb +6 -38
  15. data/lib/project_types/extension/features/argo_serve.rb +30 -1
  16. data/lib/project_types/extension/features/runtimes/admin.rb +29 -0
  17. data/lib/project_types/extension/features/runtimes/base.rb +19 -0
  18. data/lib/project_types/extension/features/runtimes/checkout_post_purchase.rb +23 -0
  19. data/lib/project_types/extension/features/runtimes/checkout_ui_extension.rb +29 -0
  20. data/lib/project_types/extension/messages/messages.rb +19 -6
  21. data/lib/project_types/extension/models/product.rb +12 -0
  22. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +10 -2
  23. data/lib/project_types/extension/models/specification_handlers/default.rb +13 -4
  24. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +15 -0
  25. data/lib/project_types/extension/tasks/configure_features.rb +1 -0
  26. data/lib/project_types/extension/tasks/converters/product_converter.rb +21 -0
  27. data/lib/project_types/extension/tasks/get_product.rb +22 -0
  28. data/lib/project_types/node/messages/messages.rb +1 -1
  29. data/lib/project_types/rails/messages/messages.rb +18 -18
  30. data/lib/project_types/script/commands/create.rb +1 -1
  31. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +3 -3
  32. data/lib/project_types/script/layers/infrastructure/script_service.rb +17 -2
  33. data/lib/project_types/theme/cli.rb +1 -0
  34. data/lib/project_types/theme/commands/init.rb +42 -0
  35. data/lib/project_types/theme/commands/serve.rb +3 -1
  36. data/lib/project_types/theme/forms/select.rb +1 -1
  37. data/lib/project_types/theme/messages/messages.rb +14 -3
  38. data/lib/shopify-cli/admin_api.rb +6 -2
  39. data/lib/shopify-cli/api.rb +2 -2
  40. data/lib/shopify-cli/context.rb +1 -1
  41. data/lib/shopify-cli/core/monorail.rb +8 -3
  42. data/lib/shopify-cli/http_request.rb +6 -0
  43. data/lib/shopify-cli/messages/messages.rb +9 -8
  44. data/lib/shopify-cli/packager.rb +5 -5
  45. data/lib/shopify-cli/theme/dev_server.rb +5 -5
  46. data/lib/shopify-cli/theme/dev_server/local_assets.rb +1 -1
  47. data/lib/shopify-cli/theme/file.rb +2 -2
  48. data/lib/shopify-cli/theme/syncer.rb +9 -5
  49. data/lib/shopify-cli/theme/theme.rb +5 -5
  50. data/lib/shopify-cli/tunnel.rb +1 -1
  51. data/lib/shopify-cli/version.rb +1 -1
  52. data/shopify-cli.gemspec +1 -1
  53. metadata +15 -4
@@ -34,7 +34,7 @@ module Script
34
34
  end
35
35
 
36
36
  def self.help
37
- allowed_values = Script::Layers::Application::ExtensionPoints.types.map { |type| "{{cyan:#{type}}}" }
37
+ allowed_values = Script::Layers::Application::ExtensionPoints.available_types.map { |type| "{{cyan:#{type}}}" }
38
38
  ShopifyCli::Context.message("script.create.help", ShopifyCli::TOOL_NAME, allowed_values.join(", "))
39
39
  end
40
40
  end
@@ -9,9 +9,9 @@ mutation AppScriptUpdateOrCreate(
9
9
  $schemaMinorVersion: String,
10
10
  $useMsgpack: Boolean,
11
11
  $uuid: String,
12
- $configurationUi: Boolean,
13
- $scriptJsonVersion: String,
14
- $configurationDefinition: String,
12
+ $configurationUi: Boolean!,
13
+ $scriptJsonVersion: String!,
14
+ $configurationDefinition: String!,
15
15
  ) {
16
16
  appScriptUpdateOrCreate(
17
17
  extensionPointName: $extensionPointName
@@ -75,19 +75,30 @@ module Script
75
75
  class ScriptServiceAPI < ShopifyCli::API
76
76
  property(:api_key, accepts: String)
77
77
 
78
+ LOCAL_INSTANCE_URL = "https://script-service.myshopify.io"
79
+
78
80
  def self.query(ctx, query_name, api_key: nil, variables: {})
79
81
  api_client(ctx, api_key).query(query_name, variables: variables)
80
82
  end
81
83
 
82
84
  def self.api_client(ctx, api_key)
85
+ instance_url = spin_instance_url || LOCAL_INSTANCE_URL
83
86
  new(
84
87
  ctx: ctx,
85
- url: "https://script-service.myshopify.io/graphql",
88
+ url: "#{instance_url}/graphql",
86
89
  token: "",
87
90
  api_key: api_key
88
91
  )
89
92
  end
90
93
 
94
+ def self.spin_instance_url
95
+ workspace = ENV["SPIN_WORKSPACE"]
96
+ namespace = ENV["SPIN_NAMESPACE"]
97
+ return if workspace.nil? || namespace.nil?
98
+
99
+ "https://script-service.#{workspace}.#{namespace}.us.spin.dev"
100
+ end
101
+
91
102
  def auth_headers(*)
92
103
  tokens = { "APP_KEY" => api_key }.compact.to_json
93
104
  { "X-Shopify-Authenticated-Tokens" => tokens }
@@ -104,7 +115,7 @@ module Script
104
115
  private_constant(:PartnersProxyAPI)
105
116
 
106
117
  def script_service_request(query_name:, variables: nil, **options)
107
- resp = if ENV["BYPASS_PARTNERS_PROXY"]
118
+ resp = if bypass_partners_proxy
108
119
  ScriptServiceAPI.query(ctx, query_name, variables: variables, **options)
109
120
  else
110
121
  proxy_through_partners(query_name: query_name, variables: variables, **options)
@@ -113,6 +124,10 @@ module Script
113
124
  resp
114
125
  end
115
126
 
127
+ def bypass_partners_proxy
128
+ !ENV["BYPASS_PARTNERS_PROXY"].nil?
129
+ end
130
+
116
131
  def proxy_through_partners(query_name:, variables: nil, **options)
117
132
  options[:variables] = variables.to_json if variables
118
133
  resp = PartnersProxyAPI.query(ctx, query_name, **options)
@@ -6,6 +6,7 @@ module Theme
6
6
  end
7
7
 
8
8
  class Command < ShopifyCli::ProjectCommands
9
+ subcommand :Init, "init", Project.project_filepath("commands/init")
9
10
  subcommand :Serve, "serve", Project.project_filepath("commands/serve")
10
11
  subcommand :Pull, "pull", Project.project_filepath("commands/pull")
11
12
  subcommand :Push, "push", Project.project_filepath("commands/push")
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Theme
4
+ class Command
5
+ class Init < ShopifyCli::SubCommand
6
+ options do |parser, flags|
7
+ parser.on("-u", "--clone-url URL") { |url| flags[:clone_url] = url }
8
+ end
9
+
10
+ DEFAULT_CLONE_URL = "https://github.com/Shopify/dawn.git"
11
+
12
+ def call(args, _name)
13
+ name = args.first || ask_name
14
+ clone_url = options.flags[:clone_url] || DEFAULT_CLONE_URL
15
+ clone(clone_url, name)
16
+ end
17
+
18
+ def self.help
19
+ ShopifyCli::Context.message("theme.init.help", ShopifyCli::TOOL_NAME, ShopifyCli::TOOL_NAME)
20
+ end
21
+
22
+ private
23
+
24
+ def ask_name
25
+ CLI::UI::Prompt.ask(@ctx.message("theme.init.ask_name"))
26
+ end
27
+
28
+ def clone(url, name)
29
+ ShopifyCli::Git.clone(url, name)
30
+
31
+ @ctx.root = File.join(@ctx.root, name)
32
+
33
+ begin
34
+ @ctx.rm_r(".git")
35
+ @ctx.rm_r(".github")
36
+ rescue Errno::ENOENT => e
37
+ @ctx.debug(e)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -10,7 +10,9 @@ module Theme
10
10
 
11
11
  def call(*)
12
12
  flags = options.flags.dup
13
- ShopifyCli::Theme::DevServer.start(@ctx, ".", **flags)
13
+ ShopifyCli::Theme::DevServer.start(@ctx, ".", **flags) do |syncer|
14
+ UI::SyncProgressBar.new(syncer).progress(:upload_theme!, delay_low_priority_files: true)
15
+ end
14
16
  end
15
17
 
16
18
  def self.help
@@ -43,7 +43,7 @@ module Theme
43
43
  when "development"
44
44
  "blue"
45
45
  else
46
- "grey"
46
+ "italic"
47
47
  end
48
48
 
49
49
  tags = ["{{#{color}:[#{theme.role}]}}"]
@@ -8,6 +8,17 @@ module Theme
8
8
  Usage: {{command:%1$s theme [ %2$s ]}}
9
9
  HELP
10
10
 
11
+ init: {
12
+ help: <<~HELP,
13
+ {{command:%s theme init}}: Clones a Git repository to use as a starting point for building a new theme.
14
+
15
+ Usage: {{command:%s theme init [ NAME ]}}
16
+
17
+ Options:
18
+ {{command:-u, --clone-url=URL}} The Git URL to clone from. Defaults to Shopify's example theme, Dawn: https://github.com/Shopify/dawn.git
19
+ HELP
20
+ ask_name: "Theme name",
21
+ },
11
22
  publish: {
12
23
  confirmation: "This will change your live theme. Do you want to continue?",
13
24
  deploying: "Deploying theme",
@@ -79,7 +90,7 @@ module Theme
79
90
  Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.
80
91
  Usage: {{command:%s theme serve}}
81
92
  HELP
82
- serve: "Viewing theme...",
93
+ serve: "Viewing theme",
83
94
  open_fail: "Couldn't open the theme",
84
95
  },
85
96
  check: {
@@ -123,9 +134,9 @@ module Theme
123
134
  },
124
135
  language_server: {
125
136
  help: <<~HELP,
126
- {{command:%s theme language-server}}: Start a Language Server Protocol server.
137
+ {{command:%1$s theme language-server}}: Start a Language Server Protocol server.
127
138
 
128
- Usage: {{command:%s theme language-server}}
139
+ Usage: {{command:%1$s theme language-server}}
129
140
  HELP
130
141
  },
131
142
  pull: {
@@ -134,8 +134,12 @@ module ShopifyCli
134
134
  )
135
135
  CLI::Kit::Util.begin do
136
136
  versions = client.query("api_versions")["data"]["publicApiVersions"]
137
- latest = versions.find { |version| version["displayName"].include?("Latest") }
138
- latest["handle"]
137
+ # return the most recent supported version
138
+ versions
139
+ .select { |version| version["supported"] }
140
+ .map { |version| version["handle"] }
141
+ .sort
142
+ .reverse[0]
139
143
  end.retry_after(API::APIRequestUnauthorizedError, retries: 1) do
140
144
  ShopifyCli::IdentityAuth.new(ctx: ctx).reauthenticate
141
145
  end
@@ -81,7 +81,7 @@ module ShopifyCli
81
81
  else
82
82
  raise APIRequestUnexpectedError.new("#{response.code}\n#{response.body}", response: response)
83
83
  end
84
- rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Errno::ETIMEDOUT, Timeout::Error
84
+ rescue Errno::ETIMEDOUT, Timeout::Error
85
85
  raise APIRequestTimeoutError.new("Timeout")
86
86
  end.retry_after(APIRequestRetriableError, retries: 3) do |e|
87
87
  sleep(1) if e.is_a?(APIRequestThrottledError)
@@ -108,7 +108,7 @@ module ShopifyCli
108
108
  {
109
109
  "User-Agent" => "Shopify CLI; v=#{ShopifyCli::VERSION}",
110
110
  "Sec-CH-UA" => "Shopify CLI; v=#{ShopifyCli::VERSION} sha=#{ShopifyCli.sha}",
111
- "Sec-CH-UA-PLATFORM" => ctx.os,
111
+ "Sec-CH-UA-PLATFORM" => ctx.os.to_s,
112
112
  }.tap do |headers|
113
113
  headers["X-Shopify-Cli-Employee"] = "1" if Shopifolk.acting_as_shopify_organization?
114
114
  end.merge(auth_headers(token))
@@ -327,7 +327,7 @@ module ShopifyCli
327
327
  if linux? && which("xdg-open")
328
328
  system("xdg-open", uri.to_s)
329
329
  elsif windows?
330
- system("start", uri.to_s)
330
+ system("start \"\" \"#{uri}\"")
331
331
  elsif mac?
332
332
  system("open", uri.to_s)
333
333
  else
@@ -46,7 +46,7 @@ module ShopifyCli
46
46
 
47
47
  # we only want to send Monorail events in production or when explicitly developing
48
48
  def enabled?
49
- Context.new.system? || ENV["MONORAIL_REAL_EVENTS"] == "1"
49
+ (Context.new.system? || ENV["MONORAIL_REAL_EVENTS"] == "1") && !Context.new.ci?
50
50
  end
51
51
 
52
52
  def consented?
@@ -54,6 +54,7 @@ module ShopifyCli
54
54
  end
55
55
 
56
56
  def prompt_for_consent
57
+ return if Context.new.ci?
57
58
  return unless enabled?
58
59
  return if ShopifyCli::Config.get_section("analytics").key?("enabled")
59
60
  msg = Context.message("core.monorail.consent_prompt")
@@ -90,7 +91,7 @@ module ShopifyCli
90
91
  {
91
92
  schema_id: INVOCATIONS_SCHEMA,
92
93
  payload: {
93
- project_type: Project.current_project_type.to_s,
94
+ project_type: project_type_from_dir_or_cmd(commands[0]).to_s,
94
95
  command: commands.join(" "),
95
96
  args: args.join(" "),
96
97
  time_start: start_time,
@@ -104,7 +105,7 @@ module ShopifyCli
104
105
  is_employee: ShopifyCli::Shopifolk.acting_as_shopify_organization?,
105
106
  }.tap do |payload|
106
107
  payload[:api_key] = metadata.delete(:api_key)
107
- payload[:partner_id] = metadata.delete(:organization_id)
108
+ payload[:partner_id] = metadata.delete(:organization_id) || ShopifyCli::DB.get(:organization_id)
108
109
  if Project.has_current?
109
110
  project = Project.current(force_reload: true)
110
111
  payload[:api_key] = project.env&.api_key
@@ -114,6 +115,10 @@ module ShopifyCli
114
115
  end,
115
116
  }
116
117
  end
118
+
119
+ def project_type_from_dir_or_cmd(command)
120
+ Project.current_project_type || (command unless ShopifyCli::Commands.core_command?(command)) || nil
121
+ end
117
122
  end
118
123
  end
119
124
  end
@@ -1,4 +1,5 @@
1
1
  require "net/http"
2
+ require "openssl"
2
3
 
3
4
  module ShopifyCli
4
5
  class HttpRequest
@@ -24,8 +25,13 @@ module ShopifyCli
24
25
  end
25
26
 
26
27
  def request(uri, body, headers, req)
28
+ cert_store = OpenSSL::X509::Store.new
29
+ cert_store.set_default_paths
30
+
27
31
  http = ::Net::HTTP.new(uri.host, uri.port)
28
32
  http.use_ssl = true
33
+ http.cert_store = cert_store
34
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV["SSL_VERIFY_NONE"]
29
35
 
30
36
  req.body = body unless body.nil?
31
37
  req["Content-Type"] = "application/json"
@@ -28,7 +28,7 @@ module ShopifyCli
28
28
  },
29
29
 
30
30
  env_file: {
31
- saving_header: "writing %s file...",
31
+ saving_header: "writing %s file",
32
32
  saving: "writing %s file",
33
33
  saved: "%s saved to project root",
34
34
  },
@@ -69,7 +69,7 @@ module ShopifyCli
69
69
  no_commits_made: "No git commits have been made. Please make at least one commit.",
70
70
  },
71
71
 
72
- cloning: "Cloning %s into %s...",
72
+ cloning: "Cloning %s into %s",
73
73
  cloned: "{{v}} Cloned into %s",
74
74
  },
75
75
 
@@ -103,9 +103,9 @@ module ShopifyCli
103
103
  install_error: "An error occurred while installing dependencies",
104
104
  },
105
105
 
106
- installing: "Installing dependencies with %s...",
106
+ installing: "Installing dependencies with %s",
107
107
  installed: "Dependencies installed",
108
- npm_installing_deps: "Installing %d dependencies...",
108
+ npm_installing_deps: "Installing %d dependencies",
109
109
  npm_installed_deps: "%d npm dependencies installed",
110
110
  },
111
111
 
@@ -257,7 +257,7 @@ module ShopifyCli
257
257
  count_help: "Number of resources to generate",
258
258
  },
259
259
 
260
- populating: "Populating %d %ss...",
260
+ populating: "Populating %d %ss",
261
261
 
262
262
  completion_message: <<~COMPLETION_MESSAGE,
263
263
  Successfully added %d %s to {{green:%s}}
@@ -345,7 +345,7 @@ module ShopifyCli
345
345
  confirm_store: {
346
346
  prompt: "You are currently logged into {{green:%s}}. Do you want to proceed using this store?",
347
347
  confirmation: "Proceeding using {{green:%s}}",
348
- cancelling: "Cancelling ...",
348
+ cancelling: "Cancelling",
349
349
  },
350
350
  ensure_env: {
351
351
  organization_select: "To which partner organization does this project belong?",
@@ -414,7 +414,9 @@ module ShopifyCli
414
414
  "please make sure %s exists within %s before trying again",
415
415
  },
416
416
 
417
+ installing: "Installing ngrok…",
417
418
  not_running: "{{green:x}} ngrok tunnel not running",
419
+ prereq_command_location: "%s @ %s",
418
420
  signup_suggestion: <<~MESSAGE,
419
421
  {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
420
422
  account at {{underline:https://ngrok.com/signup}}. After you signup, install your
@@ -423,9 +425,8 @@ module ShopifyCli
423
425
  start: "{{v}} ngrok tunnel running at {{underline:%s}}",
424
426
  start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
425
427
  stopped: "{{green:x}} ngrok tunnel stopped",
426
- timed_out: "{{x}} ngrok tunnel has timed out, restarting ...",
428
+ timed_out: "{{x}} ngrok tunnel has timed out, restarting",
427
429
  will_timeout: "{{*}} This tunnel will timeout in {{red:%s}}",
428
- prereq_command_location: "%s @ %s",
429
430
  },
430
431
 
431
432
  version: {
@@ -16,7 +16,7 @@ module ShopifyCli
16
16
 
17
17
  puts "\nBuilding Debian package"
18
18
 
19
- puts "Generating metadata files..."
19
+ puts "Generating metadata files"
20
20
  Dir.glob("#{debian_dir}/*").each { |file| File.delete(file) }
21
21
 
22
22
  metadata_files = %w(control preinst prerm)
@@ -28,7 +28,7 @@ module ShopifyCli
28
28
  File.open(file_path, "w", 0775) { |f| f.write(file_contents) }
29
29
  end
30
30
 
31
- puts "Building package..."
31
+ puts "Building package"
32
32
  Dir.chdir(root_dir)
33
33
  raise "Failed to build package" unless system("dpkg-deb", "-b", "shopify-cli")
34
34
 
@@ -49,14 +49,14 @@ module ShopifyCli
49
49
  spec_path = File.join(root_dir, "shopify-cli.spec")
50
50
  puts "\nBuilding RPM package"
51
51
 
52
- puts "Generating spec file..."
52
+ puts "Generating spec file"
53
53
  File.delete(spec_path) if File.exist?(spec_path)
54
54
 
55
55
  spec_contents = File.read(File.join(root_dir, "shopify-cli.spec.base"))
56
56
  spec_contents = spec_contents.gsub("SHOPIFY_CLI_VERSION", ShopifyCli::VERSION)
57
57
  File.write(spec_path, spec_contents)
58
58
 
59
- puts "Building package..."
59
+ puts "Building package"
60
60
  Dir.chdir(root_dir)
61
61
  system("rpmbuild", "-bb", File.basename(spec_path))
62
62
 
@@ -72,7 +72,7 @@ module ShopifyCli
72
72
  build_path = File.join(BUILDS_DIR, "shopify-cli.rb")
73
73
  puts "\nBuilding Homebrew package"
74
74
 
75
- puts "Generating formula..."
75
+ puts "Generating formula"
76
76
  File.delete(build_path) if File.exist?(build_path)
77
77
 
78
78
  spec_contents = File.read(File.join(root_dir, "shopify-cli.base.rb"))
@@ -20,7 +20,7 @@ module ShopifyCli
20
20
  class << self
21
21
  attr_accessor :ctx
22
22
 
23
- def start(ctx, root, port: 9292, silent: false)
23
+ def start(ctx, root, port: 9292)
24
24
  @ctx = ctx
25
25
  theme = DevelopmentTheme.new(ctx, root: root)
26
26
  ignore_filter = IgnoreFilter.from_path(root)
@@ -43,10 +43,10 @@ module ShopifyCli
43
43
  CLI::UI::Frame.open(@ctx.message("theme.serve.serve")) do
44
44
  ctx.print_task("Syncing theme ##{theme.id} on #{theme.shop}")
45
45
  @syncer.start_threads
46
- if silent
47
- @syncer.upload_theme!(delay_low_priority_files: true)
46
+ if block_given?
47
+ yield @syncer
48
48
  else
49
- @syncer.upload_theme_with_progress_bar!(delay_low_priority_files: true)
49
+ @syncer.upload_theme!(delay_low_priority_files: true)
50
50
  end
51
51
 
52
52
  return if stopped
@@ -87,7 +87,7 @@ module ShopifyCli
87
87
  end
88
88
 
89
89
  def stop
90
- @ctx.puts("Stopping ...")
90
+ @ctx.puts("Stopping")
91
91
  @app.close
92
92
  @syncer.shutdown
93
93
  WebServer.shutdown
@@ -72,7 +72,7 @@ module ShopifyCli
72
72
  def replace_asset_urls(body)
73
73
  replaced_body = body.join.gsub(ASSET_REGEX) do |match|
74
74
  path = Pathname.new(Regexp.last_match[1])
75
- if @theme.asset_paths.include?(path)
75
+ if @theme.static_asset_paths.include?(path)
76
76
  "/#{path}"
77
77
  else
78
78
  match