shopify-cli 1.14.0 → 2.0.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 (179) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/CONTRIBUTING.md +7 -7
  4. data/.github/DESIGN.md +3 -3
  5. data/.github/workflows/build.yml +1 -1
  6. data/.gitignore +3 -0
  7. data/.rubocop.yml +3 -1
  8. data/.ruby-version +1 -1
  9. data/CHANGELOG.md +35 -29
  10. data/Gemfile +4 -0
  11. data/Gemfile.lock +32 -0
  12. data/LICENSE +4 -1
  13. data/README.md +92 -26
  14. data/RELEASING.md +29 -7
  15. data/Rakefile +2 -2
  16. data/SECURITY.md +1 -1
  17. data/bin/load_shopify.rb +1 -1
  18. data/bin/shopify +3 -3
  19. data/dev.yml +1 -1
  20. data/docs/app/node/index.md +1 -1
  21. data/docs/app/rails/index.md +1 -1
  22. data/docs/core/index.md +1 -1
  23. data/docs/getting-started/index.md +1 -1
  24. data/docs/getting-started/install/index.md +1 -1
  25. data/docs/getting-started/migrate/index.md +1 -1
  26. data/docs/getting-started/uninstall/index.md +1 -1
  27. data/docs/getting-started/upgrade/index.md +1 -1
  28. data/docs/help/start-app/index.md +1 -1
  29. data/docs/index.md +1 -1
  30. data/ext/shopify-cli/extconf.rb +17 -5
  31. data/install.sh +1 -1
  32. data/lib/docgen/index_template.md.erb +2 -2
  33. data/lib/graphql/all_orgs_with_extensions.graphql +37 -0
  34. data/lib/graphql/find_organization.graphql +2 -1
  35. data/lib/project_types/extension/cli.rb +18 -15
  36. data/lib/project_types/extension/commands/build.rb +4 -5
  37. data/lib/project_types/extension/commands/connect.rb +35 -0
  38. data/lib/project_types/extension/commands/create.rb +12 -16
  39. data/lib/project_types/extension/commands/extension_command.rb +2 -2
  40. data/lib/project_types/extension/commands/info.rb +86 -0
  41. data/lib/project_types/extension/commands/push.rb +8 -7
  42. data/lib/project_types/extension/commands/register.rb +4 -5
  43. data/lib/project_types/extension/commands/serve.rb +5 -8
  44. data/lib/project_types/extension/commands/tunnel.rb +3 -1
  45. data/lib/project_types/extension/errors.rb +9 -0
  46. data/lib/project_types/extension/extension_project.rb +5 -0
  47. data/lib/project_types/extension/features/argo.rb +6 -6
  48. data/lib/project_types/extension/features/argo_runtime.rb +22 -66
  49. data/lib/project_types/extension/features/argo_serve.rb +25 -18
  50. data/lib/project_types/extension/forms/connect.rb +42 -0
  51. data/lib/project_types/extension/forms/questions/ask_name.rb +14 -6
  52. data/lib/project_types/extension/forms/questions/ask_registration.rb +51 -0
  53. data/lib/project_types/extension/messages/messages.rb +75 -11
  54. data/lib/project_types/extension/models/specification.rb +1 -0
  55. data/lib/project_types/extension/models/specification_handlers/{checkout_argo_extension.rb → checkout_ui_extension.rb} +3 -1
  56. data/lib/project_types/extension/models/specification_handlers/default.rb +13 -3
  57. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +86 -0
  58. data/lib/project_types/extension/models/specifications.rb +1 -0
  59. data/lib/project_types/extension/tasks/configure_features.rb +6 -7
  60. data/lib/project_types/extension/tasks/configure_options.rb +20 -0
  61. data/lib/project_types/extension/tasks/get_extensions.rb +32 -0
  62. data/lib/project_types/node/cli.rb +9 -21
  63. data/lib/project_types/node/commands/connect.rb +8 -2
  64. data/lib/project_types/node/commands/create.rb +9 -5
  65. data/lib/project_types/node/commands/deploy.rb +15 -5
  66. data/lib/project_types/node/commands/deploy/heroku.rb +29 -29
  67. data/lib/project_types/node/commands/generate.rb +4 -2
  68. data/lib/project_types/node/commands/open.rb +4 -2
  69. data/lib/project_types/node/commands/serve.rb +3 -2
  70. data/lib/project_types/node/commands/tunnel.rb +4 -2
  71. data/lib/project_types/node/messages/messages.rb +46 -89
  72. data/lib/project_types/rails/cli.rb +9 -21
  73. data/lib/project_types/rails/commands/connect.rb +8 -2
  74. data/lib/project_types/rails/commands/create.rb +10 -6
  75. data/lib/project_types/rails/commands/deploy.rb +15 -5
  76. data/lib/project_types/rails/commands/deploy/heroku.rb +84 -82
  77. data/lib/project_types/rails/commands/generate.rb +15 -5
  78. data/lib/project_types/rails/commands/generate/webhook.rb +28 -26
  79. data/lib/project_types/rails/commands/open.rb +4 -2
  80. data/lib/project_types/rails/commands/serve.rb +3 -2
  81. data/lib/project_types/rails/commands/tunnel.rb +4 -2
  82. data/lib/project_types/rails/messages/messages.rb +54 -101
  83. data/lib/project_types/script/cli.rb +5 -7
  84. data/lib/project_types/script/commands/create.rb +3 -1
  85. data/lib/project_types/script/commands/push.rb +4 -2
  86. data/lib/project_types/script/messages/messages.rb +52 -45
  87. data/lib/project_types/script/ui/error_handler.rb +2 -2
  88. data/lib/project_types/theme/cli.rb +15 -27
  89. data/lib/project_types/theme/commands/check.rb +33 -0
  90. data/lib/project_types/theme/commands/delete.rb +64 -0
  91. data/lib/project_types/theme/commands/language_server.rb +16 -0
  92. data/lib/project_types/theme/commands/package.rb +55 -0
  93. data/lib/project_types/theme/commands/publish.rb +43 -0
  94. data/lib/project_types/theme/commands/pull.rb +51 -0
  95. data/lib/project_types/theme/commands/push.rb +58 -32
  96. data/lib/project_types/theme/commands/serve.rb +7 -17
  97. data/lib/project_types/theme/forms/confirm_store.rb +15 -0
  98. data/lib/project_types/theme/forms/select.rb +59 -0
  99. data/lib/project_types/theme/messages/messages.rb +110 -106
  100. data/lib/project_types/theme/ui/sync_progress_bar.rb +20 -0
  101. data/lib/shopify-cli/admin_api.rb +53 -38
  102. data/lib/shopify-cli/admin_api/populate_resource_command.rb +6 -14
  103. data/lib/shopify-cli/admin_api/schema.rb +1 -10
  104. data/lib/shopify-cli/api.rb +29 -14
  105. data/lib/shopify-cli/command.rb +15 -3
  106. data/lib/shopify-cli/commands.rb +7 -2
  107. data/lib/shopify-cli/commands/help.rb +2 -29
  108. data/lib/shopify-cli/commands/login.rb +95 -0
  109. data/lib/shopify-cli/commands/logout.rb +24 -8
  110. data/lib/shopify-cli/commands/populate.rb +23 -0
  111. data/lib/{project_types/node → shopify-cli}/commands/populate/customer.rb +2 -8
  112. data/lib/{project_types/node → shopify-cli}/commands/populate/draft_order.rb +2 -2
  113. data/lib/{project_types/node → shopify-cli}/commands/populate/product.rb +2 -8
  114. data/lib/shopify-cli/commands/store.rb +15 -0
  115. data/lib/shopify-cli/commands/switch.rb +39 -0
  116. data/lib/shopify-cli/commands/system.rb +12 -0
  117. data/lib/shopify-cli/commands/whoami.rb +28 -0
  118. data/lib/shopify-cli/connect.rb +32 -0
  119. data/lib/shopify-cli/context.rb +52 -4
  120. data/lib/shopify-cli/core/entry_point.rb +3 -22
  121. data/lib/shopify-cli/db.rb +4 -4
  122. data/lib/shopify-cli/http_request.rb +10 -0
  123. data/lib/shopify-cli/identity_auth.rb +282 -0
  124. data/lib/shopify-cli/{oauth → identity_auth}/servlet.rb +11 -12
  125. data/lib/shopify-cli/messages/messages.rb +132 -39
  126. data/lib/shopify-cli/partners_api.rb +21 -44
  127. data/lib/shopify-cli/partners_api/organizations.rb +8 -0
  128. data/lib/shopify-cli/project_commands.rb +16 -0
  129. data/lib/shopify-cli/project_type.rb +0 -31
  130. data/lib/shopify-cli/shopifolk.rb +8 -11
  131. data/lib/shopify-cli/sub_command.rb +1 -0
  132. data/lib/shopify-cli/tasks.rb +3 -0
  133. data/lib/shopify-cli/tasks/confirm_store.rb +18 -0
  134. data/lib/shopify-cli/tasks/create_api_client.rb +2 -2
  135. data/lib/shopify-cli/tasks/ensure_authenticated.rb +13 -0
  136. data/lib/shopify-cli/tasks/ensure_loopback_url.rb +1 -1
  137. data/lib/shopify-cli/tasks/ensure_project_type.rb +12 -0
  138. data/lib/shopify-cli/tasks/select_org_and_shop.rb +0 -3
  139. data/lib/shopify-cli/theme/dev_server.rb +98 -0
  140. data/lib/shopify-cli/theme/dev_server/certificate_manager.rb +79 -0
  141. data/lib/shopify-cli/theme/dev_server/header_hash.rb +94 -0
  142. data/lib/shopify-cli/theme/dev_server/hot-reload.js +93 -0
  143. data/lib/shopify-cli/theme/dev_server/hot_reload.rb +76 -0
  144. data/lib/shopify-cli/theme/dev_server/local_assets.rb +87 -0
  145. data/lib/shopify-cli/theme/dev_server/proxy.rb +205 -0
  146. data/lib/shopify-cli/theme/dev_server/sse.rb +75 -0
  147. data/lib/shopify-cli/theme/dev_server/watcher.rb +59 -0
  148. data/lib/shopify-cli/theme/dev_server/web_server.rb +140 -0
  149. data/lib/shopify-cli/theme/development_theme.rb +69 -0
  150. data/lib/shopify-cli/theme/file.rb +112 -0
  151. data/lib/shopify-cli/theme/ignore_filter.rb +109 -0
  152. data/lib/shopify-cli/theme/mime_type.rb +34 -0
  153. data/lib/shopify-cli/theme/syncer.rb +328 -0
  154. data/lib/shopify-cli/theme/theme.rb +204 -0
  155. data/lib/shopify-cli/version.rb +1 -1
  156. data/lib/shopify_cli.rb +18 -11
  157. data/shopify-cli.gemspec +12 -5
  158. data/shopify.fish +1 -1
  159. data/shopify.sh +1 -1
  160. metadata +88 -34
  161. data/.github/workflows/release.yml +0 -59
  162. data/lib/project_types/extension/features/argo_serve_options.rb +0 -42
  163. data/lib/project_types/node/commands/populate.rb +0 -23
  164. data/lib/project_types/rails/commands/populate.rb +0 -23
  165. data/lib/project_types/rails/commands/populate/customer.rb +0 -31
  166. data/lib/project_types/rails/commands/populate/draft_order.rb +0 -28
  167. data/lib/project_types/rails/commands/populate/product.rb +0 -30
  168. data/lib/project_types/theme/commands/connect.rb +0 -54
  169. data/lib/project_types/theme/commands/create.rb +0 -48
  170. data/lib/project_types/theme/commands/deploy.rb +0 -38
  171. data/lib/project_types/theme/commands/generate.rb +0 -20
  172. data/lib/project_types/theme/commands/generate/env.rb +0 -79
  173. data/lib/project_types/theme/forms/connect.rb +0 -34
  174. data/lib/project_types/theme/forms/create.rb +0 -22
  175. data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +0 -78
  176. data/lib/project_types/theme/themekit.rb +0 -113
  177. data/lib/shopify-cli/commands/connect.rb +0 -64
  178. data/lib/shopify-cli/commands/create.rb +0 -50
  179. data/lib/shopify-cli/oauth.rb +0 -198
