shopify-cli 1.12.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (208) 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/PULL_REQUEST_TEMPLATE.md +1 -1
  6. data/.github/workflows/build.yml +1 -1
  7. data/.gitignore +3 -0
  8. data/.rubocop.yml +3 -1
  9. data/.ruby-version +1 -1
  10. data/CHANGELOG.md +52 -21
  11. data/Gemfile +4 -0
  12. data/Gemfile.lock +32 -0
  13. data/LICENSE +4 -1
  14. data/README.md +92 -26
  15. data/RELEASING.md +31 -7
  16. data/Rakefile +2 -2
  17. data/SECURITY.md +1 -1
  18. data/bin/load_shopify.rb +1 -1
  19. data/bin/shopify +3 -3
  20. data/dev.yml +1 -1
  21. data/docs/app/node/index.md +1 -1
  22. data/docs/app/rails/index.md +1 -1
  23. data/docs/core/index.md +1 -1
  24. data/docs/getting-started/index.md +1 -1
  25. data/docs/getting-started/install/index.md +1 -1
  26. data/docs/getting-started/migrate/index.md +1 -1
  27. data/docs/getting-started/uninstall/index.md +1 -1
  28. data/docs/getting-started/upgrade/index.md +1 -1
  29. data/docs/help/start-app/index.md +1 -1
  30. data/docs/index.md +1 -1
  31. data/ext/shopify-cli/extconf.rb +17 -5
  32. data/install.sh +1 -1
  33. data/lib/docgen/index_template.md.erb +2 -2
  34. data/lib/graphql/all_orgs_with_extensions.graphql +37 -0
  35. data/lib/graphql/find_organization.graphql +2 -1
  36. data/lib/project_types/extension/cli.rb +18 -15
  37. data/lib/project_types/extension/commands/build.rb +4 -5
  38. data/lib/project_types/extension/commands/connect.rb +35 -0
  39. data/lib/project_types/extension/commands/create.rb +12 -16
  40. data/lib/project_types/extension/commands/extension_command.rb +2 -2
  41. data/lib/project_types/extension/commands/info.rb +86 -0
  42. data/lib/project_types/extension/commands/push.rb +8 -7
  43. data/lib/project_types/extension/commands/register.rb +4 -5
  44. data/lib/project_types/extension/commands/serve.rb +5 -8
  45. data/lib/project_types/extension/commands/tunnel.rb +3 -1
  46. data/lib/project_types/extension/errors.rb +9 -0
  47. data/lib/project_types/extension/extension_project.rb +5 -0
  48. data/lib/project_types/extension/features/argo.rb +6 -6
  49. data/lib/project_types/extension/features/argo_runtime.rb +22 -59
  50. data/lib/project_types/extension/features/argo_serve.rb +25 -21
  51. data/lib/project_types/extension/forms/connect.rb +42 -0
  52. data/lib/project_types/extension/forms/questions/ask_name.rb +14 -6
  53. data/lib/project_types/extension/forms/questions/ask_registration.rb +51 -0
  54. data/lib/project_types/extension/messages/messages.rb +75 -11
  55. data/lib/project_types/extension/models/specification.rb +1 -0
  56. data/lib/project_types/extension/models/specification_handlers/{checkout_argo_extension.rb → checkout_ui_extension.rb} +3 -1
  57. data/lib/project_types/extension/models/specification_handlers/default.rb +13 -13
  58. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +89 -0
  59. data/lib/project_types/extension/models/specifications.rb +1 -0
  60. data/lib/project_types/extension/tasks/configure_features.rb +6 -7
  61. data/lib/project_types/extension/tasks/configure_options.rb +20 -0
  62. data/lib/project_types/extension/tasks/get_extensions.rb +32 -0
  63. data/lib/project_types/node/cli.rb +9 -21
  64. data/lib/project_types/node/commands/connect.rb +8 -2
  65. data/lib/project_types/node/commands/create.rb +9 -5
  66. data/lib/project_types/node/commands/deploy.rb +15 -5
  67. data/lib/project_types/node/commands/deploy/heroku.rb +29 -29
  68. data/lib/project_types/node/commands/generate.rb +4 -2
  69. data/lib/project_types/node/commands/open.rb +4 -2
  70. data/lib/project_types/node/commands/serve.rb +3 -2
  71. data/lib/project_types/node/commands/tunnel.rb +4 -2
  72. data/lib/project_types/node/messages/messages.rb +46 -89
  73. data/lib/project_types/rails/cli.rb +9 -21
  74. data/lib/project_types/rails/commands/connect.rb +8 -2
  75. data/lib/project_types/rails/commands/create.rb +10 -6
  76. data/lib/project_types/rails/commands/deploy.rb +15 -5
  77. data/lib/project_types/rails/commands/deploy/heroku.rb +84 -82
  78. data/lib/project_types/rails/commands/generate.rb +15 -5
  79. data/lib/project_types/rails/commands/generate/webhook.rb +28 -26
  80. data/lib/project_types/rails/commands/open.rb +4 -2
  81. data/lib/project_types/rails/commands/serve.rb +3 -2
  82. data/lib/project_types/rails/commands/tunnel.rb +4 -2
  83. data/lib/project_types/rails/messages/messages.rb +54 -101
  84. data/lib/project_types/script/cli.rb +18 -20
  85. data/lib/project_types/script/commands/create.rb +3 -1
  86. data/lib/project_types/script/commands/push.rb +12 -5
  87. data/lib/project_types/script/config/extension_points.yml +0 -3
  88. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +9 -3
  89. data/lib/project_types/script/layers/application/create_script.rb +6 -5
  90. data/lib/project_types/script/layers/application/push_script.rb +2 -1
  91. data/lib/project_types/script/layers/domain/errors.rb +6 -11
  92. data/lib/project_types/script/layers/domain/push_package.rb +4 -8
  93. data/lib/project_types/script/layers/domain/script_json.rb +32 -0
  94. data/lib/project_types/script/layers/domain/script_project.rb +1 -1
  95. data/lib/project_types/script/layers/infrastructure/errors.rb +14 -18
  96. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +105 -0
  97. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +103 -0
  98. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +26 -0
  99. data/lib/project_types/script/layers/infrastructure/languages/rust_project_creator.rb +73 -0
  100. data/lib/project_types/script/layers/infrastructure/languages/rust_task_runner.rb +60 -0
  101. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +21 -0
  102. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -4
  103. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +45 -34
  104. data/lib/project_types/script/layers/infrastructure/script_service.rb +37 -16
  105. data/lib/project_types/script/messages/messages.rb +66 -55
  106. data/lib/project_types/script/tasks/ensure_env.rb +22 -1
  107. data/lib/project_types/script/ui/error_handler.rb +32 -32
  108. data/lib/project_types/theme/cli.rb +16 -27
  109. data/lib/project_types/theme/commands/check.rb +33 -0
  110. data/lib/project_types/theme/commands/delete.rb +64 -0
  111. data/lib/project_types/theme/commands/init.rb +42 -0
  112. data/lib/project_types/theme/commands/language_server.rb +16 -0
  113. data/lib/project_types/theme/commands/package.rb +55 -0
  114. data/lib/project_types/theme/commands/publish.rb +43 -0
  115. data/lib/project_types/theme/commands/pull.rb +51 -0
  116. data/lib/project_types/theme/commands/push.rb +58 -32
  117. data/lib/project_types/theme/commands/serve.rb +8 -16
  118. data/lib/project_types/theme/forms/confirm_store.rb +15 -0
  119. data/lib/project_types/theme/forms/select.rb +59 -0
  120. data/lib/project_types/theme/messages/messages.rb +117 -102
  121. data/lib/project_types/theme/ui/sync_progress_bar.rb +20 -0
  122. data/lib/shopify-cli/admin_api.rb +53 -35
  123. data/lib/shopify-cli/admin_api/populate_resource_command.rb +6 -14
  124. data/lib/shopify-cli/admin_api/schema.rb +1 -10
  125. data/lib/shopify-cli/api.rb +29 -14
  126. data/lib/shopify-cli/command.rb +15 -3
  127. data/lib/shopify-cli/commands.rb +7 -2
  128. data/lib/shopify-cli/commands/help.rb +2 -29
  129. data/lib/shopify-cli/commands/login.rb +95 -0
  130. data/lib/shopify-cli/commands/logout.rb +24 -8
  131. data/lib/shopify-cli/commands/populate.rb +23 -0
  132. data/lib/{project_types/node → shopify-cli}/commands/populate/customer.rb +2 -8
  133. data/lib/{project_types/node → shopify-cli}/commands/populate/draft_order.rb +2 -2
  134. data/lib/{project_types/node → shopify-cli}/commands/populate/product.rb +2 -8
  135. data/lib/shopify-cli/commands/store.rb +15 -0
  136. data/lib/shopify-cli/commands/switch.rb +39 -0
  137. data/lib/shopify-cli/commands/system.rb +12 -0
  138. data/lib/shopify-cli/commands/whoami.rb +28 -0
  139. data/lib/shopify-cli/connect.rb +32 -0
  140. data/lib/shopify-cli/context.rb +65 -4
  141. data/lib/shopify-cli/core/entry_point.rb +3 -22
  142. data/lib/shopify-cli/db.rb +4 -4
  143. data/lib/shopify-cli/http_request.rb +16 -0
  144. data/lib/shopify-cli/identity_auth.rb +282 -0
  145. data/lib/shopify-cli/{oauth → identity_auth}/servlet.rb +11 -12
  146. data/lib/shopify-cli/messages/messages.rb +133 -39
  147. data/lib/shopify-cli/partners_api.rb +21 -41
  148. data/lib/shopify-cli/partners_api/organizations.rb +8 -0
  149. data/lib/shopify-cli/project_commands.rb +16 -0
  150. data/lib/shopify-cli/project_type.rb +0 -31
  151. data/lib/shopify-cli/resources/env_file.rb +1 -1
  152. data/lib/shopify-cli/shopifolk.rb +8 -11
  153. data/lib/shopify-cli/sub_command.rb +1 -0
  154. data/lib/shopify-cli/tasks.rb +3 -0
  155. data/lib/shopify-cli/tasks/confirm_store.rb +18 -0
  156. data/lib/shopify-cli/tasks/create_api_client.rb +2 -2
  157. data/lib/shopify-cli/tasks/ensure_authenticated.rb +13 -0
  158. data/lib/shopify-cli/tasks/ensure_loopback_url.rb +1 -1
  159. data/lib/shopify-cli/tasks/ensure_project_type.rb +12 -0
  160. data/lib/shopify-cli/tasks/select_org_and_shop.rb +0 -3
  161. data/lib/shopify-cli/theme/dev_server.rb +98 -0
  162. data/lib/shopify-cli/theme/dev_server/certificate_manager.rb +79 -0
  163. data/lib/shopify-cli/theme/dev_server/header_hash.rb +94 -0
  164. data/lib/shopify-cli/theme/dev_server/hot-reload.js +93 -0
  165. data/lib/shopify-cli/theme/dev_server/hot_reload.rb +76 -0
  166. data/lib/shopify-cli/theme/dev_server/local_assets.rb +87 -0
  167. data/lib/shopify-cli/theme/dev_server/proxy.rb +205 -0
  168. data/lib/shopify-cli/theme/dev_server/sse.rb +75 -0
  169. data/lib/shopify-cli/theme/dev_server/watcher.rb +59 -0
  170. data/lib/shopify-cli/theme/dev_server/web_server.rb +140 -0
  171. data/lib/shopify-cli/theme/development_theme.rb +69 -0
  172. data/lib/shopify-cli/theme/file.rb +112 -0
  173. data/lib/shopify-cli/theme/ignore_filter.rb +109 -0
  174. data/lib/shopify-cli/theme/mime_type.rb +34 -0
  175. data/lib/shopify-cli/theme/syncer.rb +328 -0
  176. data/lib/shopify-cli/theme/theme.rb +204 -0
  177. data/lib/shopify-cli/version.rb +1 -1
  178. data/lib/shopify_cli.rb +18 -11
  179. data/shopify-cli.gemspec +12 -5
  180. data/shopify.fish +1 -1
  181. data/shopify.sh +1 -1
  182. metadata +96 -41
  183. data/.github/workflows/release.yml +0 -61
  184. data/lib/project_types/extension/features/argo_serve_options.rb +0 -41
  185. data/lib/project_types/node/commands/populate.rb +0 -23
  186. data/lib/project_types/rails/commands/populate.rb +0 -23
  187. data/lib/project_types/rails/commands/populate/customer.rb +0 -31
  188. data/lib/project_types/rails/commands/populate/draft_order.rb +0 -28
  189. data/lib/project_types/rails/commands/populate/product.rb +0 -30
  190. data/lib/project_types/script/layers/domain/config_ui.rb +0 -16
  191. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -95
  192. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -101
  193. data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
  194. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -71
  195. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -58
  196. data/lib/project_types/script/layers/infrastructure/task_runner.rb +0 -19
  197. data/lib/project_types/theme/commands/connect.rb +0 -54
  198. data/lib/project_types/theme/commands/create.rb +0 -48
  199. data/lib/project_types/theme/commands/deploy.rb +0 -38
  200. data/lib/project_types/theme/commands/generate.rb +0 -20
  201. data/lib/project_types/theme/commands/generate/env.rb +0 -79
  202. data/lib/project_types/theme/forms/connect.rb +0 -34
  203. data/lib/project_types/theme/forms/create.rb +0 -22
  204. data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +0 -78
  205. data/lib/project_types/theme/themekit.rb +0 -113
  206. data/lib/shopify-cli/commands/connect.rb +0 -64
  207. data/lib/shopify-cli/commands/create.rb +0 -50
  208. data/lib/shopify-cli/oauth.rb +0 -198
