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,28 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ Registry = CLI::Kit::CommandRegistry.new(
6
+ default: 'help',
7
+ contextual_resolver: nil,
8
+ )
9
+ @core_commands = []
10
+
11
+ def self.register(const, cmd, path = nil, is_core = false)
12
+ autoload(const, path) if path
13
+ Registry.add(->() { const_get(const) }, cmd)
14
+ @core_commands.push(cmd) if is_core
15
+ end
16
+
17
+ def self.core_command?(cmd)
18
+ @core_commands.include?(cmd)
19
+ end
20
+
21
+ register :Connect, 'connect', 'shopify-cli/commands/connect', true
22
+ register :Create, 'create', 'shopify-cli/commands/create', true
23
+ register :Help, 'help', 'shopify-cli/commands/help', true
24
+ register :Logout, 'logout', 'shopify-cli/commands/logout', true
25
+ register :System, 'system', 'shopify-cli/commands/system', true
26
+ register :Version, 'version', 'shopify-cli/commands/version', true
27
+ end
28
+ end
@@ -0,0 +1,108 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Connect < ShopifyCli::Command
6
+ def call(*)
7
+ project_type = ask_project_type unless Project.has_current?
8
+
9
+ if Project.has_current? && Project.current
10
+ @ctx.puts @ctx.message('core.connect.already_connected_warning')
11
+ prod_warning = @ctx.message('core.connect.production_warning')
12
+ @ctx.puts prod_warning if [:rails, :node].include?(Project.current_project_type)
13
+ end
14
+
15
+ env_data = begin
16
+ Resources::EnvFile.parse_external_env
17
+ rescue Errno::ENOENT
18
+ {}
19
+ end
20
+
21
+ org = fetch_org
22
+ id = org['id']
23
+ app = get_app(org['apps'])
24
+ shop = get_shop(org['stores'], id)
25
+
26
+ write_env(app, shop, env_data[:scopes], env_data[:extra])
27
+ write_cli_yml(project_type, id) unless Project.has_current?
28
+
29
+ @ctx.puts(@ctx.message('core.connect.connected', app.first['title']))
30
+ end
31
+
32
+ def ask_project_type
33
+ CLI::UI::Prompt.ask(@ctx.message('core.connect.project_type_select')) do |handler|
34
+ ShopifyCli::Commands::Create.all_visible_type.each do |type|
35
+ handler.option(type.project_name) { type.project_type }
36
+ end
37
+ end
38
+ end
39
+
40
+ def fetch_org
41
+ orgs = PartnersAPI::Organizations.fetch_with_app(@ctx)
42
+ org_id = if orgs.count == 1
43
+ orgs.first["id"]
44
+ else
45
+ CLI::UI::Prompt.ask(@ctx.message('core.connect.organization_select')) do |handler|
46
+ orgs.each do |org|
47
+ handler.option(
48
+ ctx.message('core.partners_api.org_name_and_id', org['businessName'], org['id'])
49
+ ) { org["id"] }
50
+ end
51
+ end
52
+ end
53
+ org = orgs.find { |o| o["id"] == org_id }
54
+ org
55
+ end
56
+
57
+ def get_app(apps)
58
+ app_id = if apps.count == 1
59
+ apps.first["id"]
60
+ else
61
+ CLI::UI::Prompt.ask(@ctx.message('core.connect.app_select')) do |handler|
62
+ apps.each { |app| handler.option(app["title"]) { app["id"] } }
63
+ end
64
+ end
65
+ apps.select { |app| app["id"] == app_id }
66
+ end
67
+
68
+ def get_shop(shops, id)
69
+ if shops.count == 1
70
+ shop = shops.first["shopDomain"]
71
+ elsif shops.count == 0
72
+ @ctx.puts(@ctx.message('core.connect.no_development_stores', id))
73
+ else
74
+ shop = CLI::UI::Prompt.ask(@ctx.message('core.connect.development_store_select')) do |handler|
75
+ shops.each { |s| handler.option(s["shopName"]) { s["shopDomain"] } }
76
+ end
77
+ end
78
+ shop
79
+ end
80
+
81
+ def write_env(app, shop, scopes, extra)
82
+ scopes = 'write_products,write_customers,write_draft_orders' if scopes.nil?
83
+ extra = {} if extra.nil?
84
+
85
+ Resources::EnvFile.new(
86
+ api_key: app.first["apiKey"],
87
+ secret: app.first["apiSecretKeys"].first["secret"],
88
+ shop: shop,
89
+ scopes: scopes,
90
+ extra: extra,
91
+ ).write(@ctx)
92
+ end
93
+
94
+ def write_cli_yml(project_type, org_id)
95
+ ShopifyCli::Project.write(
96
+ @ctx,
97
+ project_type: project_type,
98
+ organization_id: org_id,
99
+ )
100
+ @ctx.done(@ctx.message('core.connect.cli_yml_saved'))
101
+ end
102
+
103
+ def self.help
104
+ ShopifyCli::Context.message('core.connect.help', ShopifyCli::TOOL_NAME)
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,50 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Create < ShopifyCli::Command
6
+ def self.call(args, command_name)
7
+ ProjectType.load_type(args[0]) unless args.empty?
8
+ super
9
+ end
10
+
11
+ def call(args, command_name)
12
+ unless args.empty?
13
+ @ctx.puts(@ctx.message('core.create.error.invalid_app_type', args[0]))
14
+ return @ctx.puts(self.class.help)
15
+ end
16
+
17
+ type_name = CLI::UI::Prompt.ask(@ctx.message('core.create.project_type_select')) do |handler|
18
+ self.class.all_visible_type.each do |type|
19
+ handler.option(type.project_name) { type.project_type }
20
+ end
21
+ end
22
+
23
+ klass = ProjectType.load_type(type_name).create_command
24
+ klass.ctx = @ctx
25
+ klass.call(args, command_name, 'create')
26
+ end
27
+
28
+ def self.all_visible_type
29
+ ProjectType
30
+ .load_all
31
+ .select { |type| !type.hidden }
32
+ end
33
+
34
+ def self.help
35
+ project_types = all_visible_type.map(&:project_type).join(" | ")
36
+ ShopifyCli::Context.message('core.create.help', ShopifyCli::TOOL_NAME, project_types)
37
+ end
38
+
39
+ def self.extended_help
40
+ <<~HELP
41
+ #{
42
+ all_visible_type.map do |type|
43
+ type.create_command.help
44
+ end.join("\n")
45
+ }
46
+ HELP
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,79 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Help < ShopifyCli::Command
6
+ def call(args, _name)
7
+ command = args.shift
8
+ if command && command != 'help'
9
+ if Registry.exist?(command)
10
+ cmd, _ = Registry.lookup_command(command)
11
+ subcmd, _ = cmd.subcommand_registry.lookup_command(args.first)
12
+ if subcmd
13
+ display_help(subcmd)
14
+ else
15
+ display_help(cmd)
16
+ end
17
+ return
18
+ else
19
+ @ctx.puts(@ctx.message('core.help.error.command_not_found', command))
20
+ end
21
+ end
22
+
23
+ preamble = @ctx.message('core.help.preamble', ShopifyCli::TOOL_NAME)
24
+ @ctx.puts(preamble)
25
+
26
+ core_commands.each do |name, klass|
27
+ next if name == 'help'
28
+ @ctx.puts("{{command:#{name}}}: #{klass.help}\n")
29
+ end
30
+
31
+ return unless inside_supported_project?
32
+
33
+ @ctx.puts("{{bold:Project: #{Project.project_name} (#{project_type_name})}}")
34
+ @ctx.puts("{{bold:Available commands for #{project_type_name} projects:}}\n\n")
35
+
36
+ local_commands.each do |name, klass|
37
+ next if name == 'help'
38
+ @ctx.puts("{{command:#{name}}}: #{klass.help}\n")
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def project_type_name
45
+ ProjectType.load_type(Project.current_project_type).project_name
46
+ end
47
+
48
+ def core_commands
49
+ resolved_commands
50
+ .select { |_name, c| !c.hidden }
51
+ .select { |name, _c| Commands.core_command?(name) }
52
+ end
53
+
54
+ def local_commands
55
+ resolved_commands
56
+ .reject { |name, _c| Commands.core_command?(name) }
57
+ end
58
+
59
+ def display_help(klass)
60
+ output = klass.help
61
+ if klass.respond_to?(:extended_help)
62
+ output += "\n"
63
+ output += klass.extended_help
64
+ end
65
+ @ctx.puts(output)
66
+ end
67
+
68
+ def resolved_commands
69
+ ShopifyCli::Commands::Registry
70
+ .resolved_commands
71
+ .sort
72
+ end
73
+
74
+ def inside_supported_project?
75
+ Project.current_project_type && ProjectType.load_type(Project.current_project_type)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,23 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Logout < ShopifyCli::Command
6
+ LOGIN_TOKENS = [
7
+ :identity_access_token, :identity_refresh_token, :identity_exchange_token,
8
+ :admin_access_token, :admin_refresh_token, :admin_exchange_token
9
+ ]
10
+
11
+ def call(*)
12
+ LOGIN_TOKENS.each do |token|
13
+ ShopifyCli::DB.del(token) if ShopifyCli::DB.exists?(token)
14
+ end
15
+ @ctx.puts(@ctx.message('core.logout.success'))
16
+ end
17
+
18
+ def self.help
19
+ ShopifyCli::Context.message('core.logout.help', ShopifyCli::TOOL_NAME)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,135 @@
1
+ require 'shopify_cli'
2
+ require 'rbconfig'
3
+
4
+ module ShopifyCli
5
+ module Commands
6
+ class System < ShopifyCli::Command
7
+ hidden_command
8
+
9
+ def call(args, _name)
10
+ show_all_details = false
11
+ flag = args.shift
12
+ if flag && flag != 'all'
13
+ @ctx.puts(@ctx.message('core.system.error.unknown_option', flag))
14
+ @ctx.puts("\n" + self.class.help)
15
+ return
16
+ end
17
+
18
+ show_all_details = true if flag == 'all'
19
+
20
+ display_environment if show_all_details
21
+
22
+ display_cli_constants(show_all_details)
23
+ display_cli_ruby(show_all_details)
24
+ display_utility_commands(show_all_details)
25
+ display_project_commands(show_all_details)
26
+ end
27
+
28
+ def self.help
29
+ ShopifyCli::Context.message('core.system.help', ShopifyCli::TOOL_NAME)
30
+ end
31
+
32
+ private
33
+
34
+ def display_cli_constants(show_all_details)
35
+ cli_constants = %w(ROOT)
36
+ cli_constants_extra = %w(
37
+ PROJECT_TYPES_DIR
38
+ TEMP_DIR
39
+ CACHE_DIR
40
+ TOOL_CONFIG_PATH
41
+ LOG_FILE
42
+ DEBUG_LOG_FILE
43
+ )
44
+
45
+ cli_constants += cli_constants_extra if show_all_details
46
+
47
+ @ctx.puts(@ctx.message('core.system.header'))
48
+ cli_constants.each do |s|
49
+ @ctx.puts(" " + @ctx.message('core.system.const', s, ShopifyCli.const_get(s.to_sym)) + "\n")
50
+ end
51
+ end
52
+
53
+ def display_cli_ruby(_show_all_details)
54
+ rbconfig_constants = %w(host RUBY_VERSION_NAME)
55
+
56
+ @ctx.puts("\n" + @ctx.message('core.system.ruby_header', RbConfig.ruby))
57
+ rbconfig_constants.each do |s|
58
+ @ctx.puts(" " + @ctx.message('core.system.rb_config', RbConfig::CONFIG[s], s))
59
+ end
60
+ end
61
+
62
+ def display_utility_commands(_show_all_details)
63
+ commands = %w(git curl tar unzip)
64
+
65
+ @ctx.puts("\n" + @ctx.message('core.system.command_header'))
66
+ commands.each do |s|
67
+ output, status = @ctx.capture2e('which', s)
68
+ if status.success?
69
+ @ctx.puts(" " + @ctx.message('core.system.command_with_path', s, output))
70
+ else
71
+ @ctx.puts(" " + @ctx.message('core.system.command_not_found', s))
72
+ end
73
+ end
74
+ end
75
+
76
+ def display_ngrok
77
+ ngrok_location = File.join(ShopifyCli::CACHE_DIR, 'ngrok')
78
+ if File.exist?(ngrok_location)
79
+ @ctx.puts(" " + @ctx.message('core.system.ngrok_available', ngrok_location))
80
+ else
81
+ @ctx.puts(" " + @ctx.message('core.system.ngrok_not_available'))
82
+ end
83
+ end
84
+
85
+ def display_project_commands(_show_all_details)
86
+ case Project.current_project_type
87
+ when :node
88
+ display_project('Node.js', %w(npm node yarn))
89
+ when :rails
90
+ display_project('Rails', %w(gem rails rake ruby))
91
+ end
92
+ end
93
+
94
+ def display_project(project_type, commands)
95
+ @ctx.puts("\n" + @ctx.message('core.system.project.header', project_type))
96
+ commands.each do |s|
97
+ output, status = @ctx.capture2e('which', s)
98
+ if status.success?
99
+ version_output, _ = @ctx.capture2e(s, '--version')
100
+ version = version_output.match(/(\d+\.[^\s]+)/)[0]
101
+ @ctx.puts(" " + @ctx.message('core.system.project.command_with_path', s, output.strip, version.strip))
102
+ else
103
+ @ctx.puts(" " + @ctx.message('core.system.project.command_not_found', s))
104
+ end
105
+ end
106
+ display_ngrok
107
+ display_project_environment
108
+ end
109
+
110
+ def display_project_environment
111
+ @ctx.puts("\n " + @ctx.message('core.system.project.env_header'))
112
+ if File.exist?('./.env')
113
+ Project.current.env.to_h.each do |k, v|
114
+ display_value = if v.nil? || v.strip == ''
115
+ @ctx.message('core.system.project.env_not_set')
116
+ else
117
+ k.match(/^SHOPIFY_API/) ? "********" : v
118
+ end
119
+ @ctx.puts(" " + @ctx.message('core.system.project.env', k, display_value))
120
+ end
121
+ else
122
+ @ctx.puts(" " + @ctx.message('core.system.project.no_env'))
123
+ end
124
+ end
125
+
126
+ def display_environment
127
+ @ctx.puts(@ctx.message('core.system.environment_header'))
128
+ %w(TERM SHELL PATH USING_SHOPIFY_CLI LANG).each do |k|
129
+ @ctx.puts(" " + @ctx.message('core.system.env', k, ENV[k])) unless ENV[k].nil?
130
+ end
131
+ @ctx.puts("")
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,15 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Commands
5
+ class Version < ShopifyCli::Command
6
+ def self.help
7
+ ShopifyCli::Context.message('core.version.help', ShopifyCli::TOOL_NAME)
8
+ end
9
+
10
+ def call(_args, _name)
11
+ @ctx.puts(ShopifyCli::VERSION.to_s)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,372 @@
1
+ # frozen_string_literal: true
2
+ require 'shopify_cli'
3
+ require 'fileutils'
4
+ require 'rbconfig'
5
+
6
+ module ShopifyCli
7
+ ##
8
+ # Context captures a lot about the current running command. It captures the
9
+ # environment, output, system and file operations. It is useful to have the
10
+ # context especially in tests so that you have a single access point to these
11
+ # resoures.
12
+ #
13
+ class Context
14
+ class << self
15
+ attr_reader :messages
16
+
17
+ # adds a new set of messages to be used by the CLI. The messages are expected to be a hash of symbols, and
18
+ # multiple levels are allowed. When fetching messages a dot notation is used to separate different levels. See
19
+ # Context::message for more information.
20
+ #
21
+ # #### Parameters
22
+ # * `messages` - Hash containing the new keys to register
23
+ def load_messages(messages)
24
+ @messages ||= {}
25
+ @messages = @messages.merge(messages) do |key|
26
+ Context.new.abort("Message key '#{key}' already exists and cannot be registered") if @messages.key?(key)
27
+ end
28
+ end
29
+
30
+ # returns the user-facing messages for the given key. Returns the key if no message is available.
31
+ #
32
+ # #### Parameters
33
+ # * `key` - a symbol representing the message
34
+ # * `params` - the parameters to format the string with
35
+ def message(key, *params)
36
+ key_parts = key.split('.').map(&:to_sym)
37
+ str = Context.messages.dig(*key_parts)
38
+ str ? str % params : key
39
+ end
40
+ end
41
+
42
+ # is the directory root that the current command is running in. If you want to
43
+ # simulate a `cd` for the file operations, you can change this variable.
44
+ attr_accessor :root
45
+ # is an accessor for environment variables. These variables are also added to
46
+ # any command run by the context.
47
+ attr_accessor :env
48
+
49
+ def initialize(root: Dir.pwd, env: ($original_env || ENV).clone) # :nodoc:
50
+ self.root = root
51
+ self.env = env
52
+ end
53
+
54
+ # will return which operating system that the cli is running on [:mac, :linux]
55
+ def os
56
+ host = uname
57
+ return :mac if /darwin/.match(host)
58
+ return :linux if /linux/.match(host)
59
+ end
60
+
61
+ # will return true if the cli is running on an apple computer.
62
+ def mac?
63
+ os == :mac
64
+ end
65
+
66
+ # will return true if the cli is running on a linux distro
67
+ def linux?
68
+ os == :linux
69
+ end
70
+
71
+ # will return true if the cli is being run from an installation, and not a
72
+ # development instance. The gem installation will not have a 'test' directory.
73
+ # See `#development?` for checking for development environment.
74
+ #
75
+ def system?
76
+ !Dir.exist?(File.join(ShopifyCli::ROOT, 'test'))
77
+ end
78
+
79
+ # will return true if the cli is running on your development instance.
80
+ #
81
+ def development?
82
+ !system? && !testing?
83
+ end
84
+
85
+ # will return true while tests are running, either locally or on CI
86
+ def testing?
87
+ ci? || ENV['TEST']
88
+ end
89
+
90
+ ##
91
+ # will return true if the cli is being tested on CI
92
+ def ci?
93
+ ENV['CI']
94
+ end
95
+
96
+ # get a environment variable value by name.
97
+ #
98
+ # #### Parameters
99
+ # * `name` - the name of the environment variable that you want to fetch
100
+ #
101
+ # #### Returns
102
+ # * `value` - will return the value, or nil if the variable does not exist
103
+ #
104
+ def getenv(name)
105
+ v = @env[name]
106
+ v == '' ? nil : v
107
+ end
108
+
109
+ # set a environment variable value by name.
110
+ #
111
+ # #### Parameters
112
+ # * `key` - the name of the environment variable that you want to set
113
+ # * `value` - the value of the variable
114
+ #
115
+ def setenv(key, value)
116
+ @env[key] = value
117
+ end
118
+
119
+ # will write/overwrite a file with the provided contents, relative to the context root
120
+ # unless the file path is absolute.
121
+ #
122
+ # #### Parameters
123
+ # * `fname` - filename of the file that you are writing, relative to root unless it is absolute.
124
+ # * `content` - the body contents of the file that you are writing
125
+ #
126
+ # #### Example
127
+ #
128
+ # @ctx.write('new.txt', 'hello world')
129
+ #
130
+ def write(fname, content)
131
+ File.write(ctx_path(fname), content)
132
+ end
133
+
134
+ # will rename a file from one place to another, relative to the command root
135
+ # unless the path is absolute.
136
+ #
137
+ # #### Parameters
138
+ # * `from` - the path of the original file
139
+ # * `to` - the destination path
140
+ #
141
+ def rename(from, to)
142
+ File.rename(ctx_path(from), ctx_path(to))
143
+ end
144
+
145
+ # will remove a plain file from the FS, the filepath is relative to the command
146
+ # root unless absolute.
147
+ #
148
+ # #### Parameters
149
+ # * `fname` - the file path relative to the context root to remove from the FS
150
+ #
151
+ def rm(fname)
152
+ FileUtils.rm(ctx_path(fname))
153
+ end
154
+
155
+ # will remove a directory from the FS, the filepath is relative to the command
156
+ # root unless absolute
157
+ #
158
+ # #### Parameters
159
+ # * `fname` - the file path to a directory, relative to the context root to remove from the FS
160
+ #
161
+ def rm_r(fname)
162
+ FileUtils.rm_r(ctx_path(fname))
163
+ end
164
+
165
+ # will create a directory, recursively if it does not exist. So if you create
166
+ # a directory `foo/bar/dun`, this will also create the directories `foo` and
167
+ # `foo/bar` if they do not exist. The path will be made relative to the command
168
+ # root unless absolute
169
+ #
170
+ # #### Parameters
171
+ # * `path` - file path of the directory that you want to create
172
+ #
173
+ def mkdir_p(path)
174
+ FileUtils.mkdir_p(path)
175
+ end
176
+
177
+ # will output to the console a link for the user to either copy/paste
178
+ # or click on.
179
+ #
180
+ # #### Parameters
181
+ # * `uri` - a http URI to open in a browser
182
+ #
183
+ def open_url!(uri)
184
+ help = message('core.context.open_url', uri)
185
+ puts(help)
186
+ end
187
+
188
+ # will output a message, prefixed by a yellow star, indicating that task
189
+ # started.
190
+ #
191
+ # #### Parameters
192
+ # * `text` - a string message to output
193
+ #
194
+ def print_task(text)
195
+ puts "{{yellow:*}} #{text}"
196
+ end
197
+
198
+ # a wrapper around Kernel.puts to allow for easy formatting
199
+ #
200
+ # #### Parameters
201
+ # * `text` - a string message to output
202
+ #
203
+ def puts(*args)
204
+ Kernel.puts(CLI::UI.fmt(*args))
205
+ end
206
+
207
+ # outputs a message, prefixed by a checkmark indicating that something completed
208
+ #
209
+ # #### Parameters
210
+ # * `text` - a string message to output
211
+ #
212
+ def done(text)
213
+ puts("{{v}} #{text}")
214
+ end
215
+
216
+ # aborts the current running command and outputs an error message, prefixed
217
+ # by a red x
218
+ #
219
+ # #### Parameters
220
+ # * `text` - a string message to output
221
+ #
222
+ def abort(text)
223
+ raise ShopifyCli::Abort, "{{x}} #{text}"
224
+ end
225
+
226
+ # outputs a message, prefixed by a red `DEBUG` tag. This will only output to
227
+ # the console if you have `DEBUG=1` set in your shell environment.
228
+ #
229
+ # #### Parameters
230
+ # * `text` - a string message to output
231
+ #
232
+ def debug(text)
233
+ puts("{{red:DEBUG}} #{text}") if getenv('DEBUG')
234
+ end
235
+
236
+ # proxy call to Context.message.
237
+ #
238
+ # #### Parameters
239
+ # * `key` - a symbol representing the message
240
+ # * `params` - the parameters to format the string with
241
+ def message(key, *params)
242
+ Context.message(key, *params)
243
+ end
244
+
245
+ # will grab the host info of the computer running the cli. This indicates the
246
+ # computer architecture and operating system
247
+ def uname
248
+ @uname ||= RbConfig::CONFIG["host"]
249
+ end
250
+
251
+ # Execute a command in the user's environment
252
+ # Outputs result of the command without capturing it
253
+ #
254
+ # #### Parameters
255
+ # - `*args`: A splat of arguments evaluated as a command. (e.g. `'rm', folder` is equivalent to `rm #{folder}`)
256
+ # - `**kwargs`: additional keyword arguments to pass to Process.spawn
257
+ #
258
+ # #### Returns
259
+ # - `status`: boolean success status of the command execution
260
+ #
261
+ # #### Usage
262
+ #
263
+ # stat = @ctx.system('ls', 'a_folder')
264
+ #
265
+ def system(*args, **kwargs)
266
+ CLI::Kit::System.system(*args, env: @env, **kwargs)
267
+ end
268
+
269
+ # Execute a command in the user's environment
270
+ # This is meant to be largely equivalent to backticks, only with the env passed in.
271
+ # Captures the results of the command without output to the console
272
+ #
273
+ # #### Parameters
274
+ # - `*args`: A splat of arguments evaluated as a command. (e.g. `'rm', folder` is equivalent to `rm #{folder}`)
275
+ # - `**kwargs`: additional arguments to pass to Open3.capture2
276
+ #
277
+ # #### Returns
278
+ # - `output`: output (STDOUT) of the command execution
279
+ # - `status`: boolean success status of the command execution
280
+ #
281
+ # #### Usage
282
+ #
283
+ # out, stat = @ctx.capture2('ls', 'a_folder')
284
+ #
285
+ def capture2(*args, **kwargs)
286
+ CLI::Kit::System.capture2(*args, env: @env, **kwargs)
287
+ end
288
+
289
+ # Execute a command in the user's environment
290
+ # This is meant to be largely equivalent to backticks, only with the env passed in.
291
+ # Captures the results of the command without output to the console
292
+ #
293
+ # #### Parameters
294
+ # - `*args`: A splat of arguments evaluated as a command. (e.g. `'rm', folder` is equivalent to `rm #{folder}`)
295
+ # - `**kwargs`: additional arguments to pass to Open3.capture2e
296
+ #
297
+ # #### Returns
298
+ # - `output`: output (STDOUT merged with STDERR) of the command execution
299
+ # - `status`: boolean success status of the command execution
300
+ #
301
+ # #### Usage
302
+ #
303
+ # out_and_err, stat = @ctx.capture2e('ls', 'a_folder')
304
+ #
305
+ def capture2e(*args, **kwargs)
306
+ CLI::Kit::System.capture2e(*args, env: @env, **kwargs)
307
+ end
308
+
309
+ # Execute a command in the user's environment
310
+ # This is meant to be largely equivalent to backticks, only with the env passed in.
311
+ # Captures the results of the command without output to the console
312
+ #
313
+ # #### Parameters
314
+ # - `*args`: A splat of arguments evaluated as a command. (e.g. `'rm', folder` is equivalent to `rm #{folder}`)
315
+ # - `**kwargs`: additional arguments to pass to Open3.capture3
316
+ #
317
+ # #### Returns
318
+ # - `output`: STDOUT of the command execution
319
+ # - `error`: STDERR of the command execution
320
+ # - `status`: boolean success status of the command execution
321
+ #
322
+ # #### Usage
323
+ #
324
+ # out, err, stat = @ctx.capture3('ls', 'a_folder')
325
+ #
326
+ def capture3(*args, **kwargs)
327
+ CLI::Kit::System.capture3(*args, env: @env, **kwargs)
328
+ end
329
+
330
+ # captures the info signal (ctrl-T) and provide a handler to it.
331
+ #
332
+ # #### Example
333
+ #
334
+ # @ctx.on_siginfo do
335
+ # @ctx.open_url!("http://google.com")
336
+ # end
337
+ #
338
+ def on_siginfo
339
+ # Reset any previous SIGINFO handling we had so the only action we take is the given block
340
+ trap('INFO', 'DEFAULT')
341
+
342
+ fork do
343
+ begin
344
+ r, w = IO.pipe
345
+ @signal = false
346
+ trap('SIGINFO') do
347
+ @signal = true
348
+ w.write(0)
349
+ end
350
+ while r.read(1)
351
+ next unless @signal
352
+ @signal = false
353
+ yield
354
+ end
355
+ rescue Interrupt
356
+ exit(0)
357
+ end
358
+ end
359
+ end
360
+
361
+ private
362
+
363
+ def ctx_path(fname)
364
+ require 'pathname'
365
+ if Pathname.new(fname).absolute?
366
+ fname
367
+ else
368
+ File.join(root, fname)
369
+ end
370
+ end
371
+ end
372
+ end