@@ -25,12 +25,11 @@ module ShopifyCli
25
25
  end
26
26
 
27
27
  def call(args, _)
28
- return unless Project.current
29
- Tasks::EnsureEnv.call(@ctx)
30
28
  @args = args
31
29
  @input = Hash.new
32
30
  @count = DEFAULT_COUNT
33
31
  @help = false
32
+ @skip_shop_confirmation = false
34
33
  input_options
35
34
  resource_options.parse(@args)
36
35
 
@@ -41,8 +40,8 @@ module ShopifyCli
41
40
  return @ctx.puts(output)
42
41
  end
43
42
 
44
- @shop ||= Project.current.env.shop || get_shop(@ctx)
45
-
43
+ ShopifyCli::Tasks::ConfirmStore.call(@ctx) unless @skip_shop_confirmation
44
+ @shop = AdminAPI.get_shop_or_abort(@ctx)
46
45
  if @silent
47
46
  spin_group = CLI::UI::SpinGroup.new
48
47
  spin_group.add(@ctx.message("core.populate.populating", @count, camel_case_resource_type)) do |spinner|
@@ -89,7 +88,7 @@ module ShopifyCli
89
88
 
90
89
  opts.on("--silent") { |v| @silent = v }
91
90
 
92
- opts.on("--shop=", "-s") { |value| @shop = value }
91
+ opts.on("--skip-shop-confirmation") { |v| @skip_shop_confirmation = v }
93
92
  end