@@ -1,4 +1,5 @@
1
1
  require "net/http"
2
+ require "openssl"
2
3
 
3
4
  module ShopifyCli
4
5
  class HttpRequest
@@ -8,14 +9,29 @@ module ShopifyCli
8
9
  request(uri, body, headers, req)
9
10
  end
10
11
 
12
+ def put(uri, body, headers)
13
+ req = ::Net::HTTP::Put.new(uri.request_uri)
14
+ request(uri, body, headers, req)
15
+ end
16
+
11
17
  def get(uri, body, headers)
12
18
  req = ::Net::HTTP::Get.new(uri.request_uri)
13
19
  request(uri, body, headers, req)
14
20
  end
15
21
 
22
+ def delete(uri, body, headers)
23
+ req = ::Net::HTTP::Delete.new(uri.request_uri)
24
+ request(uri, body, headers, req)
25
+ end
26
+
16
27
  def request(uri, body, headers, req)
28
+ cert_store = OpenSSL::X509::Store.new
29
+ cert_store.set_default_paths
30
+
17
31
  http = ::Net::HTTP.new(uri.host, uri.port)
18
32
  http.use_ssl = true
33
+ http.cert_store = cert_store
34
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE if ENV["SSL_VERIFY_NONE"]
19
35
 
20
36
  req.body = body unless body.nil?
