shopify-cli 2.0.0 → 2.2.0

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