shopify-cli 1.13.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +1 -1
  3. data/.github/CONTRIBUTING.md +7 -7
  4. data/.github/DESIGN.md +3 -3
  5. data/.github/workflows/build.yml +1 -1
  6. data/.gitignore +3 -0
  7. data/.rubocop.yml +3 -1
  8. data/.ruby-version +1 -1
  9. data/CHANGELOG.md +60 -26
  10. data/Gemfile +4 -0
  11. data/Gemfile.lock +32 -0
  12. data/LICENSE +4 -1
  13. data/README.md +94 -26
  14. data/RELEASING.md +31 -7
  15. data/Rakefile +2 -2
  16. data/SECURITY.md +1 -1
  17. data/THEMEKIT_MIGRATION.md +18 -0
  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/api_versions.graphql +1 -1
  36. data/lib/graphql/find_organization.graphql +2 -1
  37. data/lib/project_types/extension/cli.rb +19 -15
  38. data/lib/project_types/extension/commands/build.rb +4 -5
  39. data/lib/project_types/extension/commands/check.rb +44 -0
  40. data/lib/project_types/extension/commands/connect.rb +35 -0
  41. data/lib/project_types/extension/commands/create.rb +12 -16
  42. data/lib/project_types/extension/commands/extension_command.rb +2 -2
  43. data/lib/project_types/extension/commands/info.rb +86 -0
  44. data/lib/project_types/extension/commands/push.rb +8 -7
  45. data/lib/project_types/extension/commands/register.rb +4 -5
  46. data/lib/project_types/extension/commands/serve.rb +5 -8
  47. data/lib/project_types/extension/commands/tunnel.rb +3 -1
  48. data/lib/project_types/extension/errors.rb +9 -0
  49. data/lib/project_types/extension/extension_project.rb +24 -1
  50. data/lib/project_types/extension/extension_project_keys.rb +1 -0
  51. data/lib/project_types/extension/features/argo.rb +6 -6
  52. data/lib/project_types/extension/features/argo_runtime.rb +22 -66
  53. data/lib/project_types/extension/features/argo_serve.rb +25 -18
  54. data/lib/project_types/extension/forms/connect.rb +42 -0
  55. data/lib/project_types/extension/forms/questions/ask_name.rb +14 -6
  56. data/lib/project_types/extension/forms/questions/ask_registration.rb +51 -0
  57. data/lib/project_types/extension/messages/messages.rb +84 -16
  58. data/lib/project_types/extension/models/specification.rb +1 -0
  59. data/lib/project_types/extension/models/specification_handlers/{checkout_argo_extension.rb → checkout_ui_extension.rb} +3 -1
  60. data/lib/project_types/extension/models/specification_handlers/default.rb +13 -3
  61. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +89 -0
  62. data/lib/project_types/extension/models/specifications.rb +1 -0
  63. data/lib/project_types/extension/tasks/configure_features.rb +6 -7
  64. data/lib/project_types/extension/tasks/configure_options.rb +20 -0
  65. data/lib/project_types/extension/tasks/get_extensions.rb +32 -0
  66. data/lib/project_types/node/cli.rb +9 -21
  67. data/lib/project_types/node/commands/connect.rb +8 -2
  68. data/lib/project_types/node/commands/create.rb +9 -5
  69. data/lib/project_types/node/commands/deploy.rb +15 -5
  70. data/lib/project_types/node/commands/deploy/heroku.rb +29 -29
  71. data/lib/project_types/node/commands/generate.rb +4 -2
  72. data/lib/project_types/node/commands/open.rb +4 -2
  73. data/lib/project_types/node/commands/serve.rb +3 -2
  74. data/lib/project_types/node/commands/tunnel.rb +4 -2
  75. data/lib/project_types/node/messages/messages.rb +47 -90
  76. data/lib/project_types/rails/cli.rb +9 -21
  77. data/lib/project_types/rails/commands/connect.rb +8 -2
  78. data/lib/project_types/rails/commands/create.rb +10 -6
  79. data/lib/project_types/rails/commands/deploy.rb +15 -5
  80. data/lib/project_types/rails/commands/deploy/heroku.rb +84 -82
  81. data/lib/project_types/rails/commands/generate.rb +15 -5
  82. data/lib/project_types/rails/commands/generate/webhook.rb +28 -26
  83. data/lib/project_types/rails/commands/open.rb +4 -2
  84. data/lib/project_types/rails/commands/serve.rb +3 -2
  85. data/lib/project_types/rails/commands/tunnel.rb +4 -2
  86. data/lib/project_types/rails/messages/messages.rb +72 -119
  87. data/lib/project_types/script/cli.rb +6 -8
  88. data/lib/project_types/script/commands/create.rb +3 -1
  89. data/lib/project_types/script/commands/push.rb +7 -4
  90. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +9 -3
  91. data/lib/project_types/script/layers/application/create_script.rb +4 -3
  92. data/lib/project_types/script/layers/domain/errors.rb +6 -11
  93. data/lib/project_types/script/layers/domain/push_package.rb +4 -8
  94. data/lib/project_types/script/layers/domain/script_json.rb +32 -0
  95. data/lib/project_types/script/layers/domain/script_project.rb +1 -1
  96. data/lib/project_types/script/layers/infrastructure/errors.rb +13 -17
  97. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +29 -21
  98. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -4
  99. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +45 -34
  100. data/lib/project_types/script/layers/infrastructure/script_service.rb +37 -16
  101. data/lib/project_types/script/messages/messages.rb +64 -54
  102. data/lib/project_types/script/tasks/ensure_env.rb +3 -1
  103. data/lib/project_types/script/ui/error_handler.rb +32 -32
  104. data/lib/project_types/theme/cli.rb +16 -27
  105. data/lib/project_types/theme/commands/check.rb +33 -0
  106. data/lib/project_types/theme/commands/delete.rb +64 -0
  107. data/lib/project_types/theme/commands/init.rb +42 -0
  108. data/lib/project_types/theme/commands/language_server.rb +16 -0
  109. data/lib/project_types/theme/commands/package.rb +55 -0
  110. data/lib/project_types/theme/commands/publish.rb +43 -0
  111. data/lib/project_types/theme/commands/pull.rb +51 -0
  112. data/lib/project_types/theme/commands/push.rb +58 -32
  113. data/lib/project_types/theme/commands/serve.rb +8 -16
  114. data/lib/project_types/theme/forms/confirm_store.rb +15 -0
  115. data/lib/project_types/theme/forms/select.rb +59 -0
  116. data/lib/project_types/theme/messages/messages.rb +118 -103
  117. data/lib/project_types/theme/ui/sync_progress_bar.rb +20 -0
  118. data/lib/shopify-cli/admin_api.rb +57 -38
  119. data/lib/shopify-cli/admin_api/populate_resource_command.rb +6 -14
  120. data/lib/shopify-cli/admin_api/schema.rb +1 -10
  121. data/lib/shopify-cli/api.rb +29 -14
  122. data/lib/shopify-cli/command.rb +15 -3
  123. data/lib/shopify-cli/commands.rb +7 -2
  124. data/lib/shopify-cli/commands/help.rb +2 -29
  125. data/lib/shopify-cli/commands/login.rb +95 -0
  126. data/lib/shopify-cli/commands/logout.rb +24 -8
  127. data/lib/shopify-cli/commands/populate.rb +23 -0
  128. data/lib/{project_types/node → shopify-cli}/commands/populate/customer.rb +2 -8
  129. data/lib/{project_types/node → shopify-cli}/commands/populate/draft_order.rb +2 -2
  130. data/lib/{project_types/node → shopify-cli}/commands/populate/product.rb +2 -8
  131. data/lib/shopify-cli/commands/store.rb +15 -0
  132. data/lib/shopify-cli/commands/switch.rb +39 -0
  133. data/lib/shopify-cli/commands/system.rb +12 -0
  134. data/lib/shopify-cli/commands/whoami.rb +28 -0
  135. data/lib/shopify-cli/connect.rb +32 -0
  136. data/lib/shopify-cli/context.rb +65 -4
  137. data/lib/shopify-cli/core/entry_point.rb +3 -22
  138. data/lib/shopify-cli/core/monorail.rb +6 -2
  139. data/lib/shopify-cli/db.rb +4 -4
  140. data/lib/shopify-cli/http_request.rb +16 -0
  141. data/lib/shopify-cli/identity_auth.rb +282 -0
  142. data/lib/shopify-cli/{oauth → identity_auth}/servlet.rb +11 -12
  143. data/lib/shopify-cli/messages/messages.rb +140 -46
  144. data/lib/shopify-cli/packager.rb +5 -5
  145. data/lib/shopify-cli/partners_api.rb +21 -44
  146. data/lib/shopify-cli/partners_api/organizations.rb +8 -0
  147. data/lib/shopify-cli/project_commands.rb +16 -0
  148. data/lib/shopify-cli/project_type.rb +0 -31
  149. data/lib/shopify-cli/shopifolk.rb +8 -11
  150. data/lib/shopify-cli/sub_command.rb +1 -0
  151. data/lib/shopify-cli/tasks.rb +3 -0
  152. data/lib/shopify-cli/tasks/confirm_store.rb +18 -0
  153. data/lib/shopify-cli/tasks/create_api_client.rb +2 -2
  154. data/lib/shopify-cli/tasks/ensure_authenticated.rb +13 -0
  155. data/lib/shopify-cli/tasks/ensure_loopback_url.rb +1 -1
  156. data/lib/shopify-cli/tasks/ensure_project_type.rb +12 -0
  157. data/lib/shopify-cli/tasks/select_org_and_shop.rb +0 -3
  158. data/lib/shopify-cli/theme/dev_server.rb +98 -0
  159. data/lib/shopify-cli/theme/dev_server/certificate_manager.rb +79 -0
  160. data/lib/shopify-cli/theme/dev_server/header_hash.rb +94 -0
  161. data/lib/shopify-cli/theme/dev_server/hot-reload.js +93 -0
  162. data/lib/shopify-cli/theme/dev_server/hot_reload.rb +76 -0
  163. data/lib/shopify-cli/theme/dev_server/local_assets.rb +87 -0
  164. data/lib/shopify-cli/theme/dev_server/proxy.rb +205 -0
  165. data/lib/shopify-cli/theme/dev_server/sse.rb +75 -0
  166. data/lib/shopify-cli/theme/dev_server/watcher.rb +59 -0
  167. data/lib/shopify-cli/theme/dev_server/web_server.rb +140 -0
  168. data/lib/shopify-cli/theme/development_theme.rb +69 -0
  169. data/lib/shopify-cli/theme/file.rb +112 -0
  170. data/lib/shopify-cli/theme/ignore_filter.rb +109 -0
  171. data/lib/shopify-cli/theme/mime_type.rb +34 -0
  172. data/lib/shopify-cli/theme/syncer.rb +332 -0
  173. data/lib/shopify-cli/theme/theme.rb +204 -0
  174. data/lib/shopify-cli/tunnel.rb +1 -1
  175. data/lib/shopify-cli/version.rb +1 -1
  176. data/lib/shopify_cli.rb +18 -11
  177. data/shopify-cli.gemspec +12 -5
  178. data/shopify.fish +1 -1
  179. data/shopify.sh +1 -1
  180. metadata +92 -35
  181. data/.github/workflows/release.yml +0 -59
  182. data/lib/project_types/extension/features/argo_serve_options.rb +0 -42
  183. data/lib/project_types/node/commands/populate.rb +0 -23
  184. data/lib/project_types/rails/commands/populate.rb +0 -23
  185. data/lib/project_types/rails/commands/populate/customer.rb +0 -31
  186. data/lib/project_types/rails/commands/populate/draft_order.rb +0 -28
  187. data/lib/project_types/rails/commands/populate/product.rb +0 -30
  188. data/lib/project_types/script/layers/domain/config_ui.rb +0 -16
  189. data/lib/project_types/theme/commands/connect.rb +0 -54
  190. data/lib/project_types/theme/commands/create.rb +0 -48
  191. data/lib/project_types/theme/commands/deploy.rb +0 -38
  192. data/lib/project_types/theme/commands/generate.rb +0 -20
  193. data/lib/project_types/theme/commands/generate/env.rb +0 -79
  194. data/lib/project_types/theme/forms/connect.rb +0 -34
  195. data/lib/project_types/theme/forms/create.rb +0 -22
  196. data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +0 -78
  197. data/lib/project_types/theme/themekit.rb +0 -113
  198. data/lib/shopify-cli/commands/connect.rb +0 -64
  199. data/lib/shopify-cli/commands/create.rb +0 -50
  200. data/lib/shopify-cli/oauth.rb +0 -198