94
93
  end
95
94
 
@@ -130,7 +129,7 @@ module ShopifyCli
130
129
  "core.populate.completion_message",
131
130
  @count,
132
131
  "#{camel_case_resource_type}#{plural}",
133
- Project.current.env.shop,
132
+ @shop,
134
133
  camel_case_resource_type,
135
134
  admin_url,
136
135
  snake_case_resource_type
@@ -138,7 +137,7 @@ module ShopifyCli
138
137
  end
139
138
 
140
139
  def admin_url
141
- "https://#{Project.current.env.shop}/admin/"
140
+ "https://#{@shop}/admin/"
142
141
  end
143
142
 
144
143
  def price
@@ -147,13 +146,6 @@ module ShopifyCli
147
146
 
148
147
  private
149
148
 
150
- def get_shop(ctx)
151
- res = ShopifyCli::Tasks::SelectOrgAndShop.call(ctx)
152
- domain = res[:shop_domain]
153
- Project.current.env.update(ctx, :shop, domain)
154
- domain
155
- end
156
-
157
149
  def camel_case_resource_type
158
150
  @camel_case_resource_type ||= self.class.to_s.split("::").last
159
151
  end
@@ -6,7 +6,7 @@ module ShopifyCli
6
6
  class << self
7
7
  def get(ctx)