21
37
  req["Content-Type"] = "application/json"
@@ -0,0 +1,282 @@
1
+ require "base64"
2
+ require "digest"
3
+ require "json"
4
+ require "net/http"
5
+ require "securerandom"
6
+ require "openssl"
7
+ require "shopify_cli"
8
+ require "uri"
9
+ require "webrick"
10
+
11
+ module ShopifyCli
12
+ class IdentityAuth
13
+ include SmartProperties
14
+
15
+ autoload :Servlet, "shopify-cli/identity_auth/servlet"
16
+
17
+ class Error < StandardError; end
18
+ class Timeout < StandardError; end
19
+ LocalRequest = Struct.new(:method, :path, :query, :protocol)
20
+ LOCAL_DEBUG = "SHOPIFY_APP_CLI_LOCAL_PARTNERS"
21
+
22
+ DEFAULT_PORT = 3456
23
+ REDIRECT_HOST = "http://127.0.0.1:#{DEFAULT_PORT}"
24
+
25
+ APPLICATION_SCOPES = {
26
+ "shopify" => %w[https://api.shopify.com/auth/shop.admin.graphql https://api.shopify.com/auth/shop.admin.themes https://api.shopify.com/auth/partners.collaborator-relationships.readonly],
27
+ "storefront_renderer_production" => %w[https://api.shopify.com/auth/shop.storefront-renderer.devtools],
28
+ "partners" => %w[https://api.shopify.com/auth/partners.app.cli.access],
29
+ }
30
+
31
+ APPLICATION_CLIENT_IDS = {
32
+ "shopify" => "7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c",
33
+ "storefront_renderer_production" => "ee139b3d-5861-4d45-b387-1bc3ada7811c",
34
+ "partners" => "271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6",
35
+ }
36
+
37
+ DEV_APPLICATION_CLIENT_IDS = {
38
+ "shopify" => "e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52",
39
+ "storefront_renderer_production" => "46f603de-894f-488d-9471-5b721280ff49",
40
+ "partners" => "df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978",
41
+ }
42
+
43
+ EXCHANGE_TOKENS = APPLICATION_SCOPES.keys.map do |key|
44
+ "#{key}_exchange_token".to_sym
45
+ end
46
+
47
+ IDENTITY_ACCESS_TOKENS = %i[
48
+ identity_access_token
49
+ identity_refresh_token
50
+ ]
51
+
52
+ property! :ctx
53
+ property :store, default: -> { ShopifyCli::DB.new }
54
+ property :state_token, accepts: String, default: SecureRandom.hex(30)
55
+ property :code_verifier, accepts: String, default: SecureRandom.hex(30)
56
+
57
+ attr_accessor :response_query
58
+
59
+ def authenticate
60
+ return if refresh_exchange_tokens || refresh_access_tokens
61
+
62
+ initiate_authentication
63
+
64
+ begin
65
+ request_access_token(code: receive_access_code)
66
+ rescue IdentityAuth::Timeout => e
67
+ ctx.abort(e.message)
68
+ end
69
+ request_exchange_tokens
70
+ end
71
+
72
+ def reauthenticate
73
+ return if refresh_exchange_tokens || refresh_access_tokens
74
+ ctx.abort(ctx.message("core.identity_auth.error.reauthenticate", ShopifyCli::TOOL_NAME))
75
+ end
76
+
77
+ def code_challenge
78
+ @code_challenge ||= Base64.urlsafe_encode64(
79
+ OpenSSL::Digest::SHA256.digest(code_verifier),
80
+ padding: false,
81
+ )
82
+ end
83
+
84
+ def server
85
+ @server ||= begin
86
+ server = WEBrick::HTTPServer.new(
87
+ Port: DEFAULT_PORT,
88
+ Logger: WEBrick::Log.new(File.open(File::NULL, "w")),
89
+ AccessLog: [],
90
+ )
91
+ server.mount("/", Servlet, self, state_token)
92
+ server
93
+ end
94
+ end
95
+
96
+ def self.delete_tokens_and_keys
97
+ ShopifyCli::DB.del(*IDENTITY_ACCESS_TOKENS)
98
+ ShopifyCli::DB.del(*EXCHANGE_TOKENS)
99
+ end
100
+
101
+ private
102
+
103
+ def initiate_authentication
104
+ @server_thread = Thread.new { server.start }
105
+ params = {
106
+ client_id: client_id,
107
+ scope: scopes(APPLICATION_SCOPES.values.flatten),
108
+ redirect_uri: REDIRECT_HOST,
109
+ state: state_token,
110
+ response_type: :code,
111
+ }
112
+ params.merge!(challange_params)
113
+ uri = URI.parse("#{auth_url}/authorize")
114
+ uri.query = URI.encode_www_form(params)
115
+ open_browser_authentication(uri)
116
+ end
117
+
118
+ def open_browser_authentication(uri)
119
+ ctx.open_browser_url!(uri)
120
+ end
121
+
122
+ def receive_access_code
123
+ @access_code ||= begin
124
+ @server_thread.join(240)
125
+ raise Timeout, ctx.message("core.identity_auth.error.timeout") if response_query.nil?
126
+ raise Error, response_query["error_description"] unless response_query["error"].nil?
127
+ response_query["code"]
128
+ end
129
+ end
130
+
131
+ def request_access_token(code:)
132
+ resp = post_token_request(
133
+ grant_type: :authorization_code,
134
+ code: code,
135
+ redirect_uri: REDIRECT_HOST,
136
+ client_id: client_id,
137
+ code_verifier: code_verifier,
138
+ )
139
+ store.set(
140
+ identity_access_token: resp["access_token"],
141
+ identity_refresh_token: resp["refresh_token"],
142
+ )
143
+ end
144
+
145
+ def refresh_access_tokens
146
+ return false unless IDENTITY_ACCESS_TOKENS.all? { |key| store.exists?(key) }
147
+
148
+ resp = post_token_request(
149
+ grant_type: :refresh_token,
150
+ access_token: store.get(:identity_access_token),
151
+ refresh_token: store.get(:identity_refresh_token),
152
+ client_id: client_id,
153
+ )
154
+ store.set(
155
+ identity_access_token: resp["access_token"],
156
+ identity_refresh_token: resp["refresh_token"],
157
+ )
158
+
159
+ # Need to refresh the exchange token on successful access token refresh
160
+ request_exchange_tokens
161
+
162
+ true
163
+ rescue
164
+ store.del(*IDENTITY_ACCESS_TOKENS)
165
+ false
166
+ end
167
+
168
+ def refresh_exchange_tokens
169
+ return false unless EXCHANGE_TOKENS.all? { |key| store.exists?(key) }
170
+
171
+ request_exchange_tokens
172
+
173
+ true
174
+ rescue
175
+ store.del(*EXCHANGE_TOKENS)
176
+ false
177
+ end
178
+
179
+ def request_exchange_tokens
180
+ APPLICATION_SCOPES.each do |key, scopes|
181
+ request_exchange_token(key, client_id_for_application(key), scopes)
182
+ end
183
+ end
184
+
185
+ def request_exchange_token(name, audience, additional_scopes)
186
+ return if name == "shopify" && !store.exists?(:shop)
187
+
188
+ params = {
189
+ grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
190
+ requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
191
+ subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
192
+ client_id: client_id,
193
+ audience: audience,
194
+ scope: scopes(additional_scopes),
195
+ subject_token: store.get(:identity_access_token),
196
+ }.tap do |result|
197
+ if name == "shopify"
198
+ result[:destination] = "https://#{store.get(:shop)}/admin"
199
+ end
200
+ end
201
+ # ctx.debug(params)
202
+ resp = post_token_request(params)
203
+ store.set("#{name}_exchange_token".to_sym => resp["access_token"])
204
+ ctx.debug("#{name}_exchange_token: " + resp["access_token"])
205
+ end
206
+
207
+ def post_token_request(params)
208
+ post_request("/token", params)
209
+ end
210
+
211
+ def post_request(endpoint, params)
212
+ uri = URI.parse("#{auth_url}#{endpoint}")
213
+ https = Net::HTTP.new(uri.host, uri.port)
214
+ https.use_ssl = true
215
+ request = Net::HTTP::Post.new(uri.path)
216
+ request["User-Agent"] = "Shopify CLI #{::ShopifyCli::VERSION}"
217
+ request.body = URI.encode_www_form(params)
218
+ res = https.request(request)
219
+ unless res.is_a?(Net::HTTPSuccess)
220
+ error_msg = JSON.parse(res.body)["error_description"]
221
+ shop = store.get(:shop)
222
+ if error_msg.include?("destination")
223
+ store.del(:shop)
224
+ ctx.abort(ctx.message("core.identity_auth.error.invalid_destination", shop))
225
+ end
226
+ raise Error, error_msg
227
+ end
228
+ JSON.parse(res.body)
229
+ end
230
+
231
+ def challange_params
232
+ {
233
+ code_challenge: code_challenge,
234
+ code_challenge_method: "S256",
235
+ }
236
+ end
237
+
238
+ def auth_url
239
+ return "https://accounts.shopify.com/oauth" if ENV[LOCAL_DEBUG].nil?
240
+ "https://identity.myshopify.io/oauth"
241
+ end
242
+
243
+ def client_id_for_application(application_name)
244
+ client_ids = if ENV[LOCAL_DEBUG]
245
+ DEV_APPLICATION_CLIENT_IDS
246
+ else
247
+ APPLICATION_CLIENT_IDS
248
+ end
249
+
250
+ client_ids[application_name]
251
+ end
252
+
253
+ def scopes(additional_scopes = [])
254
+ (["openid"] + additional_scopes).tap do |result|
255
+ result << "employee" if ShopifyCli::Shopifolk.acting_as_shopify_organization?
256
+ end.join(" ")
257
+ end
258
+
259
+ def client_id
260
+ return "fbdb2649-e327-4907-8f67-908d24cfd7e3" if ENV[LOCAL_DEBUG].nil?
261
+
262
+ ctx.abort(ctx.message("core.identity_auth.error.local_identity_not_running")) unless local_identity_running?
263
+
264
+ # Fetch the client ID from the local Identity Dynamic Registration endpoint
265
+ response = post_request("/client", {
266
+ name: "shopify-cli-development",
267
+ public_type: "native",
268
+ })
269
+
270
+ response["client_id"]
271
+ end
272
+
273
+ def local_identity_running?
274
+ Net::HTTP.start("identity.myshopify.io", 443, use_ssl: true, open_timeout: 1, read_timeout: 10) do |http|
275
+ req = Net::HTTP::Get.new(URI.join("https://identity.myshopify.io", "/services/ping"))
276
+ http.request(req).is_a?(Net::HTTPSuccess)
277
+ end
278
+ rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::EHOSTDOWN, Errno::EADDRNOTAVAIL, Errno::ECONNREFUSED
279
+ false
280
+ end
281
+ end
282
+ end
@@ -1,5 +1,5 @@
1
1
  module ShopifyCli
2
- class OAuth
2
+ class IdentityAuth
3
3
  class Servlet < WEBrick::HTTPServlet::AbstractServlet
4
4
  TEMPLATE = %{<!DOCTYPE html>
5
5
  <html>
@@ -13,15 +13,13 @@ module ShopifyCli
13
13
  </html>
14
14
  }
15
15
  AUTOCLOSE_TEMPLATE = %{
16
- <script>
17
- setTimeout(function() { window.close(); }, 3000)
18
- </script>
16
+ <script>window.close();</script>
19
17
  }
20
18
 
21
- def initialize(server, oauth, token)
19
+ def initialize(server, identity_auth, token)
22
20
  super
23
21
  @server = server
24
- @oauth = oauth
22
+ @identity_auth = identity_auth
25
23
  @state_token = token
26
24
  end
27
25
 
@@ -30,16 +28,16 @@ module ShopifyCli
30
28
  respond_with(
31
29
  res,
32
30
  400,
33
- Context.message("core.oauth.servlet.invalid_request_response", req.query["error_description"])
31
+ Context.message("core.identity_auth.servlet.invalid_request_response", req.query["error_description"])
34
32
  )
35
33
  elsif req.query["state"] != @state_token
36
- response_message = Context.message("core.oauth.servlet.invalid_state_response")
34
+ response_message = Context.message("core.identity_auth.servlet.invalid_state_response")
37
35
  req.query.merge!("error" => "invalid_state", "error_description" => response_message)
38
36
  respond_with(res, 403, response_message)
39
37
  else
40
- respond_with(res, 200, Context.message("core.oauth.servlet.success_response"))
38
+ respond_with(res, 200, Context.message("core.identity_auth.servlet.success_response"))
41
39
  end
42
- @oauth.response_query = req.query
40
+ @identity_auth.response_query = req.query
43
41
  @server.shutdown
44
42
  end
45
43
 
@@ -49,8 +47,9 @@ module ShopifyCli
49
47
  status: status,
50
48
  message: message,
51
49
  color: successful ? "black" : "red",
52
- title:
53
- Context.message(successful ? "core.oauth.servlet.authenticated" : "core.oauth.servlet.not_authenticated"),
50
+ title: Context.message(
51
+ successful ? "core.identity_auth.servlet.authenticated" : "core.identity_auth.servlet.not_authenticated"
52
+ ),
54
53
  autoclose: successful ? AUTOCLOSE_TEMPLATE : "",
55
54
  }
56
55
  response.status = status
@@ -7,19 +7,14 @@ module ShopifyCli
7
7
  create: {
8
8
  info: {
9
9
  created: "{{v}} {{green:%s}} was created in the organization's Partner Dashboard {{underline:%s}}",
10
- serve: "{{*}} Change directories to your new project folder {{green:%s}} and run {{command:%s serve}} " \
11
- "to start a local server",
10
+ serve: "{{*}} Change directories to your new project folder {{green:%s}} and run "\
11
+ "{{command:%s %s serve}} to start a local server",
12
12
  install: "{{*}} Then, visit {{underline:%s/test}} to install {{green:%s}} on your Dev Store",
13
13
  },
14
14
  },
15
15
  },
16
16
  core: {
17
17
  connect: {
18
- help: <<~HELP,
19
- Connect (or re-connect) an existing project to a Shopify partner organization and/or a store. Creates or updates the {{green:.env}} file, and creates the {{green:.shopify-cli.yml}} file.
20
- Usage: {{command:%s connect}}
21
- HELP
22
-
23
18
  already_connected_warning: "{{yellow:! This app appears to be already connected}}",
24
19
  project_type_select: "What type of project would you like to connect?",
25
20
  cli_yml_saved: ".shopify-cli.yml saved to project root",
@@ -32,19 +27,6 @@ module ShopifyCli
32
27
  OPEN
33
28
  },
34
29
 
35
- create: {
36
- help: <<~HELP,
37
- Create a new project.
38
- Usage: {{command:%s create [ %s ]}}
39
- HELP
40
-
41
- error: {
42
- invalid_app_type: "{{red:Error}}: invalid app type {{bold:%s}}",
43
- },
44
-
45
- project_type_select: "What type of project would you like to create?",
46
- },
47
-
48
30
  env_file: {
49
31
  saving_header: "writing %s file...",
50
32
  saving: "writing %s file",
@@ -99,8 +81,6 @@ module ShopifyCli
99
81
  preamble: <<~MESSAGE,
100
82
  Use {{command:%s help <command>}} to display detailed information about a specific command.
101
83
 
102
- {{bold:Available core commands:}}
103
-
104
84
  MESSAGE
105
85
  },
106
86
 
@@ -129,13 +109,35 @@ module ShopifyCli
129
109
  npm_installed_deps: "%d npm dependencies installed",
130
110
  },
131
111
 
112
+ login: {
113
+ help: <<~HELP,
114
+ Log in to the Shopify CLI by authenticating with a store or partner organization
115
+ Usage: {{command:%s login [--store=STORE]}}
116
+ HELP
117
+ invalid_shop: <<~MESSAGE,
118
+ Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
119
+ MESSAGE
120
+ shop_prompt: <<~PROMPT,
121
+ What store are you connecting to? (e.g. my-store.myshopify.com; do {{bold:NOT}} include protocol part, e.g., https://)
122
+ PROMPT
123
+ },
124
+
132
125
  logout: {
133
126
  help: <<~HELP,
134
- Log out of a currently authenticated partner organization and store, or clear invalid credentials
127
+ Log out of an authenticated partner organization and store, or clear invalid credentials
135
128
  Usage: {{command:%s logout}}
136
129
  HELP
137
130
 
138
- success: "Logged out of partner organization and store",
131
+ success: "Successfully logged out of your account",
132
+ },
133
+
134
+ switch: {
135
+ help: <<~HELP,
136
+ Switch between development stores in your partner organization
137
+ Usage: {{command:%s switch [--store=STORE]}}
138
+ HELP
139
+ disabled_as_shopify_org: "Can't switch development stores logged in as {{green:Shopify partners org}}",
140
+ success: "Switched development store to {{green:%s}}",
139
141
  },
140
142
 
141
143
  monorail: {
@@ -146,9 +148,12 @@ module ShopifyCli
146
148
  MSG
147
149
  },
148
150
 
149
- oauth: {
151
+ identity_auth: {
150
152
  error: {
151
153
  timeout: "Timed out while waiting for response from Shopify",
154
+ local_identity_not_running: "Identity needs to be running locally in order to proceed.",
155
+ reauthenticate: "Please login again with {{command:shopify login}}",
156
+ invalid_destination: "The store %s doesn't exist. Please log out and try again.",
152
157
  },
153
158
 
154
159
  location: {
@@ -166,6 +171,7 @@ module ShopifyCli
166
171
  authenticated: "Authenticated successfully",
167
172
  not_authenticated: "Failed to authenticate",
168
173
  },
174
+ login_prompt: "Please ensure you've logged in with {{command:%s login}} and try again",
169
175
  },
170
176
 
171
177
  options: {
@@ -184,6 +190,11 @@ module ShopifyCli
184
190
 
185
191
  api: {
186
192
  error: {
193
+ failed_auth: "Failed to authenticate with Shopify. Please try again later.",
194
+ failed_auth_debugging: "{{red:Please provide this information with your report:}}\n%s\n\n",
195
+ forbidden: <<~FORBIDDEN,
196
+ Command not allowed with current login. Please check your login details with {{command:%s whoami}}. You may need to request additional permissions for this action.
197
+ FORBIDDEN
187
198
  internal_server_error: "{{red:{{x}} An unexpected error occurred on Shopify.}}",
188
199
  internal_server_error_debug: "\n{{red:Response details:}}\n%s\n\n",
189
200
  invalid_url: "Invalid URL: %s",
@@ -191,22 +202,78 @@ module ShopifyCli
191
202
  },
192
203
 
193
204
  populate: {
205
+ help: <<~HELP,
206
+ Populate a Shopify store with example customers, orders, or products.
207
+ Usage: {{command:%s populate [ customers | draftorders | products ]}}
208
+ HELP
209
+
210
+ extended_help: <<~HELP,
211
+ {{bold:Subcommands:}}
212
+
213
+ {{cyan:customers [options]}}: Add dummy customers to the specified store.
214
+ Usage: {{command:%1$s populate customers}}
215
+
216
+ {{cyan:draftorders [options]}}: Add dummy orders to the specified store.
217
+ Usage: {{command:%1$s populate draftorders}}
218
+
219
+ {{cyan:products [options]}}: Add dummy products to the specified store.
220
+ Usage: {{command:%1$s populate products}}
221
+
222
+ {{bold:Options:}}
223
+
224
+ {{cyan:--count [integer]}}: The number of dummy items to populate. Defaults to 5.
225
+ {{cyan:--silent}}: Silence the populate output.
226
+ {{cyan:--help}}: Display more options specific to each subcommand.
227
+
228
+ {{bold:Examples:}}
229
+
230
+ {{command:%1$s populate products}}
231
+ Populate your store with 5 additional products.
232
+
233
+ {{command:%1$s populate customers --count 30}}
234
+ Populate your store with 30 additional customers.
235
+
236
+ {{command:%1$s populate draftorders}}
237
+ Populate your store with 5 additional orders.
238
+
239
+ {{command:%1$s populate products --help}}
240
+ Display the list of options available to customize the {{command:%1$s populate products}} command.
241
+ HELP
242
+
243
+ error: {
244
+ no_shop: "No store found. Please run {{command:%s login --store=STORE}} to login to a specific store",
245
+ },
246
+
247
+ customer: {
248
+ added: "%s added to {{green:%s}} at {{underline:%scustomers/%d}}",
249
+ },
250
+
251
+ draft_order: {
252
+ added: "DraftOrder added to {{green:%s}} at {{underline:%sdraft_orders/%d}}",
253
+ },
254
+
194
255
  options: {
195
256
  header: "{{bold:{{cyan:%s}} options:}}",
196
257
  count_help: "Number of resources to generate",
197
258
  },
259
+
198
260
  populating: "Populating %d %ss...",
261
+
199
262
  completion_message: <<~COMPLETION_MESSAGE,
200
263
  Successfully added %d %s to {{green:%s}}
201
264
  {{*}} View all %ss at {{underline:%s%ss}}
202
265
  COMPLETION_MESSAGE
266
+
267
+ product: {
268
+ added: "%s added to {{green:%s}} at {{underline:%sproducts/%d}}",
269
+ },
203
270
  },
204
271
 
205
272
  project: {
206
273
  error: {
207
274
  not_in_project: <<~MESSAGE,
208
275
  {{x}} You are not in a Shopify app project
209
- {{yellow:{{*}}}}{{reset: Run}}{{cyan: shopify create}}{{reset: to create your app}}
276
+ {{yellow:{{*}}}}{{reset: Run}}{{cyan: shopify rails create}}{{reset: or}}{{cyan: shopify node create}}{{reset: to create your app}}
210
277
  MESSAGE
211
278
  },
212
279
  },
@@ -238,7 +305,8 @@ module ShopifyCli
238
305
  unknown_option: "{{x}} {{red:unknown option '%s'}}",
239
306
  },
240
307
 
241
- header: "{{bold:Shopify App CLI}}",
308
+ header: "{{bold:Shopify CLI}}",
309
+ shop_header: "{{bold:Current Shop}}",
242
310
  const: "%17s = %s",
243
311
  ruby_header: <<~RUBY_MESSAGE,
244
312
  {{bold:Ruby (via RbConfig)}}
@@ -265,7 +333,20 @@ module ShopifyCli
265
333
  identity_is_shopifolk: "{{v}} Checked user settings: you’re Shopify staff!",
266
334
  },
267
335
 
336
+ store: {
337
+ help: <<~HELP,
338
+ Display current store.
339
+ Usage: {{command:%s store}}
340
+ HELP
341
+ shop: "You're currently logged into {{green:%s}}",
342
+ },
343
+
268
344
  tasks: {
345
+ confirm_store: {
346
+ prompt: "You are currently logged into {{green:%s}}. Do you want to proceed using this store?",
347
+ confirmation: "Proceeding using {{green:%s}}",
348
+ cancelling: "Cancelling ...",
349
+ },
269
350
  ensure_env: {
270
351
  organization_select: "To which partner organization does this project belong?",
271
352
  no_development_stores: <<~MESSAGE,
@@ -292,6 +373,9 @@ module ShopifyCli
292
373
  MESSAGE
293
374
  transfer_disabled: "{{v}} Transfer has been disabled on %s.",
294
375
  },
376
+ ensure_project_type: {
377
+ wrong_project_type: "This command can only be run within %s projects.",
378
+ },
295
379
  update_dashboard_urls: {
296
380
  updated: "{{v}} Whitelist URLS updated in Partners Dashboard}}",
297
381
  update_error:
@@ -309,10 +393,11 @@ module ShopifyCli
309
393
  organization_not_found: "Cannot find a partner organization with that ID",
310
394
  shopifolk_notice: <<~MESSAGE,
311
395
  {{i}} As a {{green:Shopify}} employee, the authentication should take you to the Shopify Okta login,
312
- NOT the Partner account login. Please run {{command:%s logout}} and try again.
396
+ NOT the partner account login. Please run {{command:%s logout}} and try again.
313
397
  MESSAGE
314
398
  },
315
- first_party: "Are you working on a 1P (1st Party) app?",
399
+ first_party: "Are you working on a {{green:Shopify project}} on behalf of the"\
400
+ " {{green:Shopify partners org}}?",
316
401
  identified_as_shopify: "We've identified you as a {{green:Shopify}} employee.",
317
402
  organization: "Partner organization {{green:%s (%s)}}",
318
403
  organization_select: "Select partner organization",
@@ -333,7 +418,7 @@ module ShopifyCli
333
418
  signup_suggestion: <<~MESSAGE,
334
419
  {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
335
420
  account at {{underline:https://ngrok.com/signup}}. After you signup, install your
336
- personalized authorization token using {{command:%s tunnel auth <token>}}.
421
+ personalized authorization token using {{command:%s [ node | rails ] tunnel auth <token>}}.
337
422
  MESSAGE
338
423
  start: "{{v}} ngrok tunnel running at {{underline:%s}}",
339
424
  start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
@@ -357,21 +442,30 @@ module ShopifyCli
357
442
 
358
443
  DEVELOPMENT
359
444
 
360
- shell_shim: <<~MESSAGE,
361
- {{x}} This version of Shopify App CLI is no longer supported. You’ll need to migrate to the new CLI version to continue.
445
+ new_version: <<~MESSAGE,
446
+ {{*}} {{yellow:A new version of Shopify CLI is available! You have version %s and the latest version is %s.
362
447
 
363
- Please visit this page for complete instructions:
364
- {{underline:https://shopify.dev/tools/cli/troubleshooting#migrate-from-a-legacy-version}}
448
+ To upgrade, follow the instructions for the package manager you’re using:
449
+ {{underline:https://shopify.dev/tools/cli/troubleshooting#upgrade-shopify-cli}}}}
365
450
 
366
451
  MESSAGE
452
+ },
367
453
 
368
- new_version: <<~MESSAGE,
369
- {{*}} {{yellow:A new version of Shopify App CLI is available! You have version %s and the latest version is %s.
370
-
371
- To upgrade, follow the instructions for the package manager you’re using:
372
- {{underline:https://shopify.dev/tools/cli/troubleshooting#upgrade-shopify-app-cli}}}}
454
+ whoami: {
455
+ help: <<~HELP,
456
+ Identifies which partner organization or store you are currently logged into.
457
+ Usage: {{command:%s whoami}}
458
+ HELP
459
+ not_logged_in: <<~MESSAGE,
460
+ It doesn't appear that you're logged in. You must log into a partner organization or a store staff account.
373
461
 
462
+ If trying to log into a store staff account, please use {{command:%s login --store=STORE}} to log in.
463
+ MESSAGE
464
+ logged_in_shop_only: <<~MESSAGE,
465
+ Logged into store {{green:%s}} as staff (no partner organizations available for this login)
374
466
  MESSAGE
467
+ logged_in_partner_only: "Logged into partner organization {{green:%s}}",
468
+ logged_in_partner_and_shop: "Logged into store {{green:%s}} in partner organization {{green:%s}}",
375
469
  },
376
470
  },
377
471
  }.freeze