@@ -1,34 +0,0 @@
1
- module Theme
2
- module Forms
3
- class Connect < ShopifyCli::Form
4
- attr_accessor :name
5
- flag_arguments :themeid, :password, :store, :env
6
-
7
- def ask
8
- self.store ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_store"), allow_empty: false)
9
- ctx.puts(ctx.message("theme.forms.connect.private_app", store))
10
- self.password ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_password"), allow_empty: false)
11
-
12
- errors = []
13
- errors << "store" if store.strip.empty?
14
- errors << "password" if password.strip.empty?
15
- ctx.abort(ctx.message("theme.forms.errors", errors.join(", ").capitalize)) unless errors.empty?
16
-
17
- self.themeid, self.name = ask_theme(store: store, password: password, themeid: themeid)
18
- end
19
-
20
- private
21
-
22
- def ask_theme(store:, password:, themeid:)
23
- themes = Themekit.query_themes(@ctx, store: store, password: password)
24
-
25
- themeid ||= CLI::UI::Prompt.ask("Select theme") do |handler|
26
- themes.each do |name, id|
27
- handler.option(name) { id }
28
- end
29
- end
30
- [themeid, themes.key(themeid.to_i)]
31
- end
32
- end
33
- end
34
- end
@@ -1,22 +0,0 @@
1
- module Theme
2
- module Forms
3
- class Create < ShopifyCli::Form
4
- attr_accessor :name
5
- flag_arguments :title, :password, :store, :env
6
-
7
- def ask
8
- self.store ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_store"), allow_empty: false)
9
- ctx.puts(ctx.message("theme.forms.create.private_app", store))
10
- self.password ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_password"), allow_empty: false)
11
- self.title ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.create.ask_title"), allow_empty: false)
12
- self.name = self.title.downcase.split(" ").join("_")
13
-
14
- errors = []
15
- errors << "store" if store.strip.empty?
16
- errors << "password" if password.strip.empty?
17
- errors << "title" if title.strip.empty?
18
- ctx.abort(ctx.message("theme.forms.errors", errors.join(", ").capitalize)) unless errors.empty?
19
- end
20
- end
21
- end
22
- end
@@ -1,78 +0,0 @@
1
- module Theme
2
- module Tasks
3
- class EnsureThemekitInstalled < ShopifyCli::Task
4
- URL = "https://shopify-themekit.s3.amazonaws.com/releases/latest.json"
5
- OSMAP = {
6
- mac: "darwin-amd64",
7
- linux: "linux-amd64",
8
- windows: "windows-amd64",
9
- }
10
- VERSION_CHECK_INTERVAL = 604800
11
- VERSION_CHECK_SECTION = "themekit_version_check"
12
- LAST_CHECKED_AT_FIELD = "last_checked_at"
13
-
14
- def call(ctx)
15
- _out, stat = ctx.capture2e(Themekit::THEMEKIT)
16
- unless stat.success?
17
- CLI::UI::Frame.open(ctx.message("theme.tasks.ensure_themekit_installed.installing_themekit")) do
18
- install_themekit(ctx)
19
- end
20
- end
21
-
22
- now = Time.now.to_i
23
- if ShopifyCli::Feature.enabled?(:themekit_auto_update) && (time_of_last_check + VERSION_CHECK_INTERVAL) < now
24
- CLI::UI::Frame.open(ctx.message("theme.tasks.ensure_themekit_installed.updating_themekit")) do
25
- unless Themekit.update(ctx)
26
- ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.update_fail"))
27
- end
28
- update_time_of_last_check(now)
29
- end
30
- end
31
- end
32
-
33
- private
34
-
35
- def install_themekit(ctx)
36
- require "json"
37
- require "fileutils"
38
- require "digest"
39
- require "open-uri"
40
-
41
- begin
42
- begin
43
- releases = JSON.parse(Net::HTTP.get(URI(URL)))
44
- release = releases["platforms"].find { |r| r["name"] == OSMAP[ctx.os] }
45
- rescue
46
- ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.releases_fail"))
47
- end
48
-
49
- ctx.puts(ctx.message("theme.tasks.ensure_themekit_installed.downloading", releases["version"]))
50
- _out, stat = ctx.capture2e("curl", "-o", Themekit::THEMEKIT, release["url"])
51
- ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.write_fail")) unless stat.success?
52
-
53
- ctx.puts(ctx.message("theme.tasks.ensure_themekit_installed.verifying"))
54
- if Digest::MD5.file(Themekit::THEMEKIT) == release["digest"]
55
- FileUtils.chmod("+x", Themekit::THEMEKIT)
56
- ctx.puts(ctx.message("theme.tasks.ensure_themekit_installed.successful"))
57
-
58
- auto = CLI::UI.confirm(ctx.message("theme.tasks.ensure_themekit_installed.auto_update"))
59
- ShopifyCli::Feature.set(:themekit_auto_update, auto)
60
- else
61
- ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.digest_fail"))
62
- end
63
- rescue StandardError, ShopifyCli::Abort => e
64
- FileUtils.rm(Themekit::THEMEKIT) if File.exist?(Themekit::THEMEKIT)
65
- raise e
66
- end
67
- end
68
-
69
- def time_of_last_check
70
- (val = ShopifyCli::Config.get(VERSION_CHECK_SECTION, LAST_CHECKED_AT_FIELD)) ? val.to_i : 0
71
- end
72
-
73
- def update_time_of_last_check(time)
74
- ShopifyCli::Config.set(VERSION_CHECK_SECTION, LAST_CHECKED_AT_FIELD, time)
75
- end
76
- end
77
- end
78
- end
@@ -1,113 +0,0 @@
1
- module Theme
2
- class Themekit
3
- THEMEKIT = File.join(ShopifyCli.cache_dir, "themekit")
4
-
5
- class << self
6
- def add_flags(flags)
7
- flags.map do |key, value|
8
- flag = "--#{key}"
9
- flag += "=#{value}" if value.is_a?(String)
10
- flag
11
- end
12
- end
13
-
14
- def connect(ctx, store:, password:, themeid:, env:)
15
- command = build_command("get", env)
16
- command << "--password=#{password}"
17
- command << "--store=#{store}"
18
- command << "--themeid=#{themeid}"
19
-
20
- stat = ctx.system(*command)
21
- stat.success?
22
- end
23
-
24
- def create(ctx, password:, store:, name:, env:)
25
- command = build_command("new", env)
26
- command << "--password=#{password}"
27
- command << "--store=#{store}"
28
- command << "--name=#{name}"
29
-
30
- stat = ctx.system(*command)
31
- stat.success?
32
- end
33
-
34
- def deploy(ctx, flags: nil, env:)
35
- unless push(ctx, flags: flags, env: env)
36
- ctx.abort(ctx.message("theme.deploy.push_fail"))
37
- end
38
- ctx.done(ctx.message("theme.deploy.info.pushed"))
39
-
40
- command = build_command("publish", env)
41
- (command << flags).compact!
42
- command.flatten!
43
-
44
- stat = ctx.system(*command)
45
- stat.success?
46
- end
47
-
48
- def generate_env(ctx, store:, password:, themeid:, env:)
49
- command = build_command("configure", env)
50
- command << "--password=#{password}"
51
- command << "--store=#{store}"
52
- command << "--themeid=#{themeid}"
53
-
54
- stat = ctx.system(*command)
55
- stat.success?
56
- end
57
-
58
- def push(ctx, files: nil, flags: nil, remove: false, env:)
59
- action = remove ? "remove" : "deploy"
60
- command = build_command(action, env)
61
-
62
- (command << files << flags).compact!
63
- command.flatten!
64
-
65
- stat = ctx.system(*command)
66
- stat.success?
67
- end
68
-
69
- def query_themes(ctx, store:, password:)
70
- begin
71
- resp = ::ShopifyCli::AdminAPI.rest_request(
72
- ctx,
73
- shop: store,
74
- token: password,
75
- path: "themes.json",
76
- )
77
- rescue ShopifyCli::API::APIRequestUnauthorizedError
78
- ctx.abort(ctx.message("theme.themekit.query_themes.bad_password"))
79
- rescue StandardError
80
- ctx.abort(ctx.message("theme.themekit.query_themes.not_connect"))
81
- end
82
-
83
- resp[1]["themes"].map { |theme| [theme["name"], theme["id"]] }.to_h
84
- end
85
-
86
- def serve(ctx, flags: nil, env:)
87
- command = build_command("open", env)
88
- out, stat = ctx.capture2e(*command)
89
- ctx.puts(out)
90
- ctx.abort(ctx.message("theme.serve.open_fail")) unless stat.success?
91
-
92
- command = build_command("watch", env)
93
- (command << flags).compact!
94
- command.flatten!
95
- ctx.system(*command)
96
- end
97
-
98
- def update(ctx)
99
- command = build_command("update")
100
- ctx.system(*command)
101
- end
102
-
103
- private
104
-
105
- def build_command(action, env = nil)
106
- command = [THEMEKIT, action]
107
- command << "--no-update-notifier"
108
- command << "--env=#{env}" if env
109
- command
110
- end
111
- end
112
- end
113
- end
@@ -1,64 +0,0 @@
1
- require "shopify_cli"
2
-
3
- module ShopifyCli
4
- module Commands
5
- class Connect < ShopifyCli::Command
6
- class << self
7
- def call(args, command_name)
8
- ProjectType.load_type(args[0]) unless args.empty?
9
- super
10
- end
11
-
12
- def help
13
- ShopifyCli::Context.message("core.connect.help", ShopifyCli::TOOL_NAME)
14
- end
15
- end
16
-
17
- def call(args, command_name)
18
- if Project.current&.env
19
- @ctx.puts(@ctx.message("core.connect.already_connected_warning"))
20
- end
21
-
22
- project_type = ask_project_type
23
-
24
- klass = ProjectType.load_type(project_type)&.connect_command
25
-
26
- if klass
27
- klass.ctx = @ctx
28
- klass.call(args, command_name, "connect")
29
- else
30
- app = default_connect(project_type)
31
- @ctx.done(@ctx.message("core.connect.connected", app))
32
- end
33
- end
34
-
35
- def ask_project_type
36
- CLI::UI::Prompt.ask(@ctx.message("core.connect.project_type_select")) do |handler|
37
- ShopifyCli::Commands::Create.all_visible_type.each do |type|
38
- handler.option(type.project_name) { type.project_type }
39
- end
40
- end
41
- end
42
-
43
- def default_connect(project_type)
44
- org = ShopifyCli::Tasks::EnsureEnv.call(@ctx, regenerate: true)
45
- write_cli_yml(project_type, org["id"]) unless Project.has_current?
46
- api_key = Project.current(force_reload: true).env["api_key"]
47
- get_app(org["apps"], api_key).first["title"]
48
- end
49
-
50
- def write_cli_yml(project_type, org_id)
51
- ShopifyCli::Project.write(
52
- @ctx,
53
- project_type: project_type,
54
- organization_id: org_id,
55
- )
56
- @ctx.done(@ctx.message("core.connect.cli_yml_saved"))
57
- end
58
-
59
- def get_app(apps, api_key)
60
- apps.select { |app| app["apiKey"] == api_key }
61
- end
62
- end
63
- end
64
- end
@@ -1,50 +0,0 @@
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).sort.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
@@ -1,198 +0,0 @@
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 = if service == "admin"
84
- ctx.message("core.oauth.location.admin")
85
- elsif Shopifolk.acting_as_shopify_organization?
86
- ctx.message("core.oauth.location.shopifolk")
87
- else
88
- ctx.message("core.oauth.location.partner")
89
- end
90
- ctx.puts(ctx.message("core.oauth.authentication_required", login_location))
91
- ctx.open_url!(uri)
92
- end
93
-
94
- def receive_access_code
95
- @access_code ||= begin
96
- @server_thread.join(240)
97
- raise Error, ctx.message("core.oauth.error.timeout") if response_query.nil?
98
- raise Error, response_query["error_description"] unless response_query["error"].nil?
99
- response_query["code"]
100
- end
101
- end
102
-
103
- def request_access_token(url, code:)
104
- resp = post_token_request(
105
- "#{url}#{token_path}",
106
- {
107
- grant_type: :authorization_code,
108
- code: code,
109
- redirect_uri: REDIRECT_HOST,
110
- client_id: client_id,
111
- }.merge(confirmation_param)
112
- )
113
- store.set(
114
- "#{service}_access_token".to_sym => resp["access_token"],
115
- "#{service}_refresh_token".to_sym => resp["refresh_token"],
116
- )
117
- end
118
-
119
- def refresh_access_token(url)
120
- return false if !store.exists?("#{service}_access_token".to_sym) ||
121
- !store.exists?("#{service}_refresh_token".to_sym)
122
- refresh_token(url)
123
- request_exchange_token(url) if should_exchange
124
- true
125
- rescue
126
- store.del("#{service}_access_token".to_sym, "#{service}_refresh_token".to_sym)
127
- false
128
- end
129
-
130
- def refresh_token(url)
131
- resp = post_token_request(
132
- "#{url}#{token_path}",
133
- grant_type: :refresh_token,
134
- access_token: store.get("#{service}_access_token".to_sym),
135
- refresh_token: store.get("#{service}_refresh_token".to_sym),
136
- client_id: client_id,
137
- )
138
- store.set(
139
- "#{service}_access_token".to_sym => resp["access_token"],
140
- "#{service}_refresh_token".to_sym => resp["refresh_token"],
141
- )
142
- end
143
-
144
- def refresh_exchange_token(url)
145
- return false if !should_exchange || !store.exists?("#{service}_exchange_token".to_sym)
146
- request_exchange_token(url)
147
- true
148
- rescue
149
- store.del("#{service}_exchange_token".to_sym)
150
- false
151
- end
152
-
153
- def request_exchange_token(url)
154
- resp = post_token_request(
155
- "#{url}#{token_path}",
156
- grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
157
- requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
158
- subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
159
- client_id: client_id,
160
- audience: request_exchange,
161
- scope: scopes,
162
- subject_token: store.get("#{service}_access_token".to_sym),
163
- )
164
- store.set("#{service}_exchange_token".to_sym => resp["access_token"])
165
- end
166
-
167
- def post_token_request(url, params)
168
- uri = URI.parse(url)
169
- https = Net::HTTP.new(uri.host, uri.port)
170
- https.use_ssl = true
171
- request = Net::HTTP::Post.new(uri.path)
172
- request["User-Agent"] = "Shopify App CLI #{::ShopifyCli::VERSION}"
173
- request.body = URI.encode_www_form(params)
174
- res = https.request(request)
175
- raise Error, JSON.parse(res.body)["error_description"] unless res.is_a?(Net::HTTPSuccess)
176
- JSON.parse(res.body)
177
- end
178
-
179
- def challange_params
180
- {
181
- code_challenge: code_challenge,
182
- code_challenge_method: "S256",
183
- }
184
- end
185
-
186
- def confirmation_param
187
- if secret.nil?
188
- { code_verifier: code_verifier }
189
- else
190
- { client_secret: secret }
191
- end
192
- end
193
-
194
- def should_exchange
195
- !request_exchange.nil? && !request_exchange.empty?
196
- end
197
- end
198
- end