8
8
  unless ShopifyCli::DB.exists?(:shopify_admin_schema)
9
- shop = Project.current.env.shop || get_shop(ctx)
9
+ shop = AdminAPI.get_shop_or_abort(ctx)
10
10
  schema = AdminAPI.query(ctx, "admin_introspection", shop: shop)
11
11
  ShopifyCli::DB.set(shopify_admin_schema: JSON.dump(schema))
12
12
  end
@@ -15,15 +15,6 @@ module ShopifyCli
15
15
  # available
16
16
  self[JSON.parse(ShopifyCli::DB.get(:shopify_admin_schema))]
17
17
  end
18
-
19
- private
20
-
21
- def get_shop(ctx)
22
- res = ShopifyCli::Tasks::SelectOrgAndShop.call(ctx)
23
- domain = res[:shop_domain]
24
- Project.current.env.update(ctx, :shop, domain)
25
- domain
26
- end
27
18
  end
28
19
 
29
20
  def type(name)
@@ -9,12 +9,22 @@ module ShopifyCli
9
9
  property :auth_header, accepts: String
10
10
  property! :url, accepts: String
11
11
 
12
- class APIRequestError < StandardError; end
12
+ class APIRequestError < StandardError
13
+ attr_reader :response
14
+
15
+ def initialize(message = nil, response: nil)
16
+ super(message)
17
+ @response = response
18
+ end
19
+ end
20
+
13
21
  class APIRequestNotFoundError < APIRequestError; end
