shopify-cli 0.9.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 (273) hide show
  1. checksums.yaml +7 -0
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/CODE_OF_CONDUCT.md +73 -0
  4. data/.github/CONTRIBUTING.md +51 -0
  5. data/.github/DESIGN.md +153 -0
  6. data/.github/ISSUE_TEMPLATE.md +38 -0
  7. data/.github/PULL_REQUEST_TEMPLATE.md +22 -0
  8. data/.github/probots.yml +3 -0
  9. data/.gitignore +19 -0
  10. data/.rubocop.yml +47 -0
  11. data/.ruby-version +1 -0
  12. data/.travis.yml +12 -0
  13. data/Gemfile +22 -0
  14. data/Gemfile.lock +77 -0
  15. data/LICENSE.md +7 -0
  16. data/README.md +13 -0
  17. data/Rakefile +101 -0
  18. data/SECURITY.md +59 -0
  19. data/Vagrantfile +17 -0
  20. data/bin/load_shopify.rb +20 -0
  21. data/bin/shopify +32 -0
  22. data/dev.yml +17 -0
  23. data/docs/Gemfile +5 -0
  24. data/docs/Gemfile.lock +248 -0
  25. data/docs/_config.yml +16 -0
  26. data/docs/_data/nav.yml +26 -0
  27. data/docs/_includes/footer.html +15 -0
  28. data/docs/_includes/head.html +19 -0
  29. data/docs/_includes/sidebar_nav.html +22 -0
  30. data/docs/_includes/toc.html +112 -0
  31. data/docs/_layouts/default.html +79 -0
  32. data/docs/app/node/commands/index.md +82 -0
  33. data/docs/app/node/index.md +35 -0
  34. data/docs/app/rails/commands/index.md +80 -0
  35. data/docs/app/rails/index.md +36 -0
  36. data/docs/core/index.md +70 -0
  37. data/docs/css/docs.css +157 -0
  38. data/docs/getting-started/index.md +61 -0
  39. data/docs/help/start-app/index.md +6 -0
  40. data/docs/images/header.png +0 -0
  41. data/docs/index.md +27 -0
  42. data/docs/installing-ruby.md +28 -0
  43. data/ext/shopify-cli/extconf.rb +27 -0
  44. data/install.sh +7 -0
  45. data/lib/docgen/class_template.md.erb +81 -0
  46. data/lib/docgen/index_template.md.erb +5 -0
  47. data/lib/docgen/markdown.rb +101 -0
  48. data/lib/graphql/admin_introspection.graphql +87 -0
  49. data/lib/graphql/all_organizations.graphql +19 -0
  50. data/lib/graphql/all_orgs_with_apps.graphql +30 -0
  51. data/lib/graphql/api_versions.graphql +6 -0
  52. data/lib/graphql/convert_dev_to_test_store.graphql +10 -0
  53. data/lib/graphql/create_app.graphql +20 -0
  54. data/lib/graphql/create_customer.graphql +9 -0
  55. data/lib/graphql/create_draft_order.graphql +8 -0
  56. data/lib/graphql/create_product.graphql +9 -0
  57. data/lib/graphql/extension_create.graphql +21 -0
  58. data/lib/graphql/extension_update_draft.graphql +18 -0
  59. data/lib/graphql/find_organization.graphql +17 -0
  60. data/lib/graphql/get_app_urls.graphql +6 -0
  61. data/lib/graphql/update_dashboard_urls.graphql +8 -0
  62. data/lib/project_types/extension/cli.rb +71 -0
  63. data/lib/project_types/extension/commands/build.rb +29 -0
  64. data/lib/project_types/extension/commands/create.rb +49 -0
  65. data/lib/project_types/extension/commands/extension_command.rb +22 -0
  66. data/lib/project_types/extension/commands/push.rb +69 -0
  67. data/lib/project_types/extension/commands/register.rb +78 -0
  68. data/lib/project_types/extension/commands/serve.rb +24 -0
  69. data/lib/project_types/extension/commands/tunnel.rb +69 -0
  70. data/lib/project_types/extension/extension_project.rb +85 -0
  71. data/lib/project_types/extension/extension_project_keys.rb +10 -0
  72. data/lib/project_types/extension/features/argo.rb +48 -0
  73. data/lib/project_types/extension/features/argo_dependencies.rb +28 -0
  74. data/lib/project_types/extension/features/argo_setup.rb +54 -0
  75. data/lib/project_types/extension/features/argo_setup_step.rb +31 -0
  76. data/lib/project_types/extension/features/argo_setup_steps.rb +53 -0
  77. data/lib/project_types/extension/features/tunnel_url.rb +20 -0
  78. data/lib/project_types/extension/forms/create.rb +52 -0
  79. data/lib/project_types/extension/forms/register.rb +48 -0
  80. data/lib/project_types/extension/messages/message_loading.rb +37 -0
  81. data/lib/project_types/extension/messages/messages.rb +126 -0
  82. data/lib/project_types/extension/models/app.rb +14 -0
  83. data/lib/project_types/extension/models/registration.rb +19 -0
  84. data/lib/project_types/extension/models/type.rb +76 -0
  85. data/lib/project_types/extension/models/types/checkout_post_purchase.rb +20 -0
  86. data/lib/project_types/extension/models/types/subscription_management.rb +20 -0
  87. data/lib/project_types/extension/models/validation_error.rb +17 -0
  88. data/lib/project_types/extension/models/version.rb +15 -0
  89. data/lib/project_types/extension/tasks/converters/registration_converter.rb +26 -0
  90. data/lib/project_types/extension/tasks/converters/validation_error_converter.rb +25 -0
  91. data/lib/project_types/extension/tasks/converters/version_converter.rb +28 -0
  92. data/lib/project_types/extension/tasks/create_extension.rb +31 -0
  93. data/lib/project_types/extension/tasks/get_apps.rb +34 -0
  94. data/lib/project_types/extension/tasks/update_draft.rb +29 -0
  95. data/lib/project_types/extension/tasks/user_errors.rb +45 -0
  96. data/lib/project_types/node/cli.rb +37 -0
  97. data/lib/project_types/node/commands/create.rb +117 -0
  98. data/lib/project_types/node/commands/deploy.rb +22 -0
  99. data/lib/project_types/node/commands/deploy/heroku.rb +91 -0
  100. data/lib/project_types/node/commands/generate.rb +51 -0
  101. data/lib/project_types/node/commands/generate/billing.rb +37 -0
  102. data/lib/project_types/node/commands/generate/page.rb +55 -0
  103. data/lib/project_types/node/commands/generate/webhook.rb +33 -0
  104. data/lib/project_types/node/commands/open.rb +16 -0
  105. data/lib/project_types/node/commands/populate.rb +23 -0
  106. data/lib/project_types/node/commands/populate/customer.rb +31 -0
  107. data/lib/project_types/node/commands/populate/draft_order.rb +28 -0
  108. data/lib/project_types/node/commands/populate/product.rb +30 -0
  109. data/lib/project_types/node/commands/serve.rb +45 -0
  110. data/lib/project_types/node/commands/tunnel.rb +39 -0
  111. data/lib/project_types/node/forms/create.rb +87 -0
  112. data/lib/project_types/node/messages/messages.rb +260 -0
  113. data/lib/project_types/rails/cli.rb +41 -0
  114. data/lib/project_types/rails/commands/create.rb +126 -0
  115. data/lib/project_types/rails/commands/deploy.rb +22 -0
  116. data/lib/project_types/rails/commands/deploy/heroku.rb +113 -0
  117. data/lib/project_types/rails/commands/generate.rb +49 -0
  118. data/lib/project_types/rails/commands/generate/webhook.rb +39 -0
  119. data/lib/project_types/rails/commands/open.rb +16 -0
  120. data/lib/project_types/rails/commands/populate.rb +23 -0
  121. data/lib/project_types/rails/commands/populate/customer.rb +31 -0
  122. data/lib/project_types/rails/commands/populate/draft_order.rb +28 -0
  123. data/lib/project_types/rails/commands/populate/product.rb +30 -0
  124. data/lib/project_types/rails/commands/serve.rb +47 -0
  125. data/lib/project_types/rails/commands/tunnel.rb +39 -0
  126. data/lib/project_types/rails/forms/create.rb +116 -0
  127. data/lib/project_types/rails/gem.rb +56 -0
  128. data/lib/project_types/rails/messages/messages.rb +283 -0
  129. data/lib/project_types/rails/ruby.rb +17 -0
  130. data/lib/project_types/script/cli.rb +76 -0
  131. data/lib/project_types/script/commands/create.rb +45 -0
  132. data/lib/project_types/script/commands/disable.rb +36 -0
  133. data/lib/project_types/script/commands/enable.rb +46 -0
  134. data/lib/project_types/script/commands/push.rb +39 -0
  135. data/lib/project_types/script/config/extension_points.yml +18 -0
  136. data/lib/project_types/script/errors.rb +16 -0
  137. data/lib/project_types/script/forms/create.rb +29 -0
  138. data/lib/project_types/script/forms/enable.rb +24 -0
  139. data/lib/project_types/script/forms/push.rb +19 -0
  140. data/lib/project_types/script/forms/script_form.rb +66 -0
  141. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +27 -0
  142. data/lib/project_types/script/graphql/script_service_proxy.graphql +8 -0
  143. data/lib/project_types/script/graphql/shop_script_delete.graphql +14 -0
  144. data/lib/project_types/script/graphql/shop_script_update_or_create.graphql +28 -0
  145. data/lib/project_types/script/layers/application/build_script.rb +43 -0
  146. data/lib/project_types/script/layers/application/create_script.rb +47 -0
  147. data/lib/project_types/script/layers/application/disable_script.rb +19 -0
  148. data/lib/project_types/script/layers/application/enable_script.rb +21 -0
  149. data/lib/project_types/script/layers/application/extension_points.rb +17 -0
  150. data/lib/project_types/script/layers/application/project_dependencies.rb +34 -0
  151. data/lib/project_types/script/layers/application/push_script.rb +30 -0
  152. data/lib/project_types/script/layers/domain/errors.rb +25 -0
  153. data/lib/project_types/script/layers/domain/extension_point.rb +29 -0
  154. data/lib/project_types/script/layers/domain/push_package.rb +29 -0
  155. data/lib/project_types/script/layers/domain/script.rb +18 -0
  156. data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +73 -0
  157. data/lib/project_types/script/layers/infrastructure/assemblyscript_tsconfig.rb +38 -0
  158. data/lib/project_types/script/layers/infrastructure/assemblyscript_wasm_builder.rb +39 -0
  159. data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +36 -0
  160. data/lib/project_types/script/layers/infrastructure/errors.rb +38 -0
  161. data/lib/project_types/script/layers/infrastructure/extension_point_repository.rb +31 -0
  162. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +47 -0
  163. data/lib/project_types/script/layers/infrastructure/script_builder.rb +34 -0
  164. data/lib/project_types/script/layers/infrastructure/script_repository.rb +89 -0
  165. data/lib/project_types/script/layers/infrastructure/script_service.rb +165 -0
  166. data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +59 -0
  167. data/lib/project_types/script/messages/messages.rb +204 -0
  168. data/lib/project_types/script/script_project.rb +37 -0
  169. data/lib/project_types/script/templates/ts/as-pect.config.js +21 -0
  170. data/lib/project_types/script/ui/error_handler.rb +136 -0
  171. data/lib/project_types/script/ui/strict_spinner.rb +22 -0
  172. data/lib/rubygems_plugin.rb +18 -0
  173. data/lib/shopify-cli/admin_api.rb +99 -0
  174. data/lib/shopify-cli/admin_api/populate_resource_command.rb +165 -0
  175. data/lib/shopify-cli/admin_api/schema.rb +32 -0
  176. data/lib/shopify-cli/api.rb +104 -0
  177. data/lib/shopify-cli/command.rb +67 -0
  178. data/lib/shopify-cli/commands.rb +28 -0
  179. data/lib/shopify-cli/commands/connect.rb +108 -0
  180. data/lib/shopify-cli/commands/create.rb +50 -0
  181. data/lib/shopify-cli/commands/help.rb +79 -0
  182. data/lib/shopify-cli/commands/logout.rb +23 -0
  183. data/lib/shopify-cli/commands/system.rb +135 -0
  184. data/lib/shopify-cli/commands/version.rb +15 -0
  185. data/lib/shopify-cli/context.rb +372 -0
  186. data/lib/shopify-cli/core.rb +9 -0
  187. data/lib/shopify-cli/core/entry_point.rb +40 -0
  188. data/lib/shopify-cli/core/executor.rb +21 -0
  189. data/lib/shopify-cli/core/help_resolver.rb +20 -0
  190. data/lib/shopify-cli/core/monorail.rb +118 -0
  191. data/lib/shopify-cli/db.rb +114 -0
  192. data/lib/shopify-cli/form.rb +40 -0
  193. data/lib/shopify-cli/git.rb +141 -0
  194. data/lib/shopify-cli/helpers.rb +5 -0
  195. data/lib/shopify-cli/helpers/haikunator.rb +92 -0
  196. data/lib/shopify-cli/heroku.rb +97 -0
  197. data/lib/shopify-cli/js_deps.rb +110 -0
  198. data/lib/shopify-cli/js_system.rb +98 -0
  199. data/lib/shopify-cli/messages/messages.rb +287 -0
  200. data/lib/shopify-cli/oauth.rb +192 -0
  201. data/lib/shopify-cli/oauth/servlet.rb +61 -0
  202. data/lib/shopify-cli/options.rb +40 -0
  203. data/lib/shopify-cli/packager.rb +116 -0
  204. data/lib/shopify-cli/partners_api.rb +114 -0
  205. data/lib/shopify-cli/partners_api/organizations.rb +32 -0
  206. data/lib/shopify-cli/process_supervision.rb +187 -0
  207. data/lib/shopify-cli/project.rb +191 -0
  208. data/lib/shopify-cli/project_type.rb +83 -0
  209. data/lib/shopify-cli/resources.rb +5 -0
  210. data/lib/shopify-cli/resources/env_file.rb +96 -0
  211. data/lib/shopify-cli/sub_command.rb +15 -0
  212. data/lib/shopify-cli/task.rb +10 -0
  213. data/lib/shopify-cli/tasks.rb +32 -0
  214. data/lib/shopify-cli/tasks/create_api_client.rb +29 -0
  215. data/lib/shopify-cli/tasks/ensure_dev_store.rb +41 -0
  216. data/lib/shopify-cli/tasks/ensure_env.rb +31 -0
  217. data/lib/shopify-cli/tasks/ensure_loopback_url.rb +20 -0
  218. data/lib/shopify-cli/tasks/update_dashboard_urls.rb +44 -0
  219. data/lib/shopify-cli/tunnel.rb +154 -0
  220. data/lib/shopify-cli/version.rb +3 -0
  221. data/lib/shopify_cli.rb +132 -0
  222. data/shopify-cli.gemspec +40 -0
  223. data/shopify.fish +12 -0
  224. data/shopify.sh +11 -0
  225. data/vendor/deps/cli-kit/REVISION +1 -0
  226. data/vendor/deps/cli-kit/lib/cli/kit.rb +60 -0
  227. data/vendor/deps/cli-kit/lib/cli/kit/autocall.rb +21 -0
  228. data/vendor/deps/cli-kit/lib/cli/kit/base_command.rb +49 -0
  229. data/vendor/deps/cli-kit/lib/cli/kit/command_registry.rb +94 -0
  230. data/vendor/deps/cli-kit/lib/cli/kit/config.rb +133 -0
  231. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +115 -0
  232. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +81 -0
  233. data/vendor/deps/cli-kit/lib/cli/kit/ini.rb +102 -0
  234. data/vendor/deps/cli-kit/lib/cli/kit/levenshtein.rb +82 -0
  235. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +76 -0
  236. data/vendor/deps/cli-kit/lib/cli/kit/resolver.rb +60 -0
  237. data/vendor/deps/cli-kit/lib/cli/kit/ruby_backports/enumerable.rb +6 -0
  238. data/vendor/deps/cli-kit/lib/cli/kit/support.rb +9 -0
  239. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +244 -0
  240. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +207 -0
  241. data/vendor/deps/cli-kit/lib/cli/kit/util.rb +189 -0
  242. data/vendor/deps/cli-kit/lib/cli/kit/version.rb +5 -0
  243. data/vendor/deps/cli-ui/REVISION +1 -0
  244. data/vendor/deps/cli-ui/lib/cli/ui.rb +187 -0
  245. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +153 -0
  246. data/vendor/deps/cli-ui/lib/cli/ui/box.rb +15 -0
  247. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +79 -0
  248. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +179 -0
  249. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +310 -0
  250. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +78 -0
  251. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +88 -0
  252. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +248 -0
  253. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +472 -0
  254. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +24 -0
  255. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +48 -0
  256. data/vendor/deps/cli-ui/lib/cli/ui/spinner/async.rb +40 -0
  257. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +241 -0
  258. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +227 -0
  259. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +36 -0
  260. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +102 -0
  261. data/vendor/deps/cli-ui/lib/cli/ui/version.rb +5 -0
  262. data/vendor/deps/smart_properties/REVISION +1 -0
  263. data/vendor/deps/smart_properties/lib/smart_properties.rb +174 -0
  264. data/vendor/deps/smart_properties/lib/smart_properties/errors.rb +114 -0
  265. data/vendor/deps/smart_properties/lib/smart_properties/property.rb +162 -0
  266. data/vendor/deps/smart_properties/lib/smart_properties/property_collection.rb +83 -0
  267. data/vendor/deps/smart_properties/lib/smart_properties/validations.rb +8 -0
  268. data/vendor/deps/smart_properties/lib/smart_properties/validations/ancestor.rb +27 -0
  269. data/vendor/deps/smart_properties/lib/smart_properties/version.rb +3 -0
  270. data/vendor/lib/semantic/LICENSE +20 -0
  271. data/vendor/lib/semantic/semantic.rb +4 -0
  272. data/vendor/lib/semantic/version.rb +180 -0
  273. metadata +374 -0
