shopify-cli 1.11.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 (207) 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 +48 -20
  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 +29 -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 -38
  50. data/lib/project_types/extension/features/argo_serve.rb +25 -20
  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 +21 -6
  58. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +86 -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 +20 -14
  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 +15 -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/language_server.rb +16 -0
  112. data/lib/project_types/theme/commands/package.rb +55 -0
  113. data/lib/project_types/theme/commands/publish.rb +43 -0
  114. data/lib/project_types/theme/commands/pull.rb +51 -0
  115. data/lib/project_types/theme/commands/push.rb +58 -32
  116. data/lib/project_types/theme/commands/serve.rb +7 -17
  117. data/lib/project_types/theme/forms/confirm_store.rb +15 -0
  118. data/lib/project_types/theme/forms/select.rb +59 -0
  119. data/lib/project_types/theme/messages/messages.rb +110 -106
  120. data/lib/project_types/theme/ui/sync_progress_bar.rb +20 -0
  121. data/lib/shopify-cli/admin_api.rb +53 -35
  122. data/lib/shopify-cli/admin_api/populate_resource_command.rb +6 -14
  123. data/lib/shopify-cli/admin_api/schema.rb +1 -10
  124. data/lib/shopify-cli/api.rb +29 -14
  125. data/lib/shopify-cli/command.rb +15 -3
  126. data/lib/shopify-cli/commands.rb +7 -2
  127. data/lib/shopify-cli/commands/help.rb +2 -29
  128. data/lib/shopify-cli/commands/login.rb +95 -0
  129. data/lib/shopify-cli/commands/logout.rb +24 -8
  130. data/lib/shopify-cli/commands/populate.rb +23 -0
  131. data/lib/{project_types/node → shopify-cli}/commands/populate/customer.rb +2 -8
  132. data/lib/{project_types/node → shopify-cli}/commands/populate/draft_order.rb +2 -2
  133. data/lib/{project_types/node → shopify-cli}/commands/populate/product.rb +2 -8
  134. data/lib/shopify-cli/commands/store.rb +15 -0
  135. data/lib/shopify-cli/commands/switch.rb +39 -0
  136. data/lib/shopify-cli/commands/system.rb +12 -0
  137. data/lib/shopify-cli/commands/whoami.rb +28 -0
  138. data/lib/shopify-cli/connect.rb +32 -0
  139. data/lib/shopify-cli/context.rb +65 -4
  140. data/lib/shopify-cli/core/entry_point.rb +3 -22
  141. data/lib/shopify-cli/db.rb +4 -4
  142. data/lib/shopify-cli/http_request.rb +10 -0
  143. data/lib/shopify-cli/identity_auth.rb +282 -0
  144. data/lib/shopify-cli/{oauth → identity_auth}/servlet.rb +11 -12
  145. data/lib/shopify-cli/messages/messages.rb +133 -39
  146. data/lib/shopify-cli/partners_api.rb +21 -41
  147. data/lib/shopify-cli/partners_api/organizations.rb +8 -0
  148. data/lib/shopify-cli/project_commands.rb +16 -0
  149. data/lib/shopify-cli/project_type.rb +0 -31
  150. data/lib/shopify-cli/resources/env_file.rb +1 -1
  151. data/lib/shopify-cli/shopifolk.rb +8 -11
  152. data/lib/shopify-cli/sub_command.rb +1 -0
  153. data/lib/shopify-cli/tasks.rb +3 -0
  154. data/lib/shopify-cli/tasks/confirm_store.rb +18 -0
  155. data/lib/shopify-cli/tasks/create_api_client.rb +2 -2
  156. data/lib/shopify-cli/tasks/ensure_authenticated.rb +13 -0
  157. data/lib/shopify-cli/tasks/ensure_loopback_url.rb +1 -1
  158. data/lib/shopify-cli/tasks/ensure_project_type.rb +12 -0
  159. data/lib/shopify-cli/tasks/select_org_and_shop.rb +0 -3
  160. data/lib/shopify-cli/theme/dev_server.rb +98 -0
  161. data/lib/shopify-cli/theme/dev_server/certificate_manager.rb +79 -0
  162. data/lib/shopify-cli/theme/dev_server/header_hash.rb +94 -0
  163. data/lib/shopify-cli/theme/dev_server/hot-reload.js +93 -0
  164. data/lib/shopify-cli/theme/dev_server/hot_reload.rb +76 -0
  165. data/lib/shopify-cli/theme/dev_server/local_assets.rb +87 -0
  166. data/lib/shopify-cli/theme/dev_server/proxy.rb +205 -0
  167. data/lib/shopify-cli/theme/dev_server/sse.rb +75 -0
  168. data/lib/shopify-cli/theme/dev_server/watcher.rb +59 -0
  169. data/lib/shopify-cli/theme/dev_server/web_server.rb +140 -0
  170. data/lib/shopify-cli/theme/development_theme.rb +69 -0
  171. data/lib/shopify-cli/theme/file.rb +112 -0
  172. data/lib/shopify-cli/theme/ignore_filter.rb +109 -0
  173. data/lib/shopify-cli/theme/mime_type.rb +34 -0
  174. data/lib/shopify-cli/theme/syncer.rb +328 -0
  175. data/lib/shopify-cli/theme/theme.rb +204 -0
  176. data/lib/shopify-cli/version.rb +1 -1
  177. data/lib/shopify_cli.rb +18 -11
  178. data/shopify-cli.gemspec +12 -5
  179. data/shopify.fish +1 -1
  180. data/shopify.sh +1 -1
  181. metadata +95 -41
  182. data/.github/workflows/release.yml +0 -61
  183. data/lib/project_types/extension/features/argo_serve_options.rb +0 -40
  184. data/lib/project_types/node/commands/populate.rb +0 -23
  185. data/lib/project_types/rails/commands/populate.rb +0 -23
  186. data/lib/project_types/rails/commands/populate/customer.rb +0 -31
  187. data/lib/project_types/rails/commands/populate/draft_order.rb +0 -28
  188. data/lib/project_types/rails/commands/populate/product.rb +0 -30
  189. data/lib/project_types/script/layers/domain/config_ui.rb +0 -16
  190. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -95
  191. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -101
  192. data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
  193. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -71
  194. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -58
  195. data/lib/project_types/script/layers/infrastructure/task_runner.rb +0 -19
  196. data/lib/project_types/theme/commands/connect.rb +0 -54
  197. data/lib/project_types/theme/commands/create.rb +0 -48
  198. data/lib/project_types/theme/commands/deploy.rb +0 -38
  199. data/lib/project_types/theme/commands/generate.rb +0 -20
  200. data/lib/project_types/theme/commands/generate/env.rb +0 -79
  201. data/lib/project_types/theme/forms/connect.rb +0 -34
  202. data/lib/project_types/theme/forms/create.rb +0 -22
  203. data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +0 -78
  204. data/lib/project_types/theme/themekit.rb +0 -113
  205. data/lib/shopify-cli/commands/connect.rb +0 -64
  206. data/lib/shopify-cli/commands/create.rb +0 -50
  207. data/lib/shopify-cli/oauth.rb +0 -198
@@ -8,11 +8,21 @@ module ShopifyCli
8
8
  request(uri, body, headers, req)
9
9
  end
10
10
 
11
+ def put(uri, body, headers)
12
+ req = ::Net::HTTP::Put.new(uri.request_uri)
13
+ request(uri, body, headers, req)
14
+ end
15
+
11
16
  def get(uri, body, headers)
12
17
  req = ::Net::HTTP::Get.new(uri.request_uri)
13
18
  request(uri, body, headers, req)
14
19
  end
15
20
 
21
+ def delete(uri, body, headers)
22
+ req = ::Net::HTTP::Delete.new(uri.request_uri)
23
+ request(uri, body, headers, req)
24
+ end
25
+
16
26
  def request(uri, body, headers, req)
17
27
  http = ::Net::HTTP.new(uri.host, uri.port)
18
28
  http.use_ssl = true
@@ -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