14
22
  class APIRequestClientError < APIRequestError; end
15
23
  class APIRequestUnauthorizedError < APIRequestClientError; end
24
+ class APIRequestForbiddenError < APIRequestClientError; end
16
25
  class APIRequestUnexpectedError < APIRequestError; end
17
26
  class APIRequestRetriableError < APIRequestError; end
27
+ class APIRequestTimeoutError < APIRequestRetriableError; end
18
28
  class APIRequestServerError < APIRequestRetriableError; end
19
29
  class APIRequestThrottledError < APIRequestRetriableError; end
20
30
 
@@ -46,26 +56,33 @@ module ShopifyCli
46
56
  headers = default_headers.merge(headers)
47
57
  response = if method == "POST"
48
58
  HttpRequest.post(uri, body, headers)
59
+ elsif method == "PUT"
60
+ HttpRequest.put(uri, body, headers)
49
61
  elsif method == "GET"
50
62
  HttpRequest.get(uri, body, headers)
63
+ elsif method == "DELETE"
64
+ HttpRequest.delete(uri, body, headers)
51
65
  end
52
-
53
66
  case response.code.to_i
54
67
  when 200..399
55
- [response.code.to_i, JSON.parse(response.body)]
68
+ [response.code.to_i, JSON.parse(response.body), response]
56
69
  when 401
57
- raise APIRequestUnauthorizedError, "#{response.code}\n#{response.body}"
70
+ raise APIRequestUnauthorizedError.new("#{response.code}\n#{response.body}", response: response)
71
+ when 403
72
+ raise APIRequestForbiddenError.new("#{response.code}\n#{response.body}", response: response)
58
73
  when 404
59
- raise APIRequestNotFoundError, "#{response.code}\n#{response.body}"
74
+ raise APIRequestNotFoundError.new("#{response.code}\n#{response.body}", response: response)
60
75
  when 429
61
- raise APIRequestThrottledError, "#{response.code}\n#{response.body}"
76
+ raise APIRequestThrottledError.new("#{response.code}\n#{response.body}", response: response)
62
77
  when 400..499
63
- raise APIRequestClientError, "#{response.code}\n#{response.body}"
78
+ raise APIRequestClientError.new("#{response.code}\n#{response.body}", response: response)
64
79
  when 500..599
65
- raise APIRequestServerError, "#{response.code}\n#{response.body}"
80
+ raise APIRequestServerError.new("#{response.code}\n#{response.body}", response: response)
66
81
  else
67
- raise APIRequestUnexpectedError, "#{response.code}\n#{response.body}"
82
+ raise APIRequestUnexpectedError.new("#{response.code}\n#{response.body}", response: response)
68
83
  end
84
+ rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Errno::ETIMEDOUT, Timeout::Error
85
+ raise APIRequestTimeoutError.new("Timeout")
69
86
  end.retry_after(APIRequestRetriableError, retries: 3) do |e|
70
87
  sleep(1) if e.is_a?(APIRequestThrottledError)
71
88
  end
@@ -87,13 +104,11 @@ module ShopifyCli
87
104
 