@@ -0,0 +1,192 @@
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 OAuth
13
+ include SmartProperties
14
+
15
+ autoload :Servlet, 'shopify-cli/oauth/servlet'
16
+
17
+ class Error < StandardError; end
18
+ LocalRequest = Struct.new(:method, :path, :query, :protocol)
19
+
20
+ DEFAULT_PORT = 3456
21
+ REDIRECT_HOST = "http://127.0.0.1:#{DEFAULT_PORT}"
22
+
23
+ property! :ctx
24
+ property! :service, accepts: String
25
+ property! :client_id, accepts: String
26
+ property! :scopes
27
+ property :store, default: ShopifyCli::DB.new
28
+ property :secret, accepts: String
29
+ property :request_exchange, accepts: String
30
+ property :options, default: {}, accepts: Hash
31
+ property :auth_path, default: "/authorize", accepts: ->(path) { path.is_a?(String) && path.start_with?("/") }
32
+ property :token_path, default: "/token", accepts: ->(path) { path.is_a?(String) && path.start_with?("/") }
33
+ property :state_token, accepts: String, default: SecureRandom.hex(30)
34
+ property :code_verifier, accepts: String, default: SecureRandom.hex(30)
35
+
36
+ attr_accessor :response_query
37
+
38
+ def authenticate(url)
39
+ return if refresh_exchange_token(url)
40
+ return if refresh_access_token(url)
41
+ initiate_authentication(url)
42
+ request_access_token(url, code: receive_access_code)
43
+ request_exchange_token(url) if should_exchange
44
+ end
45
+
46
+ def code_challenge
47
+ @code_challenge ||= Base64.urlsafe_encode64(
48
+ OpenSSL::Digest::SHA256.digest(code_verifier),
49
+ padding: false,
50
+ )
51
+ end
52
+
53
+ def server
54
+ @server ||= begin
55
+ server = WEBrick::HTTPServer.new(
56
+ Port: DEFAULT_PORT,
57
+ Logger: WEBrick::Log.new(File.open(File::NULL, 'w')),
58
+ AccessLog: [],
59
+ )
60
+ server.mount('/', Servlet, self, state_token)
61
+ server
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def initiate_authentication(url)
68
+ @server_thread = Thread.new { server.start }
69
+ params = {
70
+ client_id: client_id,
71
+ scope: scopes,
72
+ redirect_uri: REDIRECT_HOST,
73
+ state: state_token,
74
+ response_type: :code,
75
+ }
76
+ params.merge!(challange_params) if secret.nil?
77
+ uri = URI.parse("#{url}#{auth_path}")
78
+ uri.query = URI.encode_www_form(params.merge(options))
79
+ output_authentication_info(uri)
80
+ end
81
+
82
+ def output_authentication_info(uri)
83
+ login_location = ctx.message(service == 'admin' ? 'core.oauth.location.admin' : 'core.oauth.location.partner')
84
+ ctx.puts(ctx.message('core.oauth.authentication_required', login_location))
85
+ ctx.open_url!(uri)
86
+ end
87
+
88
+ def receive_access_code
89
+ @access_code ||= begin
90
+ @server_thread.join(240)
91
+ raise Error, ctx.message('core.oauth.error.timeout') if response_query.nil?
92
+ raise Error, response_query['error_description'] unless response_query['error'].nil?
93
+ response_query['code']
94
+ end
95
+ end
96
+
97
+ def request_access_token(url, code:)
98
+ resp = post_token_request(
99
+ "#{url}#{token_path}",
100
+ {
101
+ grant_type: :authorization_code,
102
+ code: code,
103
+ redirect_uri: REDIRECT_HOST,
104
+ client_id: client_id,
105
+ }.merge(confirmation_param)
106
+ )
107
+ store.set(
108
+ "#{service}_access_token".to_sym => resp['access_token'],
109
+ "#{service}_refresh_token".to_sym => resp['refresh_token'],
110
+ )
111
+ end
112
+
113
+ def refresh_access_token(url)
114
+ return false if !store.exists?("#{service}_access_token".to_sym) ||
115
+ !store.exists?("#{service}_refresh_token".to_sym)
116
+ refresh_token(url)
117
+ request_exchange_token(url) if should_exchange
118
+ true
119
+ rescue
120
+ store.del("#{service}_access_token".to_sym, "#{service}_refresh_token".to_sym)
121
+ false
122
+ end
123
+
124
+ def refresh_token(url)
125
+ resp = post_token_request(
126
+ "#{url}#{token_path}",
127
+ grant_type: :refresh_token,
128
+ access_token: store.get("#{service}_access_token".to_sym),
129
+ refresh_token: store.get("#{service}_refresh_token".to_sym),
130
+ client_id: client_id,
131
+ )
132
+ store.set(
133
+ "#{service}_access_token".to_sym => resp['access_token'],
134
+ "#{service}_refresh_token".to_sym => resp['refresh_token'],
135
+ )
136
+ end
137
+
138
+ def refresh_exchange_token(url)
139
+ return false if !should_exchange || !store.exists?("#{service}_exchange_token".to_sym)
140
+ request_exchange_token(url)
141
+ true
142
+ rescue
143
+ store.del("#{service}_exchange_token".to_sym)
144
+ false
145
+ end
146
+
147
+ def request_exchange_token(url)
148
+ resp = post_token_request(
149
+ "#{url}#{token_path}",
150
+ grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
151
+ requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
152
+ subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
153
+ client_id: client_id,
154
+ audience: request_exchange,
155
+ scope: scopes,
156
+ subject_token: store.get("#{service}_access_token".to_sym),
157
+ )
158
+ store.set("#{service}_exchange_token".to_sym => resp['access_token'])
159
+ end
160
+
161
+ def post_token_request(url, params)
162
+ uri = URI.parse(url)
163
+ https = Net::HTTP.new(uri.host, uri.port)
164
+ https.use_ssl = true
165
+ request = Net::HTTP::Post.new(uri.path)
166
+ request['User-Agent'] = "Shopify App CLI #{::ShopifyCli::VERSION}"
167
+ request.body = URI.encode_www_form(params)
168
+ res = https.request(request)
169
+ raise Error, JSON.parse(res.body)['error_description'] unless res.is_a?(Net::HTTPSuccess)
170
+ JSON.parse(res.body)
171
+ end
172
+
173
+ def challange_params
174
+ {
175
+ code_challenge: code_challenge,
176
+ code_challenge_method: 'S256',
177
+ }
178
+ end
179
+
180
+ def confirmation_param
181
+ if secret.nil?
182
+ { code_verifier: code_verifier }
183
+ else
184
+ { client_secret: secret }
185
+ end
186
+ end
187
+
188
+ def should_exchange
189
+ !request_exchange.nil? && !request_exchange.empty?
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,61 @@
1
+ module ShopifyCli
2
+ class OAuth
3
+ class Servlet < WEBrick::HTTPServlet::AbstractServlet
4
+ TEMPLATE = %{<!DOCTYPE html>
5
+ <html>
6
+ <head>
7
+ <title>%{title}</title>
8
+ </head>
9
+ <body>
10
+ <h1 style="color: #%{color};">%{message}</h1>
11
+ %{autoclose}
12
+ </body>
13
+ </html>
14
+ }
15
+ AUTOCLOSE_TEMPLATE = %{
16
+ <script>
17
+ setTimeout(function() { window.close(); }, 3000)
18
+ </script>
19
+ }
20
+
21
+ def initialize(server, oauth, token)
22
+ super
23
+ @server = server
24
+ @oauth = oauth
25
+ @state_token = token
26
+ end
27
+
28
+ def do_GET(req, res) # rubocop:disable Naming/MethodName
29
+ if !req.query['error'].nil?
30
+ respond_with(
31
+ res,
32
+ 400,
33
+ Context.message('core.oauth.servlet.invalid_request_response', req.query['error_description'])
34
+ )
35
+ elsif req.query['state'] != @state_token
36
+ response_message = Context.message('core.oauth.servlet.invalid_state_response')
37
+ req.query.merge!('error' => 'invalid_state', 'error_description' => response_message)
38
+ respond_with(res, 403, response_message)
39
+ else
40
+ respond_with(res, 200, Context.message('core.oauth.servlet.success_response'))
41
+ end
42
+ @oauth.response_query = req.query
43
+ @server.shutdown
44
+ end
45
+
46
+ def respond_with(response, status, message)
47
+ successful = status == 200
48
+ locals = {
49
+ status: status,
50
+ message: message,
51
+ color: successful ? 'black' : 'red',
52
+ title:
53
+ Context.message(successful ? 'core.oauth.servlet.authenticated' : 'core.oauth.servlet.not_authenticated'),
54
+ autoclose: successful ? AUTOCLOSE_TEMPLATE : '',
55
+ }
56
+ response.status = status
57
+ response.body = format(TEMPLATE, locals)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+ require 'shopify_cli'
3
+ require 'optparse'
4
+
5
+ module ShopifyCli
6
+ class Options
7
+ include SmartProperties
8
+
9
+ attr_reader :flags, :subcommand, :help
10
+
11
+ def initialize
12
+ @flags = {}
13
+ @help = false
14
+ end
15
+
16
+ def parse(options_block, args)
17
+ @args = args
18
+ if options_block.respond_to?(:call) && args
19
+ parse_flags(options_block)
20
+ else
21
+ parser.permute!(@args)
22
+ end
23
+ @args
24
+ end
25
+
26
+ def parse_flags(block)
27
+ block.call(parser, @flags)
28
+ parser.permute!(@args)
29
+ end
30
+
31
+ def parser
32
+ @parser ||= begin
33
+ opt = OptionParser.new
34
+ opt.on('--help', '-h', Context.message('core.options.help_text')) do |v|
35
+ @help = v
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,116 @@
1
+ module ShopifyCli
2
+ class Packager
3
+ PACKAGING_DIR = File.join(ShopifyCli::ROOT, 'packaging')
4
+ BUILDS_DIR = File.join(PACKAGING_DIR, 'builds', ShopifyCli::VERSION)
5
+
6
+ def initialize
7
+ FileUtils.mkdir_p(BUILDS_DIR)
8
+ end
9
+
10
+ def build_gem
11
+ build_path = gem_path
12
+ puts "\nBuilding gem"
13
+
14
+ puts "Outputting gem to:\n #{build_path}\n\n"
15
+ raise "Failed to build gem" unless system(
16
+ 'gem',
17
+ 'build',
18
+ '-o',
19
+ build_path,
20
+ File.join(ShopifyCli::ROOT, 'shopify-cli.gemspec')
21
+ )
22
+ end
23
+
24
+ def build_debian
25
+ ensure_program_installed('dpkg-deb')
26
+
27
+ root_dir = File.join(PACKAGING_DIR, 'debian')
28
+ debian_dir = File.join(root_dir, 'shopify-cli', 'DEBIAN')
29
+ FileUtils.mkdir_p(debian_dir)
30
+
31
+ puts "\nBuilding Debian package"
32
+
33
+ puts "Generating metadata files..."
34
+ Dir.glob("#{debian_dir}/*").each { |file| File.delete(file) }
35
+
36
+ metadata_files = %w(control preinst prerm)
37
+ metadata_files.each do |file|
38
+ file_path = File.join(debian_dir, file)
39
+
40
+ file_contents = File.read(File.join(root_dir, "#{file}.base"))
41
+ file_contents = file_contents.gsub('SHOPIFY_CLI_VERSION', ShopifyCli::VERSION)
42
+ File.open(file_path, 'w', 0775) { |f| f.write(file_contents) }
43
+ end
44
+
45
+ puts "Building package..."
46
+ Dir.chdir(root_dir)
47
+ raise "Failed to build package" unless system('dpkg-deb', '-b', 'shopify-cli')
48
+
49
+ output_path = File.join(root_dir, 'shopify-cli.deb')
50
+ final_path = File.join(BUILDS_DIR, "shopify-cli-#{ShopifyCli::VERSION}.deb")
51
+
52
+ puts "Moving generated package: \n From: #{output_path}\n To: #{final_path}\n\n"
53
+ FileUtils.mv(output_path, final_path)
54
+ end
55
+
56
+ def build_rpm
57
+ ensure_program_installed('rpmbuild')
58
+
59
+ root_dir = File.join(PACKAGING_DIR, 'rpm')
60
+ rpm_build_dir = File.join(root_dir, 'build')
61
+ FileUtils.mkdir_p(rpm_build_dir)
62
+
63
+ spec_path = File.join(root_dir, 'shopify-cli.spec')
64
+ puts "\nBuilding RPM package"
65
+
66
+ puts "Generating spec file..."
67
+ File.delete(spec_path) if File.exist?(spec_path)
68
+
69
+ spec_contents = File.read(File.join(root_dir, 'shopify-cli.spec.base'))
70
+ spec_contents = spec_contents.gsub('SHOPIFY_CLI_VERSION', ShopifyCli::VERSION)
71
+ File.write(spec_path, spec_contents)
72
+
73
+ puts "Building package..."
74
+ Dir.chdir(root_dir)
75
+ system('rpmbuild', '-bb', File.basename(spec_path))
76
+
77
+ output_dir = File.join(root_dir, 'build', 'noarch')
78
+
79
+ puts "Moving generated packages: \n From: #{output_dir}\n To: #{BUILDS_DIR}\n\n"
80
+ FileUtils.mv(Dir.glob("#{output_dir}/*.rpm"), BUILDS_DIR)
81
+ end
82
+
83
+ def build_homebrew
84
+ root_dir = File.join(PACKAGING_DIR, 'homebrew')
85
+
86
+ build_path = File.join(BUILDS_DIR, "shopify-cli.rb")
87
+ puts "\nBuilding Homebrew package"
88
+
89
+ puts "Generating formula..."
90
+ File.delete(build_path) if File.exist?(build_path)
91
+
92
+ spec_contents = File.read(File.join(root_dir, 'shopify-cli.base.rb'))
93
+ spec_contents = spec_contents.gsub('SHOPIFY_CLI_VERSION', ShopifyCli::VERSION)
94
+
95
+ checksum = %x`shasum -a 256 #{gem_path}`
96
+ gem_checksum = checksum.split(' ')[0]
97
+
98
+ puts "Got sha256 sum for gem: #{gem_checksum}"
99
+ spec_contents = spec_contents.gsub('SHOPIFY_CLI_GEM_CHECKSUM', gem_checksum)
100
+
101
+ puts "Writing generated formula\n To: #{build_path}\n\n"
102
+ File.write(build_path, spec_contents)
103
+ end
104
+
105
+ private
106
+
107
+ def ensure_program_installed(program)
108
+ raise "Could not find program #{program} which is required to build the package" unless
109
+ system(program, '--version', out: File::NULL, err: File::NULL)
110
+ end
111
+
112
+ def gem_path
113
+ File.join(BUILDS_DIR, "shopify-cli.gem")
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,114 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ ##
5
+ # ShopifyCli::PartnersAPI provides easy access to the partners dashboard CLI
6
+ # schema.
7
+ #
8
+ class PartnersAPI < API
9
+ autoload :Organizations, 'shopify-cli/partners_api/organizations'
10
+
11
+ # Defines the environment variable that this API looks for to operate on local
12
+ # services. If you set this environment variable in your shell then the partners
13
+ # api will operation on your local instance
14
+ #
15
+ # #### Example
16
+ #
17
+ # SHOPIFY_APP_CLI_LOCAL_PARTNERS=1 shopify create
18
+ #
19
+ LOCAL_DEBUG = 'SHOPIFY_APP_CLI_LOCAL_PARTNERS'
20
+
21
+ class << self
22
+ ##
23
+ # issues a graphql query or mutation to the Shopify Partners Dashboard CLI Schema.
24
+ # It loads a graphql query from a file so that you do not need to use large
25
+ # unwieldy query strings. It also handles authentication for you as well.
26
+ #
27
+ # #### Parameters
28
+ # - `ctx`: running context from your command
29
+ # - `query_name`: name of the query you want to use, loaded from the `lib/graphql` directory.
30
+ # - `**variable`: a hash of variables to be supplied to the query ro mutation
31
+ #
32
+ # #### Raises
33
+ #
34
+ # * http 404 will raise a ShopifyCli::API::APIRequestNotFoundError
35
+ # * http 400..499 will raise a ShopifyCli::API::APIRequestClientError
36
+ # * http 500..599 will raise a ShopifyCli::API::APIRequestServerError
37
+ # * All other codes will raise ShopifyCli::API::APIRequestUnexpectedError
38
+ #
39
+ # #### Returns
40
+ #
41
+ # * `resp` - graphql response data hash. This can be a different shape for every query.
42
+ #
43
+ # #### Example
44
+ #
45
+ # ShopifyCli::PartnersAPI.query(@ctx, 'all_organizations')
46
+ #
47
+ def query(ctx, query_name, **variables)
48
+ authenticated_req(ctx) do
49
+ api_client(ctx).query(query_name, variables: variables)
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def authenticated_req(ctx)
56
+ yield
57
+ rescue API::APIRequestUnauthorizedError
58
+ authenticate(ctx)
59
+ retry
60
+ rescue API::APIRequestNotFoundError
61
+ ctx.puts(ctx.message('core.partners_api.error.account_not_found', ShopifyCli::TOOL_NAME))
62
+ end
63
+
64
+ def api_client(ctx)
65
+ new(
66
+ ctx: ctx,
67
+ token: access_token(ctx),
68
+ url: "#{endpoint}/api/cli/graphql",
69
+ )
70
+ end
71
+
72
+ def access_token(ctx)
73
+ ShopifyCli::DB.get(:identity_exchange_token) do
74
+ authenticate(ctx)
75
+ ShopifyCli::DB.get(:identity_exchange_token)
76
+ end
77
+ end
78
+
79
+ def authenticate(ctx)
80
+ OAuth.new(
81
+ ctx: ctx,
82
+ service: 'identity',
83
+ client_id: cli_id,
84
+ scopes: 'openid https://api.shopify.com/auth/partners.app.cli.access',
85
+ request_exchange: partners_id,
86
+ ).authenticate("#{auth_endpoint}/oauth")
87
+ end
88
+
89
+ def partners_id
90
+ return '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6' if ENV[LOCAL_DEBUG].nil?
91
+ 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978'
92
+ end
93
+
94
+ def cli_id
95
+ return 'fbdb2649-e327-4907-8f67-908d24cfd7e3' if ENV[LOCAL_DEBUG].nil?
96
+ 'e5380e02-312a-7408-5718-e07017e9cf52'
97
+ end
98
+
99
+ def auth_endpoint
100
+ return 'https://accounts.shopify.com' if ENV[LOCAL_DEBUG].nil?
101
+ 'https://identity.myshopify.io'
102
+ end
103
+
104
+ def endpoint
105
+ return 'https://partners.shopify.com' if ENV[LOCAL_DEBUG].nil?
106
+ 'https://partners.myshopify.io/'
107
+ end
108
+ end
109
+
110
+ def auth_headers(token)
111
+ { Authorization: "Bearer #{token}" }
112
+ end
113
+ end
114
+ end