88
105
  private
89
106
 
90
- def current_sha
91
- @current_sha ||= Git.sha(dir: ShopifyCli::ROOT)
92
- end
93
-
94
107
  def default_headers
95
108
  {
96
- "User-Agent" => "Shopify App CLI #{ShopifyCli::VERSION} #{current_sha} | #{ctx.uname}",
109
+ "User-Agent" => "Shopify CLI; v=#{ShopifyCli::VERSION}",
110
+ "Sec-CH-UA" => "Shopify CLI; v=#{ShopifyCli::VERSION} sha=#{ShopifyCli.sha}",
111
+ "Sec-CH-UA-PLATFORM" => ctx.os,
97
112
  }.tap do |headers|
98
113
  headers["X-Shopify-Cli-Employee"] = "1" if Shopifolk.acting_as_shopify_organization?
99
114
  end.merge(auth_headers(token))
@@ -42,13 +42,16 @@ module ShopifyCli
42
42
  )
43
43
  end
44
44
 
45
- def prerequisite_task(*tasks)
45
+ def prerequisite_task(*tasks_without_args, **tasks_with_args)
46
46
  @prerequisite_tasks ||= []
47
- @prerequisite_tasks += tasks
47
+ @prerequisite_tasks += tasks_without_args.map { |t| PrerequisiteTask.new(t) }
48
+ @prerequisite_tasks += tasks_with_args.map { |t, args| PrerequisiteTask.new(t, args) }
48
49
  end
49
50
 
50
51
  def run_prerequisites
51
- (@prerequisite_tasks || []).each { |task| task_registry[task]&.call(@ctx) }
52
+ (@prerequisite_tasks || []).each do |task|
53
+ task_registry[task.name]&.call(@ctx, *task.args)
54
+ end
52
55
  end
53
56
 
54
57
  def task_registry
@@ -59,6 +62,15 @@ module ShopifyCli
59
62
  help = Commands::Help.new(@ctx)
60
63
  help.call(cmds, nil)
61
64
  end
65
+
66
+ class PrerequisiteTask
67
+ attr_reader :name, :args
68
+
69
+ def initialize(name, args = [])
70
+ @name = name
71
+ @args = args
72
+ end
73
+ end
62
74
  end
63
75
 
64
76
  def initialize(ctx = nil)
@@ -19,11 +19,16 @@ module ShopifyCli
19
19
  end
20
20
 
21
21
  register :Config, "config", "shopify-cli/commands/config", true
22
- register :Connect, "connect", "shopify-cli/commands/connect", true
23
- register :Create, "create", "shopify-cli/commands/create", true
24
22
  register :Help, "help", "shopify-cli/commands/help", true
23
+ register :Login, "login", "shopify-cli/commands/login", true
25
24
  register :Logout, "logout", "shopify-cli/commands/logout", true
25
+ register :Populate, "populate", "shopify-cli/commands/populate", true
26
+ register :Store, "store", "shopify-cli/commands/store", true
27
+ register :Switch, "switch", "shopify-cli/commands/switch", true
26
28
  register :System, "system", "shopify-cli/commands/system", true
27
29
  register :Version, "version", "shopify-cli/commands/version", true
30
+ register :Whoami, "whoami", "shopify-cli/commands/whoami", true
31
+
32
+ autoload :Connect, "shopify-cli/commands/connect"
28
33
  end
29
34
  end
@@ -23,17 +23,9 @@ module ShopifyCli
23
23
  preamble = @ctx.message("core.help.preamble", ShopifyCli::TOOL_NAME)
24
24
  @ctx.puts(preamble)
25
25
 
26
- core_commands.each do |name, klass|
27
- next if name == "help"
28
- @ctx.puts("{{command:#{name}}}: #{klass.help}\n")
29
- end
30
-
31
- return unless inside_supported_project?
26
+ available_commands = resolved_commands.select { |_name, c| !c.hidden? }
32
27
 
33
- @ctx.puts("{{bold:Project: #{Project.project_name} (#{project_type_name})}}")
34
- @ctx.puts("{{bold:Available commands for #{project_type_name} projects:}}\n\n")
35
-
36
- local_commands.each do |name, klass|
28
+ available_commands.each do |name, klass|
37
29
  next if name == "help"
38
30
  @ctx.puts("{{command:#{name}}}: #{klass.help}\n")
39
31
  end
@@ -41,21 +33,6 @@ module ShopifyCli
41
33
 
42
34
  private
43
35
 
44
- def project_type_name
45
- ProjectType.load_type(Project.current_project_type).project_name
46
- end
47
-
48
- def core_commands
49
- resolved_commands
50
- .select { |_name, c| !c.hidden? }
51
- .select { |name, _c| Commands.core_command?(name) }
52
- end
53
-
54
- def local_commands
55
- resolved_commands
56
- .reject { |name, _c| Commands.core_command?(name) }
57
- end
58
-
59
36
  def display_help(klass)
60
37
  output = klass.help
61
38
  if klass.respond_to?(:extended_help)
@@ -70,10 +47,6 @@ module ShopifyCli
70
47
  .resolved_commands
71
48
  .sort
72
49
  end
73
-
74
- def inside_supported_project?
75
- Project.current_project_type && ProjectType.load_type(Project.current_project_type)
76
- end
77
50
  end
78
51
  end
79
52
  end
@@ -0,0 +1,95 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Login < ShopifyCli::Command
6
+ PROTOCOL_REGEX = /^https?\:\/\//
7
+ PERMANENT_DOMAIN_SUFFIX = /\.myshopify\.(com|io)$/
8
+
9
+ options do |parser, flags|
10
+ parser.on("--store=STORE") { |url| flags[:shop] = url }
11
+ # backwards compatibility allow 'shop' for now
12
+ parser.on("--shop=SHOP") { |url| flags[:shop] = url }
13
+ parser.on("--password=PASSWORD") { |password| flags[:password] = password }
14
+ end
15
+
16
+ def call(*)
17
+ shop = (options.flags[:shop] || @ctx.getenv("SHOPIFY_SHOP" || nil))
18
+ ShopifyCli::DB.set(shop: self.class.validate_shop(shop)) unless shop.nil?
19
+
20
+ if shop.nil? && Shopifolk.check
21
+ Shopifolk.reset
22
+ @ctx.puts(@ctx.message("core.tasks.select_org_and_shop.identified_as_shopify"))
23
+ message = @ctx.message("core.tasks.select_org_and_shop.first_party")
24
+ if CLI::UI::Prompt.confirm(message, default: false)
25
+ Shopifolk.act_as_shopify_organization
26
+ end
27
+ end
28
+
29
+ # As password auth will soon be deprecated, we enable only in CI
30
+ if @ctx.ci? && (password = options.flags[:password] || @ctx.getenv("SHOPIFY_PASSWORD"))
31
+ ShopifyCli::DB.set(shopify_exchange_token: password)
32
+ else
33
+ IdentityAuth.new(ctx: @ctx).authenticate
34
+ org = select_organization
35
+ ShopifyCli::DB.set(organization_id: org["id"].to_i) unless org.nil?
36
+ Whoami.call([], "whoami")
37
+ end
38
+ end
39
+
40
+ def self.help
41
+ ShopifyCli::Context.message("core.login.help", ShopifyCli::TOOL_NAME)
42
+ end
43
+
44
+ def self.validate_shop(shop)
45
+ permanent_domain = shop_to_permanent_domain(shop)
46
+ @ctx.abort(@ctx.message("core.login.invalid_shop", shop)) unless permanent_domain
47
+ permanent_domain
48
+ end
49
+
50
+ def self.shop_to_permanent_domain(shop)
51
+ url = if PROTOCOL_REGEX =~ shop
52
+ shop
53
+ elsif shop.include?(".")
54
+ "https://#{shop}"
55
+ else
56
+ "https://#{shop}.myshopify.com"
57
+ end
58
+
59
+ # Make a request to see if it exists or if we get redirected to the permanent domain one
60
+ uri = URI.parse(url)
61
+ Net::HTTP.start(uri.host, use_ssl: true) do |http|
62
+ response = http.request_head("/admin")
63
+ case response
64
+ when Net::HTTPSuccess, Net::HTTPSeeOther
65
+ uri.host
66
+ when Net::HTTPFound
67
+ domain = URI.parse(response["location"]).host
68
+ if PERMANENT_DOMAIN_SUFFIX =~ domain
69
+ domain
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def select_organization
78
+ organizations = ShopifyCli::PartnersAPI::Organizations.fetch_all(@ctx)
79
+
80
+ if organizations.count == 0
81
+ nil
82
+ elsif organizations.count == 1
83
+ organizations.first
84
+ else
85
+ org_id = CLI::UI::Prompt.ask(@ctx.message("core.tasks.select_org_and_shop.organization_select")) do |handler|
86
+ organizations.each do |o|
87
+ handler.option(@ctx.message("core.partners_api.org_name_and_id", o["businessName"], o["id"])) { o["id"] }
88
+ end
89
+ end
90
+ organizations.find { |o| o["id"] == org_id }
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,23 +1,39 @@
1
1
  require "shopify_cli"
2
+ require "shopify-cli/theme/development_theme"
2
3
 
3
4
  module ShopifyCli
4
5
  module Commands
5
6
  class Logout < ShopifyCli::Command
6
- LOGIN_TOKENS = [
7
- :identity_access_token, :identity_refresh_token, :identity_exchange_token,
8
- :admin_access_token, :admin_refresh_token, :admin_exchange_token
9
- ]
10
-
11
7
  def call(*)
12
- LOGIN_TOKENS.each do |token|
13
- ShopifyCli::DB.del(token) if ShopifyCli::DB.exists?(token)
14
- end
8
+ try_delete_development_theme
9
+ ShopifyCli::IdentityAuth.delete_tokens_and_keys
10
+ ShopifyCli::DB.del(:shop) if has_shop?
11
+ ShopifyCli::DB.del(:organization_id) if has_organization_id?
12
+ ShopifyCli::Shopifolk.reset
15
13
  @ctx.puts(@ctx.message("core.logout.success"))
16
14
  end
17
15
 
18
16
  def self.help
19
17
  ShopifyCli::Context.message("core.logout.help", ShopifyCli::TOOL_NAME)
20
18
  end
19
+
20
+ private
21
+
22
+ def has_shop?
23
+ ShopifyCli::DB.exists?(:shop)
24
+ end
25
+
26
+ def has_organization_id?
27
+ ShopifyCli::DB.exists?(:organization_id)
28
+ end
29
+
30
+ def try_delete_development_theme
31
+ return unless has_shop?
32
+
33
+ ShopifyCli::Theme::DevelopmentTheme.delete(@ctx)
34
+ rescue ShopifyCli::API::APIRequestError
35
+ # Ignore since we can't delete it
36
+ end
21
37
  end
22
38
  end
23
39
  end
@@ -0,0 +1,23 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Populate < ShopifyCli::Command
6
+ subcommand :Customer, "customers", "shopify-cli/commands/populate/customer"
7
+ subcommand :DraftOrder, "draftorders", "shopify-cli/commands/populate/draft_order"
8
+ subcommand :Product, "products", "shopify-cli/commands/populate/product"
9
+
10
+ def call(_args, _name)
11
+ @ctx.puts(self.class.help)
12
+ end
13
+
14
+ def self.help
15
+ ShopifyCli::Context.message("core.populate.help", ShopifyCli::TOOL_NAME)
16
+ end
17
+
18
+ def self.extended_help
19
+ ShopifyCli::Context.message("core.populate.extended_help", ShopifyCli::TOOL_NAME)
20
+ end
21
+ end
22
+ end
23
+ end